mitai-jinkendo/backend/dashboard_widget_entitlements.py

88 lines
2.8 KiB
Python
Raw Normal View History

"""
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