pdf-quiz-generator/backend/app/routers/admin.py
ifedan-ed b876f13fac Initial commit: PDF Quiz Generator app
- FastAPI backend with JWT auth, roles (admin/moderator/user)
- PDF upload (up to 500MB) with streaming, PyMuPDF text extraction
- ChromaDB vectorization per page with metadata
- LiteLLM AI question extraction from PDF (not generation)
- Image extraction from PDF pages, graceful fallback
- Quiz modes: timed (countdown timer) + learning (answers shown inline)
- Page-by-page question navigation with dot navigator
- TTS endpoint using LiteLLM (Google Vertex / OpenAI voices)
- Admin dashboard: AI model management per task, user role management
- Moderator role: upload PDFs, create sections, generate quizzes
- Spaced repetition reminders via SMTP email (SM-2 intervals)
- APScheduler daily reminder jobs
- Celery + Redis for background PDF processing
- React frontend with all pages
- Docker Compose deployment (nginx + backend + celery + redis)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 20:04:53 +00:00

119 lines
3.7 KiB
Python

from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from app.database import get_db
from app.models.user import User
from app.models.ai_model_config import AIModelConfig
from app.schemas.auth import UserResponse, UserUpdateRole
from app.schemas.admin import AIModelConfigCreate, AIModelConfigResponse, AIModelConfigUpdate
from app.utils.auth import require_admin
router = APIRouter()
# --- User Management ---
@router.get("/users", response_model=list[UserResponse])
def list_users(
db: Session = Depends(get_db),
admin: User = Depends(require_admin),
):
return db.query(User).order_by(User.created_at.desc()).all()
@router.put("/users/{user_id}/role", response_model=UserResponse)
def update_user_role(
user_id: int,
role_data: UserUpdateRole,
db: Session = Depends(get_db),
admin: User = Depends(require_admin),
):
if role_data.role not in ("admin", "moderator", "user"):
raise HTTPException(status_code=400, detail="Role must be admin, moderator, or user")
user = db.query(User).filter(User.id == user_id).first()
if not user:
raise HTTPException(status_code=404, detail="User not found")
if user.id == admin.id:
raise HTTPException(status_code=400, detail="Cannot change your own role")
user.role = role_data.role
db.commit()
db.refresh(user)
return user
# --- AI Model Configuration ---
@router.get("/models", response_model=list[AIModelConfigResponse])
def list_models(
db: Session = Depends(get_db),
admin: User = Depends(require_admin),
):
return db.query(AIModelConfig).order_by(AIModelConfig.task, AIModelConfig.name).all()
@router.post("/models", response_model=AIModelConfigResponse)
def create_model(
data: AIModelConfigCreate,
db: Session = Depends(get_db),
admin: User = Depends(require_admin),
):
if data.task not in ("extraction", "tts", "general"):
raise HTTPException(status_code=400, detail="Task must be extraction, tts, or general")
# If setting as default, unset other defaults for same task
if data.is_default:
db.query(AIModelConfig).filter(
AIModelConfig.task == data.task,
AIModelConfig.is_default == True,
).update({"is_default": False})
model = AIModelConfig(**data.model_dump())
db.add(model)
db.commit()
db.refresh(model)
return model
@router.put("/models/{model_id}", response_model=AIModelConfigResponse)
def update_model(
model_id: int,
data: AIModelConfigUpdate,
db: Session = Depends(get_db),
admin: User = Depends(require_admin),
):
model = db.query(AIModelConfig).filter(AIModelConfig.id == model_id).first()
if not model:
raise HTTPException(status_code=404, detail="Model config not found")
update_data = data.model_dump(exclude_unset=True)
# If setting as default, unset other defaults for same task
task = update_data.get("task", model.task)
if update_data.get("is_default"):
db.query(AIModelConfig).filter(
AIModelConfig.task == task,
AIModelConfig.is_default == True,
AIModelConfig.id != model_id,
).update({"is_default": False})
for key, value in update_data.items():
setattr(model, key, value)
db.commit()
db.refresh(model)
return model
@router.delete("/models/{model_id}", status_code=204)
def delete_model(
model_id: int,
db: Session = Depends(get_db),
admin: User = Depends(require_admin),
):
model = db.query(AIModelConfig).filter(AIModelConfig.id == model_id).first()
if not model:
raise HTTPException(status_code=404, detail="Model config not found")
db.delete(model)
db.commit()