from typing import Any, List, Optional from fastapi import APIRouter, Depends, HTTPException, Path, status from sqlalchemy.orm import Session from app.api.deps import get_current_active_user from app.db.session import get_db from app.models.user import User from app.schemas.quiz import ( Quiz, QuizAttempt, QuizCreate, QuizSubmitAnswer, QuizUpdate, QuizWithoutDetails, ) from app.services import quiz as quiz_service router = APIRouter() @router.get("/", response_model=List[QuizWithoutDetails]) def read_quizzes( db: Session = Depends(get_db), skip: int = 0, limit: int = 100, current_user: User = Depends(get_current_active_user), ) -> Any: """ Retrieve all quizzes accessible to the current user. Includes all public quizzes and the user's own quizzes. """ # Get public quizzes public_quizzes = quiz_service.get_public_quizzes(db, skip=skip, limit=limit) # Get user's quizzes user_quizzes = quiz_service.get_user_quizzes(db, user_id=current_user.id, skip=skip, limit=limit) # Combine and deduplicate (using dict comprehension) quizzes = {quiz.id: quiz for quiz in public_quizzes + user_quizzes}.values() # Add question count result = [] for quiz in quizzes: setattr(quiz, "question_count", len(quiz.questions)) result.append(quiz) return result @router.post("/", response_model=Quiz) def create_quiz( *, db: Session = Depends(get_db), quiz_in: QuizCreate, current_user: User = Depends(get_current_active_user), ) -> Any: """ Create new quiz. """ quiz = quiz_service.create(db, obj_in=quiz_in, user_id=current_user.id) return quiz @router.get("/{quiz_id}", response_model=Quiz) def read_quiz( *, db: Session = Depends(get_db), quiz_id: int = Path(..., gt=0), current_user: User = Depends(get_current_active_user), ) -> Any: """ Get quiz by ID. """ quiz = quiz_service.get_by_id(db, quiz_id=quiz_id) if not quiz: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Quiz not found", ) # Check if the user can access this quiz if not quiz.is_public and quiz.user_id != current_user.id: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="You don't have permission to access this quiz", ) return quiz @router.put("/{quiz_id}", response_model=Quiz) def update_quiz( *, db: Session = Depends(get_db), quiz_id: int = Path(..., gt=0), quiz_in: QuizUpdate, current_user: User = Depends(get_current_active_user), ) -> Any: """ Update a quiz. """ quiz = quiz_service.get_by_id(db, quiz_id=quiz_id) if not quiz: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Quiz not found", ) # Only the quiz owner can update it if quiz.user_id != current_user.id: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="You don't have permission to update this quiz", ) quiz = quiz_service.update(db, db_obj=quiz, obj_in=quiz_in) return quiz @router.delete("/{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: User = Depends(get_current_active_user), ) -> Any: """ Delete a quiz. """ quiz = quiz_service.get_by_id(db, quiz_id=quiz_id) if not quiz: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Quiz not found", ) # Only the quiz owner can delete it if quiz.user_id != current_user.id: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="You don't have permission to delete this quiz", ) quiz_service.remove(db, quiz_id=quiz_id) return None # Quiz attempt endpoints @router.get("/attempts/", response_model=List[QuizAttempt]) def read_user_attempts( db: Session = Depends(get_db), skip: int = 0, limit: int = 100, quiz_id: Optional[int] = None, current_user: User = Depends(get_current_active_user), ) -> Any: """ Retrieve user's quiz attempts. """ attempts = quiz_service.get_user_attempts( db, user_id=current_user.id, quiz_id=quiz_id, skip=skip, limit=limit, ) return attempts @router.post("/{quiz_id}/start", response_model=QuizAttempt) def start_quiz_attempt( *, db: Session = Depends(get_db), quiz_id: int = Path(..., gt=0), current_user: User = Depends(get_current_active_user), ) -> Any: """ Start a new quiz attempt. """ quiz = quiz_service.get_by_id(db, quiz_id=quiz_id) if not quiz: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Quiz not found", ) # Check if the user can access this quiz if not quiz.is_public and quiz.user_id != current_user.id: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="You don't have permission to access this quiz", ) attempt = quiz_service.create_attempt( db, obj_in={"quiz_id": quiz_id, "status": "started"}, user_id=current_user.id, ) return attempt @router.get("/attempts/{attempt_id}", response_model=QuizAttempt) def read_quiz_attempt( *, db: Session = Depends(get_db), attempt_id: int = Path(..., gt=0), current_user: User = Depends(get_current_active_user), ) -> Any: """ Get quiz attempt by ID. """ attempt = quiz_service.get_attempt_by_id(db, attempt_id=attempt_id) if not attempt: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Quiz attempt not found", ) # Only the attempt owner can access it if attempt.user_id != current_user.id: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="You don't have permission to access this quiz attempt", ) return attempt @router.post("/attempts/{attempt_id}/submit/{question_id}", response_model=dict) def submit_quiz_answer( *, db: Session = Depends(get_db), attempt_id: int = Path(..., gt=0), question_id: int = Path(..., gt=0), answer_in: QuizSubmitAnswer, current_user: User = Depends(get_current_active_user), ) -> Any: """ Submit an answer for a quiz question. """ attempt = quiz_service.get_attempt_by_id(db, attempt_id=attempt_id) if not attempt: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Quiz attempt not found", ) # Only the attempt owner can submit answers if attempt.user_id != current_user.id: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="You don't have permission to submit answers for this attempt", ) # Check if the attempt is still in progress if attempt.status != "started": raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Cannot submit answers for an attempt with status '{attempt.status}'", ) answer = quiz_service.submit_answer( db, attempt_id=attempt_id, question_id=question_id, selected_option_id=answer_in.selected_option_id, ) if not answer: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid question or option ID", ) return {"status": "success", "is_correct": answer.is_correct} @router.post("/attempts/{attempt_id}/complete", response_model=dict) def complete_quiz_attempt( *, db: Session = Depends(get_db), attempt_id: int = Path(..., gt=0), current_user: User = Depends(get_current_active_user), ) -> Any: """ Complete a quiz attempt and calculate the final score. """ attempt = quiz_service.get_attempt_by_id(db, attempt_id=attempt_id) if not attempt: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Quiz attempt not found", ) # Only the attempt owner can complete it if attempt.user_id != current_user.id: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="You don't have permission to complete this attempt", ) # Check if the attempt is still in progress if attempt.status != "started": raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Cannot complete an attempt with status '{attempt.status}'", ) attempt, score = quiz_service.complete_attempt(db, attempt_id=attempt_id) return { "status": "completed", "score": score, "passed": score >= attempt.quiz.pass_percentage, "pass_percentage": attempt.quiz.pass_percentage, }