Automated Action 53d9909fb6 Create gamified kids learning API with FastAPI and SQLite
- 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
2025-06-17 18:25:16 +00:00

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