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

1059 lines
32 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 (
<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:**
```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