Services: Admin-Lehrer, Backend-Lehrer, Studio v2, Website, Klausur-Service, School-Service, Voice-Service, Geo-Service, BreakPilot Drive, Agent-Core Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
383 lines
13 KiB
Python
383 lines
13 KiB
Python
"""
|
|
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)
|