feat: adding recomendation

This commit is contained in:
akhdanre 2025-05-25 19:08:51 +07:00
parent a099da34f4
commit faf6a0f4b9
11 changed files with 100 additions and 63 deletions

View File

@ -56,14 +56,25 @@ def get_answer(controller: QuizController = Provide[Container.quiz_controller]):
) )
@quiz_bp.route("/recomendation", methods=["GET"]) @quiz_bp.route("/populer", methods=["GET"])
@inject @inject
def get_quiz_recommendation( def get_quiz_populer(
controller: QuizController = Provide[Container.quiz_controller], controller: QuizController = Provide[Container.quiz_controller],
): ):
page = request.args.get("page") page = request.args.get("page")
limit = request.args.get("limit") limit = request.args.get("limit")
return controller.get_quiz_recommendation(page=page, limit=limit) return controller.get_quiz_populer(page=page, limit=limit)
@quiz_bp.route("/recommendation", methods=["GET"])
@inject
def get_quiz_recommendation(
controller: QuizController = Provide[Container.quiz_controller],
):
user_id = request.args.get("user_id")
page = request.args.get("page")
limit = request.args.get("limit")
return controller.get_quiz_recommendation(user_id=user_id, page=page, limit=limit)
@quiz_bp.route("/user/<user_id>", methods=["GET"]) @quiz_bp.route("/user/<user_id>", methods=["GET"])

View File

@ -43,9 +43,9 @@ class QuizController:
except Exception as e: except Exception as e:
return make_error_response(e) return make_error_response(e)
def quiz_recomendation(self): def quiz_populer(self):
try: try:
result = self.quiz_service.get_quiz_recommendation() result = self.quiz_service.get_quiz_populer()
if not result: if not result:
return make_response(message="Quiz not found", status_code=404) return make_response(message="Quiz not found", status_code=404)
return make_response(message="Quiz Found", data=result.model_dump()) return make_response(message="Quiz Found", data=result.model_dump())
@ -94,14 +94,33 @@ class QuizController:
except Exception as e: except Exception as e:
return make_error_response(e) return make_error_response(e)
def get_quiz_recommendation(self, page, limit): def get_quiz_populer(self, page, limit):
try: try:
page = int(page) if page is not None else 1 page = int(page) if page is not None else 1
limit = int(limit) if limit is not None else 3 limit = int(limit) if limit is not None else 3
result = self.quiz_service.get_quiz_recommendation(page=page, limit=limit) result = self.quiz_service.get_quiz_populer(page=page, limit=limit)
return make_response(message="success retrieve populer quiz", data=result)
except DataNotFoundException as e:
return make_response(message=e.message, status_code=e.status_code)
except ValueError as e:
return make_response(message=str(e), data=None, status_code=400)
except ValidationError as e:
return make_response( return make_response(
message="success retrieve recommendation quiz", data=result message="validation error", data=json.loads(e.json()), status_code=400
) )
except Exception as e:
return make_error_response(e)
def get_quiz_recommendation(self, user_id, page, limit):
try:
page = int(page) if page is not None else 1
limit = int(limit) if limit is not None else 3
result = self.quiz_service.get_quiz_recommendation(
user_id=user_id,
page=page,
limit=limit,
)
return make_response(message="success retrieve populer quiz", data=result)
except DataNotFoundException as e: except DataNotFoundException as e:
return make_response(message=e.message, status_code=e.status_code) return make_response(message=e.message, status_code=e.status_code)
except ValueError as e: except ValueError as e:

View File

@ -75,6 +75,7 @@ class Container(containers.DeclarativeContainer):
quiz_repository, quiz_repository,
user_repository, user_repository,
subject_repository, subject_repository,
answer_repository,
) )
answer_service = providers.Factory( answer_service = providers.Factory(
AnswerService, AnswerService,

View File

@ -76,7 +76,7 @@ class QuizMapper:
) )
@staticmethod @staticmethod
def quiz_to_recomendation_mapper( def quiz_to_populer_mapper(
quiz_entity: QuizEntity, quiz_entity: QuizEntity,
user_entity: UserEntity, user_entity: UserEntity,
) -> ListingQuizResponse: ) -> ListingQuizResponse:

View File

@ -14,8 +14,9 @@ class QuizEntity(BaseModel):
is_public: bool = False is_public: bool = False
date: datetime date: datetime
total_quiz: int = 0 total_quiz: int = 0
limit_duration: Optional[int] = 0 # in limit_duration: Optional[int] = 0
total_user_playing: int = 0 total_user_playing: int = 0
language_code: Optional[str] = "id"
question_listings: Optional[list[QuestionItemEntity]] = [] question_listings: Optional[list[QuestionItemEntity]] = []
class ConfigDict: class ConfigDict:

View File

