Spaces:
Runtime error
Runtime error
File size: 4,634 Bytes
d2075b0 4c4a722 d2075b0 4c4a722 d2075b0 4c4a722 d2075b0 4c4a722 108b5b1 4c4a722 108b5b1 4c4a722 d2075b0 4c4a722 d2075b0 4c4a722 d2075b0 4c4a722 d2075b0 4c4a722 d2075b0 4c4a722 108b5b1 d2075b0 108b5b1 d2075b0 108b5b1 4c4a722 d2075b0 4c4a722 d2075b0 4c4a722 d2075b0 4c4a722 d2075b0 4c4a722 d2075b0 4c4a722 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | """Dashboard API.
Two access levels share these routes:
- Admins (is_admin) see ALL teachers' data and can manage users.
- Regular teachers see ONLY their own data (their quizzes/students/results) and
cannot reach user-management or other teachers' records.
Scoping is enforced server-side: list endpoints filter by the caller's teacher
id, and detail endpoints verify ownership before returning anything.
"""
from __future__ import annotations
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
from .auth import get_current_admin, get_current_user
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"])
def _scope(user: dict) -> Optional[int]:
"""None for admins (see everything), else the teacher id to scope to."""
return None if user.get("is_admin") else user["id"]
def _owns_quiz(user: dict, quiz: dict) -> bool:
return user.get("is_admin") or quiz.get("teacher_id") == user["id"]
def _owns_student(user: dict, student: dict) -> bool:
return user.get("is_admin") or student.get("teacher_id") == user["id"]
@router.get("/stats")
async def dashboard_stats(user=Depends(get_current_user)):
stats = await get_stats(_scope(user))
stats["is_admin"] = bool(user.get("is_admin"))
if user.get("is_admin"):
stats["schema_version"] = await get_schema_version()
return stats
# ---- User management (admin only) ----
@router.get("/users")
async def admin_users(_admin=Depends(get_current_admin)):
return {"users": await list_all_users()}
class SetAdminRequest(BaseModel):
is_admin: bool
@router.put("/users/{user_id}/admin")
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}
@router.delete("/users/{user_id}")
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"}
# ---- Sessions / quizzes (scoped) ----
@router.get("/sessions")
async def dashboard_sessions(user=Depends(get_current_user)):
return {"sessions": await admin_list_sessions(_scope(user))}
@router.get("/sessions/{session_id}")
async def dashboard_session_detail(session_id: int, user=Depends(get_current_user)):
"""Full quiz detail: raw files (signed URLs), structured parsed data, results."""
quiz = await get_session(session_id)
if not quiz or not _owns_quiz(user, quiz):
raise HTTPException(status_code=404, detail="Session not found")
parsed = {}
for item in await get_parsed_data(session_id):
parsed[item["data_type"]] = item["structured_data"]
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),
}
@router.delete("/sessions/{session_id}")
async def dashboard_delete_session(session_id: int, user=Depends(get_current_user)):
quiz = await get_session(session_id)
if not quiz or not _owns_quiz(user, quiz):
raise HTTPException(status_code=404, detail="Session not found")
await delete_session(session_id)
return {"status": "deleted"}
# ---- Students (scoped) ----
@router.get("/students")
async def dashboard_students(user=Depends(get_current_user)):
return {"students": await admin_list_students(_scope(user))}
@router.get("/students/{student_id}")
async def dashboard_student_detail(student_id: int, user=Depends(get_current_user)):
student = await get_student(student_id)
if not student or not _owns_student(user, student):
raise HTTPException(status_code=404, detail="Student not found")
return {"student": student, "timeline": await get_student_timeline(student_id)}
|