mitai-jinkendo/docs/MEMBERSHIP_SYSTEM.md
Lars ef8008a75d
All checks were successful
Deploy Development / deploy (push) Successful in 33s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 12s
docs: update CLAUDE.md and add comprehensive membership system documentation
Updates:
- CLAUDE.md: Reflect current v9c-dev status (enforcement disabled, history working)
- CLAUDE.md: Document simple AI limit system currently active
- CLAUDE.md: Update implementation status (admin UI complete, enforcement rolled back)

New Documentation:
- docs/MEMBERSHIP_SYSTEM.md: Complete v9c architecture documentation
  - Design decisions and rationale
  - Complete database schema (11 tables)
  - Backend API overview (7 routers, 30+ endpoints)
  - Frontend components (6 admin pages)
  - Feature enforcement rollback analysis
  - Lessons learned and next steps
  - Testing strategy
  - Deployment notes
  - Troubleshooting guide

The new doc provides complete reference for:
- Feature-Registry-Pattern implementation
- Tier system architecture
- Coupon system (3 types with stacking logic)
- User-Override system
- Access-Grant mechanics
- What went wrong with enforcement attempt
- Roadmap for v9d/v9e

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-20 15:44:29 +01:00

32 KiB
Raw Permalink Blame History

Mitai Jinkendo - Membership & Subscription System (v9c)

Version: v9c-dev Status: Backend & Admin-UI komplett, Enforcement deaktiviert Letzte Aktualisierung: 20. März 2026


Inhaltsverzeichnis

  1. Überblick
  2. Architektur-Entscheidungen
  3. Datenbank-Schema
  4. Backend-API
  5. Frontend-Komponenten
  6. Feature-Enforcement-System
  7. Lessons Learned
  8. Roadmap

Überblick

Das Mitai Jinkendo Membership-System (v9c) ist ein flexibles, tier-basiertes Subscription-System mit folgenden Kern-Features:

Implementierte Features

  • 4 Tier-Stufen: free, basic, premium, selfhosted
  • Feature-Registry-Pattern: Zentrale Definition aller limitierbaren Features
  • Flexible Limit-Matrix: Admin kann Tier × Feature Limits konfigurieren
  • User-Override-System: Individuelle Limits pro User
  • Coupon-System: 3 Typen (single_use, multi_use_period, gift)
  • Coupon-Stacking: Intelligente Pause/Resume-Logik bei temporären Zugriffen
  • Access-Grants: Zeitlich begrenzte Tier-Zugriffe mit Quelle-Tracking
  • User-Activity-Log: JSONB-basierte Aktivitätsverfolgung
  • Admin-UI: Vollständige Verwaltungsoberfläche für alle Aspekte

Geplante Features 🔲

  • Feature-Enforcement in Endpoints (needs redesign)
  • Selbst-Registrierung mit E-Mail-Verifizierung
  • Trial-System mit automatischem Downgrade
  • Bonus-System (Login-Streaks)
  • Stripe-Integration
  • Partner-Integration (Wellpass, Hansefit)

Architektur-Entscheidungen

1. Feature-Registry-Pattern

Entscheidung: Alle limitierbaren Features werden zentral in einer features Tabelle definiert.

Rationale:

  • Neue Features können ohne Schema-Migration hinzugefügt werden
  • Metadaten (name, description, category, unit) sind direkt verfügbar
  • Konsistenz zwischen Backend-Checks und Frontend-Display
  • Admin-UI kann automatisch generiert werden

Schema:

CREATE TABLE features (
    id TEXT PRIMARY KEY,              -- 'ai_calls', 'data_export', etc.
    name TEXT NOT NULL,               -- 'KI-Analysen', 'Daten exportieren'
    description TEXT,
    category TEXT,                    -- 'ai', 'export', 'data', 'integration'
    limit_type TEXT DEFAULT 'count',  -- 'count' oder 'boolean'
    reset_period TEXT DEFAULT 'never', -- 'never', 'daily', 'monthly'
    default_limit INTEGER,            -- NULL = unbegrenzt
    active BOOLEAN DEFAULT true,
    sort_order INTEGER DEFAULT 0,
    created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated TIMESTAMP
);

Beispiel-Features:

-- Count-based mit monatlichem Reset
('ai_calls', 'KI-Analysen', 'KI-Auswertungen pro Monat', 'ai', 'count', 'monthly', 0, true)

-- Boolean-Feature (an/aus)
('ai_pipeline', 'KI-Pipeline', 'Vollständige Pipeline-Analyse', 'ai', 'boolean', 'never', 0, true)

