""" DB-Override für Dashboard-Widget → Feature(s): AND-Semantik. Ohne Zeile in dashboard_widget_requirement_custom → Fallback auf widget_catalog.requires_feature. Mit Marker-Zeile → nur widget_feature_requirements (0..n, leer = kein Feature erforderlich). """ from __future__ import annotations from typing import Any from db import get_cursor, get_db from widget_catalog import WIDGET_CATALOG _WIDGET_ENTRY_BY_ID: dict[str, dict[str, Any]] = {e["id"]: e for e in WIDGET_CATALOG} def _catalog_feature_ids_for_widget(widget_id: str) -> list[str]: entry = _WIDGET_ENTRY_BY_ID.get(widget_id) if not entry: return [] fid = entry.get("requires_feature") return [fid] if fid else [] def get_widget_required_feature_ids(widget_id: str, conn: Any | None) -> list[str]: """Liste der Feature-IDs, die alle erlaubt sein müssen (AND). Leer = ohne Feature-Gate.""" def _query(c: Any) -> list[str]: cur = get_cursor(c) cur.execute( "SELECT 1 FROM dashboard_widget_requirement_custom WHERE widget_id = %s", (widget_id,), ) if cur.fetchone() is None: return _catalog_feature_ids_for_widget(widget_id) cur.execute( """ SELECT feature_id FROM widget_feature_requirements WHERE widget_id = %s ORDER BY feature_id """, (widget_id,), ) return [row["feature_id"] for row in cur.fetchall()] if conn is not None: return _query(conn) with get_db() as c: return _query(c) def fetch_assignments_bundle(conn: Any) -> tuple[set[str], dict[str, list[str]]]: """ Alle Custom-Marker und Junction-Zeilen für Admin-GET (ein Roundtrip). Returns (custom_widget_ids, feature_ids_by_widget). """ cur = get_cursor(conn) cur.execute("SELECT widget_id FROM dashboard_widget_requirement_custom") custom_ids = {row["widget_id"] for row in cur.fetchall()} cur.execute( """ SELECT widget_id, feature_id FROM widget_feature_requirements ORDER BY widget_id, feature_id """ ) by_w: dict[str, list[str]] = {} for row in cur.fetchall(): by_w.setdefault(row["widget_id"], []).append(row["feature_id"]) return custom_ids, by_w def set_custom_requirements(conn: Any, widget_id: str, feature_ids: list[str]) -> None: """Custom aktivieren und Anforderungen ersetzen (dedupliziert).""" seen: set[str] = set() unique: list[str] = [] for fid in feature_ids: if fid and fid not in seen: seen.add(fid) unique.append(fid) cur = get_cursor(conn) cur.execute( """ INSERT INTO dashboard_widget_requirement_custom (widget_id) VALUES (%s) ON CONFLICT (widget_id) DO UPDATE SET updated_at = CURRENT_TIMESTAMP """, (widget_id,), ) cur.execute("DELETE FROM widget_feature_requirements WHERE widget_id = %s", (widget_id,)) for fid in unique: cur.execute( """ INSERT INTO widget_feature_requirements (widget_id, feature_id) VALUES (%s, %s) """, (widget_id, fid), ) def clear_custom_requirements(conn: Any, widget_id: str) -> None: """Zurück auf Katalog-Fallback.""" cur = get_cursor(conn) cur.execute( "DELETE FROM dashboard_widget_requirement_custom WHERE widget_id = %s", (widget_id,), )