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

14 KiB
Raw Blame History

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)

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

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

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

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

if (loading) return (
  <div style={{display:'flex',justifyContent:'center',padding:40}}>
    <div className="spinner"/>
  </div>
)

Fehlerzustand:

if (error) return (
  <div style={{color:'var(--danger)',padding:16,textAlign:'center'}}>
    {error}
  </div>
)

Leerer Zustand:

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

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

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

// ❌ 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

# ❌ 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

// ❌ Falsch:
<Bar fill={(entry) => entry.color}/>

// ✅ Richtig:
<Bar fill="#1D9E75"/>

SQLite neue Spalten hinzufügen

# In _safe_alters Liste hinzufügen (NICHT direkt ALTER TABLE):
_safe_alters = [
    ("profiles", "neue_spalte TEXT DEFAULT NULL"),
]