fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.
This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).
Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
382
backend/classroom/routes/templates.py
Normal file
382
backend/classroom/routes/templates.py
Normal file
@@ -0,0 +1,382 @@
|
||||
"""
|
||||
Classroom API - Template Routes
|
||||
|
||||
Lesson template management endpoints (Feature f37).
|
||||
"""
|
||||
|
||||
from uuid import uuid4
|
||||
from typing import Dict, Optional, List
|
||||
from datetime import datetime
|
||||
import logging
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
|
||||
from classroom_engine import (
|
||||
LessonSession,
|
||||
LessonTemplate,
|
||||
SYSTEM_TEMPLATES,
|
||||
get_default_durations,
|
||||
)
|
||||
|
||||
from ..models import (
|
||||
TemplateCreate,
|
||||
TemplateUpdate,
|
||||
TemplateResponse,
|
||||
TemplateListResponse,
|
||||
)
|
||||
from ..services.persistence import (
|
||||
sessions,
|
||||
init_db_if_needed,
|
||||
persist_session,
|
||||
DB_ENABLED,
|
||||
SessionLocal,
|
||||
)
|
||||
from .sessions import build_session_response, SessionResponse
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(tags=["Templates"])
|
||||
|
||||
|
||||
def _build_template_response(template: LessonTemplate, is_system: bool = False) -> TemplateResponse:
|
||||
"""Baut eine Template-Response."""
|
||||
return TemplateResponse(
|
||||
template_id=template.template_id,
|
||||
teacher_id=template.teacher_id,
|
||||
name=template.name,
|
||||
description=template.description,
|
||||
subject=template.subject,
|
||||
grade_level=template.grade_level,
|
||||
phase_durations=template.phase_durations,
|
||||
default_topic=template.default_topic,
|
||||
default_notes=template.default_notes,
|
||||
is_public=template.is_public,
|
||||
usage_count=template.usage_count,
|
||||
total_duration_minutes=sum(template.phase_durations.values()),
|
||||
created_at=template.created_at.isoformat() if template.created_at else None,
|
||||
updated_at=template.updated_at.isoformat() if template.updated_at else None,
|
||||
is_system_template=is_system,
|
||||
)
|
||||
|
||||
|
||||
def _get_system_templates() -> List[TemplateResponse]:
|
||||
"""Gibt die vordefinierten System-Templates zurueck."""
|
||||
templates = []
|
||||
for t in SYSTEM_TEMPLATES:
|
||||
template = LessonTemplate(
|
||||
template_id=t["template_id"],
|
||||
teacher_id="system",
|
||||
name=t["name"],
|
||||
description=t.get("description", ""),
|
||||
phase_durations=t["phase_durations"],
|
||||
is_public=True,
|
||||
usage_count=0,
|
||||
)
|
||||
templates.append(_build_template_response(template, is_system=True))
|
||||
return templates
|
||||
|
||||
|
||||
@router.get("/templates", response_model=TemplateListResponse)
|
||||
async def list_templates(
|
||||
teacher_id: Optional[str] = Query(None, description="Filter nach Lehrer"),
|
||||
subject: Optional[str] = Query(None, description="Filter nach Fach"),
|
||||
include_system: bool = Query(True, description="System-Vorlagen einbeziehen")
|
||||
) -> TemplateListResponse:
|
||||
"""
|
||||
Listet verfuegbare Stunden-Vorlagen (Feature f37).
|
||||
|
||||
Ohne teacher_id werden nur oeffentliche und System-Vorlagen gezeigt.
|
||||
Mit teacher_id werden auch private Vorlagen des Lehrers angezeigt.
|
||||
"""
|
||||
init_db_if_needed()
|
||||
|
||||
templates: List[TemplateResponse] = []
|
||||
|
||||
# System-Templates hinzufuegen
|
||||
if include_system:
|
||||
system_templates = _get_system_templates()
|
||||
templates.extend(system_templates)
|
||||
|
||||
# DB-Templates laden wenn verfuegbar
|
||||
if DB_ENABLED:
|
||||
try:
|
||||
from classroom_engine.repository import TemplateRepository
|
||||
db = SessionLocal()
|
||||
repo = TemplateRepository(db)
|
||||
|
||||
if subject:
|
||||
db_templates = repo.get_by_subject(subject, teacher_id)
|
||||
elif teacher_id:
|
||||
db_templates = repo.get_by_teacher(teacher_id, include_public=True)
|
||||
else:
|
||||
db_templates = repo.get_public_templates()
|
||||
|
||||
for db_t in db_templates:
|
||||
template = repo.to_dataclass(db_t)
|
||||
templates.append(_build_template_response(template))
|
||||
|
||||
db.close()
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to load templates from DB: {e}")
|
||||
|
||||
return TemplateListResponse(
|
||||
templates=templates,
|
||||
total_count=len(templates)
|
||||
)
|
||||
|
||||
|
||||
@router.get("/templates/{template_id}", response_model=TemplateResponse)
|
||||
async def get_template(template_id: str) -> TemplateResponse:
|
||||
"""
|
||||
Ruft eine einzelne Vorlage ab.
|
||||
"""
|
||||
init_db_if_needed()
|
||||
|
||||
# System-Template pruefen
|
||||
for t in SYSTEM_TEMPLATES:
|
||||
if t["template_id"] == template_id:
|
||||
template = LessonTemplate(
|
||||
template_id=t["template_id"],
|
||||
teacher_id="system",
|
||||
name=t["name"],
|
||||
description=t.get("description", ""),
|
||||
phase_durations=t["phase_durations"],
|
||||
is_public=True,
|
||||
)
|
||||
return _build_template_response(template, is_system=True)
|
||||
|
||||
# DB-Template pruefen
|
||||
if DB_ENABLED:
|
||||
try:
|
||||
from classroom_engine.repository import TemplateRepository
|
||||
db = SessionLocal()
|
||||
repo = TemplateRepository(db)
|
||||
db_template = repo.get_by_id(template_id)
|
||||
if db_template:
|
||||
template = repo.to_dataclass(db_template)
|
||||
db.close()
|
||||
return _build_template_response(template)
|
||||
db.close()
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get template {template_id}: {e}")
|
||||
|
||||
raise HTTPException(status_code=404, detail="Vorlage nicht gefunden")
|
||||
|
||||
|
||||
@router.post("/templates", response_model=TemplateResponse, status_code=201)
|
||||
async def create_template(
|
||||
request: TemplateCreate,
|
||||
teacher_id: str = Query(..., description="ID des erstellenden Lehrers")
|
||||
) -> TemplateResponse:
|
||||
"""
|
||||
Erstellt eine neue Stunden-Vorlage.
|
||||
"""
|
||||
init_db_if_needed()
|
||||
|
||||
if not DB_ENABLED:
|
||||
raise HTTPException(
|
||||
status_code=503,
|
||||
detail="Datenbank nicht verfuegbar - Vorlagen koennen nicht gespeichert werden"
|
||||
)
|
||||
|
||||
phase_durations = request.phase_durations or get_default_durations()
|
||||
|
||||
template = LessonTemplate(
|
||||
template_id=str(uuid4()),
|
||||
teacher_id=teacher_id,
|
||||
name=request.name,
|
||||
description=request.description,
|
||||
subject=request.subject,
|
||||
grade_level=request.grade_level,
|
||||
phase_durations=phase_durations,
|
||||
default_topic=request.default_topic,
|
||||
default_notes=request.default_notes,
|
||||
is_public=request.is_public,
|
||||
created_at=datetime.utcnow(),
|
||||
)
|
||||
|
||||
try:
|
||||
from classroom_engine.repository import TemplateRepository
|
||||
db = SessionLocal()
|
||||
repo = TemplateRepository(db)
|
||||
db_template = repo.create(template)
|
||||
template = repo.to_dataclass(db_template)
|
||||
db.close()
|
||||
return _build_template_response(template)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to create template: {e}")
|
||||
raise HTTPException(status_code=500, detail="Fehler beim Erstellen der Vorlage")
|
||||
|
||||
|
||||
@router.put("/templates/{template_id}", response_model=TemplateResponse)
|
||||
async def update_template(
|
||||
template_id: str,
|
||||
request: TemplateUpdate,
|
||||
teacher_id: str = Query(..., description="ID des Lehrers (zur Berechtigung)")
|
||||
) -> TemplateResponse:
|
||||
"""
|
||||
Aktualisiert eine Stunden-Vorlage.
|
||||
|
||||
Nur der Ersteller kann die Vorlage bearbeiten.
|
||||
"""
|
||||
init_db_if_needed()
|
||||
|
||||
# System-Templates koennen nicht bearbeitet werden
|
||||
for t in SYSTEM_TEMPLATES:
|
||||
if t["template_id"] == template_id:
|
||||
raise HTTPException(status_code=403, detail="System-Vorlagen koennen nicht bearbeitet werden")
|
||||
|
||||
if not DB_ENABLED:
|
||||
raise HTTPException(status_code=503, detail="Datenbank nicht verfuegbar")
|
||||
|
||||
try:
|
||||
from classroom_engine.repository import TemplateRepository
|
||||
db = SessionLocal()
|
||||
repo = TemplateRepository(db)
|
||||
|
||||
db_template = repo.get_by_id(template_id)
|
||||
if not db_template:
|
||||
db.close()
|
||||
raise HTTPException(status_code=404, detail="Vorlage nicht gefunden")
|
||||
|
||||
if db_template.teacher_id != teacher_id:
|
||||
db.close()
|
||||
raise HTTPException(status_code=403, detail="Keine Berechtigung")
|
||||
|
||||
# Nur uebergebene Felder aktualisieren
|
||||
template = repo.to_dataclass(db_template)
|
||||
if request.name is not None:
|
||||
template.name = request.name
|
||||
if request.description is not None:
|
||||
template.description = request.description
|
||||
if request.subject is not None:
|
||||
template.subject = request.subject
|
||||
if request.grade_level is not None:
|
||||
template.grade_level = request.grade_level
|
||||
if request.phase_durations is not None:
|
||||
template.phase_durations = request.phase_durations
|
||||
if request.default_topic is not None:
|
||||
template.default_topic = request.default_topic
|
||||
if request.default_notes is not None:
|
||||
template.default_notes = request.default_notes
|
||||
if request.is_public is not None:
|
||||
template.is_public = request.is_public
|
||||
|
||||
db_template = repo.update(template)
|
||||
template = repo.to_dataclass(db_template)
|
||||
db.close()
|
||||
return _build_template_response(template)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to update template {template_id}: {e}")
|
||||
raise HTTPException(status_code=500, detail="Fehler beim Aktualisieren der Vorlage")
|
||||
|
||||
|
||||
@router.delete("/templates/{template_id}")
|
||||
async def delete_template(
|
||||
template_id: str,
|
||||
teacher_id: str = Query(..., description="ID des Lehrers (zur Berechtigung)")
|
||||
) -> Dict[str, str]:
|
||||
"""
|
||||
Loescht eine Stunden-Vorlage.
|
||||
"""
|
||||
init_db_if_needed()
|
||||
|
||||
# System-Templates koennen nicht geloescht werden
|
||||
for t in SYSTEM_TEMPLATES:
|
||||
if t["template_id"] == template_id:
|
||||
raise HTTPException(status_code=403, detail="System-Vorlagen koennen nicht geloescht werden")
|
||||
|
||||
if not DB_ENABLED:
|
||||
raise HTTPException(status_code=503, detail="Datenbank nicht verfuegbar")
|
||||
|
||||
try:
|
||||
from classroom_engine.repository import TemplateRepository
|
||||
db = SessionLocal()
|
||||
repo = TemplateRepository(db)
|
||||
|
||||
db_template = repo.get_by_id(template_id)
|
||||
if not db_template:
|
||||
db.close()
|
||||
raise HTTPException(status_code=404, detail="Vorlage nicht gefunden")
|
||||
|
||||
if db_template.teacher_id != teacher_id:
|
||||
db.close()
|
||||
raise HTTPException(status_code=403, detail="Keine Berechtigung")
|
||||
|
||||
repo.delete(template_id)
|
||||
db.close()
|
||||
return {"status": "deleted", "template_id": template_id}
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to delete template {template_id}: {e}")
|
||||
raise HTTPException(status_code=500, detail="Fehler beim Loeschen der Vorlage")
|
||||
|
||||
|
||||
@router.post("/sessions/from-template", response_model=SessionResponse)
|
||||
async def create_session_from_template(
|
||||
template_id: str = Query(..., description="ID der Vorlage"),
|
||||
teacher_id: str = Query(..., description="ID des Lehrers"),
|
||||
class_id: str = Query(..., description="ID der Klasse"),
|
||||
topic: Optional[str] = Query(None, description="Optionales Thema (ueberschreibt Default)")
|
||||
) -> SessionResponse:
|
||||
"""
|
||||
Erstellt eine neue Session basierend auf einer Vorlage.
|
||||
|
||||
Erhoeht automatisch den Usage-Counter der Vorlage.
|
||||
"""
|
||||
init_db_if_needed()
|
||||
|
||||
# Template laden
|
||||
template_data = None
|
||||
is_system = False
|
||||
|
||||
# System-Template pruefen
|
||||
for t in SYSTEM_TEMPLATES:
|
||||
if t["template_id"] == template_id:
|
||||
template_data = t
|
||||
is_system = True
|
||||
break
|
||||
|
||||
# DB-Template pruefen
|
||||
if not template_data and DB_ENABLED:
|
||||
try:
|
||||
from classroom_engine.repository import TemplateRepository
|
||||
db = SessionLocal()
|
||||
repo = TemplateRepository(db)
|
||||
db_template = repo.get_by_id(template_id)
|
||||
if db_template:
|
||||
template_data = {
|
||||
"phase_durations": db_template.phase_durations or get_default_durations(),
|
||||
"subject": db_template.subject or "",
|
||||
"default_topic": db_template.default_topic or "",
|
||||
"default_notes": db_template.default_notes or "",
|
||||
}
|
||||
# Usage Counter erhoehen
|
||||
repo.increment_usage(template_id)
|
||||
db.close()
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to load template {template_id}: {e}")
|
||||
|
||||
if not template_data:
|
||||
raise HTTPException(status_code=404, detail="Vorlage nicht gefunden")
|
||||
|
||||
# Session erstellen
|
||||
session = LessonSession(
|
||||
session_id=str(uuid4()),
|
||||
teacher_id=teacher_id,
|
||||
class_id=class_id,
|
||||
subject=template_data.get("subject", ""),
|
||||
topic=topic or template_data.get("default_topic", ""),
|
||||
phase_durations=template_data["phase_durations"],
|
||||
notes=template_data.get("default_notes", ""),
|
||||
)
|
||||
|
||||
sessions[session.session_id] = session
|
||||
persist_session(session)
|
||||
|
||||
return build_session_response(session)
|
||||
Reference in New Issue
Block a user