shinkan-jinkendo/.claude/docs/technical/CLUB_MEMBERSHIP_AND_FEATURES.v1.md
Lars c294c27de8
All checks were successful
Deploy Development / deploy (push) Successful in 44s
Test Suite / pytest-backend (push) Successful in 40s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m12s
Update Access Layer and Governance Documentation
- Enhanced the ACCESS_LAYER_AND_GOVERNANCE_PLAN.md with new specifications for capability documentation and community features.
- Added references to new documents detailing capability IDs and club membership features.
- Updated MULTI_TENANCY_RBAC_ARCHITECTURE.md to include links to the new specifications.
- Marked certain features as deprecated in backend/auth.py, indicating migration paths for club feature access.
- Incremented DB_SCHEMA_VERSION to 20260606078 in version.py to reflect recent changes.
2026-06-06 20:44:51 +02:00

17 KiB
Raw Blame History

Vereins-Membership & Feature-System Shinkan v1

Status: Konzept (Schema- und Enforcement-Zielbild vor Implementierung)
Stand: 2026-06-06
Bezüge: Schwesterprojekt Mitai (v9c_subscription_system.sql, FEATURE_ENFORCEMENT.md), CAPABILITY_CATALOG.v1.md, MULTI_TENANCY_RBAC_ARCHITECTURE.md §4.6, ACCESS_LAYER_AND_GOVERNANCE_PLAN.md


1. Zweck

Shinkan verkauft und limitiert nicht Einzelpersonen (wie Mitai), sondern Vereine. Dieses Dokument definiert:

  • das Feature-Registry-Muster (limitierbare Funktionen),
  • das Vereins-Abo (club_plans, club_subscriptions),
  • Kontingente und Enforcement,
  • die Abbildung von Mitai und Vermeidung von Refactoring-Schulden.

Capabilities (Rollen: darf ich die Funktion?) → CAPABILITY_CATALOG.v1.md.


2. Grundprinzip: Zwei Achsen

flowchart TB
    subgraph cap [Achse 1 — Capabilities]
        CR[club_role_capability_grants]
        PR[portal_role_capability_grants]
    end

    subgraph feat [Achse 2 — Features / Kontingente]
        FP[club_plans]
        FPL[club_plan_limits]
        FS[club_subscriptions]
        FU[club_feature_usage]
    end

    subgraph gov [Achse 3 — Governance]
        GV[visibility / club_id / created_by]
    end

    REQ[HTTP Request] --> ACCT[Account-Lifecycle]
    ACCT --> cap
    cap --> gov
    gov --> feat
    feat --> EXEC[Ausführung + increment]
Frage System Subjekt
Darf Trainer X KI nutzen? Capability exercises.ai.suggest profile_id + club_role
Wie viele KI-Aufrufe hat Verein Y? Feature ai_calls club_id
Darf ich diese Übung ändern? Governance Objekt + Mitgliedschaft

Beide Achsen müssen erfüllt sein (AND), außer dokumentierte Plattform-Ausnahmen.


3. Mitai-Mapping (was übernehmen, was nicht)

3.1 Übernehmen (Pattern)

Mitai (Person) Shinkan (Verein) Anmerkung
features (TEXT-PK, Registry) features (app='shinkan') Gemeinsames Muster, ggf. später Jinkendo-weit
tiers club_plans Produktdefinition
tier_limits club_plan_limits Matrix Plan × Feature
user_feature_restrictions club_feature_overrides Admin-Override pro Verein
user_feature_usage club_feature_usage Verbrauch pro Verein
access_grants club_access_grants Trial, Promo, manuelle Freischaltung
check_feature_access() check_club_feature_access() Subjekt club_id
increment_feature_usage() increment_club_feature_usage() Nur bei INSERT / KI-Call
4-Phasen-Rollout identisch Log → UI → Hard-Block
GET /api/features/usage GET /api/clubs/{id}/entitlements siehe Capability-Doc §7

3.2 Nicht übernehmen