-- Count-based ohne Reset (Gesamt-Limit)
('weight_entries', 'Gewichtseinträge', 'Anzahl Gewichtsmessungen', 'data', 'count', 'never', NULL, true)

2. Tier-System

Entscheidung: Vereinfachte Tier-Tabelle ohne hart-codierte Limits.

Rationale:

  • Limits werden in separate tier_limits Tabelle ausgelagert
  • Tiers können dynamisch hinzugefügt werden
  • Pricing-Informationen zentral verwaltet
  • Flexibilität für zukünftige Tier-Erweiterungen

Schema:

CREATE TABLE tiers (
    id TEXT PRIMARY KEY,              -- 'free', 'basic', 'premium', 'selfhosted'
    slug TEXT UNIQUE NOT NULL,
    name TEXT NOT NULL,               -- 'Free', 'Basic', 'Premium', 'Self-Hosted'
    description TEXT,
    price_monthly DECIMAL(10,2),
    price_yearly DECIMAL(10,2),
    sort_order INTEGER DEFAULT 0,
    active BOOLEAN DEFAULT true,
    created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated TIMESTAMP
);

Initial Tiers:

  • free: Eingeschränkt (30 Daten-Einträge, 0 KI-Calls, kein Export)
  • basic: Kernfunktionen (unbegrenzte Daten, 3 KI-Calls/Monat, Export erlaubt)
  • premium: Alles unbegrenzt (inkl. KI-Pipeline, Connectoren)
  • selfhosted: Admin-Tier für selbst-gehostete Installationen

3. Zugriffs-Hierarchie

Entscheidung: Drei-stufige Priorität für effektive Tier-Ermittlung.

Priorität (höchste zuerst):

  1. Admin-Override: profiles.tier_locked = true → nutzt profiles.tier
  2. Access-Grant: Aktiver, nicht-pausierter Grant → nutzt access_grants.granted_tier
  3. Trial: profiles.trial_ends_at > NOW() → nutzt trial tier
  4. Base Tier: profiles.tier

Implementierung:

def get_effective_tier(profile_id: str) -> str:
    """Get effective tier considering all overrides."""
    with get_db() as conn:
        cur = get_cursor(conn)

        # 1. Check if tier is locked by admin
        cur.execute("SELECT tier, tier_locked FROM profiles WHERE id = %s", (profile_id,))
        profile = cur.fetchone()
        if profile['tier_locked']:
            return profile['tier']

        # 2. Check for active access grant
        cur.execute("""
            SELECT granted_tier FROM access_grants
            WHERE profile_id = %s
              AND is_active = true
              AND valid_from <= CURRENT_TIMESTAMP
              AND (valid_until IS NULL OR valid_until > CURRENT_TIMESTAMP)
            ORDER BY created DESC LIMIT 1
        """, (profile_id,))
        grant = cur.fetchone()
        if grant:
            return grant['granted_tier']

        # 3. Check trial
        cur.execute("""
            SELECT tier FROM profiles
            WHERE id = %s AND trial_ends_at > CURRENT_TIMESTAMP
        """, (profile_id,))
        trial = cur.fetchone()
        if trial:
            return 'premium'  # or configurable trial tier

        # 4. Base tier
        return profile['tier']

Rationale:

  • Admin kann User dauerhaft einem Tier zuweisen (Support-Fälle)
  • Temporäre Zugriffe (Coupons, Wellpass) haben Vorrang vor Base-Tier
  • Trial-Logik ist transparent und automatisch
  • Base-Tier ist Fallback

4. Coupon-System

Entscheidung: 3 Coupon-Typen mit unterschiedlicher Stacking-Logik.

Typen:

4.1 Single-Use Coupon

  • Verwendung: Einmalig einlösbar (z.B. Geschenk-Coupon)
  • Verhalten: Erstellt access_grant mit fester Laufzeit
  • Stacking: Zeitlich sequenziell (startet nach Ablauf vorheriger Grants)
  • Beispiel: "30 Tage Premium geschenkt"
INSERT INTO coupons (code, type, grants_tier, duration_days, max_redemptions) VALUES
('FRIEND-GIFT-ABC', 'single_use', 'premium', 30, 1);

4.2 Multi-Use Period Coupon

  • Verwendung: Unbegrenzt einlösbar während Gültigkeitszeitraum
  • Verhalten: Pausiert andere Grants, reaktiviert sie nach Ablauf
  • Stacking: Override mit Pause/Resume
  • Beispiel: "Wellpass-Monatszugang März 2026"
INSERT INTO coupons (code, type, grants_tier, valid_from, valid_until, max_redemptions) VALUES
('WELLPASS-2026-03', 'multi_use_period', 'premium', '2026-03-01', '2026-03-31', NULL);