@ -20,34 +20,6 @@ class QuizRepository:
return QuizEntity(**data) return QuizEntity(**data)
return None return None
# def search_by_title_or_category(
# self, keyword: str, page: int, page_size: int
# ) -> List[QuizEntity]:
# skip = (page - 1) * page_size
# pipeline = [
# {
# "$lookup": {
# "from": "category",
# "localField": "category_id",
# "foreignField": "_id",
# "as": "category_info",
# }
# },
# {"$unwind": "$category_info"},
# {
# "$match": {
# "$or": [
# {"title": {"$regex": keyword, "$options": "i"}},
# {"category_info.name": {"$regex": keyword, "$options": "i"}},
# ]
# }
# },
# {"$skip": skip},
# {"$limit": page_size},
# ]
# cursor = self.collection.aggregate(pipeline)
# return [QuizEntity(**doc) for doc in cursor]
def search_by_title_or_category( def search_by_title_or_category(
self, keyword: str, subject_id: Optional[str], page: int, page_size: int self, keyword: str, subject_id: Optional[str], page: int, page_size: int
) -> List[QuizEntity]: ) -> List[QuizEntity]:
@ -86,7 +58,6 @@ class QuizRepository:
object_ids = [ObjectId(qid) for qid in quiz_ids] object_ids = [ObjectId(qid) for qid in quiz_ids]
cursor = self.collection.find({"_id": {"$in": object_ids}}) cursor = self.collection.find({"_id": {"$in": object_ids}})
datas = list(cursor) datas = list(cursor)
if not datas: if not datas:
return None return None
@ -137,3 +108,13 @@ class QuizRepository:
.limit(limit) .limit(limit)
) )
return [QuizEntity(**doc) for doc in cursor] return [QuizEntity(**doc) for doc in cursor]
def get_random_quizzes_by_subjects(
self, subject_ids: List[str], limit: int = 3
) -> List[QuizEntity]:
pipeline = [
{"$match": {"subject_id": {"$in": subject_ids}, "is_public": True}},
{"$sample": {"size": limit}},
]
cursor = self.collection.aggregate(pipeline)
return [QuizEntity(**doc) for doc in cursor]

View File