Mitai Shinkan-Grund
profiles.tier als Haupt-Abo Verein zahlt, nicht Einzeltrainer
subscriptions (Shinkan 001, INT-Features) Ungenutzt, Schema-Drift
get_effective_tier(profile_id) für Shinkan-Limits Ersetzen durch get_effective_club_plan(club_id)
Profil-zentrierte Enforcement-Hooks allein Primär club_id; Profil nur für Attribution

3.3 Parallelität Jinkendo-Familie (später)

CENTRAL_SUBSCRIPTION_SYSTEM.md (Mitai): zentrales Personen-Abo über Apps.

Zielbild ohne Refactoring:

features.enforcement_subject ∈ { 'club', 'profile', 'portal' }

effektives_limit(feature) = merge(
    club_plan_limit(club_id, feature),      # Shinkan-Hauptquelle
    profile_grant_limit(profile_id, feature) # optional Jinkendo-Bonus
)

Merge-Regel (Vorschlag): Maximum der erlaubten Kontingente, boolean = OR. Details vor Stripe festlegen.


4. Ist-Zustand Shinkan (Drift — zuerst bereinigen)

Artefakt Problem
backend/migrations/001_auth_membership.sql features.id SERIAL, tier_limits.tier VARCHAR
backend/auth.py check_feature_access() Erwartet Mitai-v9c-Schema (features.id TEXT, tier_id, limit_type, …)
Kein Router Ruft check_feature_access auf
profiles.tier Existiert, ohne Shinkan-Enforcement

Pflicht vor Phase 3 (Enforcement): Migration 0XX_club_features_v1.sql — v9c-kompatibles Feature-Schema + Vereins-Tabellen; alte 001-Feature-Zeilen migrieren oder deprecaten.


5. Ziel-Schema (v1)

5.1 Feature-Registry (app-weit, Mitai-kompatibel)

-- Konzept — Implementierung als nummerierte Migration
CREATE TABLE features (
    id              TEXT PRIMARY KEY,           -- z.B. 'ai_calls'
    app             TEXT NOT NULL DEFAULT 'shinkan',
    name            TEXT NOT NULL,
    description     TEXT,
    category        TEXT NOT NULL,              -- 'content'|'planning'|'ai'|'org'|'integration'|'platform'
    limit_type      TEXT NOT NULL DEFAULT 'count',  -- 'count' | 'boolean'
    reset_period    TEXT NOT NULL DEFAULT 'never',  -- 'never' | 'daily' | 'monthly'
    default_limit   INTEGER,                    -- NULL=∞, 0=aus
    enforcement_subject TEXT NOT NULL DEFAULT 'club',  -- 'club'|'profile'|'portal'
    active          BOOLEAN NOT NULL DEFAULT true,
    created_at      TIMESTAMPTZ DEFAULT NOW(),
    updated_at      TIMESTAMPTZ DEFAULT NOW()
);

5.2 Vereins-Produkte & Abo

CREATE TABLE club_plans (
    id              TEXT PRIMARY KEY,           -- 'free', 'verein_starter', 'verein_pro'
    name            TEXT NOT NULL,
    description     TEXT,
    price_monthly_cents INTEGER,
    price_yearly_cents  INTEGER,
    stripe_price_id_monthly TEXT,
    stripe_price_id_yearly  TEXT,
    active          BOOLEAN NOT NULL DEFAULT true,
    sort_order      INTEGER NOT NULL DEFAULT 0,
    created_at      TIMESTAMPTZ DEFAULT NOW(),
    updated_at      TIMESTAMPTZ DEFAULT NOW()
);

CREATE TABLE club_subscriptions (
    id              SERIAL PRIMARY KEY,
    club_id         INT NOT NULL REFERENCES clubs(id) ON DELETE CASCADE,
    plan_id         TEXT NOT NULL REFERENCES club_plans(id),
    status          TEXT NOT NULL DEFAULT 'active',  -- active|trial|past_due|cancelled
    started_at      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    ends_at         TIMESTAMPTZ,
    trial_ends_at   TIMESTAMPTZ,
    created_at      TIMESTAMPTZ DEFAULT NOW(),
    updated_at      TIMESTAMPTZ DEFAULT NOW(),
    UNIQUE (club_id)  -- ein aktiver Plan pro Verein (v1)
);

