""" API: Katalog-Prompt-Slots (Stammdaten × Slot-Typ). Globaler Admin-Katalog (wie catalogs.py) — require_auth + Admin-Rolle, kein TenantContext. Eingetragen in backend/scripts/check_access_layer_hints.py EXEMPT_ROUTERS. """ from __future__ import annotations from typing import Any, Dict, Optional from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel, Field from auth import require_auth from catalog_prompt_slots import ( CATALOG_KINDS, get_catalog_entry_slots, list_slot_type_definitions, upsert_catalog_entry_slots, ) from db import get_cursor, get_db router = APIRouter(prefix="/api", tags=["catalog_prompt_slots"]) _VALID_KINDS = frozenset(c.kind for c in CATALOG_KINDS) class CatalogPromptSlotsBody(BaseModel): slots: Dict[str, Optional[str]] = Field(default_factory=dict) def _require_admin(session: dict = Depends(require_auth)) -> dict: role = (session.get("role") or "").strip().lower() if role not in ("admin", "superadmin"): raise HTTPException(status_code=403, detail="Nur Admins") return session def _slots_table_ready(cur) -> bool: cur.execute("SELECT to_regclass(%s)::text AS t", ("public.catalog_prompt_slots",)) row = cur.fetchone() if not row: return False val = row.get("t") if isinstance(row, dict) else row[0] return val is not None and str(val).strip() != "" @router.get("/catalog-prompt-slot-types") def api_list_catalog_prompt_slot_types(session: dict = Depends(_require_admin)): with get_db() as conn: cur = get_cursor(conn) if not _slots_table_ready(cur): raise HTTPException(status_code=503, detail="Tabelle catalog_prompt_slots fehlt.") return {"slot_types": list_slot_type_definitions(cur)} @router.get("/catalog-prompt-slots/{catalog_kind}/{catalog_id}") def api_get_catalog_prompt_slots( catalog_kind: str, catalog_id: int, session: dict = Depends(_require_admin), ): kind = (catalog_kind or "").strip().lower() if kind not in _VALID_KINDS: raise HTTPException(status_code=400, detail=f"Unbekannter catalog_kind: {catalog_kind!r}") with get_db() as conn: cur = get_cursor(conn) if not _slots_table_ready(cur): raise HTTPException(status_code=503, detail="Tabelle catalog_prompt_slots fehlt.") try: return get_catalog_entry_slots(cur, kind, catalog_id) except LookupError as exc: raise HTTPException(status_code=404, detail=str(exc)) from exc except ValueError as exc: raise HTTPException(status_code=400, detail=str(exc)) from exc @router.put("/catalog-prompt-slots/{catalog_kind}/{catalog_id}") def api_put_catalog_prompt_slots( catalog_kind: str, catalog_id: int, body: CatalogPromptSlotsBody, session: dict = Depends(_require_admin), ): kind = (catalog_kind or "").strip().lower() if kind not in _VALID_KINDS: raise HTTPException(status_code=400, detail=f"Unbekannter catalog_kind: {catalog_kind!r}") with get_db() as conn: cur = get_cursor(conn) if not _slots_table_ready(cur): raise HTTPException(status_code=503, detail="Tabelle catalog_prompt_slots fehlt.") try: return upsert_catalog_entry_slots(cur, kind, catalog_id, body.slots or {}) except LookupError as exc: raise HTTPException(status_code=404, detail=str(exc)) from exc except ValueError as exc: raise HTTPException(status_code=400, detail=str(exc)) from exc