fix: answer input
This commit is contained in:
parent
bd3db93279
commit
0773609fda
|
@ -51,9 +51,9 @@ def get_quiz_recommendation(
|
|||
|
||||
@quiz_bp.route("/user/<user_id>", methods=["GET"])
|
||||
@inject
|
||||
def get_user_quiz(controller: QuizController = Provide[Container.quiz_controller]):
|
||||
def get_user_quiz(
|
||||
user_id: str, controller: QuizController = Provide[Container.quiz_controller]
|
||||
):
|
||||
page = request.args.get("page", default=1, type=int)
|
||||
page_size = request.args.get("page_size", default=10, type=int)
|
||||
return controller.get_user_quiz(
|
||||
user_id=request.view_args["user_id"], page=page, page_size=page_size
|
||||
)
|
||||
return controller.get_user_quiz(user_id=user_id, page=page, page_size=page_size)
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
from flask import jsonify
|
||||
import json
|
||||
from pydantic import ValidationError
|
||||
from schemas.requests import QuizCreateSchema, UserAnswerSchema
|
||||
from schemas.response import QuizCreationResponse
|
||||
from schemas import MetaSchema
|
||||
from services import QuizService, AnswerService
|
||||
from helpers import make_response, make_error_response
|
||||
from exception import ValidationException
|
||||
|
||||
|
||||
class QuizController:
|
||||
|
@ -29,7 +31,7 @@ class QuizController:
|
|||
data=QuizCreationResponse(quiz_id=quiz_id),
|
||||
status_code=201,
|
||||
)
|
||||
except ValidationError as e:
|
||||
except (ValidationError, ValidationException) as e:
|
||||
return make_response(e.errors(), status_code=400)
|
||||
except Exception as e:
|
||||
return make_error_response(e)
|
||||
|
@ -46,11 +48,11 @@ class QuizController:
|
|||
# except ValidationError as e:
|
||||
# return jsonify({"error": "Validation error", "detail": e.errors()}), 400
|
||||
|
||||
def delete_quiz(self, quiz_id):
|
||||
success = self.quiz_service.delete_quiz(quiz_id)
|
||||
if not success:
|
||||
return jsonify({"error": "Quiz not found"}), 400
|
||||
return jsonify({"message": "Quiz deleted"}), 200
|
||||
# def delete_quiz(self, quiz_id):
|
||||
# success = self.quiz_service.delete_quiz(quiz_id)
|
||||
# if not success:
|
||||
# return jsonify({"error": "Quiz not found"}), 400
|
||||
# return jsonify({"message": "Quiz deleted"}), 200
|
||||
|
||||
def quiz_recomendation(self):
|
||||
try:
|
||||
|
@ -63,16 +65,21 @@ class QuizController:
|
|||
|
||||
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},
|
||||
data={"answer_id": True},
|
||||
status_code=201,
|
||||
)
|
||||
except ValidationError as e:
|
||||
return make_response(e.errors(), status_code=400)
|
||||
return make_response(
|
||||
message="validation error", data=json.loads(e.json()), status_code=400
|
||||
)
|
||||
except ValidationException as e:
|
||||
return make_response(
|
||||
message=f"validation issue {e.message}", status_code=400
|
||||
)
|
||||
except Exception as e:
|
||||
return make_error_response(e)
|
||||
|
||||
|
@ -89,7 +96,11 @@ class QuizController:
|
|||
user_id=user_id, page=page, page_size=page_size
|
||||
)
|
||||
return make_response(
|
||||
message="User quizzes retrieved successfully", data=result
|
||||
message="User quizzes retrieved successfully",
|
||||
data=result.quizzes,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
total_all_data=result.total,
|
||||
)
|
||||
except Exception as e:
|
||||
return make_error_response(e)
|
||||
|
|
|
@ -18,7 +18,12 @@ class Container(containers.DeclarativeContainer):
|
|||
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)
|
||||
answer_service = providers.Factory(
|
||||
AnswerService,
|
||||
answer_repository,
|
||||
quiz_repository,
|
||||
user_repository,
|
||||
)
|
||||
|
||||
# controllers
|
||||
auth_controller = providers.Factory(AuthController, user_service, auth_service)
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
from .auth_exception import AuthException
|
||||
from .already_exist_exception import AlreadyExistException
|
||||
from .data_not_found_exception import DataNotFoundException
|
||||
from .validation_exception import ValidationException
|
||||
|
||||
|
||||
__all__ = [
|
||||
"AuthException",
|
||||
"AlreadyExistException",
|
||||
"DataNotFoundException",
|
||||
"ValidationException",
|
||||
]
|
||||
|
|
|
@ -8,3 +8,6 @@ class BaseExceptionTemplate(Exception):
|
|||
|
||||
def __str__(self):
|
||||
return f"{self.__class__.__name__}: {self.message}"
|
||||
|
||||
def json(self):
|
||||
return {"error": self.__class__.__name__, "message": self.message}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
from .base_exception import BaseExceptionTemplate
|
||||
|
||||
|
||||
class ValidationException(BaseExceptionTemplate):
|
||||
"""Exception for validation"""
|
||||
|
||||
def __init__(self, message: str = "validation error, check yout input"):
|
||||
super().__init__(message, status_code=400)
|
|
@ -1,15 +1,37 @@
|
|||
from flask import jsonify, current_app
|
||||
from typing import Optional, Union
|
||||
from schemas import ResponseSchema
|
||||
from schemas import ResponseSchema, MetaSchema
|
||||
import math
|
||||
|
||||
|
||||
def calculate_total_page(total_data: int, page_size: int) -> int:
|
||||
if not page_size or page_size <= 0:
|
||||
return 1
|
||||
return math.ceil(total_data / page_size)
|
||||
|
||||
|
||||
def make_response(
|
||||
message: str,
|
||||
data: Optional[dict] = None,
|
||||
meta: Optional[dict] = None,
|
||||
data: Optional[Union[dict, list]] = None,
|
||||
page: Optional[int] = None,
|
||||
page_size: Optional[int] = None,
|
||||
total_all_data: Optional[int] = None,
|
||||
status_code: int = 200,
|
||||
):
|
||||
response = ResponseSchema(message=message, data=data, meta=meta)
|
||||
meta = None
|
||||
if page is not None and page_size is not None and total_all_data is not None:
|
||||
meta = MetaSchema(
|
||||
current_page=page,
|
||||
total_all_data=total_all_data,
|
||||
total_data=len(data) if data else 0,
|
||||
total_page=calculate_total_page(total_all_data, page_size),
|
||||
)
|
||||
|
||||
response = ResponseSchema(
|
||||
message=message,
|
||||
data=data,
|
||||
meta=meta,
|
||||
)
|
||||
return jsonify(response.model_dump()), status_code
|
||||
|
||||
|
||||
|
|
|
@ -4,10 +4,12 @@ from schemas import QuizGetSchema, QuestionItemSchema
|
|||
|
||||
def map_question_entity_to_schema(entity: QuestionItemEntity) -> QuestionItemSchema:
|
||||
return QuestionItemSchema(
|
||||
index=entity.index,
|
||||
question=entity.question,
|
||||
target_answer=entity.target_answer,
|
||||
duration=entity.duration,
|
||||
type=entity.type,
|
||||
options=entity.options,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ from .base import PyObjectId
|
|||
from .quiz_entity import QuizEntity
|
||||
from .question_item_entity import QuestionItemEntity
|
||||
from .user_answer_entity import UserAnswerEntity
|
||||
from .answer_item import AnswerItemEntity
|
||||
|
||||
__all__ = [
|
||||
"UserEntity",
|
||||
|
|
|
@ -3,9 +3,6 @@ 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
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
from typing import Optional, List
|
||||
from pydantic import BaseModel
|
||||
from datetime import datetime
|
||||
from .base import PyObjectId
|
||||
|
||||
|
||||
class QuestionItemEntity(BaseModel):
|
||||
_id: Optional[PyObjectId] = None
|
||||
index: int
|
||||
question: str
|
||||
target_answer: str
|
||||
duration: int
|
||||
type: str # "isian" | "true_false"
|
||||
type: str
|
||||
options: Optional[List[str]] = None
|
||||
|
|
|
@ -9,8 +9,8 @@ from .base import PyObjectId
|
|||
|
||||
class UserAnswerEntity(BaseModel):
|
||||
_id: Optional[PyObjectId] = None
|
||||
session_id: Optional[PyObjectId]
|
||||
quiz_id: PyObjectId
|
||||
session_id: Optional[str]
|
||||
quiz_id: str
|
||||
user_id: str
|
||||
answered_at: datetime
|
||||
answers: List[AnswerItemEntity]
|
||||
|
|
|
@ -2,6 +2,7 @@ from bson import ObjectId
|
|||
from typing import List, Optional
|
||||
from models import QuizEntity
|
||||
from pymongo.database import Database
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class QuizRepository:
|
||||
|
@ -24,11 +25,20 @@ class QuizRepository:
|
|||
) -> List[QuizEntity]:
|
||||
skip = (page - 1) * page_size
|
||||
cursor = (
|
||||
self.collection.find({"user_id": ObjectId(user_id)})
|
||||
.skip(skip)
|
||||
.limit(page_size)
|
||||
self.collection.find({"author_id": user_id}).skip(skip).limit(page_size)
|
||||
)
|
||||
return [QuizEntity(**doc) for doc in cursor]
|
||||
|
||||
quiz_list = []
|
||||
for doc in cursor:
|
||||
if "date" in doc and isinstance(doc["date"], str):
|
||||
try:
|
||||
doc["date"] = datetime.strptime(doc["date"], "%d-%m-%Y")
|
||||
except ValueError:
|
||||
doc["date"] = None
|
||||
|
||||
quiz_list.append(QuizEntity(**doc))
|
||||
|
||||
return quiz_list
|
||||
|
||||
def get_all(self, skip: int = 0, limit: int = 10) -> List[QuizEntity]:
|
||||
cursor = self.collection.find().skip(skip).limit(limit)
|
||||
|
@ -43,3 +53,6 @@ class QuizRepository:
|
|||
def delete(self, quiz_id: str) -> bool:
|
||||
result = self.collection.delete_one({"_id": ObjectId(quiz_id)})
|
||||
return result.deleted_count > 0
|
||||
|
||||
def count_by_user_id(self, user_id: str) -> int:
|
||||
return self.collection.count_documents({"author_id": user_id})
|
||||
|
|
|
@ -3,9 +3,6 @@ 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
|
||||
|
|
|
@ -5,11 +5,8 @@ from .answer_item_request_schema import AnswerItemSchema
|
|||
|
||||
|
||||
class UserAnswerSchema(BaseModel):
|
||||
session_id: Optional[str] = None
|
||||
quiz_id: str
|
||||
user_id: str
|
||||
session_id: Optional[str] = None
|
||||
answered_at: datetime
|
||||
total_score: int
|
||||
total_correct: int
|
||||
total_questions: int
|
||||
answers: List[AnswerItemSchema]
|
||||
|
|
|
@ -3,6 +3,7 @@ from pydantic import BaseModel
|
|||
|
||||
|
||||
class QuestionItemSchema(BaseModel):
|
||||
index: int
|
||||
question: str
|
||||
target_answer: str
|
||||
duration: int
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
from .quiz.quiz_creation_response import QuizCreationResponse
|
||||
from .quiz.quiz_get_response import QuizGetSchema
|
||||
from .quiz.question_item_schema import QuestionItemSchema
|
||||
from .quiz.quiz_data_rsp_schema import UserQuizListResponse
|
||||
|
||||
__all__ = [
|
||||
"QuizCreationResponse",
|
||||
"QuizGetSchema",
|
||||
"QuestionItemSchema",
|
||||
"UserQuizListResponse"
|
||||
]
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
from typing import List, Optional
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class QuestionItemSchema(BaseModel):
|
||||
index: int
|
||||
question: str
|
||||
target_answer: str
|
||||
duration: int
|
||||
type: str
|
||||
options: Optional[List[str]] = None
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
from typing import List
|
||||
from pydantic import BaseModel
|
||||
from schemas.response import QuizGetSchema
|
||||
|
||||
|
||||
class UserQuizListResponse(BaseModel):
|
||||
total: int
|
||||
quizzes: List[QuizGetSchema]
|
|
@ -1,9 +1,20 @@
|
|||
from repositories import UserAnswerRepository
|
||||
from repositories import UserAnswerRepository, QuizRepository, UserRepository
|
||||
from schemas.requests import UserAnswerSchema
|
||||
from models import UserAnswerEntity
|
||||
from models.entities import AnswerItemEntity
|
||||
from exception import ValidationException
|
||||
|
||||
|
||||
class AnswerService:
|
||||
def __init__(self, answer_repository: UserAnswerRepository):
|
||||
def __init__(
|
||||
self,
|
||||
answer_repository: UserAnswerRepository,
|
||||
quiz_repository: QuizRepository,
|
||||
user_repositroy: UserRepository,
|
||||
):
|
||||
self.answer_repository = answer_repository
|
||||
self.quiz_repository = quiz_repository
|
||||
self.user_repositroy = user_repositroy
|
||||
|
||||
def get_answer_by_id(self, answer_id):
|
||||
return self.answer_repository.get_answer_by_id(answer_id)
|
||||
|
@ -12,8 +23,76 @@ class AnswerService:
|
|||
if quiz_id is not None:
|
||||
return self.answer_repository
|
||||
|
||||
def create_answer(self, answer_data):
|
||||
return self.answer_repository.create(answer_data)
|
||||
def create_answer(self, answer_data: UserAnswerSchema):
|
||||
quiz_data = self.quiz_repository.get_by_id(answer_data.quiz_id)
|
||||
user_data = self.user_repositroy.get_user_by_id(answer_data.user_id)
|
||||
if not user_data:
|
||||
raise ValidationException(message="user is not registered")
|
||||
|
||||
question_map = {q.index: q for q in quiz_data.question_listings}
|
||||
|
||||
answer_item_Entity = []
|
||||
total_correct = 0
|
||||
for user_answer in answer_data.answers:
|
||||
question = question_map.get(user_answer.question_index)
|
||||
if question is None:
|
||||
raise ValueError(
|
||||
f"Question index {user_answer.question_index} tidak ditemukan di kuis."
|
||||
)
|
||||
|
||||
correct = False
|
||||
if question.type in ["fill_the_blank", "true_false"]:
|
||||
correct = (
|
||||
user_answer.answer.strip().lower()
|
||||
== question.target_answer.strip().lower()
|
||||
)
|
||||
elif question.type == "option":
|
||||
try:
|
||||
answer_index = int(user_answer.answer)
|
||||
if 0 <= answer_index < len(question.options):
|
||||
correct = str(answer_index) == question.target_answer
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Index jawaban tidak valid untuk soal {question.index}"
|
||||
)
|
||||
except ValueError:
|
||||
raise ValueError(
|
||||
f"Jawaban bukan index valid untuk soal {question.index}"
|
||||
)
|
||||
else:
|
||||
raise ValueError(f"Tipe soal tidak dikenali: {question.type}")
|
||||
|
||||
user_answer.is_correct = correct
|
||||
if correct:
|
||||
total_correct += 1
|
||||
|
||||
answer_item_Entity.append(
|
||||
AnswerItemEntity(
|
||||
question_index=user_answer.question_index,
|
||||
answer=user_answer.answer,
|
||||
is_correct=user_answer.is_correct,
|
||||
time_spent=user_answer.time_spent,
|
||||
)
|
||||
)
|
||||
total_questions = len(quiz_data.question_listings)
|
||||
total_score = (
|
||||
total_correct * 100 // total_questions
|
||||
) # contoh perhitungan: nilai 100 dibagi rata
|
||||
|
||||
# Buat entitas yang akan disimpan
|
||||
answer_entity = UserAnswerEntity(
|
||||
session_id=answer_data.session_id,
|
||||
quiz_id=answer_data.quiz_id,
|
||||
user_id=answer_data.user_id,
|
||||
answered_at=answer_data.answered_at,
|
||||
answers=answer_item_Entity,
|
||||
total_correct=total_correct,
|
||||
total_questions=total_questions,
|
||||
total_score=total_score,
|
||||
)
|
||||
|
||||
self.answer_repository.create(answer_entity)
|
||||
return True
|
||||
|
||||
def update_answer(self, answer_id, answer_data):
|
||||
return self.answer_repository.update(answer_id, answer_data)
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
from typing import List
|
||||
from repositories import QuizRepository
|
||||
from schemas import QuizGetSchema
|
||||
from schemas.requests import QuizCreateSchema
|
||||
from schemas.response import UserQuizListResponse
|
||||
from exception import DataNotFoundException
|
||||
from mapper import map_quiz_entity_to_schema
|
||||
from exception import ValidationException
|
||||
|
||||
|
||||
class QuizService:
|
||||
|
@ -13,16 +16,27 @@ class QuizService:
|
|||
data = self.quiz_repository.get_by_id(quiz_id)
|
||||
if data is None:
|
||||
raise DataNotFoundException("Quiz not found")
|
||||
|
||||
return map_quiz_entity_to_schema(data)
|
||||
|
||||
def get_user_quiz(
|
||||
self, user_id: str, page: int = 1, page_size: int = 10
|
||||
) -> List[QuizGetSchema]:
|
||||
) -> UserQuizListResponse:
|
||||
quizzes = self.quiz_repository.get_by_user_id(user_id, page, page_size)
|
||||
return [QuizGetSchema.model_validate(quiz) for quiz in quizzes]
|
||||
total_user_quiz = self.quiz_repository.count_by_user_id(user_id)
|
||||
return UserQuizListResponse(
|
||||
total=total_user_quiz,
|
||||
quizzes=[map_quiz_entity_to_schema(quiz) for quiz in quizzes],
|
||||
)
|
||||
|
||||
def create_quiz(self, quiz_data: QuizCreateSchema):
|
||||
for question in quiz_data.question_listings:
|
||||
if question.type == "option" and (
|
||||
not question.options or len(question.options) != 4
|
||||
):
|
||||
raise ValidationException(
|
||||
"Option type questions must have exactly 4 options."
|
||||
)
|
||||
|
||||
def create_quiz(self, quiz_data):
|
||||
return self.quiz_repository.create(quiz_data)
|
||||
|
||||
def update_quiz(self, quiz_id, quiz_data):
|
||||
|
|
Loading…
Reference in New Issue