""" Vereins-Kontingent-Bypass über das Capability-System (kein Parallel-Rechtemodell). Capabilities: - platform.club_quota.bypass — alle Vereins-Features (Portal-Admin, Grant via portal_role) - platform.club_quota.bypass.{feature_id} — ein Feature (domain quota_bypass, auch für Nicht-Admins per Grant) """ from __future__ import annotations from typing import Any, Dict, List, Optional, TYPE_CHECKING if TYPE_CHECKING: from tenant_context import TenantContext QUOTA_BYPASS_ALL = "platform.club_quota.bypass" QUOTA_BYPASS_FEATURE_PREFIX = "platform.club_quota.bypass." def quota_bypass_capability_id_for_feature(feature_id: str) -> str: return f"{QUOTA_BYPASS_FEATURE_PREFIX}{feature_id}" def ensure_quota_bypass_capability(cur, feature_id: str) -> str: """Legt feature-spezifische Bypass-Capability an falls nötig.""" cap_id = quota_bypass_capability_id_for_feature(feature_id) cur.execute( """ INSERT INTO capabilities (id, name, domain, min_account_state, linked_feature_id) VALUES (%s, %s, 'quota_bypass', 'active_member', %s) ON CONFLICT (id) DO NOTHING """, (cap_id, f"Vereins-Kontingent umgehen: {feature_id}", feature_id), ) return cap_id def _bypass_capability_ids(cur, feature_id: str) -> List[str]: ids: List[str] = [QUOTA_BYPASS_ALL, quota_bypass_capability_id_for_feature(feature_id)] cur.execute( """ SELECT id FROM capabilities WHERE active = true AND domain = 'quota_bypass' AND linked_feature_id = %s AND id <> %s """, (feature_id, quota_bypass_capability_id_for_feature(feature_id)), ) for row in cur.fetchall(): cid = row.get("id") if cid and cid not in ids: ids.append(str(cid)) return ids def _portal_role_has_grant(cur, portal_role: str, capability_id: str) -> bool: role = (portal_role or "").strip().lower() if not role: return False cur.execute( """ SELECT 1 FROM portal_role_capability_grants WHERE portal_role = %s AND capability_id = %s LIMIT 1 """, (role, capability_id), ) return cur.fetchone() is not None def _profile_has_grant(cur, profile_id: int, capability_id: str) -> bool: cur.execute( """ SELECT 1 FROM profile_capability_grants WHERE profile_id = %s AND capability_id = %s LIMIT 1 """, (int(profile_id), capability_id), ) return cur.fetchone() is not None def is_club_feature_quota_bypassed( cur, *, profile_id: Optional[int], portal_role: Optional[str], feature_id: str, tenant: Optional["TenantContext"] = None, ) -> bool: """ True wenn ein konfigurierter Capability-Grant das Vereins-Kontingent für feature_id umgeht. """ if tenant is not None: from capabilities import check_capability for cap_id in _bypass_capability_ids(cur, feature_id): if check_capability(cur, tenant, cap_id).get("allowed"): return True return False for cap_id in _bypass_capability_ids(cur, feature_id): if _portal_role_has_grant(cur, portal_role or "", cap_id): return True if profile_id is not None and _profile_has_grant(cur, int(profile_id), cap_id): return True return False def quota_bypass_access( *, feature_id: str, club_id: Optional[int] = None, plan_id: Optional[str] = None, capability_id: Optional[str] = None, ) -> Dict[str, Any]: return { "allowed": True, "limit": None, "used": 0, "remaining": None, "reason": "capability_quota_bypass", "platform_exempt": True, "quota_bypass_capability": capability_id, "plan_id": plan_id, "club_id": club_id, "feature_id": feature_id, } def list_quota_bypass_grants(cur) -> Dict[str, Any]: """Admin: alle Grants zu Kontingent-Bypass-Capabilities.""" cur.execute( """ SELECT g.portal_role, g.capability_id, c.name AS capability_name, c.linked_feature_id, c.domain FROM portal_role_capability_grants g INNER JOIN capabilities c ON c.id = g.capability_id WHERE g.capability_id = %s OR g.capability_id LIKE %s OR c.domain = 'quota_bypass' ORDER BY g.portal_role, g.capability_id """, (QUOTA_BYPASS_ALL, f"{QUOTA_BYPASS_FEATURE_PREFIX}%"), ) portal_grants = [dict(r) for r in cur.fetchall()] cur.execute( """ SELECT g.profile_id, p.email, p.name AS profile_name, g.capability_id, c.name AS capability_name, c.linked_feature_id, g.reason, g.granted_by_profile_id, g.created_at FROM profile_capability_grants g INNER JOIN profiles p ON p.id = g.profile_id INNER JOIN capabilities c ON c.id = g.capability_id WHERE g.capability_id = %s OR g.capability_id LIKE %s OR c.domain = 'quota_bypass' ORDER BY g.profile_id, g.capability_id """, (QUOTA_BYPASS_ALL, f"{QUOTA_BYPASS_FEATURE_PREFIX}%"), ) profile_grants = [dict(r) for r in cur.fetchall()] cur.execute( """ SELECT id, name, domain, linked_feature_id FROM capabilities WHERE id = %s OR id LIKE %s OR domain = 'quota_bypass' ORDER BY id """, (QUOTA_BYPASS_ALL, f"{QUOTA_BYPASS_FEATURE_PREFIX}%"), ) capabilities = [dict(r) for r in cur.fetchall()] return { "capabilities": capabilities, "portal_role_grants": portal_grants, "profile_grants": profile_grants, }