Spaces:
Sleeping
Sleeping
| """Admin-only API: analytics dashboard, user/session management, schema info. | |
| All routes require an admin teacher (see auth.get_current_admin). | |
| """ | |
| from __future__ import annotations | |
| from fastapi import APIRouter, Depends, HTTPException | |
| from pydantic import BaseModel | |
| from .auth import get_current_admin | |
| from .database import ( | |
| admin_list_sessions, | |
| admin_list_students, | |
| delete_session, | |
| delete_user, | |
| get_parsed_data, | |
| get_quiz_results, | |
| get_raw_files, | |
| get_schema_version, | |
| get_session, | |
| get_stats, | |
| get_student, | |
| get_student_timeline, | |
| list_all_users, | |
| set_user_admin, | |
| ) | |
| from .storage import create_signed_url | |
| router = APIRouter(prefix="/api/admin", tags=["admin"]) | |
| async def admin_stats(_admin=Depends(get_current_admin)): | |
| stats = await get_stats() | |
| stats["schema_version"] = await get_schema_version() | |
| return stats | |
| async def admin_users(_admin=Depends(get_current_admin)): | |
| return {"users": await list_all_users()} | |
| class SetAdminRequest(BaseModel): | |
| is_admin: bool | |
| async def admin_set_admin(user_id: int, body: SetAdminRequest, admin=Depends(get_current_admin)): | |
| if user_id == admin["id"] and not body.is_admin: | |
| raise HTTPException(status_code=400, detail="You cannot remove your own admin access") | |
| await set_user_admin(user_id, body.is_admin) | |
| return {"status": "ok", "user_id": user_id, "is_admin": body.is_admin} | |
| async def admin_delete_user(user_id: int, admin=Depends(get_current_admin)): | |
| if user_id == admin["id"]: | |
| raise HTTPException(status_code=400, detail="You cannot delete your own account here") | |
| await delete_user(user_id) | |
| return {"status": "deleted"} | |
| async def admin_sessions(_admin=Depends(get_current_admin)): | |
| return {"sessions": await admin_list_sessions()} | |
| async def admin_session_detail(session_id: int, _admin=Depends(get_current_admin)): | |
| """Full quiz detail: raw files (signed URLs), structured parsed data, results.""" | |
| quiz = await get_session(session_id) | |
| if not quiz: | |
| raise HTTPException(status_code=404, detail="Session not found") | |
| # Group structured parsed data by type (questions / student_answers / etc.) | |
| parsed = {} | |
| for item in await get_parsed_data(session_id): | |
| parsed[item["data_type"]] = item["structured_data"] | |
| # Raw files with short-lived signed download URLs (None if Storage disabled/missing). | |
| raw_files = [] | |
| for f in await get_raw_files(session_id): | |
| f["download_url"] = await create_signed_url(f.get("storage_path", "")) | |
| raw_files.append(f) | |
| return { | |
| "quiz": quiz, | |
| "raw_files": raw_files, | |
| "parsed_data": parsed, | |
| "results": await get_quiz_results(session_id), | |
| } | |
| async def admin_delete_session(session_id: int, _admin=Depends(get_current_admin)): | |
| if not await get_session(session_id): | |
| raise HTTPException(status_code=404, detail="Session not found") | |
| await delete_session(session_id) | |
| return {"status": "deleted"} | |
| async def admin_students(_admin=Depends(get_current_admin)): | |
| return {"students": await admin_list_students()} | |
| async def admin_student_detail(student_id: int, _admin=Depends(get_current_admin)): | |
| student = await get_student(student_id) | |
| if not student: | |
| raise HTTPException(status_code=404, detail="Student not found") | |
| return {"student": student, "timeline": await get_student_timeline(student_id)} | |