"""API des Viewers (Phase 2). Lese-Routen über viewer.py, Lauf-Routen über scan_dienst.""" from pathlib import Path from fastapi import APIRouter, HTTPException import agents import database as db import projekte import scan_dienst import viewer router = APIRouter(prefix="/api") def _wurzel(name: str) -> Path: wurzel = projekte.finde(name) if wurzel is None: raise HTTPException(404, f"Projekt '{name}' nicht in projekte.txt") return wurzel @router.get("/health") async def health(): return {"ok": True} @router.get("/projekte") async def liste(): out = [] for p in projekte.lade(): eintrag = dict(p) if "fehler" not in p: wurzel = Path(p["pfad"]) eintrag["hat_planer"] = (wurzel / ".planer" / "kern.md").is_file() staende = {d: s for d, (s, _) in scan_dienst.einheiten_bestand(wurzel).items()} eintrag["drift"] = viewer.drift(wurzel, staende)["veraltet"] eintrag["letzter_scan_ts"] = await db.letzter_agent_ts(p["name"]) lauf = scan_dienst.status(p["name"]) eintrag["scan_laeuft"] = bool(lauf and lauf["laeuft"]) out.append(eintrag) return out @router.get("/projekte/{name}") async def projekt(name: str): wurzel = _wurzel(name) daten = viewer.lade_projekt(wurzel) einheiten_kurz = {eid: {"beschreibung": e["beschreibung"]} for eid, e in daten["einheiten"].items()} return { "name": name, "kern": daten["kern"], "bereiche": daten["bereiche"], "architektur": daten["architektur"], "flows": daten["flows"], "pruefung": daten["pruefung"], "einheiten": einheiten_kurz, "doku_ordnung": viewer.doku_ordnung(daten), "drift": viewer.drift(wurzel, daten["staende"]), } @router.get("/projekte/{name}/einheit") async def einheit(name: str, id: str): wurzel = _wurzel(name) daten = viewer.lade_projekt(wurzel) e = daten["einheiten"].get(id) if e is None: raise HTTPException(404, f"Einheit '{id}' unbekannt") quelldatei = id.partition("::")[0] veraltet = quelldatei in viewer.drift(wurzel, daten["staende"])["veraltet"] code = viewer.code_extrahieren(wurzel, id) for anker in e["anker"]: zusatz = viewer.code_extrahieren(wurzel, anker) if zusatz and zusatz not in code: code += ("\n\n" if code else "") + zusatz return {**e, "code": code, "veraltet": veraltet} @router.post("/projekte/{name}/scan") async def scan_starten(name: str, body: dict): wurzel = _wurzel(name) art = body.get("art", "scan") if art not in ("scan", "rescan"): raise HTTPException(400, f"unbekannte Art '{art}'") try: status = scan_dienst.starte(name, wurzel, art) except scan_dienst.VorflugFehler as e: raise HTTPException(409, str(e)) return {"status": status} @router.get("/projekte/{name}/scan-status") async def scan_status(name: str): _wurzel(name) lauf = scan_dienst.status(name) if lauf is None: return {"laeuft": False, "art": None, "ergebnis": None, "stufen": {}, "agenten": [], "dead": []} out = dict(lauf) if lauf["laeuft"]: out["stufen"] = (await db.kanban_stage_counts(name)).get("scan", {}) out["agenten"] = agents.active_agents(f"scan-{name}-") out["dead"] = [c["card_id"] for c in await db.kanban_dead(name)] else: out["stufen"] = {} out["agenten"] = [] out["dead"] = (lauf["ergebnis"] or {}).get("dead", []) return out