CREATE TABLE club_plan_limits (
    id              SERIAL PRIMARY KEY,
    plan_id         TEXT NOT NULL REFERENCES club_plans(id) ON DELETE CASCADE,
    feature_id      TEXT NOT NULL REFERENCES features(id) ON DELETE CASCADE,
    limit_value     INTEGER,                    -- NULL=∞, 0=deaktiviert
    UNIQUE (plan_id, feature_id)
);

5.3 Overrides, Grants, Verbrauch

CREATE TABLE club_feature_overrides (
    id              SERIAL PRIMARY KEY,
    club_id         INT NOT NULL REFERENCES clubs(id) ON DELETE CASCADE,
    feature_id      TEXT NOT NULL REFERENCES features(id) ON DELETE CASCADE,
    limit_value     INTEGER NOT NULL,
    reason          TEXT,
    set_by_profile_id INT REFERENCES profiles(id) ON DELETE SET NULL,
    created_at      TIMESTAMPTZ DEFAULT NOW(),
    UNIQUE (club_id, feature_id)
);

CREATE TABLE club_access_grants (
    id              SERIAL PRIMARY KEY,
    club_id         INT NOT NULL REFERENCES clubs(id) ON DELETE CASCADE,
    plan_id         TEXT REFERENCES club_plans(id),
    feature_id      TEXT REFERENCES features(id),  -- optional Einzel-Feature
    grant_limit     INTEGER,
    starts_at       TIMESTAMPTZ NOT NULL,
    ends_at         TIMESTAMPTZ NOT NULL,
    reason          TEXT,
    created_by_profile_id INT REFERENCES profiles(id) ON DELETE SET NULL
);

CREATE TABLE club_feature_usage (
    id              SERIAL PRIMARY KEY,
    club_id         INT NOT NULL REFERENCES clubs(id) ON DELETE CASCADE,
    feature_id      TEXT NOT NULL REFERENCES features(id) ON DELETE CASCADE,
    usage_count     INTEGER NOT NULL DEFAULT 0,
    reset_at        TIMESTAMPTZ,
    last_used_at    TIMESTAMPTZ,
    UNIQUE (club_id, feature_id)
);

-- Optional: Attribution / Fairness / Audit
CREATE TABLE club_feature_usage_events (
    id              BIGSERIAL PRIMARY KEY,
    club_id         INT NOT NULL,
    feature_id      TEXT NOT NULL,
    profile_id      INT REFERENCES profiles(id) ON DELETE SET NULL,
    action          TEXT NOT NULL,              -- 'ai_suggest', 'exercise_create', ...
    created_at      TIMESTAMPTZ DEFAULT NOW()
);

5.4 Capabilities (Rollen — Kurzreferenz)

Siehe CAPABILITY_CATALOG.v1.md für IDs. Tabellen:

CREATE TABLE capabilities (
    id              TEXT PRIMARY KEY,
    name            TEXT NOT NULL,
    description     TEXT,
    domain          TEXT NOT NULL,
    min_account_state TEXT NOT NULL DEFAULT 'active_member',
    linked_feature_id TEXT REFERENCES features(id),  -- optional Kontingent
    active          BOOLEAN NOT NULL DEFAULT true
);

CREATE TABLE club_role_capability_grants (
    role_code       TEXT NOT NULL,              -- club_admin, trainer, ...
    capability_id   TEXT NOT NULL REFERENCES capabilities(id) ON DELETE CASCADE,
    PRIMARY KEY (role_code, capability_id)
);

CREATE TABLE portal_role_capability_grants (
    portal_role     TEXT NOT NULL,              -- admin, superadmin
    capability_id   TEXT NOT NULL REFERENCES capabilities(id) ON DELETE CASCADE,
    PRIMARY KEY (portal_role, capability_id)
);

6. Shinkan Feature-Katalog (Seed v1)

Übernahme aus 001_auth_membership.sql + Ist-Endpoints, angereichert:

