# 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](#überblick) 2. [Architektur-Entscheidungen](#architektur-entscheidungen) 3. [Datenbank-Schema](#datenbank-schema) 4. [Backend-API](#backend-api) 5. [Frontend-Komponenten](#frontend-komponenten) 6. [Feature-Enforcement-System](#feature-enforcement-system) 7. [Lessons Learned](#lessons-learned) 8. [Roadmap](#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:** ```sql 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:** ```sql -- 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:** ```sql 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:** ```python 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" ```sql 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" ```sql 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:** ```python # 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:** ```sql 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:** ```sql -- 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 ```sql 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) ```sql 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) ```sql 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 ```sql 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** ```json { "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** ```json { "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** ```json { "code": "FRIEND-GIFT-ABC" } ``` Response: ```json { "success": true, "granted_tier": "premium", "valid_until": "2026-04-19", "message": "30 Tage Premium-Zugang aktiviert!" } ``` #### Admin-Only **GET /api/tier-limits** ```json [ { "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** ```json { "tier_id": "basic", "feature_id": "ai_calls", "limit_value": 5 } ``` **POST /api/access-grants** ```json { "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:** ```python # 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:** ```jsx import { FeatureGate, FeatureBadge } from '../components/FeatureGate' function MyComponent() { return ( ) } ``` ### 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:** ```sql -- 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) ```python 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) ```bash # 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 ```bash # 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 ```sql -- 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** ```sql -- 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** ```sql -- 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:** ```sql -- 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:** ```sql -- 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 - **Repository**: http://192.168.2.144:3000/Lars/mitai-jinkendo - **Dokumentation**: `/docs/` im Repository - **Issues**: Gitea Issues oder direkt an Lars --- **Letzte Aktualisierung:** 20. März 2026 **Autor:** Lars Stommer + Claude Opus 4.6 **Version:** v9c-dev