635 lines
19 KiB
Markdown
635 lines
19 KiB
Markdown
|
|
# 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
|