feat: new subject request

This commit is contained in:
akhdanre 2025-05-01 15:14:09 +07:00
parent a2df303e02
commit ad00d2b9de
20 changed files with 330 additions and 15 deletions

View File

@ -4,6 +4,7 @@ from .auth import auth_blueprint
from .user import user_blueprint
from .quiz import quiz_bp
from .history import history_blueprint
from .subject import subject_blueprint
__all__ = [
"default_blueprint",
@ -11,6 +12,7 @@ __all__ = [
"user_blueprint",
"quiz_bp",
"history_blueprint",
"subject_blueprint",
]

50
app/blueprints/subject.py Normal file
View File

@ -0,0 +1,50 @@
from flask import Blueprint, request
from di_container import Container
from dependency_injector.wiring import inject, Provide
from controllers import SubjectController
subject_blueprint = Blueprint("subject", __name__)
@subject_blueprint.route("", methods=["POST"])
@inject
def create_subject(
controller: SubjectController = Provide[Container.subject_controller],
):
return controller.create(request.get_json())
@subject_blueprint.route("", methods=["GET"])
@inject
def get_all_subjects(
controller: SubjectController = Provide[Container.subject_controller],
):
return controller.get_all()
@subject_blueprint.route("/<subject_id>", methods=["GET"])
@inject
def get_subject(
subject_id: str,
controller: SubjectController = Provide[Container.subject_controller],
):
return controller.get_by_id(subject_id)
@subject_blueprint.route("/<subject_id>", methods=["PUT"])
@inject
def update_subject(
subject_id: str,
controller: SubjectController = Provide[Container.subject_controller],
):
return controller.update(subject_id, request.get_json())
@subject_blueprint.route("/<subject_id>", methods=["DELETE"])
@inject
def delete_subject(
subject_id: str,
controller: SubjectController = Provide[Container.subject_controller],
):
return controller.delete(subject_id)

View File

@ -2,11 +2,12 @@ from .auth_controller import AuthController
from .user_controller import UserController
from .quiz_controller import QuizController
from .history_controller import HistoryController
from .subject_controller import SubjectController
__all__ = [
"AuthController",
"UserController",
"QuizController",
"HistoryController",
"SubjectController",
]

View File

@ -0,0 +1,48 @@
from services.subject_service import SubjectService
from helpers import make_response, make_error_response
class SubjectController:
def __init__(self, service: SubjectService):
self.service = service
def create(self, req_body):
try:
new_id = self.service.create_subject(req_body)
return make_response(message="Subject created", data={"id": new_id})
except Exception as e:
return make_error_response(e)
def get_all(self):
try:
subjects = self.service.get_all_subjects()
return make_response(message="success retrieve subject", data=subjects)
except Exception as e:
return make_error_response(e)
def get_by_id(self, subject_id: str):
try:
subject = self.service.get_subject_by_id(subject_id)
if not subject:
return make_response(message="Subject not found", status_code=404)
return make_response(data=subject.model_dump())
except Exception as e:
return make_error_response(e)
def update(self, subject_id: str, req_body):
try:
updated = self.service.update_subject(subject_id, req_body)
if not updated:
return make_response(message="No subject updated", status_code=404)
return make_response(message="Subject updated")
except Exception as e:
return make_error_response(e)
def delete(self, subject_id: str):
try:
deleted = self.service.delete_subject(subject_id)
if not deleted:
return make_response(message="No subject deleted", status_code=404)
return make_response(message="Subject deleted")
except Exception as e:
return make_error_response(e)

View File

@ -1,5 +1,6 @@
from flask_pymongo import PyMongo
from flask import Flask, current_app
from .seed.subject_seed import seed_subjects
def init_db(app: Flask) -> PyMongo:
@ -8,8 +9,8 @@ def init_db(app: Flask) -> PyMongo:
mongo.cx.server_info()
app.logger.info("MongoDB connection established")
seed_subjects(mongo)
return mongo
except Exception as e:
app.logger.error(f"MongoDB connection failed: {e}")
return None # Handle failure gracefully
return None

View File

@ -0,0 +1,42 @@
from flask_pymongo import PyMongo
def seed_subjects(mongo: PyMongo):
subject_collection = mongo.db.subjects
base_subjects = [
{
"name": "Ilmu Pengetahuan Alam",
"short_name": "IPA",
"description": "Pelajaran tentang sains dan alam",
},
{
"name": "Ilmu Pengetahuan Sosial",
"short_name": "IPS",
"description": "Pelajaran tentang masyarakat dan geografi",
},
{
"name": "Sejarah",
"short_name": "Sejarah",
"description": "Pelajaran mengenai sejarah di indonesia",
},
{
"name": "Matematika",
"short_name": "Matematika",
"description": "Pelajaran tentang angka dan logika",
},
{
"name": "Bahasa Indonesia",
"short_name": "B.Indonesia",
"description": "Pelajaran tentang bahasa nasional",
},
{
"name": "Sejarah",
"short_name": "Sejarah",
"description": "Pelajaran sejarah Indonesia",
},
]
for subject in base_subjects:
if not subject_collection.find_one({"name": subject["name"]}):
subject_collection.insert_one(subject)

View File

@ -1,17 +1,26 @@
from dependency_injector import containers, providers
from controllers import (
UserController,
AuthController,
QuizController,
HistoryController,
from repositories import (
UserRepository,
QuizRepository,
UserAnswerRepository,
SubjectRepository,
)
from repositories import UserRepository, QuizRepository, UserAnswerRepository
from services import (
UserService,
AuthService,
QuizService,
AnswerService,
HistoryService,
SubjectService,
)
from controllers import (
UserController,
AuthController,
QuizController,
HistoryController,
SubjectController,
)
@ -24,10 +33,19 @@ class Container(containers.DeclarativeContainer):
user_repository = providers.Factory(UserRepository, mongo.provided.db)
quiz_repository = providers.Factory(QuizRepository, mongo.provided.db)
answer_repository = providers.Factory(UserAnswerRepository, mongo.provided.db)
subject_repository = providers.Factory(SubjectRepository, mongo.provided.db)
# services
auth_service = providers.Factory(AuthService, user_repository)
user_service = providers.Factory(UserService, user_repository)
auth_service = providers.Factory(
AuthService,
user_repository,
)
user_service = providers.Factory(
UserService,
user_repository,
)
quiz_service = providers.Factory(
QuizService,
quiz_repository,
@ -46,8 +64,14 @@ class Container(containers.DeclarativeContainer):
answer_repository,
)
subject_service = providers.Factory(
SubjectService,
subject_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, answer_service)
history_controller = providers.Factory(HistoryController, history_service)
subject_controller = providers.Factory(SubjectController, subject_service)

View File

@ -12,6 +12,7 @@ from blueprints import (
quiz_bp,
default_blueprint,
history_blueprint,
subject_blueprint,
)
from database import init_db
import logging
@ -34,16 +35,13 @@ def createApp() -> Flask:
if mongo is not None:
container.mongo.override(mongo)
# container.wire(modules=["blueprints.auth"])
# container.wire(modules=["blueprints.user"])
# container.wire(modules=["blueprints.quiz"])
container.wire(
modules=[
"blueprints.auth",
"blueprints.user",
"blueprints.quiz",
"blueprints.history",
"blueprints.subject",
]
)
@ -53,6 +51,7 @@ def createApp() -> Flask:
app.register_blueprint(user_blueprint, url_prefix="/api")
app.register_blueprint(quiz_bp, url_prefix="/api/quiz")
app.register_blueprint(history_blueprint, url_prefix="/api/history")
app.register_blueprint(subject_blueprint, url_prefix="/api/subject")
# for rule in app.url_map.iter_rules():
# print(f"Route: {rule} -> Methods: {rule.methods}")

View File

@ -4,6 +4,7 @@ from .quiz_entity import QuizEntity
from .question_item_entity import QuestionItemEntity
from .user_answer_entity import UserAnswerEntity
from .answer_item import AnswerItemEntity
from .subject_entity import SubjectEntity
__all__ = [
"UserEntity",
@ -11,4 +12,6 @@ __all__ = [
"QuizEntity",
"QuestionItemEntity",
"UserAnswerEntity",
"AnswerItemEntity",
"SubjectEntity",
]

View File

@ -10,6 +10,7 @@ class QuizEntity(BaseModel):
author_id: Optional[str] = None
title: str
description: Optional[str] = None
subject: str
is_public: bool = False
date: Optional[datetime] = None
total_quiz: Optional[int] = 0

View File

@ -0,0 +1,24 @@
from typing import Optional
from bson import ObjectId
from pydantic import BaseModel, Field
from models.entities import PyObjectId
class SubjectEntity(BaseModel):
id: Optional[PyObjectId] = Field(default=None, alias="_id")
name: str
short_name: str
description: Optional[str] = None
icon: Optional[str] = None
class Config:
populate_by_name = True
json_encoders = {ObjectId: str}
json_schema_extra = {
"example": {
"_id": "sejarah",
"name": "Sejarah",
"description": "Kuis tentang sejarah Indonesia",
"icon": "http://",
}
}

View File

@ -1,9 +1,11 @@
from .user_repository import UserRepository
from .quiz_repositroy import QuizRepository
from .answer_repository import UserAnswerRepository
from .subject_repository import SubjectRepository
__all__ = [
"UserRepository",
"QuizRepository",
"UserAnswerRepository",
"SubjectRepository",
]

View File

@ -0,0 +1,35 @@
from typing import List, Optional
from pymongo.database import Database
from pymongo.collection import Collection
from models.entities import SubjectEntity
from bson import ObjectId
class SubjectRepository:
def __init__(self, db: Database):
self.collection: Collection = db.subjects
def create(self, subject: SubjectEntity) -> str:
subject_dict = subject.dict(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]
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
def update(self, subject_id: str, update_data: dict) -> bool:
result = self.collection.update_one(
{"_id": ObjectId(subject_id)}, {"$set": update_data}
)
return result.modified_count > 0
def delete(self, subject_id: str) -> bool:
result = self.collection.delete_one({"_id": ObjectId(subject_id)})
return result.deleted_count > 0

View File

@ -8,6 +8,9 @@ from .quiz import (
from .answer.answer_request_schema import UserAnswerSchema
from .answer.answer_item_request_schema import AnswerItemSchema
from .subject.create_subject_schema import SubjectCreateRequest
from .subject.update_subject_schema import SubjectUpdateRequest
__all__ = [
"RegisterSchema",
@ -15,4 +18,6 @@ __all__ = [
"QuizCreateSchema",
"UserAnswerSchema",
"AnswerItemSchema",
"SubjectCreateRequest",
"SubjectUpdateRequest",
]

View File

@ -0,0 +1,10 @@
from pydantic import BaseModel, Field
from typing import Optional
class SubjectCreateRequest(BaseModel):
name: str = Field(..., example="Ilmu Pengetahuan ALam")
alias: str = Field(..., examples="IPA", alias="short_name")
description: Optional[str] = Field(
None, example="Pelajaran tentang angka dan logika"
)

View File

@ -0,0 +1,7 @@
from pydantic import BaseModel, Field
from typing import Optional
class SubjectUpdateRequest(BaseModel):
name: Optional[str] = Field(None, example="Fisika")
description: Optional[str] = Field(None, example="Pelajaran tentang hukum alam")

View File

@ -5,6 +5,7 @@ from .quiz.quiz_data_rsp_schema import UserQuizListResponse
from .history.history_response import HistoryResultSchema
from .history.detail_history_response import QuizHistoryResponse, QuestionResult
from .recomendation.recomendation_response_schema import RecomendationResponse
from .subject.get_subject_schema import GetSubjectResponse
__all__ = [
"QuizCreationResponse",
@ -15,4 +16,5 @@ __all__ = [
"QuizHistoryResponse",
"QuestionResult",
"RecomendationResponse",
"GetSubjectResponse",
]

View File

@ -0,0 +1,13 @@
from pydantic import BaseModel, Field
from typing import Optional
class GetSubjectResponse(BaseModel):
id: str
name: str
alias: str
description: Optional[str]
class Config:
orm_mode = True
allow_population_by_field_name = True

View File

@ -3,6 +3,7 @@ from .user_service import UserService
from .quiz_service import QuizService
from .answer_service import AnswerService
from .history_service import HistoryService
from .subject_service import SubjectService
__all__ = [
"AuthService",
@ -10,4 +11,5 @@ __all__ = [
"QuizService",
"AnswerService",
"HistoryService",
"SubjectService",
]

View File

@ -0,0 +1,44 @@
from typing import List, Optional
from models.entities import SubjectEntity
from schemas.requests import SubjectCreateRequest, SubjectUpdateRequest
from schemas.response import GetSubjectResponse
from repositories import SubjectRepository
class SubjectService:
def __init__(self, repository: SubjectRepository):
self.repository = repository
def create_subject(self, request: SubjectCreateRequest) -> str:
subject = SubjectEntity(**request)
return self.repository.create(subject)
def get_all_subjects(self) -> List[GetSubjectResponse]:
subjects = self.repository.get_all()
return [
GetSubjectResponse(
id=str(subject.id),
name=subject.name,
alias=subject.short_name,
description=subject.description,
)
for subject in subjects
]
def get_subject_by_id(self, subject_id: str) -> Optional[GetSubjectResponse]:
subject = self.repository.get_by_id(subject_id)
if subject:
return GetSubjectResponse(
id=str(subject.id),
name=subject.name,
alias=subject.short_name,
description=subject.description,
)
return None
def update_subject(self, subject_id: str, request: SubjectUpdateRequest) -> bool:
update_data = request.dict(exclude_unset=True)
return self.repository.update(subject_id, update_data)
def delete_subject(self, subject_id: str) -> bool:
return self.repository.delete(subject_id)