
- Set up project structure with FastAPI and SQLite - Implement user authentication with JWT - Create models for learning content (subjects, lessons, quizzes) - Add progress tracking and gamification features - Implement comprehensive API documentation - Add error handling and validation - Set up proper logging and health check endpoint
280 lines
8.6 KiB
Python
280 lines
8.6 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import Any, Dict, List
|
|
|
|
from fastapi import APIRouter, Body, 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.models.progress import ProgressStatus
|
|
from app.services.lesson import lesson as lesson_service
|
|
from app.services.progress import user_answer, user_progress
|
|
from app.services.question import question as question_service
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.get("/user/{user_id}/stats", response_model=Dict[str, Any])
|
|
def get_user_stats(
|
|
*,
|
|
db: Session = Depends(get_db),
|
|
user_id: int = Path(..., gt=0),
|
|
current_user: models.User = Depends(deps.get_current_active_user),
|
|
) -> Any:
|
|
"""
|
|
Get a user's progress statistics.
|
|
"""
|
|
# Regular users can only get their own stats
|
|
if user_id != current_user.id and not current_user.is_superuser:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail="Not enough permissions",
|
|
)
|
|
|
|
stats = user_progress.get_user_progress_stats(db, user_id=user_id)
|
|
return stats
|
|
|
|
|
|
@router.get("/user/{user_id}/lessons", response_model=List[schemas.UserProgress])
|
|
def get_user_lesson_progress(
|
|
*,
|
|
db: Session = Depends(get_db),
|
|
user_id: int = Path(..., gt=0),
|
|
status: ProgressStatus = Query(None, description="Filter by progress status"),
|
|
skip: int = 0,
|
|
limit: int = 100,
|
|
current_user: models.User = Depends(deps.get_current_active_user),
|
|
) -> Any:
|
|
"""
|
|
Get a user's progress for all lessons.
|
|
"""
|
|
# Regular users can only get their own progress
|
|
if user_id != current_user.id and not current_user.is_superuser:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail="Not enough permissions",
|
|
)
|
|
|
|
if status:
|
|
progress_items = user_progress.get_by_user_status(
|
|
db, user_id=user_id, status=status, skip=skip, limit=limit
|
|
)
|
|
else:
|
|
progress_items = user_progress.get_by_user(db, user_id=user_id, skip=skip, limit=limit)
|
|
|
|
return progress_items
|
|
|
|
|
|
@router.get("/user/{user_id}/lessons/{lesson_id}", response_model=schemas.UserProgress)
|
|
def get_user_lesson_progress_by_id(
|
|
*,
|
|
db: Session = Depends(get_db),
|
|
user_id: int = Path(..., gt=0),
|
|
lesson_id: int = Path(..., gt=0),
|
|
current_user: models.User = Depends(deps.get_current_active_user),
|
|
) -> Any:
|
|
"""
|
|
Get a user's progress for a specific lesson.
|
|
"""
|
|
# Regular users can only get their own progress
|
|
if user_id != current_user.id and not current_user.is_superuser:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail="Not enough permissions",
|
|
)
|
|
|
|
# Check if lesson exists
|
|
lesson = lesson_service.get(db, id=lesson_id)
|
|
if not lesson:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Lesson not found",
|
|
)
|
|
|
|
progress = user_progress.get_by_user_lesson(db, user_id=user_id, lesson_id=lesson_id)
|
|
|
|
if not progress:
|
|
# Return default progress if none exists
|
|
return {
|
|
"id": None,
|
|
"user_id": user_id,
|
|
"lesson_id": lesson_id,
|
|
"status": ProgressStatus.NOT_STARTED,
|
|
"progress_percentage": 0,
|
|
"points_earned": 0,
|
|
"completed_at": None,
|
|
"created_at": None,
|
|
"updated_at": None,
|
|
}
|
|
|
|
return progress
|
|
|
|
|
|
@router.post("/user/{user_id}/lessons/{lesson_id}/progress", response_model=schemas.UserProgress)
|
|
def update_user_lesson_progress(
|
|
*,
|
|
db: Session = Depends(get_db),
|
|
user_id: int = Path(..., gt=0),
|
|
lesson_id: int = Path(..., gt=0),
|
|
progress_percentage: float = Body(..., ge=0, le=100),
|
|
current_user: models.User = Depends(deps.get_current_active_user),
|
|
) -> Any:
|
|
"""
|
|
Update a user's progress for a lesson.
|
|
"""
|
|
# Regular users can only update their own progress
|
|
if user_id != current_user.id and not current_user.is_superuser:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail="Not enough permissions",
|
|
)
|
|
|
|
# Check if lesson exists
|
|
lesson = lesson_service.get(db, id=lesson_id)
|
|
if not lesson:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Lesson not found",
|
|
)
|
|
|
|
progress = user_progress.update_progress(
|
|
db, user_id=user_id, lesson_id=lesson_id, progress_percentage=progress_percentage
|
|
)
|
|
|
|
return progress
|
|
|
|
|
|
@router.post("/user/{user_id}/lessons/{lesson_id}/complete", response_model=schemas.UserProgress)
|
|
def complete_user_lesson(
|
|
*,
|
|
db: Session = Depends(get_db),
|
|
user_id: int = Path(..., gt=0),
|
|
lesson_id: int = Path(..., gt=0),
|
|
current_user: models.User = Depends(deps.get_current_active_user),
|
|
) -> Any:
|
|
"""
|
|
Mark a lesson as completed for a user and award points.
|
|
"""
|
|
# Regular users can only update their own progress
|
|
if user_id != current_user.id and not current_user.is_superuser:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail="Not enough permissions",
|
|
)
|
|
|
|
# Check if lesson exists
|
|
lesson = lesson_service.get(db, id=lesson_id)
|
|
if not lesson:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Lesson not found",
|
|
)
|
|
|
|
progress = user_progress.complete_lesson(
|
|
db, user_id=user_id, lesson_id=lesson_id, points_earned=lesson.points
|
|
)
|
|
|
|
return progress
|
|
|
|
|
|
@router.post("/user/{user_id}/questions/{question_id}/answer", response_model=schemas.UserAnswer)
|
|
def submit_answer(
|
|
*,
|
|
db: Session = Depends(get_db),
|
|
user_id: int = Path(..., gt=0),
|
|
question_id: int = Path(..., gt=0),
|
|
answer_id: int = Body(..., gt=0),
|
|
time_taken_seconds: int = Body(None, ge=0),
|
|
current_user: models.User = Depends(deps.get_current_active_user),
|
|
) -> Any:
|
|
"""
|
|
Submit an answer to a question.
|
|
"""
|
|
# Regular users can only submit their own answers
|
|
if user_id != current_user.id and not current_user.is_superuser:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail="Not enough permissions",
|
|
)
|
|
|
|
# Check if question exists
|
|
question = question_service.get(db, id=question_id)
|
|
if not question:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Question not found",
|
|
)
|
|
|
|
try:
|
|
user_answer_obj = user_answer.submit_answer(
|
|
db,
|
|
user_id=user_id,
|
|
question_id=question_id,
|
|
answer_id=answer_id,
|
|
time_taken_seconds=time_taken_seconds,
|
|
)
|
|
return user_answer_obj
|
|
except ValueError as e:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail=str(e),
|
|
)
|
|
|
|
|
|
@router.get(
|
|
"/user/{user_id}/questions/{question_id}/answers", response_model=List[schemas.UserAnswer]
|
|
)
|
|
def get_user_question_answers(
|
|
*,
|
|
db: Session = Depends(get_db),
|
|
user_id: int = Path(..., gt=0),
|
|
question_id: int = Path(..., gt=0),
|
|
current_user: models.User = Depends(deps.get_current_active_user),
|
|
) -> Any:
|
|
"""
|
|
Get a user's answers for a specific question.
|
|
"""
|
|
# Regular users can only get their own answers
|
|
if user_id != current_user.id and not current_user.is_superuser:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail="Not enough permissions",
|
|
)
|
|
|
|
# Check if question exists
|
|
question = question_service.get(db, id=question_id)
|
|
if not question:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Question not found",
|
|
)
|
|
|
|
answers = user_answer.get_by_user_question(db, user_id=user_id, question_id=question_id)
|
|
|
|
return answers
|
|
|
|
|
|
@router.get("/user/{user_id}/quizzes/{quiz_id}/results", response_model=Dict[str, Any])
|
|
def get_quiz_results(
|
|
*,
|
|
db: Session = Depends(get_db),
|
|
user_id: int = Path(..., gt=0),
|
|
quiz_id: int = Path(..., gt=0),
|
|
current_user: models.User = Depends(deps.get_current_active_user),
|
|
) -> Any:
|
|
"""
|
|
Get results for a user's quiz attempt.
|
|
"""
|
|
# Regular users can only get their own results
|
|
if user_id != current_user.id and not current_user.is_superuser:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail="Not enough permissions",
|
|
)
|
|
|
|
results = user_answer.get_quiz_results(db, user_id=user_id, quiz_id=quiz_id)
|
|
|
|
return results
|