2026-03-16 13:35:11 +01:00
# Mitai Jinkendo – Entwickler-Kontext für Claude Code
## Projekt-Übersicht
2026-03-17 14:08:44 +01:00
**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).
2026-03-16 13:35:11 +01:00
2026-03-17 14:08:44 +01:00
**Produktfamilie:** mitai · miken · ikigai · shinkan · kenkou (alle unter jinkendo.de)
2026-03-16 13:35:11 +01:00
## Tech-Stack
| Komponente | Technologie | Version |
|-----------|-------------|---------|
| Frontend | React 18 + Vite + PWA | Node 20 |
| Backend | FastAPI (Python) | Python 3.12 |
2026-03-18 21:39:14 +01:00
| Datenbank | PostgreSQL 16 (Alpine) | v9b |
2026-03-16 13:35:11 +01:00
| Container | Docker + Docker Compose | - |
2026-03-17 14:08:44 +01:00
| Webserver | nginx (Reverse Proxy) | Alpine |
| Auth | Token-basiert + bcrypt | - |
2026-03-16 13:35:11 +01:00
| KI | OpenRouter API (claude-sonnet-4) | - |
## Ports
2026-03-17 14:08:44 +01:00
| Service | Prod | Dev |
|---------|------|-----|
| Frontend | 3002 | 3099 |
| Backend | 8002 | 8099 |
2026-03-16 13:35:11 +01:00
## Verzeichnisstruktur
```
mitai-jinkendo/
├── backend/
2026-03-17 14:08:44 +01:00
│ ├── main.py # FastAPI App, alle Endpoints (~2000 Zeilen)
2026-03-16 13:35:11 +01:00
│ ├── 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
2026-03-17 14:08:44 +01:00
│ │ └── 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
2026-03-16 13:35:11 +01:00
```
2026-03-18 21:39:14 +01:00
## Aktuelle Version: v9b
2026-03-17 14:08:44 +01:00
2026-03-16 13:35:11 +01:00
### Was implementiert ist:
2026-03-17 14:08:44 +01:00
- ✅ Multi-User mit E-Mail + Passwort Login (bcrypt)
2026-03-18 21:39:14 +01:00
- ✅ Auth-Middleware auf ALLE Endpoints (60+ Endpoints geschützt)
2026-03-17 14:08:44 +01:00
- ✅ Rate Limiting (Login: 5/min, Reset: 3/min)
- ✅ CORS konfigurierbar via ALLOWED_ORIGINS in .env
2026-03-16 13:35:11 +01:00
- ✅ 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)
2026-03-18 21:39:14 +01:00
- ✅ Konfigurierbare Prompts mit Template-Variablen (Admin kann bearbeiten)
2026-03-16 13:35:11 +01:00
- ✅ Verlauf mit 5 Tabs + Zeitraumfilter + KI pro Sektion
- ✅ Dashboard mit Kennzahlen, Zielfortschritt, Combo-Chart
- ✅ Assistent-Modus (Schritt-für-Schritt Messung)
2026-03-17 14:08:44 +01:00
- ✅ PWA (iPhone Home Screen), Jinkendo Ensō-Logo
2026-03-16 13:35:11 +01:00
- ✅ E-Mail (SMTP) für Password-Recovery
2026-03-18 21:39:14 +01:00
- ✅ Admin-Panel: User verwalten, KI-Limits, E-Mail-Test, PIN/Email setzen
2026-03-17 14:08:44 +01:00
- ✅ Multi-Environment: Prod (mitai.jinkendo.de) + Dev (dev.mitai.jinkendo.de)
- ✅ Gitea CI/CD mit Auto-Deploy auf Raspberry Pi 5
2026-03-18 21:39:14 +01:00
- ✅ PostgreSQL 16 Migration (vollständig von SQLite migriert)
- ✅ Export: CSV, JSON, ZIP (mit Fotos)
- ✅ Automatische SQLite→PostgreSQL Migration bei Container-Start
2026-03-16 13:35:11 +01:00
2026-03-18 21:39:14 +01:00
### Was in v9c kommt:
2026-03-16 13:35:11 +01:00
- 🔲 Selbst-Registrierung mit E-Mail-Bestätigung
- 🔲 Freemium Tier-System (free/basic/premium/selfhosted)
2026-03-17 14:08:44 +01:00
- 🔲 14-Tage Trial automatisch
- 🔲 Einladungslinks für Beta-Nutzer
- 🔲 Admin kann Tiers manuell setzen
2026-03-18 21:39:14 +01:00
### Was in v9d kommt:
2026-03-17 14:08:44 +01:00
- 🔲 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)
```
2026-03-16 13:35:11 +01:00
2026-03-17 14:08:44 +01:00
### 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
```
2026-03-18 21:39:14 +01:00
## Datenbank-Schema (PostgreSQL 16, v9b)
2026-03-16 13:35:11 +01:00
### Wichtige Tabellen:
2026-03-18 21:39:14 +01:00
- `profiles` – Nutzer (role, pin_hash/bcrypt, email, auth_type, ai_enabled, tier)
2026-03-16 13:35:11 +01:00
- `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)
2026-03-18 21:39:14 +01:00
- `photos` – Progress Photos
2026-03-16 13:35:11 +01:00
- `ai_insights` – KI-Auswertungen (scope = prompt-slug)
2026-03-17 14:08:44 +01:00
- `ai_prompts` – Konfigurierbare Prompts mit Templates (11 Prompts)
2026-03-16 13:35:11 +01:00
- `ai_usage` – KI-Calls pro Tag pro Profil
2026-03-18 21:39:14 +01:00
**Schema-Datei:** `backend/schema.sql` (vollständiges PostgreSQL-Schema)
**Migration-Script:** `backend/migrate_to_postgres.py` (SQLite→PostgreSQL, automatisch)
## Auth-Flow (v9b)
2026-03-16 13:35:11 +01:00
```
2026-03-17 14:08:44 +01:00
Login-Screen → E-Mail + Passwort → Token im localStorage
2026-03-16 13:35:11 +01:00
Token → X-Auth-Token Header → Backend require_auth()
Profile-Id → aus Session (nicht aus Header!)
2026-03-17 14:08:44 +01:00
SHA256 Passwörter → automatisch zu bcrypt migriert beim Login
2026-03-16 13:35:11 +01:00
```
## API-Konventionen
- Alle Endpoints: `/api/...`
- Auth-Header: `X-Auth-Token: <token>`
- Responses: immer JSON
- Fehler: `{"detail": "Fehlermeldung"}`
2026-03-17 14:08:44 +01:00
- Rate Limit überschritten: HTTP 429
2026-03-16 13:35:11 +01:00
## Umgebungsvariablen (.env)
```
2026-03-18 21:39:14 +01:00
# Database (PostgreSQL)
DB_HOST=postgres
DB_PORT=5432
DB_NAME=mitai_prod
DB_USER=mitai_prod
DB_PASSWORD= # REQUIRED
# AI
OPENROUTER_API_KEY= # KI-Calls (optional, alternativ ANTHROPIC_API_KEY)
2026-03-16 13:35:11 +01:00
OPENROUTER_MODEL=anthropic/claude-sonnet-4
2026-03-18 21:39:14 +01:00
ANTHROPIC_API_KEY= # Direkte Anthropic API (optional)
# Email
SMTP_HOST= # E-Mail (für Recovery)
2026-03-16 13:35:11 +01:00
SMTP_PORT=587
SMTP_USER=
SMTP_PASS=
SMTP_FROM=
2026-03-18 21:39:14 +01:00
# App
2026-03-17 14:08:44 +01:00
APP_URL=https://mitai.jinkendo.de
ALLOWED_ORIGINS=https://mitai.jinkendo.de
DATA_DIR=/app/data
2026-03-16 13:35:11 +01:00
PHOTOS_DIR=/app/photos
2026-03-18 21:39:14 +01:00
ENVIRONMENT=production
2026-03-16 13:35:11 +01:00
```
## Wichtige Hinweise für Claude Code
2026-03-17 14:08:44 +01:00
1. **Ports immer 3002/8002 (Prod) oder 3099/8099 (Dev)** – nie ändern
2026-03-16 13:35:11 +01:00
2. **npm install** (nicht npm ci) – kein package-lock.json vorhanden
2026-03-18 21:39:14 +01:00
3. **PostgreSQL-Migrations** – Schema-Änderungen in `backend/schema.sql` , dann Container neu bauen
2026-03-16 13:35:11 +01:00
4. **Pipeline-Prompts** haben slug-Prefix `pipeline_` – nie als Einzelanalyse zeigen
2026-03-17 14:08:44 +01:00
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
2026-03-18 21:39:14 +01:00
10. **RealDictCursor verwenden** – `get_cursor(conn)` statt `conn.cursor()` für dict-like row access
2026-03-16 13:35:11 +01:00
## 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
2026-03-18 06:13:58 +01:00
## 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
```
2026-03-18 08:27:33 +01:00
## 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" / >
```
2026-03-18 21:39:14 +01:00
### PostgreSQL Boolean-Syntax
2026-03-18 08:27:33 +01:00
```python
2026-03-18 21:39:14 +01:00
# ❌ Falsch (SQLite-Syntax):
cur.execute("SELECT * FROM ai_prompts WHERE active=1")
# ✅ Richtig (PostgreSQL):
cur.execute("SELECT * FROM ai_prompts WHERE active=true")
2026-03-18 08:27:33 +01:00
```
2026-03-18 21:39:14 +01:00
### RealDictCursor für dict-like row access
```python
# ❌ Falsch:
cur = conn.cursor()
cur.execute("SELECT COUNT(*) FROM weight_log")
count = cur.fetchone()[0] # Tuple index
# ✅ Richtig:
cur = get_cursor(conn) # Returns RealDictCursor
cur.execute("SELECT COUNT(*) as count FROM weight_log")
count = cur.fetchone()['count'] # Dict key
```
## v9b Migration – Lessons Learned
### PostgreSQL Migration (SQLite → PostgreSQL)
**Problem:** Docker Build hing 30+ Minuten bei `apt-get install postgresql-client`
**Lösung:** Alle apt-get dependencies entfernt, reine Python-Lösung mit psycopg2-binary
**Problem:** Leere date-Strings (`''`) führten zu PostgreSQL-Fehlern
**Lösung:** Migration-Script konvertiert leere Strings zu NULL für DATE-Spalten
**Problem:** Boolean-Felder (SQLite INTEGER 0/1 vs PostgreSQL BOOLEAN)
**Lösung:** Migration konvertiert automatisch, Backend nutzt `active=true` statt `active=1`
### API Endpoint Consistency (März 2026)
**Problem:** 11 kritische Endpoint-Mismatches zwischen Frontend und Backend gefunden
**Gelöst:**
- AI-Endpoints konsistent: `/api/insights/run/{slug}` , `/api/insights/pipeline`
- Password-Reset: `/api/auth/forgot-password` , `/api/auth/reset-password`
- Admin-Endpoints: `/permissions` , `/email` , `/pin` Sub-Routes
- Export: JSON + ZIP Endpoints hinzugefügt
- Prompt-Bearbeitung: PUT-Endpoint für Admins
**Tool:** Vollständiger Audit via Explore-Agent empfohlen bei größeren Änderungen