# ============================================== # 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 []