Restructure: Move 43 files into 8 domain packages (backend-lehrer)
Some checks failed
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-school (push) Successful in 27s
CI / test-go-edu-search (push) Successful in 40s
CI / test-python-klausur (push) Failing after 2m30s
CI / test-python-agent-core (push) Successful in 28s
CI / test-nodejs-website (push) Successful in 20s

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-04-25 22:32:45 +02:00
parent 165c493d1e
commit dde45b29db
93 changed files with 9469 additions and 9290 deletions

View 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 .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 []