@ -4,11 +4,11 @@ from .quiz.question_item_schema import QuestionItemSchema
from .quiz.quiz_data_rsp_schema import UserQuizListResponse from .quiz.quiz_data_rsp_schema import UserQuizListResponse
from .history.history_response import HistoryResultSchema from .history.history_response import HistoryResultSchema
from .history.detail_history_response import QuizHistoryResponse, QuestionResult from .history.detail_history_response import QuizHistoryResponse, QuestionResult
from .recomendation.recomendation_response_schema import ListingQuizResponse
from .subject.get_subject_schema import GetSubjectResponse from .subject.get_subject_schema import GetSubjectResponse
from .auth.login_response import LoginResponseSchema from .auth.login_response import LoginResponseSchema
from .user.user_response_scema import UserResponseSchema from .user.user_response_scema import UserResponseSchema
from .answer.answer_session_response import AnsweredQuizResponse from .answer.answer_session_response import AnsweredQuizResponse
from .recomendation.recomendation_response_schema import ListingQuizResponse
__all__ = [ __all__ = [
"QuizCreationResponse", "QuizCreationResponse",

View File

@ -1,8 +1,6 @@
from typing import List from typing import List
from pydantic import BaseModel from pydantic import BaseModel
from app.schemas.response.recomendation.recomendation_response_schema import ( from ..recomendation.recomendation_response_schema import ListingQuizResponse
ListingQuizResponse,
)
class UserQuizListResponse(BaseModel): class UserQuizListResponse(BaseModel):

View File

@ -1,25 +1,34 @@
from app.repositories import QuizRepository, UserRepository, SubjectRepository from app.repositories import (
QuizRepository,
UserRepository,
SubjectRepository,
UserAnswerRepository,
)
from app.schemas.requests import QuizCreateSchema from app.schemas.requests import QuizCreateSchema
from app.schemas.response import ( from app.schemas.response import (
UserQuizListResponse, UserQuizListResponse,
ListingQuizResponse,
QuizGetSchema, QuizGetSchema,
ListingQuizResponse,
) )
from app.exception import DataNotFoundException, ValidationException from app.exception import DataNotFoundException, ValidationException
from app.mapper import QuizMapper from app.mapper import QuizMapper
from app.helpers import DatetimeUtil from app.helpers import DatetimeUtil
from flask import current_app
class QuizService: class QuizService:
def __init__( def __init__(
self, self,
quiz_repository=QuizRepository, quiz_repository: QuizRepository,
user_repository=UserRepository, user_repository: UserRepository,
subject_repository=SubjectRepository, subject_repository: SubjectRepository,
answer_repository: UserAnswerRepository,
): ):
self.quiz_repository = quiz_repository self.quiz_repository = quiz_repository
self.user_repostory = user_repository self.user_repostory = user_repository
self.subject_repository = subject_repository self.subject_repository = subject_repository
self.answer_repository = answer_repository
def get_quiz(self, quiz_id) -> QuizGetSchema: def get_quiz(self, quiz_id) -> QuizGetSchema:
data = self.quiz_repository.get_by_id(quiz_id) data = self.quiz_repository.get_by_id(quiz_id)
@ -46,7 +55,7 @@ class QuizService:
if author is None: if author is None:
continue continue
mapped_quizzes.append( mapped_quizzes.append(
QuizMapper.quiz_to_recomendation_mapper( QuizMapper.quiz_to_populer_mapper(
quiz_entity=quiz, quiz_entity=quiz,
user_entity=author, user_entity=author,
) )
@ -63,13 +72,9 @@ class QuizService:
total_user_quiz = self.quiz_repository.count_by_user_id(user_id) total_user_quiz = self.quiz_repository.count_by_user_id(user_id)
user = self.user_repostory.get_user_by_id(user_id) user = self.user_repostory.get_user_by_id(user_id)
quiz_data = [ quiz_data = [QuizMapper.quiz_to_populer_mapper(quiz, user) for quiz in quizzes]
QuizMapper.quiz_to_recomendation_mapper(quiz, user) for quiz in quizzes
]
return UserQuizListResponse(total=total_user_quiz, quizzes=quiz_data) return UserQuizListResponse(total=total_user_quiz, quizzes=quiz_data)
@ -100,7 +105,7 @@ class QuizService:
def delete_quiz(self, quiz_id): def delete_quiz(self, quiz_id):
return self.quiz_repository.delete(quiz_id) return self.quiz_repository.delete(quiz_id)
def get_quiz_recommendation(self, page: int, limit: int): def get_quiz_populer(self, page: int, limit: int):
data = self.quiz_repository.get_top_played_quizzes(page=page, limit=limit) data = self.quiz_repository.get_top_played_quizzes(page=page, limit=limit)
if not data: if not data:
@ -110,9 +115,30 @@ class QuizService:
for quiz in data: for quiz in data:
author = self.user_repostory.get_user_by_id(user_id=quiz.author_id) author = self.user_repostory.get_user_by_id(user_id=quiz.author_id)
result.append( result.append(
QuizMapper.quiz_to_recomendation_mapper( QuizMapper.quiz_to_populer_mapper(
quiz_entity=quiz, quiz_entity=quiz,
user_entity=author, user_entity=author,
) )
) )
return result return result
def get_quiz_recommendation(self, user_id: str, page: int, limit: int):
current_app.logger.info("in here is should execute")
user_answer = self.answer_repository.get_by_user(user_id=user_id)
if not user_answer:
return []
quiz_ids = list({answer.quiz_id for answer in user_answer})
quiz_work = self.quiz_repository.get_by_ids(quiz_ids)
if not quiz_work:
return []
quiz_subjects = list({quiz.subject_id for quiz in quiz_work})
result = self.quiz_repository.get_random_quizzes_by_subjects(
subject_ids=quiz_subjects, limit=limit
)
return result

View File

@ -225,7 +225,7 @@ paths:
"404": "404":
$ref: "#/components/responses/NotFound" $ref: "#/components/responses/NotFound"
/quiz/recomendation: /quiz/populer:
get: get:
summary: Get recommended quizzes summary: Get recommended quizzes
description: Returns a list of recommended quizzes for the user description: Returns a list of recommended quizzes for the user
@ -235,7 +235,7 @@ paths:
- $ref: "#/components/parameters/LimitParam" - $ref: "#/components/parameters/LimitParam"
responses: responses:
"200": "200":
description: Successfully retrieved recommendation quiz list description: Successfully retrieved populer quiz list
content: content:
application/json: application/json:
schema: schema:
@ -243,7 +243,7 @@ paths:
properties: properties:
message: message:
type: string type: string
example: success retrieve recommendation quiz example: success retrieve populer quiz
data: data:
type: array type: array
items: items:

View File

@ -114,7 +114,7 @@
# assert result.quizzes == [] # assert result.quizzes == []
# def test_get_quiz_recommendation_found(quiz_service, mock_repositories): # def test_get_quiz_populer_found(quiz_service, mock_repositories):
# quiz = QuizEntity( # quiz = QuizEntity(
# id=ObjectId(), # id=ObjectId(),
# author_id="user1", # author_id="user1",
@ -131,14 +131,14 @@
# mock_repositories["quiz_repository"].get_top_played_quizzes.return_value = [quiz] # mock_repositories["quiz_repository"].get_top_played_quizzes.return_value = [quiz]
# mock_repositories["user_repository"].get_user_by_id.return_value = MagicMock() # mock_repositories["user_repository"].get_user_by_id.return_value = MagicMock()
# result = quiz_service.get_quiz_recommendation(1, 5) # result = quiz_service.get_quiz_populer(1, 5)
# assert len(result) == 1 # assert len(result) == 1
# def test_get_quiz_recommendation_not_found(quiz_service, mock_repositories): # def test_get_quiz_populer_not_found(quiz_service, mock_repositories):
# mock_repositories["quiz_repository"].get_top_played_quizzes.return_value = [] # mock_repositories["quiz_repository"].get_top_played_quizzes.return_value = []
# with pytest.raises(DataNotFoundException): # with pytest.raises(DataNotFoundException):
# quiz_service.get_quiz_recommendation(1, 5) # quiz_service.get_quiz_populer(1, 5)
# def test_update_quiz(quiz_service, mock_repositories): # def test_update_quiz(quiz_service, mock_repositories):