from __future__ import annotations import os from typing import Any from fastapi import FastAPI, HTTPException, Request, Response from fastapi.responses import HTMLResponse, JSONResponse from .renderer import ALLOWED_THEMES, pdf_filename, render_documents, render_pdf_with_gotenberg from .theme_catalog import public_theme_catalog from .validation import validate_report app = FastAPI( title="Monthly Report Renderer", version="2.0.0", description="Validiert Report-Payloads, rendert feste HTML-Templates und erzeugt PDFs via Gotenberg.", ) @app.middleware("http") async def limit_request_size(request: Request, call_next): max_bytes = int(os.getenv("MAX_PAYLOAD_BYTES", "2097152")) content_length = request.headers.get("content-length") if content_length and int(content_length) > max_bytes: return JSONResponse(status_code=413, content={"detail": "Payload too large"}) return await call_next(request) @app.get("/health") def health() -> dict[str, Any]: return {"status": "ok", "service": "monthly-report-renderer", "version": "2.0.0", "theme_count": len(ALLOWED_THEMES)} @app.get("/themes") def themes() -> dict[str, Any]: return {"themes": sorted(ALLOWED_THEMES), "catalog": public_theme_catalog()} @app.post("/validate") def validate(payload: dict[str, Any]) -> dict[str, Any]: errors = validate_report(payload) return {"valid": not errors, "errors": errors} def _ensure_valid(payload: dict[str, Any]) -> None: errors = validate_report(payload) if errors: raise HTTPException(status_code=422, detail={"message": "Invalid report payload", "errors": errors}) @app.post("/preview/html", response_class=HTMLResponse) def preview_html(payload: dict[str, Any]) -> HTMLResponse: _ensure_valid(payload) html, _ = render_documents(payload) return HTMLResponse(content=html, status_code=200) @app.post("/render") def render(payload: dict[str, Any]) -> Response: _ensure_valid(payload) try: pdf = render_pdf_with_gotenberg(payload) except Exception as exc: raise HTTPException(status_code=502, detail=f"PDF rendering failed: {exc}") from exc filename = pdf_filename(payload) return Response( content=pdf, media_type="application/pdf", headers={ "Content-Disposition": f'attachment; filename="{filename}"', "X-Report-Version": payload["report"]["report_version"], "X-Template-Version": payload["report"]["template_version"], }, )