# Mitai Jinkendo – Entwickler-Kontext für Claude Code ## Projekt-Übersicht **Mitai Jinkendo** (身体 Jinkendo) ist eine selbst-gehostete PWA für Körper-Tracking (Gewicht, Körperfett, Umfänge, Ernährung, Aktivität) mit KI-Auswertung. Teil der **Jinkendo**-App-Familie (人拳道 – Der menschliche Weg der Kampfkunst). **Produktfamilie:** mitai · miken · ikigai · shinkan · kenkou (alle unter jinkendo.de) ## Tech-Stack | Komponente | Technologie | Version | |-----------|-------------|---------| | Frontend | React 18 + Vite + PWA | Node 20 | | Backend | FastAPI (Python) | Python 3.12 | | Datenbank | PostgreSQL 16 (Alpine) | v9b | | Container | Docker + Docker Compose | - | | Webserver | nginx (Reverse Proxy) | Alpine | | Auth | Token-basiert + bcrypt | - | | KI | OpenRouter API (claude-sonnet-4) | - | ## Ports | Service | Prod | Dev | |---------|------|-----| | Frontend | 3002 | 3099 | | Backend | 8002 | 8099 | ## Verzeichnisstruktur ``` mitai-jinkendo/ ├── backend/ │ ├── main.py # FastAPI App Setup + Router Registration (~75 Zeilen) │ ├── db.py # PostgreSQL Connection Pool + Helpers │ ├── auth.py # Auth Functions (hash, verify, sessions) │ ├── models.py # Pydantic Models (11 Models) │ ├── routers/ # Modular Endpoint Structure (14 Router) │ │ ├── auth.py # Login, Logout, Password Reset (7 Endpoints) │ │ ├── profiles.py # Profile CRUD + Current User (7 Endpoints) │ │ ├── weight.py # Weight Tracking (5 Endpoints) │ │ ├── circumference.py # Body Measurements (4 Endpoints) │ │ ├── caliper.py # Skinfold Tracking (4 Endpoints) │ │ ├── activity.py # Workout Logging + CSV Import (6 Endpoints) │ │ ├── nutrition.py # Nutrition + FDDB Import (4 Endpoints) │ │ ├── photos.py # Progress Photos (3 Endpoints) │ │ ├── insights.py # AI Analysis + Pipeline (8 Endpoints) │ │ ├── prompts.py # AI Prompt Management (2 Endpoints) │ │ ├── admin.py # User Management (7 Endpoints) │ │ ├── stats.py # Dashboard Stats (1 Endpoint) │ │ ├── exportdata.py # CSV/JSON/ZIP Export (3 Endpoints) │ │ └── importdata.py # ZIP Import (1 Endpoint) │ ├── requirements.txt │ └── Dockerfile ├── frontend/ │ ├── src/ │ │ ├── App.jsx # Root, Auth-Gates, Navigation │ │ ├── app.css # Globale Styles, CSS-Variablen │ │ ├── context/ │ │ │ ├── AuthContext.jsx # Session, Login, Logout │ │ │ └── ProfileContext.jsx # Aktives Profil │ │ ├── pages/ # Alle Screens │ │ └── utils/ │ │ ├── api.js # Alle API-Calls (injiziert Token automatisch) │ │ ├── calc.js # Körperfett-Formeln │ │ ├── interpret.js # Regelbasierte Auswertung │ │ ├── Markdown.jsx # Eigener MD-Renderer │ │ └── guideData.js # Messanleitungen │ └── public/ # Icons (Jinkendo Ensō-Logo) ├── .gitea/workflows/ │ ├── deploy-prod.yml # Auto-Deploy bei Push auf main │ ├── deploy-dev.yml # Auto-Deploy bei Push auf develop │ └── test.yml # Build-Test bei jedem Push ├── docker-compose.yml # Produktion (Ports 3002/8002) ├── docker-compose.dev-env.yml # Development (Ports 3099/8099) └── CLAUDE.md # Diese Datei ``` ## 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 (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 - ✅ Assistent-Modus (Schritt-für-Schritt Messung) - ✅ PWA (iPhone Home Screen), Jinkendo Ensō-Logo - ✅ E-Mail (SMTP) für Password-Recovery - ✅ Admin-Panel: User verwalten, KI-Limits, E-Mail-Test, PIN/Email setzen - ✅ Multi-Environment: Prod (mitai.jinkendo.de) + Dev (dev.mitai.jinkendo.de) - ✅ Gitea CI/CD mit Auto-Deploy auf Raspberry Pi 5 - ✅ PostgreSQL 16 Migration (vollständig von SQLite migriert) - ✅ Export: CSV, JSON, ZIP (mit Fotos) - ✅ 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 (Admin Frontend): ✅ DONE** **Phase 4 (Feature Enforcement): ⚠️ DEAKTIVIERT** (Rollback am 20.03.2026 - Bugs) **Core Features (Backend & Admin-UI komplett):** - ✅ DB-Schema (11 neue Tabellen, Feature-Registry Pattern) - ✅ 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) - ✅ Access-Grant-System (zeitlich begrenzte Zugriffe mit Quelle-Tracking) - ✅ User-Activity-Log (JSONB details) - ✅ User-Stats (Aggregierte Statistiken) - ✅ 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) **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) - ✅ Alle Routes in App.jsx registriert **⚠️ 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 **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) **📚 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 - 🔲 Einladungslink - 🔲 Passwort-Reset (bereits vorhanden) **Spätere Features (v9d/v9e):** - 🔲 Bonus-System (Login-Streaks → Punkte → Geschenk-Coupons) - 🔲 Trial-Reminder-E-Mails (3 Tage vor Ablauf) - 🔲 Monatliches Nutzungs-Summary per E-Mail - 🔲 Self-Service Upgrade (Stripe-Integration) - 🔲 Partner-Verwaltung (Wellpass, Hansefit, etc.) - 🔲 Admin-Benachrichtigungen (neue Registrierungen, etc.) ### Was in v9d kommt: - 🔲 Bonus-System & Gamification (Streaks, Achievements) - 🔲 Stripe-Integration (Self-Service Upgrade, Subscriptions) - 🔲 OAuth2-Grundgerüst für Fitness-Connectoren - 🔲 Strava Connector - 🔲 Withings Connector (Waage) - 🔲 Garmin Connector --- ## v9c Architektur-Details: Subscription & Coupon System ### Datenbank-Schema (Neue Tabellen) #### **app_settings** - Globale Konfiguration ```sql key, value, value_type, description, updated_at, updated_by Beispiele: - trial_days: 14 - trial_behavior: 'downgrade' | 'lock' - allow_registration: true/false - default_tier_trial: 'premium' - gift_coupons_per_month: 3 ``` #### **tiers** - Tier-Konfiguration (vereinfacht) ```sql id, slug, name, description, sort_order, active Initial Tiers: - free, basic, premium, selfhosted Limits sind jetzt in tier_limits Tabelle (siehe unten)! ``` #### **features** - Feature-Registry (alle limitierbaren Features) ```sql id, slug, name, category, description, unit default_limit (NULL = unbegrenzt) reset_period ('monthly' | 'daily' | 'never') visible_in_admin, sort_order, active Initial Features: - weight_entries: Gewichtseinträge, default: 30, never - circumference_entries: Umfangsmessungen, default: 30, never - caliper_entries: Caliper-Messungen, default: 30, never - nutrition_entries: Ernährungseinträge, default: 30, never - activity_entries: Aktivitäten, default: 30, never - photos: Progress-Fotos, default: 5, never - ai_calls: KI-Analysen, default: 0, monthly - ai_pipeline: KI-Pipeline, default: 0, monthly - csv_import: CSV-Importe, default: 0, monthly - data_export: Daten-Exporte, default: 0, monthly - fitness_connectors: Fitness-Connectoren, default: 0, never Neue Features einfach per INSERT hinzufügen - kein Schema-Change! ``` #### **tier_limits** - Limits pro Tier + Feature ```sql id, tier_slug, feature_slug, limit_value, enabled Beispiel Free Tier: - ('free', 'weight_entries', 30, true) - ('free', 'ai_calls', 0, false) -- KI deaktiviert - ('free', 'data_export', 0, false) Beispiel Premium: - ('premium', 'weight_entries', NULL, true) -- unbegrenzt - ('premium', 'ai_calls', NULL, true) -- unbegrenzt Admin kann in UI Matrix bearbeiten: Tier x Feature ``` #### **user_feature_restrictions** - Individuelle User-Limits ```sql id, profile_id, feature_slug, limit_value, enabled reason, set_by (admin_id) Überschreibt Tier-Limits für spezifische User. Admin kann jeden User individuell einschränken oder erweitern. ``` #### **user_feature_usage** - Nutzungs-Tracking ```sql id, profile_id, feature_slug, period_start, usage_count, last_used Für Features mit reset_period (z.B. ai_calls monthly). Wird automatisch zurückgesetzt am Monatsanfang. ``` #### **coupons** - Coupon-Verwaltung ```sql code, type ('single_use' | 'multi_use_period' | 'gift') valid_from, valid_until, grants_tier, duration_days max_redemptions, current_redemptions created_by, created_for, notes, active Beispiel Single-Use: Code: FRIEND-GIFT-XYZ, 30 Tage Premium, max 1x Beispiel Multi-Use Period: Code: WELLPASS-2026-03, gültig 01.03-31.03, unbegrenzte Einlösungen ``` #### **coupon_redemptions** - Einlösungs-Historie ```sql coupon_id, profile_id, redeemed_at, access_grant_id UNIQUE(coupon_id, profile_id) - User kann denselben Coupon nur 1x einlösen ``` #### **access_grants** - Zeitlich begrenzte Zugriffe ```sql profile_id, granted_tier, valid_from, valid_until source ('coupon' | 'admin_grant' | 'trial') active (false wenn pausiert durch Wellpass-Override) paused_at, paused_by (access_grant_id das pausiert hat) Stacking-Logik: - Multi-Use Period Coupon (Wellpass): pausiert andere grants - Single-Use Coupon: stackt zeitlich (Resume nach Ablauf) ``` #### **user_activity_log** - Aktivitäts-Tracking ```sql profile_id, activity_type, details (JSONB), ip_address, user_agent, created Activity Types: - login, password_change, email_change, coupon_redeemed - tier_change, export, ai_analysis, registration ``` #### **user_stats** - Aggregierte Statistiken ```sql profile_id, first_login, last_login, total_logins current_streak_days, longest_streak_days, last_streak_date total_weight_entries, total_ai_analyses, total_exports bonus_points (später), gift_coupons_available (später) ``` #### **profiles** - Erweiterte Spalten ```sql tier, tier_locked (Admin kann Tier festnageln) trial_ends_at, email_verified, email_verify_token invited_by, contract_type, contract_valid_until stripe_customer_id (vorbereitet für v9d) ``` --- ### Backend-Erweiterungen #### Neue Router (v9c): ``` routers/tiers.py - Tier-Verwaltung (List, Edit, Create) routers/features.py - Feature-Registry (List, Add, Edit, Delete) ⭐ NEU routers/tier_limits.py - Tier-Limits-Matrix (Admin bearbeitet Tier x Feature) ⭐ NEU routers/coupons.py - Coupon-System (Redeem, Admin CRUD) routers/access_grants.py - Zugriffs-Verwaltung (Current, Grant, Revoke) routers/user_admin.py - Erweiterte User-Verwaltung (Activity, Stats, Feature-Restrictions) routers/settings.py - App-Einstellungen (Admin) routers/registration.py - Registrierung + E-Mail-Verifizierung ``` #### Neue Middleware: ```python check_feature_access(profile_id, feature_slug, action='use') """ Zentrale Feature-Access-Prüfung. Hierarchie: 1. User-Restriction (höchste Priorität) 2. Tier-Limit 3. Feature-Default Returns: {'allowed': bool, 'limit': int, 'used': int, 'remaining': int, 'reason': str} """ increment_feature_usage(profile_id, feature_slug) """ Inkrementiert Nutzungszähler. Berücksichtigt reset_period (monthly, daily, never). """ log_activity(profile_id, activity_type, details=None) """ Loggt User-Aktivitäten in user_activity_log. """ ``` #### Hintergrund-Tasks (Cron): ```python check_expired_access() # Täglich 00:00 - Trial/Coupon-Ablauf prüfen reset_monthly_limits() # 1. jeden Monats - AI-Calls zurücksetzen update_user_streaks() # Täglich 23:59 - Login-Streaks aktualisieren ``` --- ### Zugriffs-Hierarchie ``` Effektiver Tier wird ermittelt durch (Priorität absteigend): 1. Admin-Override (tier_locked=true) → nutzt profiles.tier 2. Aktiver access_grant (nicht pausiert, valid_until > now) 3. Trial (trial_ends_at > now) 4. Base tier (profiles.tier) Wellpass-Override-Logik: - User hat Single-Use Coupon (20 Tage verbleibend) - User löst Wellpass-Coupon ein (gültig bis 31.03) - Single-Use access_grant wird pausiert (active=false, paused_by=wellpass_grant_id) - Nach Wellpass-Ablauf: Single-Use wird reaktiviert (noch 20 Tage) ``` --- ### Tier-Limits & Feature-Gates **Daten-Sichtbarkeit bei Downgrade:** - Frontend: Buttons/Features ausblenden (Export, KI, Import) - Backend: API limitiert Rückgabe (z.B. nur letzte 30 Gewichtseinträge bei free) - Daten bleiben erhalten, werden nur versteckt - Bei Upgrade wieder sichtbar **Feature-Checks:** ```python # Beispiel: Gewicht-Eintrag erstellen @check_feature_limit('weight', 'create') def create_weight_entry(): # Prüft: Hat User max_weight_entries erreicht? # Falls ja: HTTPException 403 "Limit erreicht - Upgrade erforderlich" ``` --- ### Frontend-Erweiterungen #### Neue Seiten: ``` RegisterPage.jsx - Registrierung (Name, E-Mail, Passwort) VerifyEmailPage.jsx - E-Mail-Verifizierung (Token aus URL) RedeemCouponPage.jsx - Coupon-Eingabe (oder Modal) AdminCouponsPage.jsx - Coupon-Verwaltung (Admin) AdminTiersPage.jsx - Tier-Verwaltung (CRUD) (Admin) AdminFeaturesPage.jsx - Feature-Registry (List, Add, Edit) ⭐ NEU AdminTierLimitsPage.jsx - Tier x Feature Matrix (bearbeiten) ⭐ NEU AdminUserRestrictionsPage.jsx - User-spezifische Limits (bearbeiten) ⭐ NEU AdminSettingsPage.jsx - App-Einstellungen (Admin) ``` #### Neue Komponenten: ```jsx // Tier-Anzeige mit Icon ... // Feature-basierte Sichtbarkeit ⭐ GEÄNDERT // "Trial endet in 5 Tagen" Banner // Coupon-Eingabefeld // User-Activity-Log // "5/10 verwendet" Anzeige ⭐ NEU // Matrix-Editor ⭐ NEU // Login-Streak (später) ``` #### Erweiterte Admin-Seiten: ``` AdminUsersPage.jsx erweitert um: - Activity-Log Button → zeigt user_activity_log - Stats Button → zeigt user_stats - Access-Grants Button → zeigt aktive/abgelaufene Zugriffe - Feature-Restrictions Button → individuelle Feature-Limits setzen ⭐ GEÄNDERT - Grant Access Button → manuell Tier-Zugriff gewähren - Usage-Overview → zeigt user_feature_usage für alle Features ⭐ NEU ``` #### Admin-Interface-Details: **AdminFeaturesPage.jsx** - Feature-Registry verwalten ```jsx // Alle Features auflisten + neue hinzufügen {features.map(f => ( {f.name} {f.category} {f.unit} {f.reset_period} {f.default_limit ?? '∞'} ))} ``` **AdminTierLimitsPage.jsx** - Matrix-Editor ```jsx // Matrix-View: Tiers (Spalten) x Features (Zeilen) Feature Free Basic Premium Selfhosted Gewichtseinträge KI-Analysen/Monat 0 ``` **AdminUserRestrictionsPage.jsx** - Individuelle User-Limits ```jsx {features.map(f => ( {f.name} {getTierLimit(user.tier, f.slug)} {getUsage(user.id, f.slug)} ))} ``` --- ### E-Mail Templates (v9c) **1. Registrierung + E-Mail-Verifizierung:** ``` Betreff: Willkommen bei Mitai Jinkendo - E-Mail bestätigen Hallo {name}, vielen Dank für deine Registrierung bei Mitai Jinkendo! Bitte bestätige deine E-Mail-Adresse: {app_url}/verify-email?token={token} Nach der Bestätigung startet dein 14-Tage Premium Trial automatisch. Viel Erfolg bei deinem Training! Dein Mitai Jinkendo Team ``` **2. Einladungslink (Admin):** ``` Betreff: Du wurdest zu Mitai Jinkendo eingeladen Hallo, {admin_name} hat dich zu Mitai Jinkendo eingeladen! Registriere dich jetzt: {app_url}/register?invite={token} Du erhältst {tier} Zugriff. Dein Mitai Jinkendo Team ``` --- ### Migrations-Reihenfolge (v9c) ``` Phase 1 - DB Schema: 1. app_settings Tabelle + Initialdaten 2. tiers Tabelle + 4 Standard-Tiers 3. coupons Tabelle 4. coupon_redemptions Tabelle 5. access_grants Tabelle 6. user_activity_log Tabelle 7. user_stats Tabelle 8. user_restrictions Tabelle 9. profiles Spalten erweitern 10. Bestehende Profile migrieren (Lars → tier='selfhosted', email_verified=true) Phase 2 - Backend: 11. Tier-System Router + Middleware 12. Registrierungs-Flow 13. Coupon-System 14. Access-Grant-Logik 15. Activity-Logging 16. Erweiterte Admin-Endpoints Phase 3 - Frontend: 17. Registrierungs-Seiten 18. Tier-System UI-Komponenten 19. Coupon-Eingabe 20. Erweiterte Admin-Panels 21. Feature-Gates in bestehende Seiten einbauen Phase 4 - Cron-Jobs: 22. Expired-Access-Checker 23. Monthly-Reset 24. Streak-Updater Phase 5 - Testing & Deployment: 25. Dev-Testing 26. Prod-Deployment ``` --- ## Deployment ### Infrastruktur ``` Internet → privat.stommer.com (Fritz!Box DynDNS) → Synology NAS (Reverse Proxy + Let's Encrypt) → Raspberry Pi 5 (192.168.2.49, Docker) ``` ### Git Workflow ``` develop branch → Auto-Deploy → dev.mitai.jinkendo.de (Port 3099/8099) main branch → Auto-Deploy → mitai.jinkendo.de (Port 3002/8002) ``` ### Deployment-Befehle (manuell falls nötig) ```bash # Prod cd /home/lars/docker/bodytrack docker compose -f docker-compose.yml build --no-cache docker compose -f docker-compose.yml up -d # Dev cd /home/lars/docker/bodytrack-dev docker compose -f docker-compose.dev-env.yml build --no-cache docker compose -f docker-compose.dev-env.yml up -d ``` ## Datenbank-Schema (PostgreSQL 16, v9b) ### Wichtige Tabellen: - `profiles` – Nutzer (role, pin_hash/bcrypt, email, auth_type, ai_enabled, tier) - `sessions` – Auth-Tokens mit Ablaufdatum - `weight_log` – Gewichtseinträge (profile_id, date, weight) - `circumference_log` – 8 Umfangspunkte - `caliper_log` – Hautfaltenmessung, 4 Methoden - `nutrition_log` – Kalorien + Makros (aus FDDB-CSV) - `activity_log` – Training (aus Apple Health oder manuell) - `photos` – Progress Photos - `ai_insights` – KI-Auswertungen (scope = prompt-slug) - `ai_prompts` – Konfigurierbare Prompts mit Templates (11 Prompts) - `ai_usage` – KI-Calls pro Tag pro Profil **Schema-Datei:** `backend/schema.sql` (vollständiges PostgreSQL-Schema) **Migration-Script:** `backend/migrate_to_postgres.py` (SQLite→PostgreSQL, automatisch) ## Auth-Flow (v9b) ``` Login-Screen → E-Mail + Passwort → Token im localStorage Token → X-Auth-Token Header → Backend require_auth() Profile-Id → aus Session (nicht aus Header!) SHA256 Passwörter → automatisch zu bcrypt migriert beim Login ``` ## API-Konventionen - Alle Endpoints: `/api/...` - Auth-Header: `X-Auth-Token: ` - Responses: immer JSON - Fehler: `{"detail": "Fehlermeldung"}` - Rate Limit überschritten: HTTP 429 ## Umgebungsvariablen (.env) ``` # Database (PostgreSQL) DB_HOST=postgres DB_PORT=5432 DB_NAME=mitai_prod DB_USER=mitai_prod DB_PASSWORD= # REQUIRED # AI OPENROUTER_API_KEY= # KI-Calls (optional, alternativ ANTHROPIC_API_KEY) OPENROUTER_MODEL=anthropic/claude-sonnet-4 ANTHROPIC_API_KEY= # Direkte Anthropic API (optional) # Email SMTP_HOST= # E-Mail (für Recovery) SMTP_PORT=587 SMTP_USER= SMTP_PASS= SMTP_FROM= # App APP_URL=https://mitai.jinkendo.de ALLOWED_ORIGINS=https://mitai.jinkendo.de DATA_DIR=/app/data PHOTOS_DIR=/app/photos ENVIRONMENT=production ``` ## Wichtige Hinweise für Claude Code 1. **Ports immer 3002/8002 (Prod) oder 3099/8099 (Dev)** – nie ändern 2. **npm install** (nicht npm ci) – kein package-lock.json vorhanden 3. **PostgreSQL-Migrations** – Schema-Änderungen in `backend/schema.sql`, dann Container neu bauen 4. **Pipeline-Prompts** haben slug-Prefix `pipeline_` – nie als Einzelanalyse zeigen 5. **dayjs.week()** braucht Plugin – stattdessen native JS ISO-Wochenberechnung 6. **useNavigate()** nur in React-Komponenten, nicht in Helper-Functions 7. **api.js nutzen** für alle API-Calls – injiziert Token automatisch 8. **bcrypt** für alle neuen Passwort-Operationen verwenden 9. **session=Depends(require_auth)** als separater Parameter – nie in Header() einbetten 10. **RealDictCursor verwenden** – `get_cursor(conn)` statt `conn.cursor()` für dict-like row access ## Code-Style - React: Functional Components, Hooks - CSS: Inline-Styles + globale CSS-Variablen (var(--accent), var(--text1), etc.) - API-Calls: immer über `api.js` (injiziert Token automatisch) - Kein TypeScript (bewusst, für Einfachheit) - Python: keine Type-Hints Pflicht, aber bei neuen Funktionen erwünscht ## Design-System ### Farben (CSS-Variablen) ```css --accent: #1D9E75 /* Jinkendo Grün – Buttons, Links, Akzente */ --accent-dark: #085041 /* Dunkelgrün – Icon-Hintergrund, Header */ --accent-light: #E1F5EE /* Hellgrün – Hintergründe, Badges */ --bg: /* Seitenhintergrund (hell/dunkel auto) */ --surface: /* Card-Hintergrund */ --surface2: /* Sekundäre Fläche */ --border: /* Rahmen */ --text1: /* Primärer Text */ --text2: /* Sekundärer Text */ --text3: /* Muted Text, Labels */ --danger: #D85A30 /* Fehler, Warnungen */ ``` ### CSS-Klassen ```css .card /* Weißer Container, border-radius 12px, box-shadow */ .btn /* Basis-Button */ .btn-primary /* Grüner Button (#1D9E75) */ .btn-secondary /* Grauer Button */ .btn-full /* 100% Breite */ .form-input /* Eingabefeld, volle Breite */ .form-label /* Feldbezeichnung, klein, uppercase */ .form-row /* Label + Input + Unit nebeneinander */ .form-unit /* Einheit rechts (kg, cm, etc.) */ .section-gap /* margin-bottom zwischen Sektionen */ .spinner /* Ladekreis, animiert */ ``` ### Abstände & Größen ``` Seiten-Padding: 16px seitlich Card-Padding: 16-20px Border-Radius: 12px (Cards), 8px (Buttons/Inputs), 50% (Avatare) Icon-Größe: 16-20px inline, 24px standalone Font-Größe: 12px (Labels), 14px (Body), 16-18px (Subtitel), 20-24px (Titel) Font-Weight: 400 (normal), 600 (semi-bold), 700 (bold) Bottom-Padding: 80px (für Mobile-Navigation) ``` ### Komponenten-Muster **Titelzeile einer Seite:** ```jsx
Seitentitel
``` **Ladezustand:** ```jsx if (loading) return (
) ``` **Fehlerzustand:** ```jsx if (error) return (
{error}
) ``` **Leerer Zustand:** ```jsx {items.length === 0 && (
📭
Noch keine Einträge
)} ``` **Metric Card:** ```jsx
LABEL
{value}
Einheit
``` ### Jinkendo Logo-System ``` Grundelement: Ensō-Kreis (offen, Lücke 4-5 Uhr) Farbe Ensō: #1D9E75 Hintergrund: #085041 (dunkelgrün) Kern-Symbol: #5DCAA5 (mintgrün) Wortmarke: Jin(light) + ken(bold #1D9E75) + do(light) ``` ### Verfügbare Custom Commands ``` /deploy → Commit + Push vorbereiten /merge-to-prod → develop → main mergen /test → Manuelle Tests durchführen /new-feature → Neues Feature-Template /ui-component → Neue Komponente erstellen /ui-page → Neue Seite erstellen /fix-bug → Bug analysieren und beheben /add-endpoint → Neuen API-Endpoint hinzufügen /db-add-column → Neue DB-Spalte hinzufügen ``` ## Jinkendo App-Familie & Markenarchitektur ### Philosophie **Jinkendo** (人拳道) = Jin (人 Mensch) + Ken (拳 Faust) + Do (道 Weg) "Der menschliche Weg der Kampfkunst" – ruhig aber kraftvoll, Selbstwahrnehmung, Meditation, Zielorientiert ### App-Familie (Subdomain-Architektur) ``` mitai.jinkendo.de → Körper-Tracker (身体 = eigener Körper) ← DIESE APP miken.jinkendo.de → Meditation (眉間 = drittes Auge) ikigai.jinkendo.de → Lebenssinn/Ziele (生き甲斐) shinkan.jinkendo.de → Kampfsport (真観 = wahre Wahrnehmung) kenkou.jinkendo.de → Gesundheit allgemein (健康) – für später aufsparen ``` ### Registrierte Domains - jinkendo.de, jinkendo.com, jinkendo.life – alle registriert bei Strato ## v9b Detailplan – Freemium Tier-System ### Tier-Modell ``` free → Selbst-Registrierung, 14-Tage Trial, eingeschränkt basic → Kernfunktionen (Abo Stufe 1) premium → Alles inkl. KI und Connectoren (Abo Stufe 2) selfhosted → Lars' Heimversion, keine Einschränkungen ``` ### Geplante DB-Erweiterungen (profiles Tabelle) ```sql tier TEXT DEFAULT 'free' trial_ends_at TEXT -- ISO datetime sub_valid_until TEXT -- ISO datetime email_verified INTEGER DEFAULT 0 email_verify_token TEXT invited_by TEXT -- profile_id FK invitation_token TEXT ``` ### Tier-Limits (geplant) | Feature | free | basic | premium | selfhosted | |---------|------|-------|---------|------------| | Gewicht-Einträge | 30 | unbegrenzt | unbegrenzt | unbegrenzt | | KI-Analysen/Monat | 0 | 3 | unbegrenzt | unbegrenzt | | Ernährung Import | ❌ | ✅ | ✅ | ✅ | | Export | ❌ | ✅ | ✅ | ✅ | | Fitness-Connectoren | ❌ | ❌ | ✅ | ✅ | ### Registrierungs-Flow (geplant) ``` 1. Selbst-Registrierung: Name + E-Mail + Passwort 2. Auto-Trial: tier='free', trial_ends_at=now+14d 3. E-Mail-Bestätigung → email_verified=1 4. Trial läuft ab → Upgrade-Prompt 5. Einladungslinks: Admin generiert Token → direkt basic-Tier 6. Stripe Integration: später (v9b ohne Stripe, nur Tier-Logik) ``` ## Infrastruktur Details ### Heimnetzwerk ``` Internet → Fritz!Box 7530 AX (DynDNS: privat.stommer.com) → Synology NAS (192.168.2.63, Reverse Proxy + Let's Encrypt) → Raspberry Pi 5 (192.168.2.49, Docker) → MiniPC (192.168.2.144, Gitea auf Port 3000) ``` ### Synology Reverse Proxy Regeln ``` mitai.jinkendo.de → HTTP 192.168.2.49:3002 (Prod Frontend) dev.mitai.jinkendo.de → HTTP 192.168.2.49:3099 (Dev Frontend) ``` ### AdGuard DNS Rewrites (für internes Routing) ``` mitai.jinkendo.de → 192.168.2.63 dev.mitai.jinkendo.de → 192.168.2.63 ``` ### Fritz!Box DNS-Rebind Ausnahmen ``` jinkendo.de mitai.jinkendo.de ``` ### Pi Verzeichnisstruktur ``` /home/lars/docker/ ├── bodytrack/ → Prod (main branch, docker-compose.yml) └── bodytrack-dev/ → Dev (develop branch, docker-compose.dev-env.yml) ``` ### Gitea Runner ``` Runner: raspberry-pi (auf Pi installiert) Service: /etc/systemd/system/gitea-runner.service Binary: /home/lars/gitea-runner/act_runner ``` ### Container Namen ``` Prod: mitai-api, mitai-ui Dev: dev-mitai-api, dev-mitai-ui ``` ## Bekannte Probleme & Lösungen ### Admin User-Erstellung – Email fehlt (v9c TODO) **Problem:** Bei Admin-CRUD `/api/profiles` (POST) wird keine Email-Adresse abgefragt. **Impact:** Neue User können sich nicht einloggen (Login erfordert Email). **Workaround:** Email manuell via `/api/admin/profiles/{pid}/email` setzen. **Fix-TODO:** POST `/api/profiles` sollte Email als optionales Feld akzeptieren. ### AdminUserRestrictionsPage – Effektive Werte anzeigen (v9c GELÖST) **Problem:** Ursprüngliches Design zeigte leere Felder wenn kein Override existierte. **Issues:** - User konnte nicht sehen welcher Wert aktuell gilt (Override vs Tier-Standard) - "unlimited" konnte nicht eingegeben/gespeichert werden (nur Platzhalter) - Redundante Overrides (Wert = Tier-Standard) wurden nicht verhindert - Tier-Limits verwendeten falschen Fallback (default_limit statt null) **Lösung (März 2026):** ```javascript // getDisplayValue() zeigt effektiven Wert (Override ODER Tier-Limit) const restriction = restrictions.find(r => r.feature_id === featureId) if (restriction) return formatValue(restriction.limit_value) return formatValue(tierLimits[featureId]) // Tier-Standard als Fallback // formatValue() konvertiert NULL zu "unlimited" (statt leer) if (val === null) return 'unlimited' // handleChange() entfernt Override wenn Wert = Tier-Standard if (parsedValue === tierLimit) { newChanges[featureId] = { action: 'remove' } // Redundanter Override } // Tier-Limits-Fallback wie TierLimitsPage limits[feature.id] = limitsMatrix.limits[key] ?? null // nicht default_limit! ``` **Ergebnis:** - ✅ User sieht immer den aktuellen effektiven Wert - ✅ "unlimited" kann getippt und gespeichert werden (grün gefärbt) - ✅ Redundante Overrides werden automatisch entfernt - ✅ Selfhosted-Tier zeigt korrekt "unlimited" statt "0" ### dayjs.week() – NIEMALS verwenden ```javascript // ❌ Falsch: const week = dayjs(date).week() // ✅ Richtig (ISO 8601): const weekNum = (() => { const dt = new Date(date) dt.setHours(0,0,0,0) dt.setDate(dt.getDate()+4-(dt.getDay()||7)) const y = new Date(dt.getFullYear(),0,1) return Math.ceil(((dt-y)/86400000+1)/7) })() ``` ### session=Depends(require_auth) – Korrekte Platzierung ```python # ❌ Falsch (führt zu NameError oder ungeschütztem Endpoint): def endpoint(x_profile_id: Optional[str] = Header(default=None, session=Depends(require_auth))): # ✅ Richtig (separater Parameter): def endpoint(x_profile_id: Optional[str] = Header(default=None), session: dict = Depends(require_auth)): ``` ### Recharts Bar fill=function – nicht unterstützt ```jsx // ❌ Falsch: entry.color}/> // ✅ Richtig: ``` ### PostgreSQL Boolean-Syntax ```python # ❌ Falsch (SQLite-Syntax): cur.execute("SELECT * FROM ai_prompts WHERE active=1") # ✅ Richtig (PostgreSQL): cur.execute("SELECT * FROM ai_prompts WHERE active=true") ``` ### RealDictCursor für dict-like row access ```python # ❌ Falsch: cur = conn.cursor() cur.execute("SELECT COUNT(*) FROM weight_log") count = cur.fetchone()[0] # Tuple index # ✅ Richtig: cur = get_cursor(conn) # Returns RealDictCursor cur.execute("SELECT COUNT(*) as count FROM weight_log") count = cur.fetchone()['count'] # Dict key ``` ## v9b Migration – Lessons Learned ### PostgreSQL Migration (SQLite → PostgreSQL) **Problem:** Docker Build hing 30+ Minuten bei `apt-get install postgresql-client` **Lösung:** Alle apt-get dependencies entfernt, reine Python-Lösung mit psycopg2-binary **Problem:** Leere date-Strings (`''`) führten zu PostgreSQL-Fehlern **Lösung:** Migration-Script konvertiert leere Strings zu NULL für DATE-Spalten **Problem:** Boolean-Felder (SQLite INTEGER 0/1 vs PostgreSQL BOOLEAN) **Lösung:** Migration konvertiert automatisch, Backend nutzt `active=true` statt `active=1` ### API Endpoint Consistency (März 2026) **Problem:** 11 kritische Endpoint-Mismatches zwischen Frontend und Backend gefunden **Gelöst:** - AI-Endpoints konsistent: `/api/insights/run/{slug}`, `/api/insights/pipeline` - Password-Reset: `/api/auth/forgot-password`, `/api/auth/reset-password` - Admin-Endpoints: `/permissions`, `/email`, `/pin` Sub-Routes - Export: JSON + ZIP Endpoints hinzugefügt - Prompt-Bearbeitung: PUT-Endpoint für Admins **Tool:** Vollständiger Audit via Explore-Agent empfohlen bei größeren Änderungen ## Export/Import Spezifikation (v9c) ### ZIP-Export Struktur ``` mitai-export-{name}-{YYYY-MM-DD}.zip ├── README.txt ← Erklärung des Formats + Versionsnummer ├── profile.json ← Profildaten (ohne Passwort-Hash) ├── data/ │ ├── weight.csv ← Gewichtsverlauf │ ├── circumferences.csv ← Umfänge (8 Messpunkte) │ ├── caliper.csv ← Caliper-Messungen │ ├── nutrition.csv ← Ernährungsdaten │ └── activity.csv ← Aktivitäten ├── insights/ │ └── ai_insights.json ← KI-Auswertungen (alle gespeicherten) └── photos/ ├── {date}_{id}.jpg ← Progress-Fotos └── ... ``` ### CSV Format (alle Dateien) ``` - Trennzeichen: Semikolon (;) – Excel/LibreOffice kompatibel - Encoding: UTF-8 mit BOM (für Windows Excel) - Datumsformat: YYYY-MM-DD - Dezimaltrennzeichen: Punkt (.) - Erste Zeile: Header - Nullwerte: leer (nicht "null" oder "NULL") ``` ### weight.csv Spalten ``` id;date;weight;note;source;created ``` ### circumferences.csv Spalten ``` id;date;waist;hip;chest;neck;upper_arm;thigh;calf;forearm;note;created ``` ### caliper.csv Spalten ``` id;date;chest;abdomen;thigh;tricep;subscapular;suprailiac;midaxillary;method;bf_percent;note;created ``` ### nutrition.csv Spalten ``` id;date;meal_name;kcal;protein;fat;carbs;fiber;note;source;created ``` ### activity.csv Spalten ``` id;date;name;type;duration_min;kcal;heart_rate_avg;heart_rate_max;distance_km;note;source;created ``` ### profile.json Struktur ```json { "export_version": "2", "export_date": "2026-03-18", "app": "Mitai Jinkendo", "profile": { "name": "Lars", "email": "lars@stommer.com", "sex": "m", "height": 178, "birth_year": 1980, "goal_weight": 82, "goal_bf_pct": 14, "avatar_color": "#1D9E75", "auth_type": "password", "session_days": 30, "ai_enabled": true, "tier": "selfhosted" }, "stats": { "weight_entries": 150, "nutrition_entries": 300, "activity_entries": 45, "photos": 12 } } ``` ### ai_insights.json Struktur ```json [ { "id": "uuid", "scope": "gesamt", "created": "2026-03-18T10:00:00", "result": "KI-Analyse Text..." } ] ``` ### README.txt Inhalt ``` Mitai Jinkendo – Datenexport Version: 2 Exportiert am: YYYY-MM-DD Profil: {name} Inhalt: - profile.json: Profildaten und Einstellungen - data/*.csv: Messdaten (Semikolon-getrennt, UTF-8) - insights/: KI-Auswertungen (JSON) - photos/: Progress-Fotos (JPEG) Import: Dieser Export kann in Mitai Jinkendo unter Einstellungen → Import → "Mitai Backup importieren" wieder eingespielt werden. Format-Version 2 (ab v9b): Alle CSV-Dateien sind UTF-8 mit BOM kodiert. Trennzeichen: Semikolon (;) Datumsformat: YYYY-MM-DD ``` ### Import-Funktion (zu implementieren) **Endpoint:** `POST /api/import/zip` **Verhalten:** - Akzeptiert ZIP-Datei (multipart/form-data) - Erkennt export_version aus profile.json - Importiert nur fehlende Einträge (kein Duplikat) - Fotos werden nicht überschrieben falls bereits vorhanden - Gibt Zusammenfassung zurück: wie viele Einträge je Kategorie importiert - Bei Fehler: vollständiger Rollback (alle oder nichts) **Duplikat-Erkennung:** ```python # INSERT ... ON CONFLICT (profile_id, date) DO NOTHING # weight: UNIQUE (profile_id, date) # nutrition: UNIQUE (profile_id, date, meal_name) # activity: UNIQUE (profile_id, date, name) # caliper: UNIQUE (profile_id, date) # circumferences: UNIQUE (profile_id, date) ``` **Frontend:** Neuer Button in SettingsPage: ``` [ZIP exportieren] [JSON exportieren] [Backup importieren] ```