Python (6 files in klausur-service): - rbac.py (1,132 → 4), admin_api.py (1,012 → 4) - routes/eh.py (1,111 → 4), ocr_pipeline_geometry.py (1,105 → 5) Python (2 files in backend-lehrer): - unit_api.py (1,226 → 6), game_api.py (1,129 → 5) Website (6 page files): - 4x klausur-korrektur pages (1,249-1,328 LOC each) → shared components in website/components/klausur-korrektur/ (17 shared files) - companion (1,057 → 10), magic-help (1,017 → 8) All re-export barrels preserve backward compatibility. Zero import errors verified. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
161 lines
5.1 KiB
Python
161 lines
5.1 KiB
Python
# ==============================================
|
|
# Breakpilot Drive - Unit Content Generation Routes
|
|
# ==============================================
|
|
# API endpoints for H5P content, worksheets, and PDF generation.
|
|
# Extracted from unit_api.py for file-size compliance.
|
|
|
|
from fastapi import APIRouter, HTTPException, Query, Depends
|
|
from typing import Optional, Dict, Any
|
|
import logging
|
|
|
|
from unit_models import UnitDefinitionResponse
|
|
from unit_helpers import get_optional_current_user, get_unit_database
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
router = APIRouter(prefix="/api/units", tags=["Breakpilot Units"])
|
|
|
|
|
|
@router.get("/content/{unit_id}/h5p")
|
|
async def generate_h5p_content(
|
|
unit_id: str,
|
|
locale: str = Query("de-DE", description="Target locale"),
|
|
user: Optional[Dict[str, Any]] = Depends(get_optional_current_user)
|
|
) -> Dict[str, Any]:
|
|
"""
|
|
Generate H5P content items for a unit.
|
|
|
|
Returns H5P-compatible content structures for:
|
|
- Drag and Drop (vocabulary matching)
|
|
- Fill in the Blanks (concept texts)
|
|
- Multiple Choice (misconception targeting)
|
|
"""
|
|
from content_generators import generate_h5p_for_unit, H5PGenerator, generate_h5p_manifest
|
|
|
|
# Get unit definition
|
|
db = await get_unit_database()
|
|
unit_def = None
|
|
|
|
if db:
|
|
try:
|
|
unit = await db.get_unit_definition(unit_id)
|
|
if unit:
|
|
unit_def = unit.get("definition", {})
|
|
except Exception as e:
|
|
logger.error(f"Failed to get unit for H5P generation: {e}")
|
|
|
|
if not unit_def:
|
|
raise HTTPException(status_code=404, detail=f"Unit not found: {unit_id}")
|
|
|
|
try:
|
|
generator = H5PGenerator(locale=locale)
|
|
contents = generator.generate_from_unit(unit_def)
|
|
manifest = generate_h5p_manifest(contents, unit_id)
|
|
|
|
return {
|
|
"unit_id": unit_id,
|
|
"locale": locale,
|
|
"generated_count": len(contents),
|
|
"manifest": manifest,
|
|
"contents": [c.to_h5p_structure() for c in contents]
|
|
}
|
|
except Exception as e:
|
|
logger.error(f"H5P generation failed: {e}")
|
|
raise HTTPException(status_code=500, detail=f"H5P generation failed: {str(e)}")
|
|
|
|
|
|
@router.get("/content/{unit_id}/worksheet")
|
|
async def generate_worksheet_html(
|
|
unit_id: str,
|
|
locale: str = Query("de-DE", description="Target locale"),
|
|
user: Optional[Dict[str, Any]] = Depends(get_optional_current_user)
|
|
) -> Dict[str, Any]:
|
|
"""
|
|
Generate worksheet HTML for a unit.
|
|
|
|
Returns HTML that can be:
|
|
- Displayed in browser
|
|
- Converted to PDF using weasyprint
|
|
- Printed directly
|
|
"""
|
|
from content_generators import PDFGenerator
|
|
|
|
# Get unit definition
|
|
db = await get_unit_database()
|
|
unit_def = None
|
|
|
|
if db:
|
|
try:
|
|
unit = await db.get_unit_definition(unit_id)
|
|
if unit:
|
|
unit_def = unit.get("definition", {})
|
|
except Exception as e:
|
|
logger.error(f"Failed to get unit for worksheet generation: {e}")
|
|
|
|
if not unit_def:
|
|
raise HTTPException(status_code=404, detail=f"Unit not found: {unit_id}")
|
|
|
|
try:
|
|
generator = PDFGenerator(locale=locale)
|
|
worksheet = generator.generate_from_unit(unit_def)
|
|
|
|
return {
|
|
"unit_id": unit_id,
|
|
"locale": locale,
|
|
"title": worksheet.title,
|
|
"sections": len(worksheet.sections),
|
|
"html": worksheet.to_html()
|
|
}
|
|
except Exception as e:
|
|
logger.error(f"Worksheet generation failed: {e}")
|
|
raise HTTPException(status_code=500, detail=f"Worksheet generation failed: {str(e)}")
|
|
|
|
|
|
@router.get("/content/{unit_id}/worksheet.pdf")
|
|
async def download_worksheet_pdf(
|
|
unit_id: str,
|
|
locale: str = Query("de-DE", description="Target locale"),
|
|
user: Optional[Dict[str, Any]] = Depends(get_optional_current_user)
|
|
):
|
|
"""
|
|
Generate and download worksheet as PDF.
|
|
|
|
Requires weasyprint to be installed on the server.
|
|
"""
|
|
from fastapi.responses import Response
|
|
|
|
# Get unit definition
|
|
db = await get_unit_database()
|
|
unit_def = None
|
|
|
|
if db:
|
|
try:
|
|
unit = await db.get_unit_definition(unit_id)
|
|
if unit:
|
|
unit_def = unit.get("definition", {})
|
|
except Exception as e:
|
|
logger.error(f"Failed to get unit for PDF generation: {e}")
|
|
|
|
if not unit_def:
|
|
raise HTTPException(status_code=404, detail=f"Unit not found: {unit_id}")
|
|
|
|
try:
|
|
from content_generators import generate_worksheet_pdf
|
|
pdf_bytes = generate_worksheet_pdf(unit_def, locale)
|
|
|
|
return Response(
|
|
content=pdf_bytes,
|
|
media_type="application/pdf",
|
|
headers={
|
|
"Content-Disposition": f'attachment; filename="{unit_id}_worksheet.pdf"'
|
|
}
|
|
)
|
|
except ImportError:
|
|
raise HTTPException(
|
|
status_code=501,
|
|
detail="PDF generation not available. Install weasyprint: pip install weasyprint"
|
|
)
|
|
except Exception as e:
|
|
logger.error(f"PDF generation failed: {e}")
|
|
raise HTTPException(status_code=500, detail=f"PDF generation failed: {str(e)}")
|