76 lines
2.5 KiB
Python
76 lines
2.5 KiB
Python
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"],
|
|
},
|
|
)
|