- Introduced tenant context resolution in the profiles API, allowing for effective club identification based on user memberships. - Updated the `GET /profiles/me` endpoint to return `effective_club_id` and removed reliance on the deprecated `X-Active-Club-Id` header. - Bumped application version to 0.8.22 in both backend and frontend files. - Enhanced changelog to document the new version and changes made in this release.
226 lines
13 KiB
Markdown
226 lines
13 KiB
Markdown
# Multi-Tenancy, Vereins-Membership und Rollenmodell – Zielarchitektur & Umsetzungsplan
|
||
|
||
**Status:** verbindliche Zielrichtung (Architekturpapier)
|
||
**Stand:** 2026-05-05
|
||
**Bezug:** `functional/shinkan_anforderungsdokument_entwurf.md` §5, §17–18 · `functional/DOMAIN_MODEL.md` (Sichtbarkeit §5.5) · `functional/TRAINING_CURRICULUM_AND_GOVERNANCE_CONCEPT.md` (CURR-004–008)
|
||
|
||
**Operative Reihenfolge & einheitliche Zugriffsschicht:** [`ACCESS_LAYER_AND_GOVERNANCE_PLAN.md`](./ACCESS_LAYER_AND_GOVERNANCE_PLAN.md) – dort sind Stufen A–F, Drift-Prävention und die Zurückstellung von Vereinsabo/Limits festgehalten; dieses Dokument bleibt das übergeordnete **Zielbild**.
|
||
|
||
---
|
||
|
||
## 1. Zweck
|
||
|
||
Dieses Dokument fasst den **Soll-Zustand** für Mandantenfähigkeit (Verein = Mandant), **rollenbasierte Zugriffskontrolle auf Vereinsebene** und ein **Membership-/Limitsystem** zusammen – und definiert einen **phasenweisen Umsetzungsplan**, der mit dem bestehenden Governance-Kern (`visibility`, `club_id`, `created_by`) konsistent bleibt.
|
||
|
||
---
|
||
|
||
## 2. Abgleich mit vorhandener Dokumentation
|
||
|
||
| Quelle | Inhalt relevant für Tenancy/Rollen | Konsistenz mit Zielbild |
|
||
|--------|-----------------------------------|-------------------------|
|
||
| `shinkan_anforderungsdokument_entwurf.md` §5.4 | Rollen: Superadmin, Vereinsadmin, Trainer, Co-Trainer, Redakteur | Deckt sich; „Superadmin“ entspricht fachlich **Systemadmin** |
|
||
| §17.1 | Erweiterung: Systemadmin, Spartenadmin | Entspricht den gewünschten **Spartenverantwortlichen** |
|
||
| §5.5 / §17 | Sichtbarkeit: privat, Verein, Sparte, global, offiziell | DOMAIN_MODEL listet ähnliche Stufen; **technische Durchsetzung** ist noch lückenhaft |
|
||
| §18.5 | MVP: Datenmodell mandantenfähig, Rechte zunächst einfach | Bestätigt schrittweise Verschärfung |
|
||
| `DOMAIN_MODEL.md` §5.5 | Freigabeebenen inkl. Sparte | Zielbild; DB/API nutzen derzeit überwiegend `private` \| `club` \| `official` |
|
||
| `TRAINING_CURRICULUM_AND_GOVERNANCE_CONCEPT.md` | CURR-004–008: Governance-Kern, spätere Policy | Datenfelder vorbereitet; **Policy/Erzwingung** folgt |
|
||
| `working/SHINKAN_PROJECT_SETUP.md` §6 | „Multi-Tenant-Administration“ ausgeschlossen (MVP-Liste) | Historisch; **technische Mandanten** sind dennoch Ziel – UI-Komplexität kontrolliert einführen |
|
||
|
||
**Fazit:** Die fachlichen Rollen und Sichtbarkeitsebenen sind **in den funktionalen Docs bereits skizziert**. Es fehlt die **stringente technische Schicht**: Vereinszugehörigkeit, aktiver Vereinskontext, effektive Berechtigungen pro Anfrage und konsequente Filterung bei `club`-sichtbaren Objekten.
|
||
|
||
---
|
||
|
||
## 3. Ist-Stand im Code (Gap-Analyse)
|
||
|
||
> **Hinweis:** Dieser Abschnitt beschreibt den Ausgangspunkt vor Ausbauschritten (**Mitgliedschaften, gefilterte Vereinsliste, Teilen von Governance für Übungen/Rahmen/Planung** sind bereits angegangen). Verbindliche **offene Arbeit und Reihenfolge** sind im Dokument [`ACCESS_LAYER_AND_GOVERNANCE_PLAN.md`](./ACCESS_LAYER_AND_GOVERNANCE_PLAN.md) festgehalten.
|
||
|
||
### 3.1 Identität und Rollen
|
||
|
||
- `profiles.role` ist eine **globale** Kennzeichnung (`admin`, `superadmin`, `trainer`, `user`, …).
|
||
- **Keine** Tabelle für Vereinsmitgliedschaft mit **Mehrfachrollen pro Verein**.
|
||
- Sessions liefern nur `profile_id` + globale `role` (`auth.py` → `get_session`).
|
||
|
||
**Konsequenz:** Mehrere Vereine mit unterschiedlichen Rollen pro User sind **nicht modelliert**; ein „Vereinsadmin“ kann nicht sauber von einem reinen Trainer unterschieden werden, sobald beides nur über `profiles.role` laufen soll.
|
||
|
||
### 3.2 Organisation & APIs
|
||
|
||
- `clubs`, `divisions`, `training_groups` existieren (`002_organization.sql`).
|
||
- `GET /api/clubs` listiert **alle** Vereine für jeden eingeloggten Nutzer.
|
||
- `POST /api/clubs` erlaubt Anlage für `trainer` und `user` – **nicht** nur Systemadmin.
|
||
- Sparten/Gruppen: Schreibzugriff über globale `admin`/`superadmin`, nicht über **Vereinsadmin** im Kontext „sein Verein“.
|
||
|
||
**Konsequenz:** Weder **Datenisolation** noch **Produktdifferenzierung** „nur Systemadmin legt Verein an“ sind umgesetzt.
|
||
|
||
### 3.3 Trainingsplanung
|
||
|
||
- Zugriff auf Einheiten gruppenbasiert: Trainer/Co-Trainer der `training_groups`, plus `lead_trainer_profile_id` (Migration/Pfad `training_planning`).
|
||
- `_assert_club_visible_for_trainer` bindet Vereinssicht für Teile der Planung an „aktive Gruppe als Trainer/Co im Verein“ – **kein** generelles Mitgliedschaftsmodell.
|
||
|
||
**Konsequenz:** Planung ist **gruppenzentriert**, nicht **mitgliedschaftszentriert**; Vereinsweite Aufgaben des Vereinsadmins fehlen als konsistentes Recht.
|
||
|
||
### 3.4 Governance / Sichtbarkeit (kritisch)
|
||
|
||
- Übungen (`list_exercises`): Bedingung sinngemäß „official OR club OR created_by = ich“ – **`club` gilt für alle Mandanten**, ohne Prüfung `exercise.club_id` ∈ Vereine des Nutzers.
|
||
- Detailzugriff `private`: nur Owner – **ok**.
|
||
- Rahmenprogramme (`training_framework_programs`): Lesen fremder Rahmen über `visibility=club` ist in `_framework_access` **nicht** gelöst (faktisch stark creator-basiert für Nicht-Admins).
|
||
|
||
**Konsequenz:** **Cross-Tenant-Leaks** bei als `club` markierten Bibliotheksobjekten sind möglich bzw. Leselogik ist inkonsistent zwischen Modulen.
|
||
|
||
### 3.5 Frontend
|
||
|
||
- **Stand 2026-05:** `GET /api/profiles/me` liefert `clubs[]`, `active_club_id`; Frontend setzt `X-Active-Club-Id`. Details und Pflicht zur serverseitigen **TenantContext**-Validierung siehe `ACCESS_LAYER_AND_GOVERNANCE_PLAN.md`.
|
||
|
||
### 3.6 Membership (kommerziell/limits)
|
||
|
||
- Mitai-artige Tabellen (`tiers`, `subscriptions`, `tier_limits`, …) sind **nutzerbezogen**, nicht **vereinsbezogen**.
|
||
- Kein konzipiertes **`club_subscription` / `club_plan`** im Schema.
|
||
|
||
---
|
||
|
||
## 4. Zielarchitektur
|
||
|
||
### 4.1 Begriffe
|
||
|
||
| Begriff | Definition |
|
||
|---------|------------|
|
||
| **Mandant** | Immer ein **Verein** (`clubs.id`). |
|
||
| **Systemadmin** | Global (`profiles.role` oder dediziertes Flag); darf Plattform-weite Objekte und **Vereinslifecycle** (Anlegen, Zuweisen Hauptverwalter). |
|
||
| **Vereinskontext** | Pro Session gewählter aktiver Verein (`active_club_id`), wenn der User Mitglied ist. |
|
||
| **Vereinsmitgliedschaft** | Zeile in einer Junction: User ↔ Verein ↔ **eine oder mehrere Rollen**. |
|
||
| **Effektive Berechtigung** | Funktion aus: globale Rolle, Mitgliedschaft im aktiven Verein, optional Sparte/Gruppe, Sichtbarkeit des Objekts. |
|
||
|
||
### 4.2 Rollenmodell (Schichten)
|
||
|
||
**Schicht A – Plattform (global, kleine Menge)**
|
||
|
||
- `system_admin` / `superadmin` (bestehende Semantik konsolidieren und benennen).
|
||
|
||
**Schicht B – Verein (pro Mitgliedschaft, mehrere Rollen möglich)**
|
||
|
||
- `club_admin` – Hauptverwalter:in (ein Verein **genau eine** „primäre“ Admin-Zuweisung empfohlen, siehe 4.4).
|
||
- `division_lead` – Spartenverantwortliche:r (Scope: `division_id` optional an Mitgliedschaft gebunden).
|
||
- `trainer` – Trainer/Übungsleitung (Abgrenzung zu Co-Trainer siehe Gruppe).
|
||
- `content_editor` – Redakteur:in / Inhaltsverantwortliche:r (fachlich wie Anforderungsdoc).
|
||
|
||
**Schicht C – Abgeleitet aus Trainingsgruppe (bereits teilweise vorhanden)**
|
||
|
||
- Haupttrainer / Co-Trainer über `training_groups.trainer_id` und `co_trainer_ids` (und ggf. `lead_trainer_profile_id` auf Einheit).
|
||
|
||
**Mapping Alt → Neu:** Bestehendes `profiles.role` kann Übergangsweise als „Default-Rolle für Pilotverein“ dienen, soll aber mittelfristig **nicht** die einzige Quelle für Vereinsrechte sein.
|
||
|
||
### 4.3 Mitgliedschaft und aktiver Verein
|
||
|
||
- Neue Kernstruktur (konzeptionell):
|
||
**`club_members`** (`profile_id`, `club_id`, `status`, `created_at`, …)
|
||
**`club_member_roles`** (`club_member_id`, `role_code`, optional `division_id` für spartenbezogene Rollen).
|
||
|
||
- **Aktiver Verein:**
|
||
- Persistenz: Nutzereinstellung (`profiles.default_club_id` oder eigene Tabelle `profile_preferences`).
|
||
- Pro Request: Header **`X-Active-Club-Id`** oder Query (einheitlich dokumentieren); Server validiert Mitgliedschaft.
|
||
|
||
- **Tenant-Switch UI:** Bei Mitgliedschaft in >1 Verein Auswahl im Frontend; alle Listen/Aktionen verwenden aktiven Kontext.
|
||
|
||
### 4.4 Vereins-Lebenszyklus
|
||
|
||
- Neuer Verein: **nur Systemadmin**; Pflichtfelder: Name, initialer `club_admin` (bestehendes Profil zuweisen oder Einladungsflow später).
|
||
- Vereinsadmin verwaltet in „Mein Verein“: Sparten, Gruppen, Trainerzuordnungen, Einladungen (später), interne Sichtbarkeit – **ohne** andere Vereine zu sehen.
|
||
|
||
### 4.5 Daten- und Funktionssicht
|
||
|
||
| Datenklasse | Leseregel (Ziel) |
|
||
|-------------|------------------|
|
||
| Global offiziell | Alle authentifizierten (ggf. später thematisch eingeschränkt). |
|
||
| Verein (`visibility=club`, `club_id` gesetzt) | Nur Profile mit Mitgliedschaft in **diesem** `club_id`; optional zusätzlich Sparte, wenn `division_id` am Objekt gepflegt wird. |
|
||
| Privat | Nur `created_by` (und explizite Shares später). |
|
||
| Geplante Einheiten | Wie heute über Gruppe + Trainer/Co; zusätzlich Vereinskontext zur Navigation/Audit. |
|
||
|
||
**Einheitlicher Governance-Kern** bleibt wie in CURR-005; Ergänzung: **`division_id`** auf Bibliotheksobjekten, wenn „Sparte“ technisch durchgesetzt werden soll (DOMAIN_MODEL / §17).
|
||
|
||
### 4.6 Membership-System (Vereinsabo / Limits) – Konzept
|
||
|
||
Ziel: **vereinszentrierte** Vertrags- und Limitlogik, analog zur bestehenden Tier-Infrastuktur, aber an **`club_id`** gebunden.
|
||
|
||
**Empfohlene Bausteine:**
|
||
|
||
1. **`club_plans`** – Produktdefinition (Name, Features, implizite Limits).
|
||
2. **`club_subscriptions`** – (`club_id`, `plan_id`, Status, Laufzeit).
|
||
3. **`club_usage_counters`** oder Ableitung aus DB – z. B. aktive Nutzer, aktive Trainingsgruppen (periodisch oder on-write).
|
||
4. **Enforcement-Schicht** – zentrale Funktion `assert_club_limit(club_id, metric)` vor `/groups`-POST, Einladungen, etc.
|
||
|
||
**Offene Produktentscheidungen** (vor Implementierung festlegen):
|
||
|
||
- Zählen „Nutzer“ alle Mitglieder oder nur aktive Trainer?
|
||
- Soft-Limits vs. Hard-Stops; Übergang für Pilotverein.
|
||
|
||
---
|
||
|
||
## 5. Umsetzungsplan (Phasen)
|
||
|
||
### Phase 0 – Foundations (kurz, risikoarm)
|
||
|
||
- Begriffe und Enums in einem Ort dokumentieren (dieses Dokument + Eintrag in `DATABASE_SCHEMA.md` nach Migration).
|
||
- Audit-Liste aller Router mit `club_id` / `visibility` / Listen-Endpunkten.
|
||
|
||
### Phase 1 – Datenmodell Mitgliedschaft & Hauptverwalter
|
||
|
||
- Migration: `club_members`, `club_member_roles`; optional `clubs.primary_admin_profile_id` (oder Primär-Flag auf Mitgliedschaft).
|
||
- Backfill: bestehende Trainer aus `training_groups` → minimale Mitgliedschaft `trainer` im jeweiligen Verein (Skript/Migration mit dokumentierter Annahme CURR-008).
|
||
- **Breaking/API:** keine – nur erweiterte Datenbasis.
|
||
|
||
### Phase 2 – Aktiver Vereinskontext & API-Kontrakte
|
||
|
||
- Backend: Validierung `X-Active-Club-Id` gegen Mitgliedschaft; Hilfsfunktion `get_effective_club_context(session, header)`.
|
||
- `GET /api/clubs` für Nicht-Systemadmins: **nur Vereine mit Mitgliedschaft**.
|
||
- `POST /api/clubs`: nur Systemadmin; Vergabe `club_admin`-Mitgliedschaft.
|
||
- Frontend: Club-Switcher + Persistenz.
|
||
|
||
### Phase 3 – Effektive Berechtigungen (RBAC)
|
||
|
||
- **Mitgliederverwaltung per API (ohne UI):** `GET/POST /api/clubs/{club_id}/members`, `GET/PUT/DELETE /api/clubs/{club_id}/members/{profile_id}` — nur Plattform-Admin oder `club_admin` im Zielverein (Stand Code **0.8.16**).
|
||
- Zentrale Modulfunktion z. B. `authorization/club_permissions.py`:
|
||
`can(club_id, profile_id, permission, division_id=None)` — optional später; aktuell `club_tenancy.can_manage_club_org` / `has_club_role`.
|
||
- Router schrittweise umbinden: Sparten/Gruppen CRUD nach Rolle `club_admin` im Kontext; Systemadmin unverändert Vollzugriff.
|
||
|
||
### Phase 4 – Sichtbarkeit & Leaks schließen
|
||
|
||
- **Übungen:** `club`-Sichtbarkeit nur bei Übereinstimmung `exercise.club_id` mit Mitgliedschaft (und später `division`).
|
||
- **Trainingsplan-Vorlagen** (`training_plan_templates`) und **Rahmenprogramme** (`training_framework_programs`): gleiches Muster für Listen/GET (Stand **0.8.17**); Schreiben weiterhin nur Ersteller oder Plattform-Admin.
|
||
- Gleiches Muster für Progressionsgraphen, ggf. Medien (offen).
|
||
- Tests: zwei Vereine, zwei Nutzer, keine Kreuzzugriffe.
|
||
|
||
### Phase 5 – Mitgliedschaft / Limits
|
||
|
||
- Tabellen `club_plans`, `club_subscriptions`; Integration mit Enforcement vor relevanten Writes.
|
||
- UI „Mein Verein“: Kennzahlenteaser oder Hinweise bei Limit (minimal).
|
||
|
||
### Phase 6 – Verfeinerung
|
||
|
||
- Einladungsflow (E-Mail), Mehrfachrollen-UI, Audit-Log für Admin-Aktionen.
|
||
- Optionale thematische Sperren (Karate vs. Gewaltschutz) als eigene Policy-Schicht.
|
||
|
||
---
|
||
|
||
## 6. Abhängigkeiten und Risiken
|
||
|
||
- **Übergang:** Pilot mit einem Verein nutzt weiterhin einfache Defaults; Multi-Verein erfordert Pflicht **aktiver Kontext**.
|
||
- **Performance:** Mitgliedschaft und Rolle sollten **einmal pro Request** geladen und gecacht werden (Request-Scope).
|
||
- **Konsistenz mit Mitai:** Nutzer-Tiers können parallel bleiben; **vereinsbezogene** Limits sind die neue Quelle für Shinkan-spezifische Kaufmotive.
|
||
|
||
---
|
||
|
||
## 7. Nächste konkrete Artefakte
|
||
|
||
1. TenantContext-Spezifikation & Endpoint-Audit (siehe `ACCESS_LAYER_AND_GOVERNANCE_PLAN.md` §6).
|
||
2. Aktualisierung `DATABASE_SCHEMA.md` bei neuen Governance-/Scope-Feldern.
|
||
3. Sicherheits-Review der `list_*`-Endpunkte mit `club`-Visibility (fortlaufend bis Governance vereinheitlicht).
|
||
|
||
---
|
||
|
||
## 8. Verwandtes Dokument
|
||
|
||
- **`ACCESS_LAYER_AND_GOVERNANCE_PLAN.md`** – verbindliche Umsetzungsstufen A–F, einheitliche Zugriffsschicht, Scope-Erweiterung (`division`, später Community), Capability-Vorbereitung ohne Custom-Rollen-UI; Vereinsabo explizit zurückgestellt.
|
||
|
||
---
|
||
|
||
**Letzte Aktualisierung:** 2026-05-05
|