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>
32 KiB
Mitai Jinkendo - Membership & Subscription System (v9c)
Version: v9c-dev Status: Backend & Admin-UI komplett, Enforcement deaktiviert Letzte Aktualisierung: 20. März 2026
Inhaltsverzeichnis
- Überblick
- Architektur-Entscheidungen
- Datenbank-Schema
- Backend-API
- Frontend-Komponenten
- Feature-Enforcement-System
- Lessons Learned
- Roadmap
Überblick
Das Mitai Jinkendo Membership-System (v9c) ist ein flexibles, tier-basiertes Subscription-System mit folgenden Kern-Features:
Implementierte Features ✅
- 4 Tier-Stufen: free, basic, premium, selfhosted
- Feature-Registry-Pattern: Zentrale Definition aller limitierbaren Features
- Flexible Limit-Matrix: Admin kann Tier × Feature Limits konfigurieren
- User-Override-System: Individuelle Limits pro User
- Coupon-System: 3 Typen (single_use, multi_use_period, gift)
- Coupon-Stacking: Intelligente Pause/Resume-Logik bei temporären Zugriffen
- Access-Grants: Zeitlich begrenzte Tier-Zugriffe mit Quelle-Tracking
- User-Activity-Log: JSONB-basierte Aktivitätsverfolgung
- Admin-UI: Vollständige Verwaltungsoberfläche für alle Aspekte
Geplante Features 🔲
- Feature-Enforcement in Endpoints (needs redesign)
- Selbst-Registrierung mit E-Mail-Verifizierung
- Trial-System mit automatischem Downgrade
- Bonus-System (Login-Streaks)
- Stripe-Integration
- Partner-Integration (Wellpass, Hansefit)
Architektur-Entscheidungen
1. Feature-Registry-Pattern
Entscheidung: Alle limitierbaren Features werden zentral in einer features Tabelle definiert.
Rationale:
- Neue Features können ohne Schema-Migration hinzugefügt werden
- Metadaten (name, description, category, unit) sind direkt verfügbar
- Konsistenz zwischen Backend-Checks und Frontend-Display
- Admin-UI kann automatisch generiert werden
Schema:
CREATE TABLE features (
id TEXT PRIMARY KEY, -- 'ai_calls', 'data_export', etc.
name TEXT NOT NULL, -- 'KI-Analysen', 'Daten exportieren'
description TEXT,
category TEXT, -- 'ai', 'export', 'data', 'integration'
limit_type TEXT DEFAULT 'count', -- 'count' oder 'boolean'
reset_period TEXT DEFAULT 'never', -- 'never', 'daily', 'monthly'
default_limit INTEGER, -- NULL = unbegrenzt
active BOOLEAN DEFAULT true,
sort_order INTEGER DEFAULT 0,
created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated TIMESTAMP
);
Beispiel-Features:
-- Count-based mit monatlichem Reset
('ai_calls', 'KI-Analysen', 'KI-Auswertungen pro Monat', 'ai', 'count', 'monthly', 0, true)
-- Boolean-Feature (an/aus)
('ai_pipeline', 'KI-Pipeline', 'Vollständige Pipeline-Analyse', 'ai', 'boolean', 'never', 0, true)
-- Count-based ohne Reset (Gesamt-Limit)
('weight_entries', 'Gewichtseinträge', 'Anzahl Gewichtsmessungen', 'data', 'count', 'never', NULL, true)
2. Tier-System
Entscheidung: Vereinfachte Tier-Tabelle ohne hart-codierte Limits.
Rationale:
- Limits werden in separate
tier_limitsTabelle ausgelagert - Tiers können dynamisch hinzugefügt werden
- Pricing-Informationen zentral verwaltet
- Flexibilität für zukünftige Tier-Erweiterungen
Schema:
CREATE TABLE tiers (
id TEXT PRIMARY KEY, -- 'free', 'basic', 'premium', 'selfhosted'
slug TEXT UNIQUE NOT NULL,
name TEXT NOT NULL, -- 'Free', 'Basic', 'Premium', 'Self-Hosted'
description TEXT,
price_monthly DECIMAL(10,2),
price_yearly DECIMAL(10,2),
sort_order INTEGER DEFAULT 0,
active BOOLEAN DEFAULT true,
created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated TIMESTAMP
);
Initial Tiers:
- free: Eingeschränkt (30 Daten-Einträge, 0 KI-Calls, kein Export)
- basic: Kernfunktionen (unbegrenzte Daten, 3 KI-Calls/Monat, Export erlaubt)
- premium: Alles unbegrenzt (inkl. KI-Pipeline, Connectoren)
- selfhosted: Admin-Tier für selbst-gehostete Installationen
3. Zugriffs-Hierarchie
Entscheidung: Drei-stufige Priorität für effektive Tier-Ermittlung.
Priorität (höchste zuerst):
- Admin-Override:
profiles.tier_locked = true→ nutztprofiles.tier - Access-Grant: Aktiver, nicht-pausierter Grant → nutzt
access_grants.granted_tier - Trial:
profiles.trial_ends_at > NOW()→ nutzt trial tier - Base Tier:
profiles.tier
Implementierung:
def get_effective_tier(profile_id: str) -> str:
"""Get effective tier considering all overrides."""
with get_db() as conn:
cur = get_cursor(conn)
# 1. Check if tier is locked by admin
cur.execute("SELECT tier, tier_locked FROM profiles WHERE id = %s", (profile_id,))
profile = cur.fetchone()
if profile['tier_locked']:
return profile['tier']
# 2. Check for active access grant
cur.execute("""
SELECT granted_tier FROM access_grants
WHERE profile_id = %s
AND is_active = true
AND valid_from <= CURRENT_TIMESTAMP
AND (valid_until IS NULL OR valid_until > CURRENT_TIMESTAMP)
ORDER BY created DESC LIMIT 1
""", (profile_id,))
grant = cur.fetchone()
if grant:
return grant['granted_tier']
# 3. Check trial
cur.execute("""
SELECT tier FROM profiles
WHERE id = %s AND trial_ends_at > CURRENT_TIMESTAMP
""", (profile_id,))
trial = cur.fetchone()
if trial:
return 'premium' # or configurable trial tier
# 4. Base tier
return profile['tier']
Rationale:
- Admin kann User dauerhaft einem Tier zuweisen (Support-Fälle)
- Temporäre Zugriffe (Coupons, Wellpass) haben Vorrang vor Base-Tier
- Trial-Logik ist transparent und automatisch
- Base-Tier ist Fallback
4. Coupon-System
Entscheidung: 3 Coupon-Typen mit unterschiedlicher Stacking-Logik.
Typen:
4.1 Single-Use Coupon
- Verwendung: Einmalig einlösbar (z.B. Geschenk-Coupon)
- Verhalten: Erstellt
access_grantmit fester Laufzeit - Stacking: Zeitlich sequenziell (startet nach Ablauf vorheriger Grants)
- Beispiel: "30 Tage Premium geschenkt"
INSERT INTO coupons (code, type, grants_tier, duration_days, max_redemptions) VALUES
('FRIEND-GIFT-ABC', 'single_use', 'premium', 30, 1);
4.2 Multi-Use Period Coupon
- Verwendung: Unbegrenzt einlösbar während Gültigkeitszeitraum
- Verhalten: Pausiert andere Grants, reaktiviert sie nach Ablauf
- Stacking: Override mit Pause/Resume
- Beispiel: "Wellpass-Monatszugang März 2026"
INSERT INTO coupons (code, type, grants_tier, valid_from, valid_until, max_redemptions) VALUES
('WELLPASS-2026-03', 'multi_use_period', 'premium', '2026-03-01', '2026-03-31', NULL);
Stacking-Logik:
# User hat Single-Use Grant (20 Tage verbleibend)
# User löst Wellpass-Coupon ein
# → Single-Use Grant wird pausiert (is_active=false, paused_by=wellpass_grant_id)
# → Nach Wellpass-Ablauf: Single-Use wird reaktiviert (noch 20 Tage)
4.3 Gift Coupon
- Verwendung: Vom System generiert als Bonus
- Verhalten: Wie Single-Use, aber spezielles Tracking
- Beispiel: "Login-Streak Belohnung"
5. User-Restrictions (Override-System)
Entscheidung: User-spezifische Overrides haben höchste Priorität.
Schema:
CREATE TABLE user_feature_restrictions (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
feature_id TEXT NOT NULL REFERENCES features(id) ON DELETE CASCADE,
limit_value INTEGER, -- NULL = unbegrenzt, überschreibt Tier-Limit
enabled BOOLEAN DEFAULT true,
reason TEXT, -- Warum wurde Override gesetzt?
set_by UUID REFERENCES profiles(id), -- Welcher Admin?
created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated TIMESTAMP,
UNIQUE(profile_id, feature_id)
);
Anwendungsfälle:
- Admin gewährt einzelnem User mehr/weniger Zugriff
- Support-Fall: User bekommt temporär mehr KI-Calls
- Beta-Tester: Zugriff auf experimentelle Features
- Problem-User: Einschränkung bestimmter Features
Beispiel:
-- User bekommt 100 KI-Calls/Monat statt Tier-Standard
INSERT INTO user_feature_restrictions (profile_id, feature_id, limit_value, reason, set_by)
VALUES ('user-uuid', 'ai_calls', 100, 'Beta-Tester', 'admin-uuid');
Datenbank-Schema
Übersicht aller v9c Tabellen
v9c Subscription System (11 neue Tabellen):
├── app_settings - Globale App-Konfiguration
├── tiers - Tier-Definitionen (free/basic/premium/selfhosted)
├── features - Feature-Registry (zentrale Feature-Definition)
├── tier_limits - Tier × Feature Matrix (Limits pro Tier)
├── user_feature_restrictions - User-spezifische Overrides
├── user_feature_usage - Usage-Tracking (für reset_period)
├── coupons - Coupon-Verwaltung (3 Typen)
├── coupon_redemptions - Einlösungs-Historie
├── access_grants - Zeitlich begrenzte Zugriffe
├── user_activity_log - Aktivitäts-Tracking (JSONB)
└── user_stats - Aggregierte Statistiken
Erweiterte Tabellen:
└── profiles - Neue Spalten: tier, tier_locked, trial_ends_at,
email_verified, invited_by, contract_type
Detaillierte Schema-Definitionen
app_settings
CREATE TABLE app_settings (
key TEXT PRIMARY KEY,
value TEXT,
value_type TEXT DEFAULT 'string', -- 'string', 'integer', 'boolean', 'json'
description TEXT,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_by UUID REFERENCES profiles(id)
);
-- Beispiel-Einstellungen:
INSERT INTO app_settings (key, value, value_type, description) VALUES
('trial_days', '14', 'integer', 'Anzahl Tage Trial-Zugang'),
('trial_behavior', 'downgrade', 'string', 'downgrade|lock nach Trial-Ende'),
('allow_registration', 'false', 'boolean', 'Selbst-Registrierung erlaubt?'),
('default_tier_trial', 'premium', 'string', 'Tier während Trial'),
('gift_coupons_per_month', '3', 'integer', 'Max Geschenk-Coupons pro User/Monat');
tier_limits (Kern der Matrix)
CREATE TABLE tier_limits (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tier_id TEXT NOT NULL REFERENCES tiers(id) ON DELETE CASCADE,
feature_id TEXT NOT NULL REFERENCES features(id) ON DELETE CASCADE,
limit_value INTEGER, -- NULL = unbegrenzt, 0 = deaktiviert
enabled BOOLEAN DEFAULT true,
created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated TIMESTAMP,
UNIQUE(tier_id, feature_id)
);
-- Beispiel Free Tier:
INSERT INTO tier_limits (tier_id, feature_id, limit_value) VALUES
('free', 'weight_entries', 30),
('free', 'ai_calls', 0), -- Deaktiviert
('free', 'data_export', 0); -- Deaktiviert
-- Beispiel Premium Tier:
INSERT INTO tier_limits (tier_id, feature_id, limit_value) VALUES
('premium', 'weight_entries', NULL), -- Unbegrenzt
('premium', 'ai_calls', NULL), -- Unbegrenzt
('premium', 'ai_pipeline', 1); -- Boolean: Aktiviert
access_grants (Zeitliche Zugriffe)
CREATE TABLE access_grants (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
granted_tier TEXT NOT NULL REFERENCES tiers(id),
valid_from TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
valid_until TIMESTAMP, -- NULL = unbegrenzt
source TEXT, -- 'coupon', 'admin_grant', 'trial'
source_reference TEXT, -- Coupon-Code oder Admin-Notiz
is_active BOOLEAN DEFAULT true, -- Kann pausiert werden
paused_at TIMESTAMP,
paused_by UUID REFERENCES access_grants(id), -- Welcher Grant hat pausiert?
created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES profiles(id)
);
-- Index für Performance
CREATE INDEX idx_access_grants_active ON access_grants(profile_id, is_active, valid_until DESC);
user_activity_log
CREATE TABLE user_activity_log (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
activity_type TEXT NOT NULL, -- 'login', 'coupon_redeemed', 'tier_change', etc.
details JSONB, -- Flexible Details
ip_address TEXT,
user_agent TEXT,
created TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Index für Abfragen
CREATE INDEX idx_activity_log_profile ON user_activity_log(profile_id, created DESC);
-- Beispiel-Einträge:
-- Login
INSERT INTO user_activity_log (profile_id, activity_type, details) VALUES
('uuid', 'login', '{"ip": "192.168.1.1", "device": "Chrome/Mac"}');
-- Coupon eingelöst
INSERT INTO user_activity_log (profile_id, activity_type, details) VALUES
('uuid', 'coupon_redeemed', '{"code": "FRIEND-GIFT-ABC", "tier": "premium", "days": 30}');
-- Tier-Änderung
INSERT INTO user_activity_log (profile_id, activity_type, details) VALUES
('uuid', 'tier_change', '{"from": "free", "to": "basic", "reason": "admin_grant"}');
Backend-API
Router-Übersicht
v9c Backend-Router (7 neue):
├── /api/subscription - User-facing: Abo-Status, Usage, Limits
├── /api/coupons - User: redeem; Admin: CRUD
├── /api/features - Admin: Feature-Registry CRUD + check-access Endpoint
├── /api/tiers - Admin: Tier-Verwaltung CRUD
├── /api/tier-limits - Admin: Matrix-Editor (Tier × Feature)
├── /api/user-restrictions- Admin: User-Override-System
└── /api/access-grants - Admin: Grant-Verwaltung (create, list, revoke)
Wichtige Endpoints
User-Facing
GET /api/subscription/me
{
"tier": "basic",
"tier_source": "base", // 'base', 'trial', 'access_grant', 'admin_locked'
"trial_ends_at": null,
"access_grants": [
{
"granted_tier": "premium",
"valid_until": "2026-04-15",
"source": "coupon",
"days_remaining": 25
}
]
}
GET /api/subscription/usage
{
"ai_calls": {
"limit": 3,
"used": 2,
"remaining": 1,
"reset_at": "2026-04-01T00:00:00"
},
"data_export": {
"limit": 5,
"used": 0,
"remaining": 5,
"reset_at": "2026-04-01T00:00:00"
}
}
POST /api/coupons/redeem
{
"code": "FRIEND-GIFT-ABC"
}
Response:
{
"success": true,
"granted_tier": "premium",
"valid_until": "2026-04-19",
"message": "30 Tage Premium-Zugang aktiviert!"
}
Admin-Only
GET /api/tier-limits
[
{
"tier_id": "free",
"feature_id": "ai_calls",
"limit_value": 0,
"enabled": false
},
{
"tier_id": "basic",
"feature_id": "ai_calls",
"limit_value": 3,
"enabled": true
}
]
PUT /api/tier-limits
{
"tier_id": "basic",
"feature_id": "ai_calls",
"limit_value": 5
}
POST /api/access-grants
{
"profile_id": "user-uuid",
"granted_tier": "premium",
"valid_until": "2026-12-31",
"source": "admin_grant",
"source_reference": "Support-Fall #123"
}
Frontend-Komponenten
Admin-UI (vollständig implementiert)
1. AdminFeaturesPage
Route: /admin/features
Funktionen:
- Feature-Liste (sortierbar, filterbar)
- Neues Feature hinzufügen
- Feature bearbeiten (Name, Beschreibung, Limits, Reset-Period)
- Feature deaktivieren (soft-delete)
UI-Elemente:
- Feature-Tabelle mit Spalten: Name, Kategorie, Limit-Typ, Reset-Period, Default-Limit
- Modal für Feature-Bearbeitung
- Kategorie-Filter (ai, export, data, integration)
2. AdminTiersPage
Route: /admin/tiers
Funktionen:
- Tier-Liste mit CRUD
- Pricing (monatlich/jährlich) konfigurierbar
- Sort-Order für Anzeige-Reihenfolge
- Tier aktivieren/deaktivieren
3. AdminTierLimitsPage
Route: /admin/tier-limits
Funktionen:
- Matrix-Editor: Tiers (Spalten) × Features (Zeilen)
- Responsive: Desktop = Tabelle, Mobile = Cards
- Inline-Editing mit Auto-Save
- Checkbox für Boolean-Features
- Number-Input für Count-Features
- NULL/∞ für unbegrenzt
UI-Konzept:
┌────────────────┬──────┬───────┬─────────┬────────────┐
│ Feature │ Free │ Basic │ Premium │ Selfhosted │
├────────────────┼──────┼───────┼─────────┼────────────┤
│ Gewicht │ 30 │ ∞ │ ∞ │ ∞ │
│ KI-Calls/Mon │ ☐ 0 │ ☑ 3 │ ☑ ∞ │ ☑ ∞ │
│ KI-Pipeline │ ☐ │ ☐ │ ☑ │ ☑ │
│ Export/Mon │ ☐ 0 │ ☑ 5 │ ☑ ∞ │ ☑ ∞ │
└────────────────┴──────┴───────┴─────────┴────────────┘
4. AdminCouponsPage
Route: /admin/coupons
Funktionen:
- Coupon-Liste (Code, Typ, Tier, Gültigkeit, Einlösungen)
- Neuer Coupon (3 Typen)
- Auto-Generate Code (Button)
- Redemption-Historie ansehen
- Coupon deaktivieren
Coupon-Typen-UI:
[ Single-Use ] [ Multi-Use Period ] [ Gift ]
Single-Use:
Code: [FRIEND-GIFT-___] [Generate]
Tier: [Premium ▼]
Dauer: [30] Tage
Max Einlösungen: [1]
Multi-Use Period:
Code: [WELLPASS-2026-__] [Generate]
Tier: [Premium ▼]
Gültig von: [2026-03-01]
Gültig bis: [2026-03-31]
Max Einlösungen: [∞]
5. AdminUserRestrictionsPage
Route: /admin/user-restrictions
Funktionen:
- User auswählen (Dropdown)
- Aktueller Tier anzeigen
- Feature-Liste mit:
- Tier-Limit (readonly, grau)
- Aktuelle Nutzung
- Override-Eingabe (leer = Tier-Standard)
- Save-Button pro Feature
- "Alle Overrides entfernen" Button
UI-Konzept:
User: [Lars Stommer ▼] Tier: selfhosted
┌─────────────┬────────────┬─────────┬──────────┬────────┐
│ Feature │ Tier-Limit │ Genutzt │ Override │ Aktion │
├─────────────┼────────────┼─────────┼──────────┼────────┤
│ KI-Calls │ ∞ │ 5/∞ │ [100] │ [Save] │
│ Export │ ∞ │ 2/∞ │ [ ] │ [Save] │
│ Gewicht │ ∞ │ 50/∞ │ [ ] │ [Save] │
└─────────────┴────────────┴─────────┴──────────┴────────┘
[Alle Overrides entfernen]
6. SubscriptionPage (User-facing)
Route: /subscription
Funktionen:
- Aktueller Tier + Quelle
- Tier-Badge mit Icon
- Feature-Liste mit Limits
- Usage-Progress-Bars
- Coupon-Einlösung
- Access-Grant-Historie
UI-Konzept:
┌─────────────────────────────────────┐
│ Dein Abo: [🟢 PREMIUM] │
│ Quelle: Coupon (noch 25 Tage) │
└─────────────────────────────────────┘
Features & Limits:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
KI-Analysen 5/10
[████████░░] 50% Reset: 1.4.2026
Daten-Export 2/5
[████░░░░░░] 40% Reset: 1.4.2026
Gewichtseinträge 120/∞
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Coupon einlösen:
[_________________] [Einlösen]
Feature-Enforcement-System
Status: ⚠️ DEAKTIVIERT (Rollback 20.03.2026)
Versuch-Implementation: Commits 3745ebd, cbad50a, cd4d912, 8415509
Rollback: Commit 4fcde4a
Was war geplant
Backend:
# In jedem limitierten Endpoint
def some_endpoint(session = Depends(require_auth)):
pid = session['profile_id']
# 1. Feature-Check
access = check_feature_access(pid, 'feature_slug')
if not access['allowed']:
if access['reason'] == 'feature_disabled':
raise HTTPException(403, "Feature nicht verfügbar")
elif access['reason'] == 'limit_exceeded':
raise HTTPException(429, f"Limit erreicht ({access['limit']})")
# 2. Funktion ausführen
result = do_something()
# 3. Usage inkrementieren
increment_feature_usage(pid, 'feature_slug')
return result
Frontend:
import { FeatureGate, FeatureBadge } from '../components/FeatureGate'
function MyComponent() {
return (
<FeatureGate feature="ai_calls" showUpgradePrompt>
<button onClick={runAnalysis}>
KI-Analyse starten
<FeatureBadge feature="ai_calls" />
</button>
</FeatureGate>
)
}
Was schief ging
-
Frontend-Cache-Problem
- FeatureGate cached Feature-Status
- Änderungen im Admin-Panel nicht sofort sichtbar
- Optimistic rendering zeigte Features kurz an bevor Block
-
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
-
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
-
Export-Buttons verschwunden
- FeatureGate blockierte sofort
- Migration nicht gelaufen → Features existierten nicht
- canExport-Flag wurde überschrieben
-
Pipeline-Duplikate
- Filter ließ 'pipeline' Prompt durch
- Scope-Bug: speicherte als 'gesamt' statt 'pipeline'
Lessons Learned
-
Nie ohne vollständiges Verständnis refactorn
- DELETE-Logik war absichtlich (1 Analyse pro Scope)
- User wollte aber History → Requirements unklar
-
Migrations müssen atomar laufen
- Features müssen VOR Enforcement existieren
- Auto-Migration muss zuverlässig sein
- Test-Daten für lokale Entwicklung
-
Frontend-Gates brauchen Refresh-Mechanismus
- WebSocket oder Polling nach Admin-Änderungen
- Oder: "Neu laden" Button prominent anzeigen
- Optimistic rendering ist riskant
-
Feature-IDs konsolidieren
- Ein Feature für Export, nicht drei (csv/json/zip)
- Konsistent zwischen DB, Backend-Code und Frontend
-
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
-
Phase 1: Cleanup
- Feature-Definitionen konsolidieren
- Migration auf Idempotenz prüfen
- Test-Daten-Script erstellen
-
Phase 2: Backend Non-Blocking
- Feature-Checks in Endpoints einbauen
- Aber nur loggen, nicht blockieren
- Monitoring: Wie oft würde blockiert?
-
Phase 3: Frontend Display
- Usage-Counter anzeigen (ohne Gates)
- Admin kann sehen was genutzt wird
- Validierung gegen tatsächliche API-Calls
-
Phase 4: Enforcement (opt-in)
- Per Feature-Flag aktivierbar
- Erst für Admin-Accounts testen
- Dann für Test-User
- Dann Rollout
Roadmap
v9c - Fertigstellung (Q2 2026)
Prio 1: Feature-Enforcement (Redesign)
- ✅ Backend-Checks als Log-Only implementieren
- ✅ Frontend Usage-Display ohne Gates
- ✅ Feature-ID Konsolidierung
- ✅ Test-Suite für alle Limits
- ⏳ Opt-in Enforcement per Feature-Flag
- ⏳ Rollout-Plan mit Rollback-Option
Prio 2: Registrierung & Trial
- Self-Registration mit E-Mail-Verifizierung
- Automatischer Trial-Start (14 Tage Premium)
- Trial-Countdown-Banner
- Auto-Downgrade nach Trial-Ende
Prio 3: App-Settings UI
- Admin-Panel für globale Konfiguration
- Trial-Einstellungen (Dauer, Verhalten)
- Registrierungs-Toggle
- E-Mail-Template-Editor
v9d - Monetarisierung (Q3 2026)
Prio 1: Stripe-Integration
- Self-Service Upgrade (Premium)
- Subscription-Management
- Webhook-Handler
- Rechnungs-E-Mails
Prio 2: Bonus-System
- Login-Streak-Tracking
- Punkte-System
- Geschenk-Coupons (automatisch)
- Achievements
Prio 3: Fitness-Connectoren
- OAuth2-Framework
- Strava-Connector
- Withings-Connector (Waage)
- Garmin-Connector
v9e - Partner & Enterprise (Q4 2026)
Prio 1: Partner-Integration
- Wellpass-Authentifizierung
- Hansefit-Authentifizierung
- Partner-Admin-UI
- Usage-Reporting für Partner
Prio 2: Enterprise Features
- Multi-Tenant-Support
- White-Label-Option
- SAML/SSO
- API-Keys für Drittanbieter
Technische Details
Performance-Überlegungen
check_feature_access() Caching:
- Cache auf Request-Level (nicht global)
- Verhindert multiple DB-Calls pro Request
- TTL: 5 Minuten für Frontend-Checks
Database Indices:
-- Kritische Indices für Performance
CREATE INDEX idx_tier_limits_lookup ON tier_limits(tier_id, feature_id);
CREATE INDEX idx_user_restrictions_lookup ON user_feature_restrictions(profile_id, feature_id);
CREATE INDEX idx_feature_usage_lookup ON user_feature_usage(profile_id, feature_id, reset_at);
CREATE INDEX idx_access_grants_active ON access_grants(profile_id, is_active, valid_until DESC);
Security-Considerations
Coupon-Code-Generation:
- Kryptografisch sicherer Zufallsgenerator
- 12 Zeichen: XXXXX-YYYYY-ZZ
- Kollisions-Check vor INSERT
Access-Grant-Validation:
- Zeitstempel-Checks auf DB-Ebene (PostgreSQL TIMESTAMP)
- Keine Client-side Validierung für Enforcement
- is_active Flag für sofortigen Widerruf
User-Restrictions:
- Nur Admins können setzen
- Audit-Log (set_by, reason)
- Kann nicht via User-API manipuliert werden
Testing-Strategie
Unit-Tests (Backend)
def test_check_feature_access_user_override():
# Setup: User mit Free-Tier + Override für ai_calls=100
profile_id = create_test_profile(tier='free')
set_user_restriction(profile_id, 'ai_calls', 100)
# Test: Override hat Vorrang vor Tier-Limit
access = check_feature_access(profile_id, 'ai_calls')
assert access['allowed'] == True
assert access['limit'] == 100
def test_coupon_stacking_pause_resume():
# Setup: Single-Use Grant aktiv
profile_id = create_test_profile()
grant1 = create_access_grant(profile_id, 'premium', days=30)
# Test: Multi-Use Coupon pausiert Single-Use
redeem_coupon(profile_id, 'WELLPASS-CODE')
grant1_reloaded = get_access_grant(grant1.id)
assert grant1_reloaded['is_active'] == False
# Test: Nach Wellpass-Ablauf wird Single-Use reaktiviert
expire_wellpass_grant(profile_id)
grant1_reloaded = get_access_grant(grant1.id)
assert grant1_reloaded['is_active'] == True
Integration-Tests (API)
# Scenario: Free User versucht KI-Analyse
curl -X POST /api/insights/run/gesamt \
-H "X-Auth-Token: $TOKEN" \
-H "X-Profile-Id: $FREE_USER_ID"
# Expected: HTTP 403 "KI nicht verfügbar"
# Scenario: User löst Coupon ein
curl -X POST /api/coupons/redeem \
-H "X-Auth-Token: $TOKEN" \
-d '{"code": "FRIEND-GIFT-ABC"}'
# Expected: HTTP 200 + Access-Grant erstellt
# Scenario: User mit Premium-Grant kann KI nutzen
curl -X POST /api/insights/run/gesamt \
-H "X-Auth-Token: $TOKEN" \
-H "X-Profile-Id: $FREE_USER_ID"
# Expected: HTTP 200 + Analyse-Ergebnis
Deployment-Notes
Migration-Reihenfolge
# 1. Backup
pg_dump mitai_prod > backup_before_v9c.sql
# 2. v9c Schema-Migration
psql mitai_prod < backend/migrations/v9c_subscription_system.sql
# 3. Feature-Fixes (falls nötig)
psql mitai_prod < backend/migrations/v9c_fix_features.sql
# 4. Backend neu starten (Auto-Migration läuft)
docker compose restart backend
# 5. Verifizieren
docker logs mitai-api | grep "v9c Migration"
# Expected: "✅ Migration completed successfully!"
Rollback-Plan
-- Emergency Rollback v9c
DROP TABLE IF EXISTS user_stats CASCADE;
DROP TABLE IF EXISTS user_activity_log CASCADE;
DROP TABLE IF EXISTS coupon_redemptions CASCADE;
DROP TABLE IF EXISTS coupons CASCADE;
DROP TABLE IF EXISTS access_grants CASCADE;
DROP TABLE IF EXISTS user_feature_usage CASCADE;
DROP TABLE IF EXISTS user_feature_restrictions CASCADE;
DROP TABLE IF EXISTS tier_limits CASCADE;
DROP TABLE IF EXISTS features CASCADE;
DROP TABLE IF EXISTS tiers CASCADE;
DROP TABLE IF EXISTS app_settings CASCADE;
-- Profiles-Spalten entfernen
ALTER TABLE profiles
DROP COLUMN tier,
DROP COLUMN tier_locked,
DROP COLUMN trial_ends_at,
DROP COLUMN email_verified,
DROP COLUMN email_verify_token,
DROP COLUMN invited_by,
DROP COLUMN contract_type,
DROP COLUMN contract_valid_until,
DROP COLUMN stripe_customer_id;
Support & Troubleshooting
Häufige Probleme
Problem: User kann Feature nicht nutzen
-- Diagnose: Effektiven Tier prüfen
SELECT tier, tier_locked, trial_ends_at FROM profiles WHERE id = 'user-uuid';
-- Diagnose: Access-Grants prüfen
SELECT * FROM access_grants
WHERE profile_id = 'user-uuid'
AND is_active = true
ORDER BY created DESC;
-- Diagnose: Feature-Limit prüfen
SELECT tl.* FROM tier_limits tl
WHERE tier_id = (SELECT tier FROM profiles WHERE id = 'user-uuid')
AND feature_id = 'ai_calls';
-- Diagnose: User-Override prüfen
SELECT * FROM user_feature_restrictions
WHERE profile_id = 'user-uuid' AND feature_id = 'ai_calls';
Problem: Coupon lässt sich nicht einlösen
-- Diagnose: Coupon gültig?
SELECT * FROM coupons WHERE code = 'COUPON-CODE';
-- Check: Bereits eingelöst?
SELECT * FROM coupon_redemptions
WHERE coupon_id = (SELECT id FROM coupons WHERE code = 'COUPON-CODE')
AND profile_id = 'user-uuid';
-- Check: Max Einlösungen erreicht?
SELECT c.max_redemptions, COUNT(cr.*) as current_redemptions
FROM coupons c
LEFT JOIN coupon_redemptions cr ON cr.coupon_id = c.id
WHERE c.code = 'COUPON-CODE'
GROUP BY c.id, c.max_redemptions;
Admin-Tools
User-Tier manuell ändern:
-- Tier setzen und locken
UPDATE profiles SET tier = 'premium', tier_locked = true WHERE id = 'user-uuid';
-- Access-Grant manuell erstellen
INSERT INTO access_grants (profile_id, granted_tier, valid_until, source, source_reference)
VALUES ('user-uuid', 'premium', '2026-12-31', 'admin_grant', 'Support-Fall #123');
Feature-Usage zurücksetzen:
-- Alle Usage für User zurücksetzen
DELETE FROM user_feature_usage WHERE profile_id = 'user-uuid';
-- Nur bestimmtes Feature zurücksetzen
DELETE FROM user_feature_usage
WHERE profile_id = 'user-uuid' AND feature_id = 'ai_calls';
Anhang
Glossar
- Tier: Subscription-Stufe (free, basic, premium, selfhosted)
- Feature: Limitierbare Funktionalität (ai_calls, data_export, etc.)
- Limit: Maximale Anzahl Nutzungen (count) oder an/aus (boolean)
- Access-Grant: Zeitlich begrenzte Tier-Berechtigung
- Coupon: Einlösbarer Code für Tier-Zugang
- Override: User-spezifische Abweichung vom Tier-Limit
- Reset-Period: Zeitraum nach dem Limit zurückgesetzt wird (never, daily, monthly)
Kontakt & Fragen
- 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