[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>
This commit is contained in:
189
backend-lehrer/game_extended_routes.py
Normal file
189
backend-lehrer/game_extended_routes.py
Normal file
@@ -0,0 +1,189 @@
|
||||
# ==============================================
|
||||
# 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 []
|
||||
Reference in New Issue
Block a user