Files
breakpilot-lehrer/backend-lehrer/game_extended_routes.py
Benjamin Admin 6811264756 [split-required] Split final batch of monoliths >1000 LOC
Python (6 files in klausur-service):
- rbac.py (1,132 → 4), admin_api.py (1,012 → 4)
- routes/eh.py (1,111 → 4), ocr_pipeline_geometry.py (1,105 → 5)

Python (2 files in backend-lehrer):
- unit_api.py (1,226 → 6), game_api.py (1,129 → 5)

Website (6 page files):
- 4x klausur-korrektur pages (1,249-1,328 LOC each) → shared components
  in website/components/klausur-korrektur/ (17 shared files)
- companion (1,057 → 10), magic-help (1,017 → 8)

All re-export barrels preserve backward compatibility.
Zero import errors verified.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-24 23:17:30 +02:00

190 lines
5.6 KiB
Python

# ==============================================
# Breakpilot Drive - Game Extended Routes
# ==============================================
# Phase 5 features: achievements, progress, parent dashboard,
# class leaderboard, and display leaderboard.
# Extracted from game_api.py for file-size compliance.
from fastapi import APIRouter, HTTPException, Query, Depends, Request
from typing import List, Optional, Dict, Any
import logging
from game_routes import (
get_optional_current_user,
get_user_id_from_auth,
get_game_database,
REQUIRE_AUTH,
)
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/game", tags=["Breakpilot Drive"])
# ==============================================
# Phase 5: Erweiterte Features
# ==============================================
@router.get("/achievements/{user_id}")
async def get_achievements(
user_id: str,
user: Optional[Dict[str, Any]] = Depends(get_optional_current_user)
) -> dict:
"""
Gibt Achievements mit Fortschritt fuer einen Benutzer zurueck.
Achievements werden basierend auf Spielstatistiken berechnet.
"""
# Verify access rights
user_id = get_user_id_from_auth(user, user_id)
db = await get_game_database()
if not db:
return {"achievements": [], "message": "Database not available"}
try:
achievements = await db.get_student_achievements(user_id)
unlocked = [a for a in achievements if a.unlocked]
locked = [a for a in achievements if not a.unlocked]
return {
"user_id": user_id,
"total": len(achievements),
"unlocked_count": len(unlocked),
"achievements": [
{
"id": a.id,
"name": a.name,
"description": a.description,
"icon": a.icon,
"category": a.category,
"threshold": a.threshold,
"progress": a.progress,
"unlocked": a.unlocked,
}
for a in achievements
]
}
except Exception as e:
logger.error(f"Failed to get achievements: {e}")
return {"achievements": [], "message": str(e)}
@router.get("/progress/{user_id}")
async def get_progress(
user_id: str,
days: int = Query(30, ge=7, le=90, description="Anzahl Tage zurueck"),
user: Optional[Dict[str, Any]] = Depends(get_optional_current_user)
) -> dict:
"""
Gibt Lernfortschritt ueber Zeit zurueck (fuer Charts).
- Taegliche Statistiken
- Fuer Eltern-Dashboard und Fortschrittsanzeige
"""
# Verify access rights
user_id = get_user_id_from_auth(user, user_id)
db = await get_game_database()
if not db:
return {"progress": [], "message": "Database not available"}
try:
progress = await db.get_progress_over_time(user_id, days)
return {
"user_id": user_id,
"days": days,
"data_points": len(progress),
"progress": progress,
}
except Exception as e:
logger.error(f"Failed to get progress: {e}")
return {"progress": [], "message": str(e)}
@router.get("/parent/children")
async def get_children_dashboard(
user: Optional[Dict[str, Any]] = Depends(get_optional_current_user)
) -> dict:
"""
Eltern-Dashboard: Statistiken fuer alle Kinder.
Erfordert Auth mit Eltern-Rolle und children_ids Claim.
"""
if not REQUIRE_AUTH or user is None:
return {
"message": "Auth required for parent dashboard",
"children": []
}
# Get children IDs from token
children_ids = user.get("raw_claims", {}).get("children_ids", [])
if not children_ids:
return {
"message": "No children associated with this account",
"children": []
}
db = await get_game_database()
if not db:
return {"children": [], "message": "Database not available"}
try:
children_stats = await db.get_children_stats(children_ids)
return {
"parent_id": user.get("user_id"),
"children_count": len(children_ids),
"children": children_stats,
}
except Exception as e:
logger.error(f"Failed to get children stats: {e}")
return {"children": [], "message": str(e)}
@router.get("/leaderboard/class/{class_id}")
async def get_class_leaderboard(
class_id: str,
timeframe: str = Query("week", description="day, week, month, all"),
limit: int = Query(10, ge=1, le=50),
user: Optional[Dict[str, Any]] = Depends(get_optional_current_user)
) -> List[dict]:
"""
Klassenspezifische Rangliste.
Nur fuer Lehrer oder Schueler der Klasse sichtbar.
"""
db = await get_game_database()
if not db:
return []
try:
leaderboard = await db.get_class_leaderboard(class_id, timeframe, limit)
return leaderboard
except Exception as e:
logger.error(f"Failed to get class leaderboard: {e}")
return []
@router.get("/leaderboard/display")
async def get_display_leaderboard(
timeframe: str = Query("day", description="day, week, month, all"),
limit: int = Query(10, ge=1, le=100),
anonymize: bool = Query(True, description="Namen anonymisieren")
) -> List[dict]:
"""
Oeffentliche Rangliste mit Anzeigenamen.
Standardmaessig anonymisiert fuer Datenschutz.
"""
db = await get_game_database()
if not db:
return []
try:
return await db.get_leaderboard_with_names(timeframe, limit, anonymize)
except Exception as e:
logger.error(f"Failed to get display leaderboard: {e}")
return []