from __future__ import annotations from typing import Any, List from fastapi import APIRouter, Depends, HTTPException, Path, Query, status from sqlalchemy.orm import Session from app import models, schemas from app.api import deps from app.db.session import get_db from app.services.lesson import lesson as lesson_service from app.services.question import question as question_service from app.services.quiz import quiz as quiz_service from app.services.subject import subject as subject_service router = APIRouter() # Subject endpoints @router.get("/subjects", response_model=List[schemas.Subject]) def read_subjects( db: Session = Depends(get_db), skip: int = 0, limit: int = 100, active_only: bool = Query(True, description="Filter only active subjects"), ) -> Any: """ Retrieve subjects. """ if active_only: subjects = subject_service.get_active(db, skip=skip, limit=limit) else: subjects = subject_service.get_multi(db, skip=skip, limit=limit) return subjects @router.post("/subjects", response_model=schemas.Subject) def create_subject( *, db: Session = Depends(get_db), subject_in: schemas.SubjectCreate, current_user: models.User = Depends(deps.get_current_active_superuser), ) -> Any: """ Create new subject. Only for superusers. """ subject = subject_service.get_by_name(db, name=subject_in.name) if subject: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Subject with this name already exists", ) subject = subject_service.create(db, obj_in=subject_in) return subject @router.get("/subjects/{subject_id}", response_model=schemas.Subject) def read_subject( *, db: Session = Depends(get_db), subject_id: int = Path(..., gt=0), ) -> Any: """ Get subject by ID. """ subject = subject_service.get(db, id=subject_id) if not subject: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Subject not found", ) return subject @router.put("/subjects/{subject_id}", response_model=schemas.Subject) def update_subject( *, db: Session = Depends(get_db), subject_id: int = Path(..., gt=0), subject_in: schemas.SubjectUpdate, current_user: models.User = Depends(deps.get_current_active_superuser), ) -> Any: """ Update subject. Only for superusers. """ subject = subject_service.get(db, id=subject_id) if not subject: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Subject not found", ) # Check if name is being changed and already exists if subject_in.name is not None and subject_in.name != subject.name: existing_subject = subject_service.get_by_name(db, name=subject_in.name) if existing_subject: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Subject with this name already exists", ) subject = subject_service.update(db, db_obj=subject, obj_in=subject_in) return subject @router.delete( "/subjects/{subject_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None ) def delete_subject( *, db: Session = Depends(get_db), subject_id: int = Path(..., gt=0), current_user: models.User = Depends(deps.get_current_active_superuser), ) -> None: """ Delete subject. Only for superusers. """ subject = subject_service.get(db, id=subject_id) if not subject: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Subject not found", ) subject_service.remove(db, id=subject_id) # Lesson endpoints @router.get("/subjects/{subject_id}/lessons", response_model=List[schemas.Lesson]) def read_lessons_by_subject( *, db: Session = Depends(get_db), subject_id: int = Path(..., gt=0), skip: int = 0, limit: int = 100, active_only: bool = Query(True, description="Filter only active lessons"), ) -> Any: """ Retrieve lessons for a subject. """ subject = subject_service.get(db, id=subject_id) if not subject: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Subject not found", ) if active_only: lessons = lesson_service.get_active_by_subject( db, subject_id=subject_id, skip=skip, limit=limit ) else: lessons = lesson_service.get_by_subject(db, subject_id=subject_id, skip=skip, limit=limit) return lessons @router.post("/lessons", response_model=schemas.Lesson) def create_lesson( *, db: Session = Depends(get_db), lesson_in: schemas.LessonCreate, current_user: models.User = Depends(deps.get_current_active_superuser), ) -> Any: """ Create new lesson. Only for superusers. """ # Check if subject exists subject = subject_service.get(db, id=lesson_in.subject_id) if not subject: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Subject not found", ) lesson = lesson_service.create(db, obj_in=lesson_in) return lesson @router.get("/lessons/{lesson_id}", response_model=schemas.Lesson) def read_lesson( *, db: Session = Depends(get_db), lesson_id: int = Path(..., gt=0), current_user: models.User = Depends(deps.get_current_active_user), ) -> Any: """ Get lesson by ID. """ lesson = lesson_service.get(db, id=lesson_id) if not lesson: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Lesson not found", ) return lesson @router.put("/lessons/{lesson_id}", response_model=schemas.Lesson) def update_lesson( *, db: Session = Depends(get_db), lesson_id: int = Path(..., gt=0), lesson_in: schemas.LessonUpdate, current_user: models.User = Depends(deps.get_current_active_superuser), ) -> Any: """ Update lesson. Only for superusers. """ lesson = lesson_service.get(db, id=lesson_id) if not lesson: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Lesson not found", ) # Check if subject exists if it's being changed if lesson_in.subject_id is not None and lesson_in.subject_id != lesson.subject_id: subject = subject_service.get(db, id=lesson_in.subject_id) if not subject: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Subject not found", ) lesson = lesson_service.update(db, db_obj=lesson, obj_in=lesson_in) return lesson @router.delete("/lessons/{lesson_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None) def delete_lesson( *, db: Session = Depends(get_db), lesson_id: int = Path(..., gt=0), current_user: models.User = Depends(deps.get_current_active_superuser), ) -> None: """ Delete lesson. Only for superusers. """ lesson = lesson_service.get(db, id=lesson_id) if not lesson: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Lesson not found", ) lesson_service.remove(db, id=lesson_id) # Quiz endpoints @router.get("/lessons/{lesson_id}/quizzes", response_model=List[schemas.Quiz]) def read_quizzes_by_lesson( *, db: Session = Depends(get_db), lesson_id: int = Path(..., gt=0), skip: int = 0, limit: int = 100, active_only: bool = Query(True, description="Filter only active quizzes"), current_user: models.User = Depends(deps.get_current_active_user), ) -> Any: """ Retrieve quizzes for a lesson. """ lesson = lesson_service.get(db, id=lesson_id) if not lesson: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Lesson not found", ) if active_only: quizzes = quiz_service.get_active_by_lesson(db, lesson_id=lesson_id, skip=skip, limit=limit) else: quizzes = quiz_service.get_by_lesson(db, lesson_id=lesson_id, skip=skip, limit=limit) return quizzes @router.post("/quizzes", response_model=schemas.Quiz) def create_quiz( *, db: Session = Depends(get_db), quiz_in: schemas.QuizCreate, current_user: models.User = Depends(deps.get_current_active_superuser), ) -> Any: """ Create new quiz. Only for superusers. """ # Check if lesson exists lesson = lesson_service.get(db, id=quiz_in.lesson_id) if not lesson: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Lesson not found", ) quiz = quiz_service.create(db, obj_in=quiz_in) return quiz @router.get("/quizzes/{quiz_id}", response_model=schemas.Quiz) def read_quiz( *, db: Session = Depends(get_db), quiz_id: int = Path(..., gt=0), current_user: models.User = Depends(deps.get_current_active_user), ) -> Any: """ Get quiz by ID. """ quiz = quiz_service.get(db, id=quiz_id) if not quiz: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Quiz not found", ) return quiz @router.put("/quizzes/{quiz_id}", response_model=schemas.Quiz) def update_quiz( *, db: Session = Depends(get_db), quiz_id: int = Path(..., gt=0), quiz_in: schemas.QuizUpdate, current_user: models.User = Depends(deps.get_current_active_superuser), ) -> Any: """ Update quiz. Only for superusers. """ quiz = quiz_service.get(db, id=quiz_id) if not quiz: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Quiz not found", ) # Check if lesson exists if it's being changed if quiz_in.lesson_id is not None and quiz_in.lesson_id != quiz.lesson_id: lesson = lesson_service.get(db, id=quiz_in.lesson_id) if not lesson: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Lesson not found", ) quiz = quiz_service.update(db, db_obj=quiz, obj_in=quiz_in) return quiz @router.delete("/quizzes/{quiz_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None) def delete_quiz( *, db: Session = Depends(get_db), quiz_id: int = Path(..., gt=0), current_user: models.User = Depends(deps.get_current_active_superuser), ) -> None: """ Delete quiz. Only for superusers. """ quiz = quiz_service.get(db, id=quiz_id) if not quiz: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Quiz not found", ) quiz_service.remove(db, id=quiz_id) # Question endpoints @router.get("/quizzes/{quiz_id}/questions", response_model=List[schemas.Question]) def read_questions_by_quiz( *, db: Session = Depends(get_db), quiz_id: int = Path(..., gt=0), skip: int = 0, limit: int = 100, active_only: bool = Query(True, description="Filter only active questions"), current_user: models.User = Depends(deps.get_current_active_user), ) -> Any: """ Retrieve questions for a quiz. """ quiz = quiz_service.get(db, id=quiz_id) if not quiz: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Quiz not found", ) if active_only: questions = question_service.get_active_by_quiz(db, quiz_id=quiz_id, skip=skip, limit=limit) else: questions = question_service.get_by_quiz(db, quiz_id=quiz_id, skip=skip, limit=limit) return questions @router.post("/questions", response_model=schemas.Question) def create_question( *, db: Session = Depends(get_db), question_in: schemas.QuestionCreate, current_user: models.User = Depends(deps.get_current_active_superuser), ) -> Any: """ Create new question with answers. Only for superusers. """ # Check if quiz exists quiz = quiz_service.get(db, id=question_in.quiz_id) if not quiz: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Quiz not found", ) # Check if at least one answer is marked as correct has_correct_answer = False for answer in question_in.answers: if answer.is_correct: has_correct_answer = True break if not has_correct_answer: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="At least one answer must be marked as correct", ) question = question_service.create_with_answers(db, obj_in=question_in) return question @router.get("/questions/{question_id}", response_model=schemas.Question) def read_question( *, db: Session = Depends(get_db), question_id: int = Path(..., gt=0), current_user: models.User = Depends(deps.get_current_active_user), ) -> Any: """ Get question by ID. """ question = question_service.get(db, id=question_id) if not question: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Question not found", ) return question @router.put("/questions/{question_id}", response_model=schemas.Question) def update_question( *, db: Session = Depends(get_db), question_id: int = Path(..., gt=0), question_in: schemas.QuestionUpdate, current_user: models.User = Depends(deps.get_current_active_superuser), ) -> Any: """ Update question with answers. Only for superusers. """ question = question_service.get(db, id=question_id) if not question: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Question not found", ) # Check if quiz exists if it's being changed if question_in.quiz_id is not None and question_in.quiz_id != question.quiz_id: quiz = quiz_service.get(db, id=question_in.quiz_id) if not quiz: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Quiz not found", ) # Check if at least one answer is marked as correct if answers are being updated if question_in.answers: has_correct_answer = False for answer in question_in.answers: if answer.is_correct: has_correct_answer = True break if not has_correct_answer: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="At least one answer must be marked as correct", ) question = question_service.update_with_answers(db, db_obj=question, obj_in=question_in) return question @router.delete( "/questions/{question_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None ) def delete_question( *, db: Session = Depends(get_db), question_id: int = Path(..., gt=0), current_user: models.User = Depends(deps.get_current_active_superuser), ) -> None: """ Delete question. Only for superusers. """ question = question_service.get(db, id=question_id) if not question: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Question not found", ) question_service.remove(db, id=question_id)