""" Classroom API - Utility Endpoints. Health-Check, Phasen-Liste und andere Utility-Endpoints. """ from typing import Dict, List, Optional, Any from datetime import datetime import logging from fastapi import APIRouter, HTTPException, Query from fastapi.responses import HTMLResponse from sqlalchemy import text from pydantic import BaseModel from classroom_engine import LESSON_PHASES, LessonStateMachine from .shared import ( init_db_if_needed, get_sessions, get_session_or_404, ws_manager, DB_ENABLED, logger, ) try: from classroom_engine.database import SessionLocal except ImportError: pass router = APIRouter(tags=["Utility"]) # === Pydantic Models === class PhasesListResponse(BaseModel): """Liste aller verfuegbaren Phasen.""" phases: List[Dict[str, Any]] class ActiveSessionsResponse(BaseModel): """Liste aktiver Sessions.""" sessions: List[Dict[str, Any]] count: int # === Endpoints === @router.get("/phases", response_model=PhasesListResponse) async def list_phases() -> PhasesListResponse: """Listet alle verfuegbaren Unterrichtsphasen mit Metadaten.""" phases = [] for phase_id, config in LESSON_PHASES.items(): phases.append({ "phase": phase_id, "display_name": config["display_name"], "default_duration_minutes": config["default_duration_minutes"], "activities": config["activities"], "icon": config["icon"], "description": config.get("description", ""), }) return PhasesListResponse(phases=phases) @router.get("/sessions", response_model=ActiveSessionsResponse) async def list_active_sessions( teacher_id: Optional[str] = Query(None) ) -> ActiveSessionsResponse: """Listet alle (optionally gefilterten) Sessions.""" sessions = get_sessions() sessions_list = [] for session in sessions.values(): if teacher_id and session.teacher_id != teacher_id: continue fsm = LessonStateMachine() sessions_list.append({ "session_id": session.session_id, "teacher_id": session.teacher_id, "class_id": session.class_id, "subject": session.subject, "current_phase": session.current_phase.value, "is_active": fsm.is_lesson_active(session), "lesson_started_at": session.lesson_started_at.isoformat() if session.lesson_started_at else None, }) return ActiveSessionsResponse(sessions=sessions_list, count=len(sessions_list)) @router.get("/health") async def health_check() -> Dict[str, Any]: """Health-Check fuer den Classroom Service.""" db_status = "disabled" if DB_ENABLED: try: db = SessionLocal() db.execute(text("SELECT 1")) db.close() db_status = "connected" except Exception as e: db_status = f"error: {str(e)}" sessions = get_sessions() return { "status": "healthy", "service": "classroom-engine", "active_sessions": len(sessions), "db_enabled": DB_ENABLED, "db_status": db_status, "websocket_connections": sum( ws_manager.get_client_count(sid) for sid in ws_manager.get_active_sessions() ), "timestamp": datetime.utcnow().isoformat(), } @router.get("/ws/status") async def websocket_status() -> Dict[str, Any]: """Status der WebSocket-Verbindungen.""" active_sessions = ws_manager.get_active_sessions() session_counts = { sid: ws_manager.get_client_count(sid) for sid in active_sessions } return { "active_sessions": len(active_sessions), "session_connections": session_counts, "total_connections": sum(session_counts.values()), "timestamp": datetime.utcnow().isoformat(), } @router.get("/export/session/{session_id}", response_class=HTMLResponse) async def export_session_html(session_id: str) -> HTMLResponse: """Exportiert eine Session als HTML-Dokument.""" session = get_session_or_404(session_id) # Einfacher HTML-Export html = f"""
{session.notes or 'Keine Notizen'}
{session.homework or 'Keine Hausaufgaben'}