feature_id category limit_type reset_period enforcement_subject Default Free Beschreibung
exercises content count never club 100 Anzahl Übungen im Verein (Bestand)
exercise_media content count monthly club 20 Medien-Uploads / Monat
training_units planning count monthly club 40 Geplante/durchgeführte Einheiten
training_programs planning count never club 5 Module + Rahmenprogramme (kombiniert v1)
training_groups org count never club 10 Trainingsgruppen
active_members org count never club 25 Aktive Mitglieder
ai_calls ai count monthly club 0 KI-Aufrufe (Suggest, Regenerate, Planung)
ai_pipeline ai boolean never club 0 Erweiterte KI-Pipelines (Batch, später)
wiki_import integration boolean never portal 0 MediaWiki-Import (Superadmin)
data_export integration boolean never club 0 Export-Funktionen (wenn eingeführt)

Hinweis: Free-Defaults sind Produktentscheidung — Tabelle dient Implementierung.

6.1 Beispiel-Pläne (Seed)

plan_id ai_calls/Monat exercises active_members
free 0 100 25
verein_starter 30 500 80
verein_pro 200 NULL (∞) NULL
pilot 100 NULL NULL

Jeder Verein erhält bei Anlage durch Superadmin initial club_subscriptions.plan_id = 'free' (oder pilot).


7. Auflösungslogik

7.1 Effektiver Vereinsplan

def get_effective_club_plan(cur, club_id: int) -> str:
    """
    1. Aktiver club_access_grants mit plan_id (höchste Priorität, Zeitfenster)
    2. club_subscriptions.status == 'active' → plan_id
    3. Fallback 'free'
    """

7.2 Feature-Limit (analog Mitai check_feature_access)

def check_club_feature_access(
    cur,
    club_id: int,
    feature_id: str,
    *,
    profile_id: int | None = None,  # nur für Logging / optionale Profil-Boni später
) -> dict:
    """
    Priorität:
    1. club_feature_overrides (club_id, feature_id)
    2. club_plan_limits für get_effective_club_plan(club_id)
    3. features.default_limit

    Auswertung:
    - limit_type boolean: limit_value == 1
    - limit_type count: used < limit (club_feature_usage, reset beachten)

    Returns: { allowed, limit, used, remaining, reason, reset_at }
    """

7.3 Vollständige Request-Kette

1. require_auth
2. assert_account_state(min_state)          # unverified / verified_pending_club / active_member
3. get_tenant_context
4. assert_capability(tenant, cap_id)        # Rollen-Achse
5. assert_content_governance(...)           # nur bei Objekt-Endpoints
6. check_club_feature_access(club_id, feature_id)
7. … Business-Logik …
8. increment_club_feature_usage(club_id, feature_id)   # nur bei INSERT / KI-Execute
9. optional: log club_feature_usage_events (profile_id)

7.4 Wer zählt als Verbrauch?

Aktion increment Subjekt
POST /exercises (neu) exercises club_id des Objekts oder effective_club_id
Medien-Upload exercise_media Verein des Mediums
KI Suggest/Regenerate ai_calls effective_club_id
Mitglied hinzufügen active_members Ziel-club_id
Trainingsgruppe anlegen training_groups club_id

Mitai-Regel: Counter nicht bei UPDATE/DELETE erhöhen.


8. API-Oberfläche

8.1 Nutzer / Vereinsadmin

GET /api/clubs/{club_id}/entitlements

Kombiniert Capabilities + Feature-Kontingente (siehe CAPABILITY_CATALOG.v1.md §7.1).

GET /api/me/entitlements?club_id=12

Bequemer Alias für aktiven Verein.

8.2 Superadmin / Plattform

Endpoint Zweck
GET/PUT /api/admin/club-plans Plan-CRUD
GET/PUT /api/admin/club-plan-limits Matrix
GET/PUT /api/admin/clubs/{id}/subscription Verein-Abo
GET/PUT /api/admin/clubs/{id}/feature-overrides Sonderkontingente
POST /api/admin/clubs/{id}/access-grants Trial/Promo

Vorbild UI: Mitai AdminTierLimitsPage.jsx, AdminUserRestrictionsPage.jsx → Vereins-Kontext.

