This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
BreakPilot Dev 19855efacc
Some checks failed
Tests / Go Tests (push) Has been cancelled
Tests / Python Tests (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / Go Lint (push) Has been cancelled
Tests / Python Lint (push) Has been cancelled
Tests / Security Scan (push) Has been cancelled
Tests / All Checks Passed (push) Has been cancelled
Security Scanning / Secret Scanning (push) Has been cancelled
Security Scanning / Dependency Vulnerability Scan (push) Has been cancelled
Security Scanning / Go Security Scan (push) Has been cancelled
Security Scanning / Python Security Scan (push) Has been cancelled
Security Scanning / Node.js Security Scan (push) Has been cancelled
Security Scanning / Docker Image Security (push) Has been cancelled
Security Scanning / Security Summary (push) Has been cancelled
CI/CD Pipeline / Go Tests (push) Has been cancelled
CI/CD Pipeline / Python Tests (push) Has been cancelled
CI/CD Pipeline / Website Tests (push) Has been cancelled
CI/CD Pipeline / Linting (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Docker Build & Push (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / CI Summary (push) Has been cancelled
ci/woodpecker/manual/build-ci-image Pipeline was successful
ci/woodpecker/manual/main Pipeline failed
feat: BreakPilot PWA - Full codebase (clean push without large binaries)
All services: admin-v2, studio-v2, website, ai-compliance-sdk,
consent-service, klausur-service, voice-service, and infrastructure.
Large PDFs and compiled binaries excluded via .gitignore.
2026-02-11 13:25:58 +01:00

186 lines
5.7 KiB
Python

"""
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"""
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Session Export - {session.subject}</title>
<style>
body {{ font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }}
h1 {{ color: #333; }}
.meta {{ color: #666; margin-bottom: 20px; }}
.section {{ margin: 20px 0; padding: 15px; background: #f5f5f5; border-radius: 8px; }}
.phase {{ display: flex; justify-content: space-between; padding: 10px 0; border-bottom: 1px solid #ddd; }}
</style>
</head>
<body>
<h1>{session.subject}: {session.topic or 'Ohne Thema'}</h1>
<div class="meta">
<p>Klasse: {session.class_id}</p>
<p>Datum: {session.lesson_started_at.strftime('%d.%m.%Y %H:%M') if session.lesson_started_at else 'Nicht gestartet'}</p>
<p>Status: {session.current_phase.value}</p>
</div>
<div class="section">
<h2>Phasen</h2>
{"".join(f'<div class="phase"><span>{p.get("phase", "")}</span><span>{p.get("duration_seconds", 0) // 60} min</span></div>' for p in session.phase_history)}
</div>
<div class="section">
<h2>Notizen</h2>
<p>{session.notes or 'Keine Notizen'}</p>
</div>
<div class="section">
<h2>Hausaufgaben</h2>
<p>{session.homework or 'Keine Hausaufgaben'}</p>
</div>
<footer style="margin-top: 40px; color: #999; font-size: 12px;">
Exportiert am {datetime.utcnow().strftime('%d.%m.%Y %H:%M')} UTC - BreakPilot Classroom
</footer>
</body>
</html>
"""
return HTMLResponse(content=html)