mitai-jinkendo/CLAUDE.md
Lars 0a871fea22
Some checks failed
Deploy Development / deploy (push) Failing after 1s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 1m6s
9b
2026-03-18 08:27:33 +01:00

447 lines
14 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.

# 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 | SQLite (v9a) → PostgreSQL (v9b geplant) | - |
| 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, alle Endpoints (~2000 Zeilen)
│ ├── 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: v9a
### Was implementiert ist:
- ✅ Multi-User mit E-Mail + Passwort Login (bcrypt)
- ✅ Auth-Middleware auf ALLE Endpoints (44 Endpoints geschützt)
- ✅ Rate Limiting (Login: 5/min, Reset: 3/min)
- ✅ CORS konfigurierbar via ALLOWED_ORIGINS in .env
- ✅ Admin/User Rollen, KI-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)
- ✅ Konfigurierbare Prompts mit Template-Variablen
- ✅ 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
- ✅ Multi-Environment: Prod (mitai.jinkendo.de) + Dev (dev.mitai.jinkendo.de)
- ✅ Gitea CI/CD mit Auto-Deploy auf Raspberry Pi 5
### Was in v9b kommt:
- 🔲 PostgreSQL Migration (aktuell noch SQLite)
- 🔲 Selbst-Registrierung mit E-Mail-Bestätigung
- 🔲 Freemium Tier-System (free/basic/premium/selfhosted)
- 🔲 14-Tage Trial automatisch
- 🔲 Einladungslinks für Beta-Nutzer
- 🔲 Admin kann Tiers manuell setzen
### Was in v9c kommt:
- 🔲 OAuth2-Grundgerüst für Fitness-Connectoren
- 🔲 Strava Connector
- 🔲 Withings Connector (Waage)
- 🔲 Garmin Connector
## 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 (SQLite, v9a)
### Wichtige Tabellen:
- `profiles` Nutzer (role, pin_hash/bcrypt, email, auth_type, ai_enabled)
- `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)
- `ai_insights` KI-Auswertungen (scope = prompt-slug)
- `ai_prompts` Konfigurierbare Prompts mit Templates (11 Prompts)
- `ai_usage` KI-Calls pro Tag pro Profil
## Auth-Flow (v9a)
```
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: <token>`
- Responses: immer JSON
- Fehler: `{"detail": "Fehlermeldung"}`
- Rate Limit überschritten: HTTP 429
## Umgebungsvariablen (.env)
```
OPENROUTER_API_KEY= # KI-Calls
OPENROUTER_MODEL=anthropic/claude-sonnet-4
SMTP_HOST= # E-Mail
SMTP_PORT=587
SMTP_USER=
SMTP_PASS=
SMTP_FROM=
APP_URL=https://mitai.jinkendo.de
ALLOWED_ORIGINS=https://mitai.jinkendo.de
DATA_DIR=/app/data
PHOTOS_DIR=/app/photos
```
## 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. **SQLite safe_alters** neue Spalten immer via safe_alters Liste
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
## 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
<div style={{display:'flex',alignItems:'center',
justifyContent:'space-between',marginBottom:20}}>
<div style={{fontSize:20,fontWeight:700,color:'var(--text1)'}}>
Seitentitel
</div>
<button className="btn btn-primary">Aktion</button>
</div>
```
**Ladezustand:**
```jsx
if (loading) return (
<div style={{display:'flex',justifyContent:'center',padding:40}}>
<div className="spinner"/>
</div>
)
```
**Fehlerzustand:**
```jsx
if (error) return (
<div style={{color:'var(--danger)',padding:16,textAlign:'center'}}>
{error}
</div>
)
```
**Leerer Zustand:**
```jsx
{items.length === 0 && (
<div style={{textAlign:'center',padding:40,color:'var(--text3)'}}>
<div style={{fontSize:32,marginBottom:8}}>📭</div>
<div>Noch keine Einträge</div>
</div>
)}
```
**Metric Card:**
```jsx
<div className="card" style={{padding:16,textAlign:'center'}}>
<div style={{fontSize:12,color:'var(--text3)',marginBottom:4}}>LABEL</div>
<div style={{fontSize:24,fontWeight:700,color:'var(--accent)'}}>
{value}
</div>
<div style={{fontSize:12,color:'var(--text3)'}}>Einheit</div>
</div>
```
### 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
### 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:
<Bar fill={(entry) => entry.color}/>
// ✅ Richtig:
<Bar fill="#1D9E75"/>
```
### SQLite neue Spalten hinzufügen
```python
# In _safe_alters Liste hinzufügen (NICHT direkt ALTER TABLE):
_safe_alters = [
("profiles", "neue_spalte TEXT DEFAULT NULL"),
]
```