feat(cmp): unified consent view — Website-Besucher + Login-Nutzer tabs

Merges two separate consent views into one unified page at /sdk/einwilligungen:
- Tab "Website-Besucher": device-based banner consents with site selector
- Tab "Login-Nutzer": user-based DSGVO consents (existing, unchanged)

Backend:
- New endpoint GET /admin/consents for paginated banner consent records
- Fix: categories JSON string parsing (was iterating chars instead of array)

CMP Dashboard:
- Dynamic site selector replacing hardcoded "preview-test-site"

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-05-10 22:41:56 +02:00
parent 9c0d471277
commit bdbc30e47b
7 changed files with 478 additions and 52 deletions
@@ -348,7 +348,14 @@ class BannerConsentService:
total = base.count()
category_stats: dict[str, int] = {}
for c in base.all():
cats: list[str] = list(c.categories or [])
raw = c.categories or []
if isinstance(raw, str):
try:
import json
raw = json.loads(raw)
except (json.JSONDecodeError, TypeError):
raw = []
cats: list[str] = list(raw) if isinstance(raw, list) else []
for cat in cats:
category_stats[cat] = category_stats.get(cat, 0) + 1
return {
@@ -362,3 +369,45 @@ class BannerConsentService:
for cat, count in category_stats.items()
},
}
def list_consents(
self, tenant_id: str, site_id: str | None = None,
limit: int = 50, offset: int = 0,
) -> dict[str, Any]:
"""List paginated banner consents with parsed categories."""
import json as _json
tid = uuid.UUID(tenant_id)
base = self.db.query(BannerConsentDB).filter(BannerConsentDB.tenant_id == tid)
if site_id:
base = base.filter(BannerConsentDB.site_id == site_id)
total = base.count()
rows = base.order_by(BannerConsentDB.created_at.desc()).offset(offset).limit(limit).all()
consents = []
for c in rows:
raw_cats = c.categories or []
if isinstance(raw_cats, str):
try:
raw_cats = _json.loads(raw_cats)
except (ValueError, TypeError):
raw_cats = []
raw_vendors = c.vendors or []
if isinstance(raw_vendors, str):
try:
raw_vendors = _json.loads(raw_vendors)
except (ValueError, TypeError):
raw_vendors = []
consents.append({
"id": str(c.id),
"site_id": c.site_id,
"device_fingerprint": c.device_fingerprint,
"categories": list(raw_cats) if isinstance(raw_cats, list) else [],
"vendors": list(raw_vendors) if isinstance(raw_vendors, list) else [],
"ip_hash": c.ip_hash,
"user_agent": c.user_agent,
"linked_email": c.linked_email,
"consent_string": c.consent_string,
"expires_at": c.expires_at.isoformat() if c.expires_at else None,
"created_at": c.created_at.isoformat() if c.created_at else None,
"updated_at": c.updated_at.isoformat() if c.updated_at else None,
})
return {"consents": consents, "total": total, "limit": limit, "offset": offset}