""" Registry-first: Module melden Rechte (capabilities) und Kontingente (features) bei Implementierung an. Kein vollständiger Vorab-Katalog — nur was ein Modul wirklich liefert, erscheint konfigurierbar in Admin „Rollen & Rechte“ (Filter: module IS NOT NULL). Spez: docs/working/RIGHTS_AND_FEATURES_REGISTRY.md """ from __future__ import annotations from dataclasses import dataclass, field from typing import Dict, List, Optional, Sequence, Tuple from db import get_db, get_cursor GrantPair = Tuple[str, str] # (role_code, capability_id) @dataclass(frozen=True) class CapabilityRegistration: id: str name: str domain: str module: str min_account_state: str = "active_member" linked_feature_id: Optional[str] = None description: Optional[str] = None default_club_grants: Sequence[GrantPair] = field(default_factory=tuple) @dataclass(frozen=True) class FeatureRegistration: id: str name: str module: str category: str = "general" limit_type: str = "count" reset_period: str = "never" default_limit: Optional[int] = None description: Optional[str] = None enforcement_subject: str = "club" _CAPABILITY_REGISTRY: Dict[str, CapabilityRegistration] = {} _FEATURE_REGISTRY: Dict[str, FeatureRegistration] = {} def register_capability(defn: CapabilityRegistration) -> None: """Modul deklariert ein Recht — wird beim Startup in die DB synchronisiert.""" if not defn.module or not defn.id: raise ValueError("CapabilityRegistration: module und id sind Pflicht") _CAPABILITY_REGISTRY[defn.id] = defn def register_feature(defn: FeatureRegistration) -> None: """Modul deklariert ein Vereins-Kontingent.""" if not defn.module or not defn.id: raise ValueError("FeatureRegistration: module und id sind Pflicht") _FEATURE_REGISTRY[defn.id] = defn def registered_capabilities() -> Dict[str, CapabilityRegistration]: return dict(_CAPABILITY_REGISTRY) def registered_features() -> Dict[str, FeatureRegistration]: return dict(_FEATURE_REGISTRY) def _upsert_capability(cur, defn: CapabilityRegistration) -> None: cur.execute( """ INSERT INTO capabilities ( id, name, description, domain, min_account_state, linked_feature_id, active, module, updated_at ) VALUES (%s, %s, %s, %s, %s, %s, true, %s, NOW()) ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name, description = EXCLUDED.description, domain = EXCLUDED.domain, min_account_state = EXCLUDED.min_account_state, linked_feature_id = EXCLUDED.linked_feature_id, active = true, module = EXCLUDED.module, updated_at = NOW() """, ( defn.id, defn.name, defn.description, defn.domain, defn.min_account_state, defn.linked_feature_id, defn.module, ), ) for role_code, cap_id in defn.default_club_grants: cur.execute( """ INSERT INTO club_role_capability_grants (role_code, capability_id) VALUES (%s, %s) ON CONFLICT DO NOTHING """, (role_code, cap_id), ) def _upsert_feature(cur, defn: FeatureRegistration) -> None: cur.execute( """ INSERT INTO features ( id, app, name, description, category, limit_type, reset_period, default_limit, enforcement_subject, active, module ) VALUES (%s, 'shinkan', %s, %s, %s, %s, %s, %s, %s, true, %s) ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name, description = EXCLUDED.description, category = EXCLUDED.category, limit_type = EXCLUDED.limit_type, reset_period = EXCLUDED.reset_period, default_limit = EXCLUDED.default_limit, enforcement_subject = EXCLUDED.enforcement_subject, active = true, module = EXCLUDED.module """, ( defn.id, defn.name, defn.description, defn.category, defn.limit_type, defn.reset_period, defn.default_limit, defn.enforcement_subject, defn.module, ), ) def sync_rights_registry_to_db() -> Dict[str, int]: """ Startup: registrierte Module → DB. Admin-Matrix zeigt nur Einträge mit module. """ import rights_registrations # noqa: F401 — lädt alle Modul-Registrierungen with get_db() as conn: cur = get_cursor(conn) for defn in _CAPABILITY_REGISTRY.values(): _upsert_capability(cur, defn) for defn in _FEATURE_REGISTRY.values(): _upsert_feature(cur, defn) conn.commit() return { "capabilities": len(_CAPABILITY_REGISTRY), "features": len(_FEATURE_REGISTRY), }