mitai-jinkendo/.claude/docs/technical/ARCHITECTURE.md
Lars 7940dc7560 docs: Struktur .claude/docs versionieren, working/, Gitea-Index, Regeln
- .gitignore: .claude/docs, rules, commands tracken; settings.local weiter ignorieren
- DOCUMENTATION.md: verbindliche Ablage functional/technical/working/issues
- .claude/README.md: Agent-Einstieg; GITEA_ISSUES_INDEX aus MCP (Stand 2026-04-08)
- Arbeitspapiere von docs/ nach .claude/docs/working/ verschoben
- docs/MEMBERSHIP_SYSTEM.md als Stub; kanonisch technical/MEMBERSHIP_SYSTEM.md
- CLAUDE.md Pflichtlektüre und Links angepasst; docs/README.md vereinfacht

Made-with: Cursor
2026-04-08 13:01:49 +02:00

19 KiB
Raw Permalink Blame History

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:

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):

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:

# 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):

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):

ALLOWED_ORIGINS: https://mitai.jinkendo.de

Development:

ALLOWED_ORIGINS: https://dev.mitai.jinkendo.de,http://localhost:3099

FastAPI Setup (main.py):

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:

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:

# ✅ 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:

_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:

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:

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:

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:

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:

{"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:

cat feature-usage.log | jq -s 'group_by(.feature) | map({feature: .[0].feature, total: length})'

2. Health Checks

Database:

healthcheck:
  test: ["CMD-SHELL", "pg_isready -U mitai_prod"]
  interval: 10s

API:

@app.get("/")
def root():
    return {"status": "ok", "service": "mitai-jinkendo", "version": "v9c-dev"}

3. Error Handling

Einheitliches Format:

raise HTTPException(status_code=404, detail="Eintrag nicht gefunden")
# → {"detail": "Eintrag nicht gefunden"}

Frontend (api.js):

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