diff --git a/app/blueprints/quiz.py b/app/blueprints/quiz.py index f3e0642..76d1da8 100644 --- a/app/blueprints/quiz.py +++ b/app/blueprints/quiz.py @@ -4,22 +4,46 @@ from dependency_injector.wiring import inject, Provide from controllers import QuizController -quiz_bp = Blueprint( - "quiz", - __name__, -) +quiz_bp = Blueprint("quiz", __name__, url_prefix="/quiz") -@quiz_bp.route("/quiz", methods=["POST"]) +@quiz_bp.route("/", methods=["POST"]) @inject def create_quiz(controller: QuizController = Provide[Container.quiz_controller]): reqBody = request.get_json() return controller.create_quiz(reqBody) -@quiz_bp.route("/quiz/", methods=["GET"]) +@quiz_bp.route("/", methods=["GET"]) @inject def get_quiz( quiz_id: str, controller: QuizController = Provide[Container.quiz_controller] ): return controller.get_quiz(quiz_id) + + +@quiz_bp.route("/answer", methods=["POST"]) +@inject +def submit_answer(controller: QuizController = Provide[Container.quiz_controller]): + req_body = request.get_json() + return controller.submit_answer(req_body) + + +@quiz_bp.route("/answer", methods=["GET"]) +@inject +def get_answer(controller: QuizController = Provide[Container.quiz_controller]): + quiz_id = request.args.get("quiz_id") + user_id = request.args.get("user_id") + session_id = request.args.get("session_id") + + return controller.get_answer( + quiz_id=quiz_id, user_id=user_id, session_id=session_id + ) + + +@quiz_bp.route("/recomendation", methods=["GET"]) +@inject +def get_quiz_recommendation( + controller: QuizController = Provide[Container.quiz_controller], +): + return controller.get_quiz_recommendation() diff --git a/app/controllers/quiz_controller.py b/app/controllers/quiz_controller.py index c7d7e1c..c378dd3 100644 --- a/app/controllers/quiz_controller.py +++ b/app/controllers/quiz_controller.py @@ -1,14 +1,15 @@ from flask import jsonify from pydantic import ValidationError -from schemas.requests import QuizCreateSchema +from schemas.requests import QuizCreateSchema, UserAnswerSchema from schemas.response import QuizCreationResponse -from services import QuizService +from services import QuizService, AnswerService from helpers import make_response, make_error_response class QuizController: - def __init__(self, quiz_service: QuizService): + def __init__(self, quiz_service: QuizService, answer_service: AnswerService): self.quiz_service = quiz_service + self.answer_service = answer_service def get_quiz(self, quiz_id): try: @@ -50,3 +51,34 @@ class QuizController: if not success: return jsonify({"error": "Quiz not found"}), 400 return jsonify({"message": "Quiz deleted"}), 200 + + def quiz_recomendation(self): + try: + result = self.quiz_service.get_quiz_recommendation() + if not result: + return make_response(message="Quiz not found", status_code=404) + return make_response(message="Quiz Found", data=result.dict()) + except Exception as e: + return make_error_response(e) + + def submit_answer(self, answer_data): + try: + # Assuming answer_data is a dictionary with the necessary fields + answer_obj = UserAnswerSchema(**answer_data) + answer_id = self.answer_service.create_answer(answer_obj) + return make_response( + message="Answer submitted", + data={"answer_id": answer_id}, + status_code=201, + ) + except ValidationError as e: + return make_response(e.errors(), status_code=400) + except Exception as e: + return make_error_response(e) + + def get_answer(self, quiz_id, user_id, session_id): + try: + # self.answer_service. + print("yps") + except Exception as e: + return make_error_response(e) diff --git a/app/di_container.py b/app/di_container.py index 01931dc..e621246 100644 --- a/app/di_container.py +++ b/app/di_container.py @@ -1,12 +1,7 @@ from dependency_injector import containers, providers -from controllers import UserController -from repositories.user_repository import UserRepository -from services import UserService, AuthService -from controllers import AuthController -from flask_pymongo import PyMongo -from repositories import QuizRepository -from services import QuizService -from controllers import QuizController +from controllers import UserController, AuthController, QuizController +from repositories import UserRepository, QuizRepository, UserAnswerRepository +from services import UserService, AuthService, QuizService, AnswerService class Container(containers.DeclarativeContainer): @@ -17,13 +12,15 @@ class Container(containers.DeclarativeContainer): # repository user_repository = providers.Factory(UserRepository, mongo.provided.db) quiz_repository = providers.Factory(QuizRepository, mongo.provided.db) + answer_repository = providers.Factory(UserAnswerRepository, mongo.provided.db) # services auth_service = providers.Factory(AuthService, user_repository) user_service = providers.Factory(UserService, user_repository) quiz_service = providers.Factory(QuizService, quiz_repository) + answer_service = providers.Factory(AnswerService, answer_repository) # controllers auth_controller = providers.Factory(AuthController, user_service, auth_service) user_controller = providers.Factory(UserController, user_service) - quiz_controller = providers.Factory(QuizController, quiz_service) + quiz_controller = providers.Factory(QuizController, quiz_service, answer_service) diff --git a/app/models/__init__.py b/app/models/__init__.py index f6eeb11..9be20b2 100644 --- a/app/models/__init__.py +++ b/app/models/__init__.py @@ -1,5 +1,5 @@ # app/models/__init__.py -from .entities import UserEntity, QuizEntity, QuestionItemEntity +from .entities import UserEntity, QuizEntity, QuestionItemEntity, UserAnswerEntity from .login import UserResponseModel @@ -9,4 +9,5 @@ __all__ = [ "UserResponseModel", "QuizEntity", "QuestionItemEntity", + "UserAnswerEntity", ] diff --git a/app/models/entities/__init__.py b/app/models/entities/__init__.py index 30e3229..afed6ab 100644 --- a/app/models/entities/__init__.py +++ b/app/models/entities/__init__.py @@ -2,10 +2,12 @@ from .user_entity import UserEntity from .base import PyObjectId from .quiz_entity import QuizEntity from .question_item_entity import QuestionItemEntity +from .user_answer_entity import UserAnswerEntity __all__ = [ "UserEntity", "PyObjectId", "QuizEntity", "QuestionItemEntity", + "UserAnswerEntity", ] diff --git a/app/models/entities/answer_item.py b/app/models/entities/answer_item.py new file mode 100644 index 0000000..e5bf0e3 --- /dev/null +++ b/app/models/entities/answer_item.py @@ -0,0 +1,11 @@ +from pydantic import BaseModel + + +class AnswerItemEntity(BaseModel): + question_index: int + question: str + answer: str + correct_answer: str + is_correct: bool + duration: int + time_spent: float diff --git a/app/models/entities/base.py b/app/models/entities/base.py index b602169..382e102 100644 --- a/app/models/entities/base.py +++ b/app/models/entities/base.py @@ -1,8 +1,10 @@ from bson import ObjectId +from pydantic import GetJsonSchemaHandler +from pydantic.json_schema import JsonSchemaValue class PyObjectId(ObjectId): - """Custom ObjectId type for Pydantic to handle MongoDB _id""" + """Custom ObjectId type for Pydantic v2 to handle MongoDB _id""" @classmethod def __get_validators__(cls): @@ -15,5 +17,7 @@ class PyObjectId(ObjectId): return ObjectId(v) @classmethod - def __modify_schema__(cls, field_schema): - field_schema.update(type="string") + def __get_pydantic_json_schema__( + cls, schema: JsonSchemaValue, handler: GetJsonSchemaHandler + ) -> JsonSchemaValue: + return {"type": "string"} diff --git a/app/models/entities/user_answer_entity.py b/app/models/entities/user_answer_entity.py new file mode 100644 index 0000000..8128ee2 --- /dev/null +++ b/app/models/entities/user_answer_entity.py @@ -0,0 +1,24 @@ +from typing import List, Optional +from pydantic import BaseModel, Field +from datetime import datetime +from bson import ObjectId + +from .answer_item import AnswerItemEntity +from .base import PyObjectId + + +class UserAnswerEntity(BaseModel): + _id: Optional[PyObjectId] = None + session_id: Optional[PyObjectId] + quiz_id: PyObjectId + user_id: str + answered_at: datetime + answers: List[AnswerItemEntity] + total_score: int + total_correct: int + total_questions: int + + class Config: + allow_population_by_field_name = True + arbitrary_types_allowed = True + json_encoders = {ObjectId: str} diff --git a/app/repositories/__init__.py b/app/repositories/__init__.py index 9168773..ba00511 100644 --- a/app/repositories/__init__.py +++ b/app/repositories/__init__.py @@ -1,8 +1,9 @@ from .user_repository import UserRepository from .quiz_repositroy import QuizRepository - +from .answer_repository import UserAnswerRepository __all__ = [ "UserRepository", "QuizRepository", + "UserAnswerRepository", ] diff --git a/app/repositories/answer_repository.py b/app/repositories/answer_repository.py new file mode 100644 index 0000000..fe0c98a --- /dev/null +++ b/app/repositories/answer_repository.py @@ -0,0 +1,32 @@ +from pymongo.collection import Collection +from bson import ObjectId +from typing import Optional, List +from models import UserAnswerEntity + + +class UserAnswerRepository: + def __init__(self, db): + self.collection: Collection = db.user_answers + + def create(self, answer_session: UserAnswerEntity) -> str: + data = answer_session.model_dump(by_alias=True) + result = self.collection.insert_one(data) + return str(result.inserted_id) + + def get_by_id(self, id: str) -> Optional[dict]: + result = self.collection.find_one({"_id": ObjectId(id)}) + return result + + def get_by_user_and_quiz(self, user_id: str, quiz_id: str) -> List[dict]: + result = self.collection.find( + {"user_id": user_id, "quiz_id": ObjectId(quiz_id)} + ) + return list(result) + + def get_by_session(self, session_id: str) -> List[dict]: + result = self.collection.find({"session_id": ObjectId(session_id)}) + return list(result) + + def delete_by_id(self, id: str) -> bool: + result = self.collection.delete_one({"_id": ObjectId(id)}) + return result.deleted_count > 0 diff --git a/app/schemas/requests/__init__.py b/app/schemas/requests/__init__.py index 94bd6c6..1a3f8d3 100644 --- a/app/schemas/requests/__init__.py +++ b/app/schemas/requests/__init__.py @@ -5,9 +5,14 @@ from .quiz import ( QuizCreateSchema, ) +from .answer.answer_request_schema import UserAnswerSchema +from .answer.answer_item_request_schema import AnswerItemSchema + __all__ = [ "RegisterSchema", "QuestionItemSchema", "QuizCreateSchema", + "UserAnswerSchema", + "AnswerItemSchema", ] diff --git a/app/schemas/requests/answer/answer_item_request_schema.py b/app/schemas/requests/answer/answer_item_request_schema.py new file mode 100644 index 0000000..cf553f4 --- /dev/null +++ b/app/schemas/requests/answer/answer_item_request_schema.py @@ -0,0 +1,11 @@ +from pydantic import BaseModel + + +class AnswerItemSchema(BaseModel): + question_index: int + question: str + answer: str + correct_answer: str + is_correct: bool + duration: int + time_spent: float diff --git a/app/schemas/requests/answer/answer_request_schema.py b/app/schemas/requests/answer/answer_request_schema.py new file mode 100644 index 0000000..6a6d1cd --- /dev/null +++ b/app/schemas/requests/answer/answer_request_schema.py @@ -0,0 +1,15 @@ +from typing import List, Optional +from pydantic import BaseModel, Field +from datetime import datetime +from .answer_item_request_schema import AnswerItemSchema + + +class UserAnswerSchema(BaseModel): + session_id: Optional[str] = None + quiz_id: str + user_id: str + answered_at: datetime + total_score: int + total_correct: int + total_questions: int + answers: List[AnswerItemSchema] diff --git a/app/services/__init__.py b/app/services/__init__.py index 116cc83..c35a7ee 100644 --- a/app/services/__init__.py +++ b/app/services/__init__.py @@ -1,10 +1,12 @@ from .auth_service import AuthService from .user_service import UserService from .quiz_service import QuizService +from .answer_service import AnswerService __all__ = [ "AuthService", "UserService", "QuizService", + "AnswerService", ] diff --git a/app/services/answer_service.py b/app/services/answer_service.py new file mode 100644 index 0000000..6291cc7 --- /dev/null +++ b/app/services/answer_service.py @@ -0,0 +1,22 @@ +from repositories import UserAnswerRepository + + +class AnswerService: + def __init__(self, answer_repository: UserAnswerRepository): + self.answer_repository = answer_repository + + def get_answer_by_id(self, answer_id): + return self.answer_repository.get_answer_by_id(answer_id) + + def get_answer(self, quiz_id, user_id, session_id): + if quiz_id is not None: + return self.answer_repository + + def create_answer(self, answer_data): + return self.answer_repository.create(answer_data) + + def update_answer(self, answer_id, answer_data): + return self.answer_repository.update(answer_id, answer_data) + + def delete_answer(self, answer_id): + return self.answer_repository.delete_by_id(answer_id) diff --git a/app/services/quiz_service.py b/app/services/quiz_service.py index 876b7ee..3959d6b 100644 --- a/app/services/quiz_service.py +++ b/app/services/quiz_service.py @@ -23,3 +23,10 @@ class QuizService: def delete_quiz(self, quiz_id): return self.quiz_repository.delete(quiz_id) + + def quiz_recommendation(self): + data = self.quiz_repository + if data is None: + raise DataNotFoundException("Quiz not found") + + return map_quiz_entity_to_schema(data)