# Architektur-Übersicht – Mitai Jinkendo ## System-Überblick **Mitai Jinkendo** ist eine selbst-gehostete Progressive Web App (PWA) für Körper-Tracking mit KI-gestützter Auswertung. Die Architektur folgt einem klassischen **3-Tier-Modell** mit klarer Trennung von Präsentation, Business-Logik und Datenhaltung. ``` Internet ↓ Fritz!Box (privat.stommer.com) ↓ Synology NAS ↓ Raspberry Pi 5 (192.168.2.49) ↓ ┌────────────────────────────────────┐ │ Docker Compose Environment │ ├────────────────────────────────────┤ │ Frontend (React + Vite) │ Port 3002 (Prod) / 3099 (Dev) │ Backend (FastAPI + Python) │ Port 8002 (Prod) / 8099 (Dev) │ Database (PostgreSQL 16 Alpine) │ Port 5432 (internal) └────────────────────────────────────┘ ``` --- ## Tech-Stack | Komponente | Technologie | Version | Zweck | |-----------|-------------|---------|-------| | **Frontend** | React | 18 | UI Framework | | | Vite | Latest | Build Tool + Dev Server | | | React Router | 6 | Client-side Routing | | | Lucide React | Latest | Icon Library | | **Backend** | FastAPI | Latest | REST API Framework | | | Python | 3.12 | Programmiersprache | | | psycopg2-binary | Latest | PostgreSQL Driver | | | bcrypt | Latest | Passwort-Hashing | | | slowapi | Latest | Rate Limiting | | **Database** | PostgreSQL | 16 Alpine | Primäre Datenbank | | **Container** | Docker | Latest | Container Runtime | | | Docker Compose | v2 | Multi-Container Orchestration | | **KI** | OpenRouter API | - | Claude Sonnet 4 | | | Anthropic API | - | Direkt-Integration (optional) | | **Email** | SMTP | - | E-Mail-Versand (Verifikation, Reset) | | **Infrastruktur** | Raspberry Pi 5 | - | Host-System | | | Gitea | 3000 | Git Server + CI/CD | | | Synology NAS | - | Reverse Proxy | --- ## Deployment-Architektur ### Umgebungen | Umgebung | Domain | Branch | Ports | Deployment | |----------|--------|--------|-------|-----------| | **Production** | mitai.jinkendo.de | `main` | 3002/8002 | Auto-Deploy via Gitea | | **Development** | dev.mitai.jinkendo.de | `develop` | 3099/8099 | Auto-Deploy via Gitea | ### Git-Workflow ``` develop → Push → Gitea Webhook → Runner → docker-compose.dev-env.yml → dev.mitai.jinkendo.de main → Push → Gitea Webhook → Runner → docker-compose.yml → mitai.jinkendo.de ``` **Runner-Details:** - Läuft auf Raspberry Pi 5 (`/home/lars/gitea-runner/`) - Watchtower für automatische Updates - Docker Compose Build + Restart bei neuen Commits ### Verzeichnisstruktur auf dem Server ``` /home/lars/docker/ ├── bodytrack/ # Production │ ├── docker-compose.yml │ └── .env └── bodytrack-dev/ # Development ├── docker-compose.dev-env.yml └── .env ``` **Externe Volumes:** - `bodytrack_bodytrack-data` → `/app/data` (Backend JSON-Daten, Legacy) - `bodytrack_bodytrack-photos` → `/app/photos` (Progress-Fotos) - `mitai_postgres_data` → PostgreSQL Datenbank --- ## Komponenten-Übersicht ### Backend-Module (26 Router) | Router | Endpunkte | Beschreibung | |--------|-----------|--------------| | **auth** | `/api/auth/*` | Login, Register, Verify, Password Reset | | **profiles** | `/api/profiles/*`, `/api/profile` | Nutzerverwaltung | | **weight** | `/api/weight/*` | Gewichts-Tracking | | **circumference** | `/api/circumferences/*` | Umfangsmessungen (8 Punkte) | | **caliper** | `/api/caliper/*` | Hautfaltenmessungen (4 Methoden) | | **activity** | `/api/activity/*` | Training & Aktivitäten | | **nutrition** | `/api/nutrition/*` | Ernährungsdaten (Kalorien + Makros) | | **photos** | `/api/photos/*` | Progress-Fotos | | **insights** | `/api/insights/*`, `/api/ai/*` | KI-Auswertungen | | **prompts** | `/api/prompts/*` | Konfigurierbare KI-Prompts | | **admin** | `/api/admin/*` | Admin-Panel (Profile, Permissions) | | **stats** | `/api/stats` | Statistiken für Dashboard | | **exportdata** | `/api/export/*` | CSV/JSON/ZIP Export | | **importdata** | `/api/import/*` | CSV Import | | **subscription** | `/api/subscription/*` | Abo-Status (v9c) | | **coupons** | `/api/coupons/*` | Coupon-System (v9c) | | **features** | `/api/features/*` | Feature-Verwaltung (v9c) | | **tiers_mgmt** | `/api/tiers/*` | Tier-Verwaltung (v9c) | | **tier_limits** | `/api/tier-limits/*` | Tier-Limits Matrix (v9c) | | **user_restrictions** | `/api/user-restrictions/*` | User-spezifische Limits (v9c) | | **access_grants** | `/api/access-grants/*` | Zeitlich begrenzte Zugriffe (v9c) | | **training_types** | `/api/training-types/*` | Trainingstypen (v9d) | | **admin_training_types** | `/api/admin/training-types/*` | Trainingstypen-Admin (v9d) | | **admin_activity_mappings** | `/api/admin/activity-mappings/*` | Activity Mapping Admin (v9d) | | **sleep** | `/api/sleep/*` | Schlaf-Modul (v9d Phase 2b) | | **rest_days** | `/api/rest-days/*` | Ruhetage (v9d Phase 2a) | | **vitals_baseline** | `/api/vitals/baseline/*` | Morgenmessungen (RHR, HRV, VO2 Max) | | **blood_pressure** | `/api/blood-pressure/*` | Blutdruck (mehrfach täglich) | | **evaluation** | `/api/evaluation/*` | Training Type Profiling (v9d Phase 2 #15) | **Registrierung in `main.py`:** ```python app.include_router(auth.router) app.include_router(profiles.router) # ... alle 26 Router ``` ### Frontend-Struktur ``` frontend/src/ ├── App.jsx # Root-Komponente + Routing ├── app.css # Globales Design-System ├── context/ │ ├── AuthContext.jsx # Session-Management │ └── ProfileContext.jsx # Aktives Profil ├── pages/ # 30+ Seiten │ ├── LoginScreen.jsx # Login + Auto-Migration SHA256→bcrypt │ ├── Register.jsx # Selbst-Registrierung (v9c) │ ├── Verify.jsx # E-Mail-Verifizierung │ ├── Dashboard.jsx # Übersicht mit Stats + Charts │ ├── CaptureHub.jsx # Quick-Entry-Auswahl │ ├── WeightScreen.jsx # Gewichts-Erfassung │ ├── CircumScreen.jsx # Umfänge │ ├── CaliperScreen.jsx # Caliper │ ├── ActivityPage.jsx # Training (mit Trainingstypen v9d) │ ├── NutritionPage.jsx # Ernährung (3-Tab: Entry/Import/Charts) │ ├── SleepPage.jsx # Schlaf (v9d Phase 2b) │ ├── RestDaysPage.jsx # Ruhetage (v9d Phase 2a) │ ├── VitalsPage.jsx # Vitalwerte (3-Tab: Baseline/BP/Import) │ ├── History.jsx # Verlauf-Charts │ ├── Analysis.jsx # KI-Analyse + Pipeline │ ├── SettingsPage.jsx # Einstellungen │ ├── SubscriptionPage.jsx # Membership-UI (v9c) │ └── Admin*.jsx # 9 Admin-Seiten └── utils/ ├── api.js # ALLE API-Calls (285 Zeilen) ├── calc.js # Body-Fat-Berechnungen ├── interpret.js # Daten-Interpretation ├── Markdown.jsx # Markdown-Renderer └── guideData.js # Guide-Inhalte ``` --- ## Datenfluss ### 1. User Request → Auth → API → Database ``` User Action (Frontend) ↓ api.js (Token injiziert via hdrs()) ↓ FastAPI Endpoint ↓ require_auth() Dependency (auth.py) ↓ (validiert X-Auth-Token → session) ↓ get_session() → SELECT FROM sessions JOIN profiles ↓ (returns session dict mit profile_id, role, ai_enabled, ...) ↓ Router-Funktion ↓ db.py → get_db() Context Manager ↓ psycopg2 → PostgreSQL Query (RealDictCursor) ↓ Response (JSON) ``` ### 2. Feature Access Control (v9c) ``` Endpoint (z.B. POST /api/insights/run) ↓ check_feature_access(profile_id, 'ai_calls') ↓ Prüf-Hierarchie: 1. user_feature_restrictions (user-spezifisch) 2. tier_limits (Tier-basiert) 3. features.default_limit (Fallback) ↓ get_effective_tier(profile_id) → access_grants (Coupon/Trial) ODER profiles.tier ↓ user_feature_usage (aktueller Zähler) ↓ Ergebnis: {allowed: bool, limit: int, used: int, remaining: int, reason: str} ↓ Falls allowed == false → HTTP 403 Falls allowed == true → increment_feature_usage() + execute ``` ### 3. KI-Pipeline (3-stufig) ``` User: "Analyse starten" ↓ POST /api/insights/pipeline ↓ check_feature_access('ai_pipeline') ↓ Stufe 1: Gewicht-Trend → claude-sonnet-4 (OpenRouter) Stufe 2: Ernährung → claude-sonnet-4 Stufe 3: Gesamtanalyse → claude-sonnet-4 (mit Ergebnissen aus 1+2) ↓ Jeder Call: increment_feature_usage('ai_calls') ↓ INSERT INTO ai_insights (scope='pipeline', content=...) ↓ Return: {content: "...", usage: {...}} ``` --- ## Sicherheitsarchitektur ### 1. Passwort-Sicherheit **Hash-Verfahren:** - **Aktuell:** bcrypt (Salting + Work Factor) - **Legacy:** SHA256 (wird beim Login automatisch zu bcrypt migriert) **Passwort-Hashing (`auth.py`):** ```python def hash_pin(pin: str) -> str: return bcrypt.hashpw(pin.encode(), bcrypt.gensalt()).decode() def verify_pin(pin: str, stored_hash: str) -> bool: if stored_hash.startswith('$2'): # bcrypt return bcrypt.checkpw(pin.encode(), stored_hash.encode()) # Legacy SHA256 → auto-upgrade beim nächsten Login return stored_hash == hashlib.sha256(pin.encode()).hexdigest() ``` ### 2. Session-Management **Token-Format:** - `secrets.token_urlsafe(32)` → 43 Zeichen Base64-URL-safe - Gespeichert in `sessions` Tabelle mit `expires_at` - Standard-Lebensdauer: 30 Tage (konfigurierbar pro Profil) **Auth-Flow:** ```python # Backend @router.post("/api/auth/login") def login(email, password): profile = SELECT ... WHERE email=... verify_pin(password, profile.pin_hash) # ✓ token = make_token() INSERT INTO sessions (token, profile_id, expires_at) return {"token": token, "profile": {...}} # Frontend (AuthContext.jsx) localStorage.setItem('mitai-jinkendo_token', token) ``` **Auth-Middleware (`auth.py`):** ```python def require_auth(x_auth_token: Optional[str] = Header(default=None)): session = get_session(x_auth_token) if not session: raise HTTPException(401, "Nicht eingeloggt") return session # dict mit profile_id, role, ai_enabled, ... ``` ### 3. CORS-Konfiguration **Produktion (`docker-compose.yml`):** ```yaml ALLOWED_ORIGINS: https://mitai.jinkendo.de ``` **Development:** ```yaml ALLOWED_ORIGINS: https://dev.mitai.jinkendo.de,http://localhost:3099 ``` **FastAPI Setup (`main.py`):** ```python app.add_middleware( CORSMiddleware, allow_origins=os.getenv("ALLOWED_ORIGINS", "*").split(","), allow_credentials=True, allow_methods=["GET","POST","PUT","DELETE","OPTIONS"], allow_headers=["*"], ) ``` ### 4. Rate Limiting **Library:** slowapi (Redis-freie In-Memory Rate Limiting) **Anwendung:** ```python from slowapi import Limiter from slowapi.util import get_remote_address limiter = Limiter(key_func=get_remote_address) app.state.limiter = limiter @router.post("/auth/login") @limiter.limit("5/minute") # Max 5 Login-Versuche pro Minute def login(request: Request, ...): ... ``` **Geschützte Endpoints:** - `/api/auth/login` → 5/minute - `/api/auth/register` → 3/minute - KI-Endpoints → via Feature-Limits (nicht Rate Limiting) ### 5. SQL Injection Protection **Parameter-Binding:** ```python # ✅ Sicher (psycopg2 escaped automatisch): cur.execute("SELECT * FROM profiles WHERE id = %s", (profile_id,)) # ❌ NIEMALS: cur.execute(f"SELECT * FROM profiles WHERE id = '{profile_id}'") ``` **RealDictCursor:** - Automatisches Escaping - Keine String-Konkatenation --- ## Versions-Historie ### v9c (Komplett) – Production seit 21.03.2026 ✅ **Features:** - Membership-System (5 Tiers: free/basic/premium/selfhosted) - Coupon-System + Trial (14 Tage) - Feature-Enforcement (4 Phasen): - Phase 1: Cleanup ✅ - Phase 2: Monitoring ✅ - Phase 3: Frontend Display ✅ - Phase 4: Enforcement (HTTP 403) ✅ - Selbst-Registrierung + E-Mail-Verifizierung - Export: CSV/JSON/ZIP - Ernährungs-Modul erweitert (3-Tab Layout) ### v9d – Phase 1b ✅ **Trainingstypen-System:** - 29 Trainingstypen in 7 Kategorien - Lernendes Mapping-System (DB-basiert) - Apple Health Import (Deutsch + English) - Bulk-Kategorisierung (selbstlernend) ### v9d – Phase 2 ✅ (Deployed 23.03.2026) **Vitalwerte & Erholung:** - **Schlaf-Modul (v9d Phase 2b):** - Tabelle `sleep_log` mit JSONB sleep_segments - Schlafphasen (Deep, REM, Light, Awake) - Apple Health CSV Import - **Ruhetage (v9d Phase 2a):** - Multi-dimensionale Ruhetage (Kraft/Cardio/Entspannung) - Quick Mode Presets + Custom Entry - **Vitalwerte erweitert (v9d Phase 2d):** - **3-Tab Architektur:** Baseline (morgens) / Blutdruck (mehrfach täglich) / Import - **Baseline Vitals:** Ruhepuls, HRV, VO2 Max, SpO2, Atemfrequenz - **Blutdruck:** Systolisch/Diastolisch + Puls, WHO/ISH-Klassifizierung - **Context-Tagging:** 8 Kontexte (nüchtern, nach Essen, Training, Stress, etc.) - **Inline-Editing:** Alle Messungen direkt in der Liste bearbeitbar - CSV Import: Omron (Deutsch) + Apple Health (Deutsch/Englisch) ### v9b (PostgreSQL-Migration) - Migration SQLite → PostgreSQL 16 - Connection Pooling (psycopg2) - UUID statt Integer Primary Keys - Trigger für auto-update timestamps ### v9a (Basis) - Login + Auth-Middleware - Gewicht, Umfänge, Caliper, Ernährung, Aktivität - KI-Analyse (6 Prompts + 3-stufige Pipeline) - PWA + Dashboard - Admin-Panel --- ## Design-Entscheidungen ### Warum PostgreSQL statt SQLite? **Vorteile:** - Concurrent Writes (SQLite locked bei Writes) - Native UUID Support - JSONB für flexible Datenstrukturen (z.B. sleep_segments) - Trigger + Stored Procedures - Production-ready für Self-Hosting **Migration:** - Automatisches Migrations-System (`db_init.py`) - Rollback-fähig via SQL-Dateien - Tracking in `schema_migrations` Tabelle ### Warum FastAPI statt Flask/Django? **Vorteile:** - Async Support (für zukünftige WebSocket-Features) - Automatic OpenAPI Docs - Type Hints → automatische Validierung - Dependency Injection (z.B. `require_auth`) - Performance (ASGI statt WSGI) ### Warum React statt Vue/Svelte? **Entscheidung:** - Bekanntes Ecosystem - Context API ausreichend (kein Redux nötig) - Gute PWA-Integration - React Router maturity ### Warum kein TypeScript? **Entscheidung (bewusst):** - Schnellere Prototyping-Geschwindigkeit - Weniger Build-Komplexität - Dokumentation via JSDoc-Kommentare - Type Safety durch Backend (Pydantic) ### Warum Connection Pooling? **Problem ohne Pooling:** - Jeder API-Call → neue DB-Verbindung → Overhead - PostgreSQL max_connections erreicht bei vielen gleichzeitigen Requests **Lösung:** ```python _pool = psycopg2.pool.SimpleConnectionPool(minconn=1, maxconn=10) @contextmanager def get_db(): conn = _pool.getconn() try: yield conn conn.commit() except: conn.rollback() raise finally: _pool.putconn(conn) ``` **Vorteil:** - Max 10 gleichzeitige Verbindungen - Automatisches Recycling - Auto-Commit/Rollback --- ## Bekannte Limitationen & Workarounds ### 1. Docker Build auf Raspberry Pi **Problem:** `apt-get install postgresql-client` hängt 30+ Minuten **Lösung:** Reine Python-Lösung mit `psycopg2-binary` (keine System-Dependencies) ### 2. Apple Health CSV-Import **Problem:** Dezimalwerte werden als String exportiert (`"65.0"` statt `65`) **Lösung:** ```python def safe_float(val): try: return float(val) if val else None except: return None ``` ### 3. Bun Crash nach langen Claude Code Sessions **Problem:** Bun (JS Runtime für Claude Code CLI) crashed bei >30 Min Sessions **Workaround:** Bei komplexen Tasks früher committen + neue Session starten ### 4. dayjs.week() ohne Plugin **Problem:** `dayjs().week()` existiert nicht ohne `isoWeek`-Plugin **Lösung:** Native ISO-Wochenberechnung: ```javascript const isoWeek = (d => Math.ceil(((new Date(d.setDate(d.getDate()+4-(d.getDay()||7)))- new Date(d.getFullYear(),0,1))/86400000+1)/7))(new Date(date)) ``` --- ## Performance-Optimierungen ### 1. Frontend - **Code Splitting:** React.lazy() für Admin-Seiten (nicht initial geladen) - **Image Loading:** `loading="lazy"` für Photos - **CSS:** Inline Critical CSS, kein CSS-in-JS Overhead - **PWA Cache:** Service Worker cached statische Assets ### 2. Backend - **Connection Pooling:** Max 10 Connections statt unlimited - **Query Optimization:** Indizes auf häufig abgefragte Spalten - **Lazy Loading:** KI-Analysen nur auf Abruf, nicht im Dashboard ### 3. Database **Indizes:** ```sql CREATE INDEX idx_weight_log_profile_date ON weight_log(profile_id, date DESC); CREATE INDEX idx_sessions_expires_at ON sessions(expires_at); CREATE INDEX idx_ai_insights_profile_scope ON ai_insights(profile_id, scope, created DESC); ``` **Unique Constraints:** ```sql CREATE UNIQUE INDEX idx_weight_log_profile_date_unique ON weight_log(profile_id, date); ``` --- ## Monitoring & Logging ### 1. Feature Usage Logging (v9c Phase 2) **Format:** JSON Lines (JSONL) **Speicherort:** `/app/logs/feature-usage.log` **Beispiel:** ```json {"timestamp":"2026-03-23T10:15:30Z","profile_id":"uuid-here","feature":"ai_calls","allowed":true,"limit":10,"used":3,"remaining":7} {"timestamp":"2026-03-23T10:16:00Z","profile_id":"uuid-here","feature":"data_export","allowed":false,"limit":0,"used":0,"remaining":0,"reason":"limit_exceeded"} ``` **Auswertung:** ```bash cat feature-usage.log | jq -s 'group_by(.feature) | map({feature: .[0].feature, total: length})' ``` ### 2. Health Checks **Database:** ```yaml healthcheck: test: ["CMD-SHELL", "pg_isready -U mitai_prod"] interval: 10s ``` **API:** ```python @app.get("/") def root(): return {"status": "ok", "service": "mitai-jinkendo", "version": "v9c-dev"} ``` ### 3. Error Handling **Einheitliches Format:** ```python raise HTTPException(status_code=404, detail="Eintrag nicht gefunden") # → {"detail": "Eintrag nicht gefunden"} ``` **Frontend (`api.js`):** ```javascript if (!res.ok) { const err = await res.text() try { const parsed = JSON.parse(err) throw new Error(parsed.detail || err) } catch { throw new Error(err) } } ``` --- ## Zusammenfassung **Architektur-Highlights:** - ✅ Modulare Router-Struktur (26 Module, single responsibility) - ✅ Connection Pooling (max 10 concurrent DB connections) - ✅ Token-basiertes Auth + bcrypt - ✅ Feature Access Control mit Tier-System - ✅ Docker Compose für einfaches Deployment - ✅ Auto-Deploy via Gitea Webhooks - ✅ PostgreSQL mit Migrations-System - ✅ PWA mit Service Worker - ✅ API-First Prinzip (Backend = Single Source of Truth) **Nächste Schritte (v9e+):** - Ziele-Modul (Goal Tracking) - HF-Zonen + Erholungsstatus - Stripe-Integration - Connectoren (Garmin, Withings, etc.) - Meditation + Selbstwahrnehmung