Stacking-Logik:

# User hat Single-Use Grant (20 Tage verbleibend)
# User löst Wellpass-Coupon ein
# → Single-Use Grant wird pausiert (is_active=false, paused_by=wellpass_grant_id)
# → Nach Wellpass-Ablauf: Single-Use wird reaktiviert (noch 20 Tage)

4.3 Gift Coupon

  • Verwendung: Vom System generiert als Bonus
  • Verhalten: Wie Single-Use, aber spezielles Tracking
  • Beispiel: "Login-Streak Belohnung"

5. User-Restrictions (Override-System)

Entscheidung: User-spezifische Overrides haben höchste Priorität.

Schema:

CREATE TABLE user_feature_restrictions (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
    feature_id TEXT NOT NULL REFERENCES features(id) ON DELETE CASCADE,
    limit_value INTEGER,              -- NULL = unbegrenzt, überschreibt Tier-Limit
    enabled BOOLEAN DEFAULT true,
    reason TEXT,                      -- Warum wurde Override gesetzt?
    set_by UUID REFERENCES profiles(id),  -- Welcher Admin?
    created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated TIMESTAMP,
    UNIQUE(profile_id, feature_id)
);

Anwendungsfälle:

  • Admin gewährt einzelnem User mehr/weniger Zugriff
  • Support-Fall: User bekommt temporär mehr KI-Calls
  • Beta-Tester: Zugriff auf experimentelle Features
  • Problem-User: Einschränkung bestimmter Features

Beispiel:

-- User bekommt 100 KI-Calls/Monat statt Tier-Standard
INSERT INTO user_feature_restrictions (profile_id, feature_id, limit_value, reason, set_by)
VALUES ('user-uuid', 'ai_calls', 100, 'Beta-Tester', 'admin-uuid');

Datenbank-Schema

Übersicht aller v9c Tabellen

v9c Subscription System (11 neue Tabellen):
├── app_settings          - Globale App-Konfiguration
├── tiers                 - Tier-Definitionen (free/basic/premium/selfhosted)
├── features              - Feature-Registry (zentrale Feature-Definition)
├── tier_limits           - Tier × Feature Matrix (Limits pro Tier)
├── user_feature_restrictions - User-spezifische Overrides
├── user_feature_usage    - Usage-Tracking (für reset_period)
├── coupons               - Coupon-Verwaltung (3 Typen)
├── coupon_redemptions    - Einlösungs-Historie
├── access_grants         - Zeitlich begrenzte Zugriffe
├── user_activity_log     - Aktivitäts-Tracking (JSONB)
└── user_stats            - Aggregierte Statistiken

Erweiterte Tabellen:
└── profiles              - Neue Spalten: tier, tier_locked, trial_ends_at,
                            email_verified, invited_by, contract_type

Detaillierte Schema-Definitionen

app_settings

CREATE TABLE app_settings (
    key TEXT PRIMARY KEY,
    value TEXT,
    value_type TEXT DEFAULT 'string',  -- 'string', 'integer', 'boolean', 'json'
    description TEXT,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_by UUID REFERENCES profiles(id)
);

-- Beispiel-Einstellungen:
INSERT INTO app_settings (key, value, value_type, description) VALUES
('trial_days', '14', 'integer', 'Anzahl Tage Trial-Zugang'),
('trial_behavior', 'downgrade', 'string', 'downgrade|lock nach Trial-Ende'),
('allow_registration', 'false', 'boolean', 'Selbst-Registrierung erlaubt?'),
('default_tier_trial', 'premium', 'string', 'Tier während Trial'),
('gift_coupons_per_month', '3', 'integer', 'Max Geschenk-Coupons pro User/Monat');

tier_limits (Kern der Matrix)

CREATE TABLE tier_limits (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    tier_id TEXT NOT NULL REFERENCES tiers(id) ON DELETE CASCADE,
    feature_id TEXT NOT NULL REFERENCES features(id) ON DELETE CASCADE,
    limit_value INTEGER,              -- NULL = unbegrenzt, 0 = deaktiviert
    enabled BOOLEAN DEFAULT true,
    created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated TIMESTAMP,
    UNIQUE(tier_id, feature_id)
);

-- Beispiel Free Tier:
INSERT INTO tier_limits (tier_id, feature_id, limit_value) VALUES
('free', 'weight_entries', 30),
('free', 'ai_calls', 0),              -- Deaktiviert
('free', 'data_export', 0);           -- Deaktiviert

-- Beispiel Premium Tier:
INSERT INTO tier_limits (tier_id, feature_id, limit_value) VALUES
('premium', 'weight_entries', NULL),  -- Unbegrenzt
('premium', 'ai_calls', NULL),        -- Unbegrenzt
('premium', 'ai_pipeline', 1);        -- Boolean: Aktiviert

