From ef8008a75dac541e7bc37cf89d1054ab1ab5f522 Mon Sep 17 00:00:00 2001 From: Lars Date: Fri, 20 Mar 2026 15:44:29 +0100 Subject: [PATCH] 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 --- CLAUDE.md | 94 ++-- docs/MEMBERSHIP_SYSTEM.md | 1058 +++++++++++++++++++++++++++++++++++++ 2 files changed, 1096 insertions(+), 56 deletions(-) create mode 100644 docs/MEMBERSHIP_SYSTEM.md diff --git a/CLAUDE.md b/CLAUDE.md index 9f11d7c..4736ac6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -71,17 +71,19 @@ mitai-jinkendo/ └── CLAUDE.md # Diese Datei ``` -## Aktuelle Version: v9b +## Aktuelle Version: v9c-dev (März 2026) ### Was implementiert ist: - ✅ Multi-User mit E-Mail + Passwort Login (bcrypt) - ✅ Auth-Middleware auf ALLE Endpoints (60+ Endpoints geschützt) - ✅ Rate Limiting (Login: 5/min, Reset: 3/min) - ✅ CORS konfigurierbar via ALLOWED_ORIGINS in .env -- ✅ Admin/User Rollen, KI-Limits, Export-Berechtigungen +- ✅ Admin/User Rollen, KI-Limits (simple daily limits), Export-Berechtigungen - ✅ Gewicht, Umfänge, Caliper (4 Formeln), Ernährung, Aktivität - ✅ FDDB CSV-Import (Ernährung), Apple Health CSV-Import (Aktivität) - ✅ KI-Analyse: 6 Einzel-Prompts + 3-stufige Pipeline (parallel) +- ✅ **KI-Analyse Historisierung**: Alle Analysen werden gespeichert (nicht überschrieben) +- ✅ **Pipeline korrekt**: Speichert unter scope='pipeline', erscheint nur 1x in UI - ✅ Konfigurierbare Prompts mit Template-Variablen (Admin kann bearbeiten) - ✅ Verlauf mit 5 Tabs + Zeitraumfilter + KI pro Sektion - ✅ Dashboard mit Kennzahlen, Zielfortschritt, Combo-Chart @@ -96,14 +98,26 @@ mitai-jinkendo/ - ✅ Automatische SQLite→PostgreSQL Migration bei Container-Start - ✅ **Modulare Backend-Architektur**: 14 Router-Module, main.py von 1878→75 Zeilen (-96%) +### Aktuelles KI-Limit-System (Simple): +```sql +-- Tägliche Limits pro Profil +profiles.ai_enabled BOOLEAN -- KI an/aus +profiles.ai_limit_day INTEGER -- Tägliches Limit (NULL = unbegrenzt) +ai_usage (profile_id, date, count) -- Täglicher Counter + +-- Funktionen in routers/insights.py: +check_ai_limit(pid) -- Prüft Limit vor KI-Call +inc_ai_usage(pid) -- Inkrementiert Counter nach Call +``` + ### Was in v9c kommt: Subscription & Coupon Management System **Phase 1 (DB-Schema): ✅ DONE** **Phase 2 (Backend API): ✅ DONE** -**Phase 3 (Frontend UI): ✅ DONE** (Feature Enforcement komplett, Self-Registration offen) +**Phase 3 (Admin Frontend): ✅ DONE** +**Phase 4 (Feature Enforcement): ⚠️ DEAKTIVIERT** (Rollback am 20.03.2026 - Bugs) -**Core Features (Backend):** +**Core Features (Backend & Admin-UI komplett):** - ✅ DB-Schema (11 neue Tabellen, Feature-Registry Pattern) -- ✅ **Feature-Access Enforcement** - Limits werden jetzt korrekt durchgesetzt! - ✅ Flexibles Tier-System (free/basic/premium/selfhosted) - Admin-editierbar via API - ✅ **Coupon-System** (3 Typen: single_use, period, wellpass) - ✅ Coupon-Stacking-Logik (Pause + Resume bei Wellpass-Override) @@ -113,67 +127,35 @@ mitai-jinkendo/ - ✅ Individuelle User-Restrictions (Admin kann Limits pro User setzen) - ✅ 7 neue Router, 30+ neue Endpoints (subscription, coupons, features, tiers, tier-limits, user-restrictions, access-grants) -**Frontend (Phase 3) - Status:** +**Admin-Frontend (vollständig implementiert):** - ✅ **AdminFeaturesPage** - Feature-Konfiguration (sortierung, reset_period, limits, visibility) - ✅ **AdminTiersPage** - Tier-Verwaltung (CRUD, pricing monthly/yearly) - ✅ **AdminTierLimitsPage** - Matrix-Editor (Tier x Feature, responsive mobile/desktop views) - ✅ **AdminCouponsPage** - Coupon-Manager (CRUD, 3 Typen, auto-generate codes, redemption history) - ✅ **AdminUserRestrictionsPage** - User-Override-System (effektive Werte, auto-remove redundant overrides) - ✅ **SubscriptionPage** - User Subscription-Info + Coupon-Einlösung (tier badge, limits, usage progress bars) -- ✅ **Feature Enforcement (März 2026)** - Backend + Frontend Feature Gates komplett implementiert - ✅ Alle Routes in App.jsx registriert -- 🔲 Selbst-Registrierung mit E-Mail-Verifizierung (Pflicht) -- 🔲 Trial-System UI (Countdown-Banner, auto-start nach E-Mail-Verifikation) -- 🔲 App-Settings Admin-Panel (globale Konfiguration: trial_days, allow_registration, etc.) -**✅ Feature Enforcement Implementation (20. März 2026):** +**⚠️ Feature Enforcement - ROLLBACK (20.03.2026):** +- Initial implementation broke core functionality (analysis history, export visibility, counters) +- Complete rollback to working state (commit 4fcde4a) +- Simple AI limit system (ai_enabled, ai_limit_day) now active +- v9c backend/admin UI remains functional but NOT enforcing limits +- Needs complete reimplementation with proper testing before re-enabling -**Backend - Feature Checks in Endpoints:** -- ✅ **routers/insights.py** - KI-Analysen geschützt - - `analyze_with_prompt()` - prüft 'ai_calls' feature, wirft HTTP 403/429 bei Limit - - `run_pipeline()` - prüft 'ai_pipeline' feature - - Beide inkrementieren usage nach erfolgreicher Ausführung -- ✅ **routers/exportdata.py** - Alle Export-Endpoints geschützt (CSV/JSON/ZIP) - - Prüft 'data_export' feature, wirft HTTP 403 bei deaktiviert/429 bei Limit - - Inkrementiert usage nach Export -- ✅ **routers/importdata.py** - ZIP-Import geschützt - - Prüft 'csv_import' feature vor Import -- ✅ **routers/nutrition.py** - FDDB CSV-Import geschützt - - Prüft 'csv_import' feature -- ✅ **routers/activity.py** - Apple Health CSV-Import geschützt - - Prüft 'csv_import' feature +**Noch NICHT implementiert:** +- 🔲 Feature-Enforcement-System (needs redesign) +- 🔲 Selbst-Registrierung mit E-Mail-Verifizierung +- 🔲 Trial-System UI (Countdown-Banner) +- 🔲 App-Settings Admin-Panel (globale Konfiguration) -**Frontend - Feature Gate System:** -- ✅ **hooks/useFeatureAccess.js** - Custom Hook für Feature-Access-Prüfung - - Ruft `/api/features/{slug}/check-access` auf - - Liefert: canUse, limit, used, remaining, reason, loading, error - - Optimistic default (canUse=true) für bessere UX -- ✅ **components/FeatureGate.jsx** - Reusable Feature Gate Component - - `` - versteckt children wenn Feature nicht verfügbar - - `` - zeigt Usage-Counter (z.B. "3/10") mit Farb-Codierung - - Upgrade-Prompt optional anzeigbar -- ✅ **pages/Analysis.jsx** - KI-Analysen Feature-Gates - - Pipeline-Button wrapped in `` - - Einzelanalysen wrapped in `` - - Usage-Counter in beiden Sektionen -- ✅ **pages/SettingsPage.jsx** - Export/Import Feature-Gates - - Export-Buttons wrapped in `` - - Import-Button wrapped in `` - - Usage-Counter bei beiden -- ✅ **utils/api.js** - API-Funktion hinzugefügt - - `checkFeatureAccess(featureSlug)` für Frontend-Checks - -**Verhalten:** -- Backend wirft HTTP 403 "Feature nicht verfügbar" bei deaktiviertem Feature -- Backend wirft HTTP 429 "Limit erreicht" bei überschrittenem Limit -- Frontend blendet Feature aus oder zeigt Upgrade-Prompt -- Usage-Counter zeigt aktuelle Nutzung (z.B. "5/10 AI-Calls") -- Farb-Codierung: Grün (0-70%), Orange (70-90%), Rot (90-100%) - -**Noch NICHT geschützt (optional für v9d):** -- 🔲 **photos.py** - Feature-Check für Progress-Fotos Upload -- 🔲 **weight.py, circumference.py, caliper.py** - Entry-Limits prüfen (verhindert neue Einträge wenn Limit erreicht) -- 🔲 **nutrition.py, activity.py** - Entry-Limits prüfen (analog zu weight) +**📚 Vollständige v9c Dokumentation:** +Siehe `/docs/MEMBERSHIP_SYSTEM.md` für: +- Vollständige Architektur-Dokumentation +- Datenbank-Schema Details +- API-Endpoints Übersicht +- Design-Entscheidungen und Rationale +- Lessons Learned vom Feature-Enforcement-Rollback **E-Mail Templates (v9c):** - 🔲 Registrierung + E-Mail-Verifizierung diff --git a/docs/MEMBERSHIP_SYSTEM.md b/docs/MEMBERSHIP_SYSTEM.md new file mode 100644 index 0000000..b48b4fa --- /dev/null +++ b/docs/MEMBERSHIP_SYSTEM.md @@ -0,0 +1,1058 @@ +# 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