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

635 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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