access_grants (Zeitliche Zugriffe)

CREATE TABLE access_grants (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
    granted_tier TEXT NOT NULL REFERENCES tiers(id),
    valid_from TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    valid_until TIMESTAMP,            -- NULL = unbegrenzt
    source TEXT,                      -- 'coupon', 'admin_grant', 'trial'
    source_reference TEXT,            -- Coupon-Code oder Admin-Notiz
    is_active BOOLEAN DEFAULT true,   -- Kann pausiert werden
    paused_at TIMESTAMP,
    paused_by UUID REFERENCES access_grants(id),  -- Welcher Grant hat pausiert?
    created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    created_by UUID REFERENCES profiles(id)
);

-- Index für Performance
CREATE INDEX idx_access_grants_active ON access_grants(profile_id, is_active, valid_until DESC);

user_activity_log

CREATE TABLE user_activity_log (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
    activity_type TEXT NOT NULL,      -- 'login', 'coupon_redeemed', 'tier_change', etc.
    details JSONB,                    -- Flexible Details
    ip_address TEXT,
    user_agent TEXT,
    created TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Index für Abfragen
CREATE INDEX idx_activity_log_profile ON user_activity_log(profile_id, created DESC);

-- Beispiel-Einträge:
-- Login
INSERT INTO user_activity_log (profile_id, activity_type, details) VALUES
('uuid', 'login', '{"ip": "192.168.1.1", "device": "Chrome/Mac"}');

-- Coupon eingelöst
INSERT INTO user_activity_log (profile_id, activity_type, details) VALUES
('uuid', 'coupon_redeemed', '{"code": "FRIEND-GIFT-ABC", "tier": "premium", "days": 30}');

-- Tier-Änderung
INSERT INTO user_activity_log (profile_id, activity_type, details) VALUES
('uuid', 'tier_change', '{"from": "free", "to": "basic", "reason": "admin_grant"}');

Backend-API

Router-Übersicht

v9c Backend-Router (7 neue):
├── /api/subscription     - User-facing: Abo-Status, Usage, Limits
├── /api/coupons          - User: redeem; Admin: CRUD
├── /api/features         - Admin: Feature-Registry CRUD + check-access Endpoint
├── /api/tiers            - Admin: Tier-Verwaltung CRUD
├── /api/tier-limits      - Admin: Matrix-Editor (Tier × Feature)
├── /api/user-restrictions- Admin: User-Override-System
└── /api/access-grants    - Admin: Grant-Verwaltung (create, list, revoke)

Wichtige Endpoints

User-Facing

GET /api/subscription/me

{
  "tier": "basic",
  "tier_source": "base",  // 'base', 'trial', 'access_grant', 'admin_locked'
  "trial_ends_at": null,
  "access_grants": [
    {
      "granted_tier": "premium",
      "valid_until": "2026-04-15",
      "source": "coupon",
      "days_remaining": 25
    }
  ]
}

GET /api/subscription/usage

{
  "ai_calls": {
    "limit": 3,
    "used": 2,
    "remaining": 1,
    "reset_at": "2026-04-01T00:00:00"
  },
  "data_export": {
    "limit": 5,
    "used": 0,
    "remaining": 5,
    "reset_at": "2026-04-01T00:00:00"
  }
}

POST /api/coupons/redeem

{
  "code": "FRIEND-GIFT-ABC"
}

Response:

{
  "success": true,
  "granted_tier": "premium",
  "valid_until": "2026-04-19",
  "message": "30 Tage Premium-Zugang aktiviert!"
}

Admin-Only

GET /api/tier-limits

[
  {
    "tier_id": "free",
    "feature_id": "ai_calls",
    "limit_value": 0,
    "enabled": false
  },
  {
    "tier_id": "basic",
    "feature_id": "ai_calls",
    "limit_value": 3,
    "enabled": true
  }
]

PUT /api/tier-limits

{
  "tier_id": "basic",
  "feature_id": "ai_calls",
  "limit_value": 5
}

POST /api/access-grants

{
  "profile_id": "user-uuid",
  "granted_tier": "premium",
  "valid_until": "2026-12-31",
  "source": "admin_grant",
  "source_reference": "Support-Fall #123"
}

Frontend-Komponenten

Admin-UI (vollständig implementiert)

1. AdminFeaturesPage

Route: /admin/features

Funktionen:

  • Feature-Liste (sortierbar, filterbar)
  • Neues Feature hinzufügen
  • Feature bearbeiten (Name, Beschreibung, Limits, Reset-Period)
  • Feature deaktivieren (soft-delete)

UI-Elemente:

  • Feature-Tabelle mit Spalten: Name, Kategorie, Limit-Typ, Reset-Period, Default-Limit
  • Modal für Feature-Bearbeitung
  • Kategorie-Filter (ai, export, data, integration)

2. AdminTiersPage

Route: /admin/tiers

Funktionen:

  • Tier-Liste mit CRUD
  • Pricing (monatlich/jährlich) konfigurierbar
  • Sort-Order für Anzeige-Reihenfolge
  • Tier aktivieren/deaktivieren

3. AdminTierLimitsPage

Route: /admin/tier-limits

Funktionen:

  • Matrix-Editor: Tiers (Spalten) × Features (Zeilen)
  • Responsive: Desktop = Tabelle, Mobile = Cards
  • Inline-Editing mit Auto-Save
  • Checkbox für Boolean-Features
  • Number-Input für Count-Features
  • NULL/∞ für unbegrenzt

UI-Konzept:

┌────────────────┬──────┬───────┬─────────┬────────────┐
│ Feature        │ Free │ Basic │ Premium │ Selfhosted │
├────────────────┼──────┼───────┼─────────┼────────────┤
│ Gewicht        │  30  │   ∞   │    ∞    │     ∞      │
│ KI-Calls/Mon   │  ☐ 0 │ ☑  3  │  ☑  ∞   │   ☑  ∞     │
│ KI-Pipeline    │  ☐   │  ☐    │   ☑     │    ☑       │
│ Export/Mon     │  ☐ 0 │ ☑  5  │  ☑  ∞   │   ☑  ∞     │
└────────────────┴──────┴───────┴─────────┴────────────┘

4. AdminCouponsPage

Route: /admin/coupons

Funktionen:

  • Coupon-Liste (Code, Typ, Tier, Gültigkeit, Einlösungen)
  • Neuer Coupon (3 Typen)
  • Auto-Generate Code (Button)
  • Redemption-Historie ansehen
  • Coupon deaktivieren

Coupon-Typen-UI:

[ Single-Use ]  [ Multi-Use Period ]  [ Gift ]

Single-Use:
  Code: [FRIEND-GIFT-___] [Generate]
  Tier: [Premium ▼]
  Dauer: [30] Tage
  Max Einlösungen: [1]

Multi-Use Period:
  Code: [WELLPASS-2026-__] [Generate]
  Tier: [Premium ▼]
  Gültig von: [2026-03-01]
  Gültig bis: [2026-03-31]
  Max Einlösungen: [∞]

5. AdminUserRestrictionsPage

Route: /admin/user-restrictions

Funktionen:

  • User auswählen (Dropdown)
  • Aktueller Tier anzeigen
  • Feature-Liste mit:
    • Tier-Limit (readonly, grau)
    • Aktuelle Nutzung
    • Override-Eingabe (leer = Tier-Standard)
    • Save-Button pro Feature
  • "Alle Overrides entfernen" Button

UI-Konzept:

User: [Lars Stommer ▼]  Tier: selfhosted

┌─────────────┬────────────┬─────────┬──────────┬────────┐
│ Feature     │ Tier-Limit │ Genutzt │ Override │ Aktion │
├─────────────┼────────────┼─────────┼──────────┼────────┤
│ KI-Calls    │     ∞      │   5/∞   │  [100]   │ [Save] │
│ Export      │     ∞      │   2/∞   │  [    ]  │ [Save] │
│ Gewicht     │     ∞      │  50/∞   │  [    ]  │ [Save] │
└─────────────┴────────────┴─────────┴──────────┴────────┘

[Alle Overrides entfernen]

6. SubscriptionPage (User-facing)

Route: /subscription

Funktionen:

  • Aktueller Tier + Quelle
  • Tier-Badge mit Icon
  • Feature-Liste mit Limits
  • Usage-Progress-Bars
  • Coupon-Einlösung
  • Access-Grant-Historie

UI-Konzept:

┌─────────────────────────────────────┐
│ Dein Abo: [🟢 PREMIUM]              │
│ Quelle: Coupon (noch 25 Tage)       │
└─────────────────────────────────────┘

Features & Limits:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
KI-Analysen                      5/10
[████████░░] 50%                 Reset: 1.4.2026

Daten-Export                     2/5
[████░░░░░░] 40%                 Reset: 1.4.2026

Gewichtseinträge              120/∞
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Coupon einlösen:
[_________________] [Einlösen]

Feature-Enforcement-System

Status: ⚠️ DEAKTIVIERT (Rollback 20.03.2026)

Versuch-Implementation: Commits 3745ebd, cbad50a, cd4d912, 8415509 Rollback: Commit 4fcde4a

Was war geplant

Backend:

# In jedem limitierten Endpoint
def some_endpoint(session = Depends(require_auth)):
    pid = session['profile_id']

    # 1. Feature-Check
    access = check_feature_access(pid, 'feature_slug')
    if not access['allowed']:
        if access['reason'] == 'feature_disabled':
            raise HTTPException(403, "Feature nicht verfügbar")
        elif access['reason'] == 'limit_exceeded':
            raise HTTPException(429, f"Limit erreicht ({access['limit']})")

    # 2. Funktion ausführen
    result = do_something()

    # 3. Usage inkrementieren
    increment_feature_usage(pid, 'feature_slug')

    return result

Frontend:

import { FeatureGate, FeatureBadge } from '../components/FeatureGate'

function MyComponent() {
  return (
    <FeatureGate feature="ai_calls" showUpgradePrompt>
      <button onClick={runAnalysis}>
        KI-Analyse starten
        <FeatureBadge feature="ai_calls" />
      </button>
    </FeatureGate>
  )
}

Was schief ging

  1. Frontend-Cache-Problem

    • FeatureGate cached Feature-Status
    • Änderungen im Admin-Panel nicht sofort sichtbar
    • Optimistic rendering zeigte Features kurz an bevor Block
  2. Backend-Inkonsistenzen

    • Feature-IDs stimmten nicht mit DB überein (data_export vs export_csv)
    • Manche Features fehlten komplett (csv_import)
    • increment_feature_usage() hatte silent failures
  3. Analyse-History zerstört

    • DELETE vor INSERT entfernt, aber das war GEWOLLT im Original
    • Führte zu "keine Historisierung"-Beschwerde
    • War Missverständnis der Original-Logik
  4. Export-Buttons verschwunden

    • FeatureGate blockierte sofort
    • Migration nicht gelaufen → Features existierten nicht
    • canExport-Flag wurde überschrieben
  5. Pipeline-Duplikate

    • Filter ließ 'pipeline' Prompt durch
    • Scope-Bug: speicherte als 'gesamt' statt 'pipeline'

Lessons Learned

  1. Nie ohne vollständiges Verständnis refactorn

    • DELETE-Logik war absichtlich (1 Analyse pro Scope)
    • User wollte aber History → Requirements unklar
  2. Migrations müssen atomar laufen

    • Features müssen VOR Enforcement existieren
    • Auto-Migration muss zuverlässig sein
    • Test-Daten für lokale Entwicklung
  3. Frontend-Gates brauchen Refresh-Mechanismus

    • WebSocket oder Polling nach Admin-Änderungen
    • Oder: "Neu laden" Button prominent anzeigen
    • Optimistic rendering ist riskant
  4. Feature-IDs konsolidieren

    • Ein Feature für Export, nicht drei (csv/json/zip)
    • Konsistent zwischen DB, Backend-Code und Frontend
  5. Inkrementelle Einführung

    • Erst Backend-Checks als Logs (nicht blockierend)
    • Dann Frontend-Display (aber funktional)
    • Dann Enforcement aktivieren nach Tests

Nächste Schritte für Re-Implementation

  1. Phase 1: Cleanup

    • Feature-Definitionen konsolidieren
    • Migration auf Idempotenz prüfen
    • Test-Daten-Script erstellen
  2. Phase 2: Backend Non-Blocking

    • Feature-Checks in Endpoints einbauen
    • Aber nur loggen, nicht blockieren
    • Monitoring: Wie oft würde blockiert?
  3. Phase 3: Frontend Display

    • Usage-Counter anzeigen (ohne Gates)
    • Admin kann sehen was genutzt wird
    • Validierung gegen tatsächliche API-Calls
  4. Phase 4: Enforcement (opt-in)

    • Per Feature-Flag aktivierbar
    • Erst für Admin-Accounts testen
    • Dann für Test-User
    • Dann Rollout

Roadmap

v9c - Fertigstellung (Q2 2026)

Prio 1: Feature-Enforcement (Redesign)

  • Backend-Checks als Log-Only implementieren
  • Frontend Usage-Display ohne Gates
  • Feature-ID Konsolidierung
  • Test-Suite für alle Limits
  • Opt-in Enforcement per Feature-Flag
  • Rollout-Plan mit Rollback-Option

Prio 2: Registrierung & Trial

  • Self-Registration mit E-Mail-Verifizierung
  • Automatischer Trial-Start (14 Tage Premium)
  • Trial-Countdown-Banner
  • Auto-Downgrade nach Trial-Ende

Prio 3: App-Settings UI

  • Admin-Panel für globale Konfiguration
  • Trial-Einstellungen (Dauer, Verhalten)
  • Registrierungs-Toggle
  • E-Mail-Template-Editor

v9d - Monetarisierung (Q3 2026)

Prio 1: Stripe-Integration

  • Self-Service Upgrade (Premium)
  • Subscription-Management
  • Webhook-Handler
  • Rechnungs-E-Mails

Prio 2: Bonus-System

  • Login-Streak-Tracking
  • Punkte-System
  • Geschenk-Coupons (automatisch)
  • Achievements

Prio 3: Fitness-Connectoren

  • OAuth2-Framework
  • Strava-Connector
  • Withings-Connector (Waage)
  • Garmin-Connector

v9e - Partner & Enterprise (Q4 2026)

Prio 1: Partner-Integration

  • Wellpass-Authentifizierung
  • Hansefit-Authentifizierung
  • Partner-Admin-UI
  • Usage-Reporting für Partner

Prio 2: Enterprise Features

  • Multi-Tenant-Support
  • White-Label-Option
  • SAML/SSO
  • API-Keys für Drittanbieter

Technische Details

Performance-Überlegungen

check_feature_access() Caching:

  • Cache auf Request-Level (nicht global)
  • Verhindert multiple DB-Calls pro Request
  • TTL: 5 Minuten für Frontend-Checks

Database Indices:

-- Kritische Indices für Performance
CREATE INDEX idx_tier_limits_lookup ON tier_limits(tier_id, feature_id);
CREATE INDEX idx_user_restrictions_lookup ON user_feature_restrictions(profile_id, feature_id);
CREATE INDEX idx_feature_usage_lookup ON user_feature_usage(profile_id, feature_id, reset_at);
CREATE INDEX idx_access_grants_active ON access_grants(profile_id, is_active, valid_until DESC);

Security-Considerations

Coupon-Code-Generation:

  • Kryptografisch sicherer Zufallsgenerator
  • 12 Zeichen: XXXXX-YYYYY-ZZ
  • Kollisions-Check vor INSERT

Access-Grant-Validation:

  • Zeitstempel-Checks auf DB-Ebene (PostgreSQL TIMESTAMP)
  • Keine Client-side Validierung für Enforcement
  • is_active Flag für sofortigen Widerruf

User-Restrictions:

  • Nur Admins können setzen
  • Audit-Log (set_by, reason)
  • Kann nicht via User-API manipuliert werden

Testing-Strategie

Unit-Tests (Backend)

def test_check_feature_access_user_override():
    # Setup: User mit Free-Tier + Override für ai_calls=100
    profile_id = create_test_profile(tier='free')
    set_user_restriction(profile_id, 'ai_calls', 100)

    # Test: Override hat Vorrang vor Tier-Limit
    access = check_feature_access(profile_id, 'ai_calls')
    assert access['allowed'] == True
    assert access['limit'] == 100

def test_coupon_stacking_pause_resume():
    # Setup: Single-Use Grant aktiv
    profile_id = create_test_profile()
    grant1 = create_access_grant(profile_id, 'premium', days=30)

    # Test: Multi-Use Coupon pausiert Single-Use
    redeem_coupon(profile_id, 'WELLPASS-CODE')
    grant1_reloaded = get_access_grant(grant1.id)
    assert grant1_reloaded['is_active'] == False

    # Test: Nach Wellpass-Ablauf wird Single-Use reaktiviert
    expire_wellpass_grant(profile_id)
    grant1_reloaded = get_access_grant(grant1.id)
    assert grant1_reloaded['is_active'] == True

Integration-Tests (API)

# Scenario: Free User versucht KI-Analyse
curl -X POST /api/insights/run/gesamt \
  -H "X-Auth-Token: $TOKEN" \
  -H "X-Profile-Id: $FREE_USER_ID"

# Expected: HTTP 403 "KI nicht verfügbar"

# Scenario: User löst Coupon ein
curl -X POST /api/coupons/redeem \
  -H "X-Auth-Token: $TOKEN" \
  -d '{"code": "FRIEND-GIFT-ABC"}'

# Expected: HTTP 200 + Access-Grant erstellt

# Scenario: User mit Premium-Grant kann KI nutzen
curl -X POST /api/insights/run/gesamt \
  -H "X-Auth-Token: $TOKEN" \
  -H "X-Profile-Id: $FREE_USER_ID"

# Expected: HTTP 200 + Analyse-Ergebnis

Deployment-Notes

Migration-Reihenfolge

# 1. Backup
pg_dump mitai_prod > backup_before_v9c.sql

# 2. v9c Schema-Migration
psql mitai_prod < backend/migrations/v9c_subscription_system.sql

# 3. Feature-Fixes (falls nötig)
psql mitai_prod < backend/migrations/v9c_fix_features.sql

# 4. Backend neu starten (Auto-Migration läuft)
docker compose restart backend

# 5. Verifizieren
docker logs mitai-api | grep "v9c Migration"
# Expected: "✅ Migration completed successfully!"

Rollback-Plan

-- Emergency Rollback v9c
DROP TABLE IF EXISTS user_stats CASCADE;
DROP TABLE IF EXISTS user_activity_log CASCADE;
DROP TABLE IF EXISTS coupon_redemptions CASCADE;
DROP TABLE IF EXISTS coupons CASCADE;
DROP TABLE IF EXISTS access_grants CASCADE;
DROP TABLE IF EXISTS user_feature_usage CASCADE;
DROP TABLE IF EXISTS user_feature_restrictions CASCADE;
DROP TABLE IF EXISTS tier_limits CASCADE;
DROP TABLE IF EXISTS features CASCADE;
DROP TABLE IF EXISTS tiers CASCADE;
DROP TABLE IF EXISTS app_settings CASCADE;

-- Profiles-Spalten entfernen
ALTER TABLE profiles
  DROP COLUMN tier,
  DROP COLUMN tier_locked,
  DROP COLUMN trial_ends_at,
  DROP COLUMN email_verified,
  DROP COLUMN email_verify_token,
  DROP COLUMN invited_by,
  DROP COLUMN contract_type,
  DROP COLUMN contract_valid_until,
  DROP COLUMN stripe_customer_id;

Support & Troubleshooting

Häufige Probleme

Problem: User kann Feature nicht nutzen

-- Diagnose: Effektiven Tier prüfen
SELECT tier, tier_locked, trial_ends_at FROM profiles WHERE id = 'user-uuid';

-- Diagnose: Access-Grants prüfen
SELECT * FROM access_grants
WHERE profile_id = 'user-uuid'
  AND is_active = true
ORDER BY created DESC;

-- Diagnose: Feature-Limit prüfen
SELECT tl.* FROM tier_limits tl
WHERE tier_id = (SELECT tier FROM profiles WHERE id = 'user-uuid')
  AND feature_id = 'ai_calls';

-- Diagnose: User-Override prüfen
SELECT * FROM user_feature_restrictions
WHERE profile_id = 'user-uuid' AND feature_id = 'ai_calls';

Problem: Coupon lässt sich nicht einlösen

-- Diagnose: Coupon gültig?
SELECT * FROM coupons WHERE code = 'COUPON-CODE';

-- Check: Bereits eingelöst?
SELECT * FROM coupon_redemptions
WHERE coupon_id = (SELECT id FROM coupons WHERE code = 'COUPON-CODE')
  AND profile_id = 'user-uuid';

-- Check: Max Einlösungen erreicht?
SELECT c.max_redemptions, COUNT(cr.*) as current_redemptions
FROM coupons c
LEFT JOIN coupon_redemptions cr ON cr.coupon_id = c.id
WHERE c.code = 'COUPON-CODE'
GROUP BY c.id, c.max_redemptions;

Admin-Tools

User-Tier manuell ändern:

-- Tier setzen und locken
UPDATE profiles SET tier = 'premium', tier_locked = true WHERE id = 'user-uuid';

-- Access-Grant manuell erstellen
INSERT INTO access_grants (profile_id, granted_tier, valid_until, source, source_reference)
VALUES ('user-uuid', 'premium', '2026-12-31', 'admin_grant', 'Support-Fall #123');

Feature-Usage zurücksetzen:

-- Alle Usage für User zurücksetzen
DELETE FROM user_feature_usage WHERE profile_id = 'user-uuid';

-- Nur bestimmtes Feature zurücksetzen
DELETE FROM user_feature_usage
WHERE profile_id = 'user-uuid' AND feature_id = 'ai_calls';

Anhang

Glossar

  • Tier: Subscription-Stufe (free, basic, premium, selfhosted)
  • Feature: Limitierbare Funktionalität (ai_calls, data_export, etc.)
  • Limit: Maximale Anzahl Nutzungen (count) oder an/aus (boolean)
  • Access-Grant: Zeitlich begrenzte Tier-Berechtigung
  • Coupon: Einlösbarer Code für Tier-Zugang
  • Override: User-spezifische Abweichung vom Tier-Limit
  • Reset-Period: Zeitraum nach dem Limit zurückgesetzt wird (never, daily, monthly)

Kontakt & Fragen


Letzte Aktualisierung: 20. März 2026 Autor: Lars Stommer + Claude Opus 4.6 Version: v9c-dev