""" Dashboard-Widgets × Feature-System: Sichtbarkeit aus check_feature_access. Katalog-Einträge optional `requires_feature` (features.id). Fehlt der Key → immer erlaubt. """ from __future__ import annotations import copy from typing import Any from widget_catalog import WIDGET_CATALOG from widget_feature_requirements_db import get_widget_required_feature_ids def _check_feature_access(profile_id: str, feature_id: str, conn) -> dict: """Indirection für Tests (monkeypatch) und spätes Laden von auth (bcrypt).""" from auth import check_feature_access return check_feature_access(profile_id, feature_id, conn) _WIDGET_ENTRY_BY_ID: dict[str, dict[str, Any]] = {e["id"]: e for e in WIDGET_CATALOG} def widget_id_allowed(widget_id: str, profile_id: str, conn) -> bool: if _WIDGET_ENTRY_BY_ID.get(widget_id) is None: return False fids = get_widget_required_feature_ids(widget_id, conn) if not fids: return True for fid in fids: if not _check_feature_access(profile_id, fid, conn)["allowed"]: return False return True def _public_row(entry: dict[str, Any], *, allowed: bool) -> dict[str, Any]: return { "id": entry["id"], "title": entry["title"], "description": entry["description"], "allowed": allowed, } def widgets_catalog_for_profile(profile_id: str, conn) -> list[dict[str, Any]]: """Zeilen für GET /api/app/widgets/catalog (ohne internes requires_feature-Feld).""" out: list[dict[str, Any]] = [] for e in WIDGET_CATALOG: allowed = widget_id_allowed(e["id"], profile_id, conn) out.append(_public_row(e, allowed=allowed)) return out def widgets_catalog_payload(profile_id: str, conn) -> dict[str, Any]: return { "catalog_version": 1, "widgets": widgets_catalog_for_profile(profile_id, conn), } def widgets_catalog_admin_payload() -> dict[str, Any]: """Admin: alle Widgets als auswählbar (ohne Feature-Filter).""" return { "catalog_version": 1, "widgets": [_public_row(e, allowed=True) for e in WIDGET_CATALOG], } def apply_entitlements_to_layout_dict(layout: dict[str, Any], profile_id: str, conn) -> dict[str, Any]: """ Setzt enabled=False für Widgets ohne Berechtigung. Mindestens ein Widget bleibt aktiv (welcome). """ out = copy.deepcopy(layout) widgets = out.get("widgets") or [] for w in widgets: wid = w.get("id") if not wid: continue if w.get("enabled") and not widget_id_allowed(wid, profile_id, conn): w["enabled"] = False if not any(w.get("enabled") for w in widgets): for w in widgets: if w.get("id") == "welcome": w["enabled"] = True break return out