# 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
