# -*- coding: utf-8 -*- """ Plugin-Adapter für die Rezept-Inventur. Nur lesend: Es gibt hier bewusst keinen Endpunkt, der etwas verändern könnte. """ from __future__ import annotations import json import sys from typing import Any from fastapi import FastAPI, HTTPException from fastapi.responses import FileResponse from pydantic import BaseModel, Field from core.jobs import job_router TOOL = "lint.py" SEVERITIES = {"err", "warn", "info"} class ScanRequest(BaseModel): only: list[str] = Field(default_factory=list) skip: list[str] = Field(default_factory=list) insecure: bool = False def create_app(ctx): tool = ctx.path("tool", TOOL) report_file = ctx.data_dir / "bericht" / "bericht.json" csv_file = ctx.data_dir / "bericht" / "befunde.csv" def tool_env() -> dict: """ Umgebung für den Subprozess. Wichtig: DATA_DIR ausdrücklich mitgeben. Ohne das schreibt das Skript seinen Rückfallpfad neben die Anwendung — im Container ist der schreibgeschützt, und die Oberfläche würde den Bericht nie finden. """ env = ctx.settings.tool_env() env["DATA_DIR"] = str(ctx.data_dir) return env app = FastAPI(title=ctx.meta.name, docs_url="/api/docs", redoc_url=None) @app.get("/", include_in_schema=False) def index(): return FileResponse(ctx.path("static", "index.html")) @app.get("/api/state") def state() -> dict[str, Any]: report = None if report_file.is_file(): try: report = json.loads(report_file.read_text(encoding="utf-8")) except Exception: report = None running = [j.info() for j in ctx.jobs.running(ctx.id)] return { "report": report, "tandoor": ctx.settings.status()["tandoor"], "running": running[0] if running else None, "has_csv": csv_file.is_file(), } @app.get("/api/export.csv") def export(): if not csv_file.is_file(): raise HTTPException(404, "Noch kein Bericht vorhanden.") return FileResponse(csv_file, media_type="text/csv", filename="befunde.csv") @app.post("/api/run/scan") async def run_scan(request: ScanRequest) -> dict[str, Any]: if ctx.jobs.running(ctx.id): raise HTTPException(409, "Die Inventur läuft bereits.") if not ctx.settings.status()["tandoor"]: raise HTTPException(400, "Tandoor-URL und Token fehlen – siehe Einstellungen.") argv = [sys.executable, str(tool)] if request.insecure: argv.append("--insecure") argv.append("pruefen") bad = [s for s in request.only if s not in SEVERITIES] if bad: raise HTTPException(400, f"Unbekannter Schweregrad: {bad}") if request.only: argv += ["--nur", ",".join(request.only)] if request.skip: # Die Schlüssel prüft das Skript selbst; hier nur grob absichern. clean = [s for s in request.skip if s.replace("_", "").isalnum()] if clean: argv += ["--ohne", ",".join(clean)] job = await ctx.jobs.start( plugin=ctx.id, label="Inventur der Sammlung", argv=argv, cwd=ctx.data_dir, env=tool_env(), ) return job.info() app.include_router(job_router(ctx.jobs, ctx.id)) return app