From 3c0d8e1e1565b0ea1c539b6e8a2f282f1c675c84 Mon Sep 17 00:00:00 2001 From: akhdanre Date: Sun, 4 May 2025 01:10:18 +0700 Subject: [PATCH] fix: response properties --- app/di_container.py | 1 + app/mapper/quiz_mapper.py | 9 +++- app/models/entities/answer_item.py | 3 +- app/models/entities/quiz_entity.py | 2 +- app/repositories/subject_repository.py | 52 ++++++++++++++----- .../answer/answer_item_request_schema.py | 3 +- .../requests/quiz/create_quiz_schema.py | 1 + .../history/detail_history_response.py | 6 +-- .../response/quiz/quiz_data_rsp_schema.py | 6 ++- .../response/quiz/quiz_get_response.py | 2 + app/services/answer_service.py | 4 +- app/services/quiz_service.py | 35 +++++++++---- 12 files changed, 91 insertions(+), 33 deletions(-) diff --git a/app/di_container.py b/app/di_container.py index af4088c..c992868 100644 --- a/app/di_container.py +++ b/app/di_container.py @@ -50,6 +50,7 @@ class Container(containers.DeclarativeContainer): QuizService, quiz_repository, user_repository, + subject_repository, ) answer_service = providers.Factory( AnswerService, diff --git a/app/mapper/quiz_mapper.py b/app/mapper/quiz_mapper.py index b4aea93..967d749 100644 --- a/app/mapper/quiz_mapper.py +++ b/app/mapper/quiz_mapper.py @@ -1,6 +1,7 @@ from datetime import datetime from helpers import DatetimeUtil from models import QuizEntity, QuestionItemEntity, UserEntity +from models.entities import SubjectEntity from schemas import QuizGetSchema, QuestionItemSchema from schemas.response import RecomendationResponse from schemas.requests import QuizCreateSchema @@ -31,10 +32,15 @@ class QuizMapper: ) @staticmethod - def map_quiz_entity_to_schema(entity: QuizEntity) -> QuizGetSchema: + def map_quiz_entity_to_schema( + entity: QuizEntity, + subjectE: SubjectEntity, + ) -> QuizGetSchema: return QuizGetSchema( id=str(entity.id), author_id=entity.author_id, + subject_id=str(subjectE.id), + subject_alias=subjectE.short_name, title=entity.title, description=entity.description, is_public=entity.is_public, @@ -56,6 +62,7 @@ class QuizMapper: ) -> QuizEntity: return QuizEntity( author_id=schema.author_id, + subject_id=schema.subject_id, title=schema.title, description=schema.description, is_public=schema.is_public, diff --git a/app/models/entities/answer_item.py b/app/models/entities/answer_item.py index 3ce6038..6caa686 100644 --- a/app/models/entities/answer_item.py +++ b/app/models/entities/answer_item.py @@ -1,8 +1,9 @@ from pydantic import BaseModel +from typing import Union class AnswerItemEntity(BaseModel): question_index: int - answer: str + answer: Union[str | int | bool] is_correct: bool time_spent: float diff --git a/app/models/entities/quiz_entity.py b/app/models/entities/quiz_entity.py index d133464..c53cc86 100644 --- a/app/models/entities/quiz_entity.py +++ b/app/models/entities/quiz_entity.py @@ -8,9 +8,9 @@ from .question_item_entity import QuestionItemEntity class QuizEntity(BaseModel): id: Optional[PyObjectId] = Field(default=None, alias="_id") author_id: Optional[str] = None + subject_id: str title: str description: Optional[str] = None - # subject: str is_public: bool = False date: datetime total_quiz: int = 0 diff --git a/app/repositories/subject_repository.py b/app/repositories/subject_repository.py index f9cd236..bf0c99b 100644 --- a/app/repositories/subject_repository.py +++ b/app/repositories/subject_repository.py @@ -1,35 +1,61 @@ from typing import List, Optional from pymongo.database import Database from pymongo.collection import Collection +from bson import ObjectId, errors as bson_errors from models.entities import SubjectEntity -from bson import ObjectId class SubjectRepository: + COLLECTION_NAME = "subjects" + def __init__(self, db: Database): - self.collection: Collection = db.subjects + self.collection: Collection = db[self.COLLECTION_NAME] def create(self, subject: SubjectEntity) -> str: - subject_dict = subject.dict(by_alias=True, exclude_none=True) + subject_dict = subject.model_dump(by_alias=True, exclude_none=True) result = self.collection.insert_one(subject_dict) return str(result.inserted_id) def get_all(self) -> List[SubjectEntity]: - cursor = self.collection.find({}) - return [SubjectEntity(**doc) for doc in cursor] + return [SubjectEntity(**doc) for doc in self.collection.find()] def get_by_id(self, subject_id: str) -> Optional[SubjectEntity]: - doc = self.collection.find_one({"_id": ObjectId(subject_id)}) - if doc: - return SubjectEntity(**doc) - return None + try: + oid = ObjectId(subject_id) + except bson_errors.InvalidId: + return None + + doc = self.collection.find_one({"_id": oid}) + return SubjectEntity(**doc) if doc else None + + def get_by_ids(self, subject_ids: List[str]) -> List[SubjectEntity]: + object_ids = [] + for sid in subject_ids: + try: + object_ids.append(ObjectId(sid)) + except bson_errors.InvalidId: + continue + + if not object_ids: + return [] + + cursor = self.collection.find({"_id": {"$in": object_ids}}) + return [SubjectEntity(**doc) for doc in cursor] def update(self, subject_id: str, update_data: dict) -> bool: - result = self.collection.update_one( - {"_id": ObjectId(subject_id)}, {"$set": update_data} - ) + try: + oid = ObjectId(subject_id) + except bson_errors.InvalidId: + return False + + result = self.collection.update_one({"_id": oid}, {"$set": update_data}) return result.modified_count > 0 def delete(self, subject_id: str) -> bool: - result = self.collection.delete_one({"_id": ObjectId(subject_id)}) + try: + oid = ObjectId(subject_id) + except bson_errors.InvalidId: + return False + + result = self.collection.delete_one({"_id": oid}) return result.deleted_count > 0 diff --git a/app/schemas/requests/answer/answer_item_request_schema.py b/app/schemas/requests/answer/answer_item_request_schema.py index 4ac6387..3d377a8 100644 --- a/app/schemas/requests/answer/answer_item_request_schema.py +++ b/app/schemas/requests/answer/answer_item_request_schema.py @@ -1,8 +1,9 @@ from pydantic import BaseModel +from typing import Union class AnswerItemSchema(BaseModel): question_index: int - answer: str + answer: Union[str | int | bool] is_correct: bool time_spent: float diff --git a/app/schemas/requests/quiz/create_quiz_schema.py b/app/schemas/requests/quiz/create_quiz_schema.py index b797a6c..7193c0e 100644 --- a/app/schemas/requests/quiz/create_quiz_schema.py +++ b/app/schemas/requests/quiz/create_quiz_schema.py @@ -8,5 +8,6 @@ class QuizCreateSchema(BaseModel): title: str description: Optional[str] = None is_public: bool = False + subject_id: str author_id: Optional[str] = None question_listings: Optional[List[QuestionItemSchema]] = [] diff --git a/app/schemas/response/history/detail_history_response.py b/app/schemas/response/history/detail_history_response.py index 60cfd53..60caf23 100644 --- a/app/schemas/response/history/detail_history_response.py +++ b/app/schemas/response/history/detail_history_response.py @@ -1,4 +1,4 @@ -from typing import List, Optional +from typing import List, Optional, Union from pydantic import BaseModel from datetime import datetime @@ -7,8 +7,8 @@ class QuestionResult(BaseModel): index: int question: str type: str - target_answer: str - user_answer: Optional[str] + target_answer: Union[str | bool | int] + user_answer: Optional[Union[str | bool | int]] is_correct: Optional[bool] time_spent: Optional[float] options: Optional[List[str]] diff --git a/app/schemas/response/quiz/quiz_data_rsp_schema.py b/app/schemas/response/quiz/quiz_data_rsp_schema.py index ac8b8fd..efbaae3 100644 --- a/app/schemas/response/quiz/quiz_data_rsp_schema.py +++ b/app/schemas/response/quiz/quiz_data_rsp_schema.py @@ -1,8 +1,10 @@ from typing import List from pydantic import BaseModel -from schemas.response import QuizGetSchema +from schemas.response.recomendation.recomendation_response_schema import ( + RecomendationResponse, +) class UserQuizListResponse(BaseModel): total: int - quizzes: List[QuizGetSchema] + quizzes: List[RecomendationResponse] diff --git a/app/schemas/response/quiz/quiz_get_response.py b/app/schemas/response/quiz/quiz_get_response.py index 6c15937..8680fb5 100644 --- a/app/schemas/response/quiz/quiz_get_response.py +++ b/app/schemas/response/quiz/quiz_get_response.py @@ -7,6 +7,8 @@ from .question_item_schema import QuestionItemSchema class QuizGetSchema(BaseModel): id: str author_id: str + subject_id: str + subject_alias: str title: str description: Optional[str] = None is_public: bool = False diff --git a/app/services/answer_service.py b/app/services/answer_service.py index 68e1010..9b46f2a 100644 --- a/app/services/answer_service.py +++ b/app/services/answer_service.py @@ -47,11 +47,13 @@ class AnswerService: ) correct = False - if question.type in ["fill_the_blank", "true_false"]: + if question.type == "fill_the_blank": correct = ( user_answer.answer.strip().lower() == question.target_answer.strip().lower() ) + elif question.type == "true_false": + correct = user_answer.answer == question.target_answer elif question.type == "option": try: answer_index = int(user_answer.answer) diff --git a/app/services/quiz_service.py b/app/services/quiz_service.py index 4ca8465..e1eb437 100644 --- a/app/services/quiz_service.py +++ b/app/services/quiz_service.py @@ -1,8 +1,6 @@ -from models import QuizEntity -from repositories import QuizRepository, UserRepository -from schemas import QuizGetSchema +from repositories import QuizRepository, UserRepository, SubjectRepository from schemas.requests import QuizCreateSchema -from schemas.response import UserQuizListResponse, RecomendationResponse +from schemas.response import UserQuizListResponse, RecomendationResponse, QuizGetSchema from exception import DataNotFoundException from mapper import QuizMapper from exception import ValidationException @@ -10,15 +8,23 @@ from helpers import DatetimeUtil class QuizService: - def __init__(self, quiz_repository=QuizRepository, user_repository=UserRepository): + def __init__( + self, + quiz_repository=QuizRepository, + user_repository=UserRepository, + subject_repository=SubjectRepository, + ): self.quiz_repository = quiz_repository self.user_repostory = user_repository + self.subject_repository = subject_repository def get_quiz(self, quiz_id) -> QuizGetSchema: data = self.quiz_repository.get_by_id(quiz_id) + if data is None: raise DataNotFoundException("Quiz not found") - return QuizMapper.map_quiz_entity_to_schema(data) + quiz_subject = self.subject_repository.get_by_id(data.subject_id) + return QuizMapper.map_quiz_entity_to_schema(data, quiz_subject) def search_quiz( self, keyword: str, page: int = 1, page_size: int = 10 @@ -46,11 +52,20 @@ class QuizService: self, user_id: str, page: int = 1, page_size: int = 10 ) -> UserQuizListResponse: quizzes = self.quiz_repository.get_by_user_id(user_id, page, page_size) + if not quizzes: + return UserQuizListResponse(total=0, quizzes=[]) + total_user_quiz = self.quiz_repository.count_by_user_id(user_id) - return UserQuizListResponse( - total=total_user_quiz, - quizzes=[QuizMapper.map_quiz_entity_to_schema(quiz) for quiz in quizzes], - ) + + print(total_user_quiz) + + user = self.user_repostory.get_user_by_id(user_id) + + quiz_data = [ + QuizMapper.quiz_to_recomendation_mapper(quiz, user) for quiz in quizzes + ] + + return UserQuizListResponse(total=total_user_quiz, quizzes=quiz_data) def create_quiz(self, quiz_data: QuizCreateSchema): total_time = 0