mitai-jinkendo/backend/routers/app_dashboard.py
Lars 01c0d1745f
All checks were successful
Deploy Development / deploy (push) Successful in 56s
Build Test / pytest-backend (push) Successful in 4s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 16s
feat: implement merge_missing_catalog_widgets function to enhance dashboard layout
- Added the `merge_missing_catalog_widgets` function to append missing widget IDs from the catalog to the dashboard layout while preserving the existing order.
- Updated the admin and app dashboard routes to utilize the new function, ensuring that new catalog entries are visible without requiring users to reset their layouts.
- Enhanced tests to validate the functionality of the new merging logic, ensuring proper integration with existing layouts.
- Bumped application version to reflect these changes.
2026-04-22 08:38:38 +02:00

119 lines
4.0 KiB
Python

"""
Geschützter App-Bereich: Dashboard-Lab Layout (kein Produktiv-Dashboard).
/api/app/dashboard-layout — nur mit Session + aktivem Profil (X-Profile-Id).
"""
from typing import Any, Optional
from fastapi import APIRouter, Depends, Header, HTTPException
from psycopg2.extras import Json
from auth import require_auth
from dashboard_layout_schema import (
DashboardLayoutPayload,
coalesce_effective_layout,
lab_default_layout_dict,
merge_missing_catalog_widgets,
)
from dashboard_widget_entitlements import apply_entitlements_to_layout_dict, widgets_catalog_payload
from db import get_cursor, get_db
from routers.profiles import get_pid
from system_dashboard_product_default import get_product_default_base_dict
router = APIRouter(prefix="/api/app", tags=["app-dashboard-lab"])
@router.get("/widgets/catalog")
def get_widgets_catalog(
x_profile_id: Optional[str] = Header(default=None),
session: dict = Depends(require_auth),
) -> dict[str, Any]:
"""Katalog inkl. allowed pro Widget (Feature / Subscription, effektiver Tier)."""
_ = session
pid = get_pid(x_profile_id)
with get_db() as conn:
return widgets_catalog_payload(pid, conn)
@router.get("/dashboard-layout")
def get_dashboard_layout(
x_profile_id: Optional[str] = Header(default=None),
session: dict = Depends(require_auth),
) -> dict[str, Any]:
_ = session
pid = get_pid(x_profile_id)
with get_db() as conn:
cur = get_cursor(conn)
cur.execute(
"SELECT dashboard_layout FROM profiles WHERE id = %s",
(pid,),
)
row = cur.fetchone()
raw = row["dashboard_layout"] if row else None
custom, effective = coalesce_effective_layout(raw)
with get_db() as conn:
base_product = merge_missing_catalog_widgets(get_product_default_base_dict(conn))
if not custom:
effective = base_product
else:
effective = merge_missing_catalog_widgets(effective)
effective = apply_entitlements_to_layout_dict(effective, pid, conn)
product_adj = apply_entitlements_to_layout_dict(base_product, pid, conn)
lab_adj = apply_entitlements_to_layout_dict(lab_default_layout_dict(), pid, conn)
return {
"custom": custom,
"layout": effective,
"product_default_layout": product_adj,
"lab_default_layout": lab_adj,
}
@router.put("/dashboard-layout")
def put_dashboard_layout(
body: dict[str, Any],
x_profile_id: Optional[str] = Header(default=None),
session: dict = Depends(require_auth),
) -> dict[str, Any]:
_ = session
pid = get_pid(x_profile_id)
try:
payload = DashboardLayoutPayload.model_validate(body)
except Exception as e:
raise HTTPException(422, str(e)) from e
with get_db() as conn:
adjusted = apply_entitlements_to_layout_dict(payload.to_stored_dict(), pid, conn)
try:
payload = DashboardLayoutPayload.model_validate(adjusted)
except Exception as e:
raise HTTPException(422, str(e)) from e
stored = payload.to_stored_dict()
with get_db() as conn:
cur = get_cursor(conn)
cur.execute(
"UPDATE profiles SET dashboard_layout = %s WHERE id = %s",
(Json(stored), pid),
)
if cur.rowcount == 0:
raise HTTPException(404, "Profil nicht gefunden")
return {"ok": True, "layout": stored}
@router.post("/dashboard-layout/reset")
def reset_dashboard_layout(
x_profile_id: Optional[str] = Header(default=None),
session: dict = Depends(require_auth),
) -> dict[str, Any]:
_ = session
pid = get_pid(x_profile_id)
with get_db() as conn:
cur = get_cursor(conn)
cur.execute(
"UPDATE profiles SET dashboard_layout = NULL WHERE id = %s",
(pid,),
)
if cur.rowcount == 0:
raise HTTPException(404, "Profil nicht gefunden")
base = get_product_default_base_dict(conn)
cleared = apply_entitlements_to_layout_dict(base, pid, conn)
return {"ok": True, "layout": cleared}