8.3 Geplant: Vereinsgründung

POST /api/club-creation-requests     # Nutzer (verified_pending_club)
GET  /api/admin/club-creation-requests
POST /api/admin/club-creation-requests/{id}/approve  # legt club + subscription an

9. Vier-Phasen-Rollout (aus Mitai)

Phase Shinkan-Aktivität Nutzer sichtbar?
0 Schema-Migration, Seed features + club_plans, Drift 001 bereinigen Nein
1 Account-Gates + Capability-Grants (ohne Limits) Onboarding-Hinweise
2 check_club_feature_accessnur JSON-Log (feature_logger analog Mitai) Nein
3 GET …/entitlements + UsageBadge im UI Ja (Kontingent-Anzeige)
4 HTTP 403 bei Limit + increment Ja (Hard-Block)

Reihenfolge innerhalb Phase 4: zuerst ai_calls, dann exercise_media, dann Bestands-Limits (exercises, active_members).


10. CI / Test-Isolation (Betrieb)

Unabhängig vom Membership-System — Pflicht wegen Prod-Vorfälle (access_layer_it_*@test.local):

Regel Umsetzung
Integrationstests nie gegen Prod-DB Eigene Test-DB oder Job-Postgres in Gitea
ENVIRONMENT=production + ALLOW_INTEGRATION_TESTS Default 0, Tests abbrechen
Test-Accounts E-Mail @test.local oder profiles.is_test_account
Cleanup Fixture-finally + Nightly-Job löscht Leichen

.gitea/workflows/test.yml: pytest-backend gegen Deploy-DB ersetzen durch isolierte DB (eigenes Epic, parallel zu Membership).


11. Implementierungs-Roadmap (gesamt)

Schritt Deliverable Membership-relevant
M0 CI-Isolation + Prod-Cleanup-Runbook Nein
M1 Migration Feature-Schema v9c + club_plans/club_subscriptions (leer nutzbar) Ja
M2 check_club_feature_access + Seed Pläne Ja
M3 Account-Lifecycle + Capability-Grants Capabilities
M4 GET /me/entitlements Ja
M5 Enforcement ai_calls (Phase 4) Ja
M6 Admin Plan-Matrix UI Ja
M7 club_creation_requests Prozess
M8 Stripe / Rechnung Später

12. Offene Produktentscheidungen

Vor M6 festlegen:

  1. Zählen active_members: alle Mitglieder oder nur Rollen mit Planungsrecht?
  2. Soft-Limit vs. Hard-Stop: Warnung bei 80 % oder sofort 403?
  3. Pilotverein: eigener Plan pilot mit hohen Limits?
  4. KI-Fairness: nur Vereinslimit oder zusätzlich Max pro Trainer/Monat?
  5. Offizielle Inhalte: für verified_pending_club sichtbar oder gesperrt? (Capability-Doc)
  6. Portal admin vs. superadmin: Wer darf Vereine anlegen? (Ziel: nur superadmin für Freigabe)

13. Referenzen

Pfad Inhalt
c:/dev/mitai-jinkendo/backend/migrations/v9c_subscription_system.sql Mitai-Schema-Vorlage
c:/dev/mitai-jinkendo/.claude/docs/architecture/FEATURE_ENFORCEMENT.md 4-Phasen-Modell
c:/dev/mitai-jinkendo/.claude/docs/technical/MEMBERSHIP_SYSTEM.md Mitai-Hauptdoku
c:/dev/mitai-jinkendo/.claude/docs/technical/CENTRAL_SUBSCRIPTION_SYSTEM.md Jinkendo-Familie später
CAPABILITY_CATALOG.v1.md Rollen & Capabilities
MULTI_TENANCY_RBAC_ARCHITECTURE.md §4.6 Ursprüngliches Vereinsabo-Zielbild
ACCESS_LAYER_AND_GOVERNANCE_PLAN.md Stufe E/F

Changelog

  • 2026-06-06: v1 — Mitai-Mapping, Ziel-Schema, Feature-Seed, Auflösungslogik, Rollout.