feat: Complete MVP setup - Docker, Frontend, Migrations, CI/CD
Docker & Deployment:
- docker-compose.yml (Prod: Port 3003/8003)
- docker-compose.dev-env.yml (Dev: Port 3098/8098)
- Backend Dockerfile (Python 3.12-slim)
- Frontend Dockerfile (Node 20 + Nginx)
- Gitea Actions (deploy-dev.yml, deploy-prod.yml)
Frontend:
- React 18 + Vite setup
- package.json, vite.config.js, index.html
- App.jsx (minimal with version display)
- api.js (complete API client)
- app.css + AuthContext from Mitai
- main.jsx entry point
Backend Migrations:
- 001_auth_membership.sql (Auth + Features + Tier Limits)
- 002_organization.sql (Clubs, Divisions, Training Groups)
- 003_catalogs.sql (Skills + Methods with sample data)
Documentation:
- .claude/rules/ (ARCHITECTURE, CODING_RULES, etc.)
- SHINKAN_PROJECT_SETUP.md (technical setup guide)
Server:
- Directories created on Pi: /home/lars/docker/shinkan[-dev]
- Gitea Runner configured and running
Ready for first deployment to dev.shinkan.jinkendo.de
version: 0.1.0
date: 2026-04-21
2026-04-21 14:36:52 +02:00
|
|
|
|
# Coding Rules – Mitai Jinkendo
|
|
|
|
|
|
|
|
|
|
|
|
Diese Regeln IMMER befolgen. Sie basieren auf Erfahrungen aus der Entwicklung.
|
|
|
|
|
|
|
|
|
|
|
|
## Backend
|
|
|
|
|
|
|
|
|
|
|
|
### 1. Auth auf jeden Endpoint
|
|
|
|
|
|
```python
|
|
|
|
|
|
# Jeder neue Endpoint braucht Auth:
|
|
|
|
|
|
@router.get("/neuer-endpoint")
|
|
|
|
|
|
def neuer_endpoint(session: dict = Depends(require_auth)):
|
|
|
|
|
|
pid = session['profile_id']
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 2. Profile-ID aus Session – nie aus Header
|
|
|
|
|
|
```python
|
|
|
|
|
|
pid = session['profile_id'] # ✅
|
|
|
|
|
|
# Nicht: request.headers.get('X-Profile-Id') ❌
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 3. bcrypt für Passwörter
|
|
|
|
|
|
```python
|
|
|
|
|
|
from auth import hash_pin, verify_pin
|
|
|
|
|
|
hashed = hash_pin(plain_password) # ✅
|
|
|
|
|
|
# Nicht: hashlib.sha256(...) ❌
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 4. PostgreSQL-Syntax
|
|
|
|
|
|
```python
|
|
|
|
|
|
cur.execute("SELECT * FROM t WHERE id = %s AND active = true", (id,))
|
|
|
|
|
|
# Nicht: ? und active = 1 (SQLite-Syntax)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 5. Rate Limiting für sensitive Endpoints
|
|
|
|
|
|
```python
|
|
|
|
|
|
from slowapi import Limiter
|
|
|
|
|
|
@router.post("/sensitive")
|
|
|
|
|
|
@limiter.limit("5/minute")
|
|
|
|
|
|
def sensitive(request: Request, ...):
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 6. Universal CSV Import / Admin-Vorlagen
|
|
|
|
|
|
Neues **Import-Zielmodul**, Änderungen an **`csv_parser`**, Executor, DB-`source`/`CHECK`, oder System-CSV-Vorlagen:
|
|
|
|
|
|
|
|
|
|
|
|
- Pflichtlektüre und Checkliste: **`.claude/docs/technical/UNIVERSAL_CSV_IMPORT_AGENT_GUIDE.md`**
|
|
|
|
|
|
- Keine zweite DB-Connection im Importpfad; Zeilenfehler ohne „aborted transaction“ (SAVEPOINT-Muster wo nötig)
|
|
|
|
|
|
- Admin Create/Update von Systemvorlagen: Validierung über `validate_csv_template` nicht umgehen
|
|
|
|
|
|
|
|
|
|
|
|
## Frontend
|
|
|
|
|
|
|
|
|
|
|
|
### 1. api.js für alle API-Calls
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
await api.listWeight() // ✅
|
|
|
|
|
|
await fetch('/api/weight') // ❌ kein Token
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 2. Fehlerbehandlung in async Funktionen
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
try {
|
|
|
|
|
|
const data = await api.meinEndpoint()
|
|
|
|
|
|
} catch(e) {
|
|
|
|
|
|
setError(e.message) // api.js wirft bereits Error mit detail-Text
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 3. Kein TypeScript
|
|
|
|
|
|
Das Projekt nutzt bewusst kein TypeScript – keine .ts/.tsx Dateien erstellen.
|
|
|
|
|
|
|
|
|
|
|
|
### 4. Keine neuen npm-Pakete ohne Absprache
|
|
|
|
|
|
Erst fragen, dann installieren.
|
|
|
|
|
|
|
|
|
|
|
|
### 5. CSS-Variablen statt Hardcoded-Farben
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
// ✅ Richtig:
|
|
|
|
|
|
style={{color: 'var(--accent)'}}
|
|
|
|
|
|
|
|
|
|
|
|
// ❌ Falsch:
|
|
|
|
|
|
style={{color: '#1D9E75'}}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
refactor: Standardisiere Formular-Layout (Label oben, volle Breite, linksbündig)
CSS (frontend/src/app.css):
- .form-row: flex-direction column, volle Breite, margin-bottom 16px
- .form-label: display block, font-weight 600, linksbündig
- .form-input: width 100%, text-align left (statt right 90px), padding 10px 12px
- textarea.form-input: resize vertical, min-height 80px
- select.form-input: cursor pointer
- .form-row--inline: Neue Variante für Ausnahmen (kurze Werte + Einheit)
Dokumentation (.claude/rules/CODING_RULES.md):
- Neue Regel Frontend §6: Formular-Standard (VERBINDLICH ab 2026-04-22)
- Code-Beispiele für Standard-Layout (input, textarea, select)
- Klare Regeln: Label = <label>, Pflichtfelder mit *, volle Breite, linksbündig
- Inline-Variante nur für Ausnahmen (Zahlen mit Einheit)
WICHTIG: Bestehende Formulare (Exercises, Clubs, Skills, TrainingPlanning)
nutzen bereits größtenteils diese Struktur und profitieren automatisch
von den CSS-Änderungen.
Vorteile:
- Konsistente UX über alle Formulare
- Mobile-friendly (volle Breite, kein horizontal scrolling)
- Bessere Lesbarkeit (Label als Überschrift)
- Einfacher wartbar (ein Standard für alle)
Nächster Schritt: Bestehende Formulare testen, ggf. kleine Anpassungen
2026-04-22 17:11:40 +02:00
|
|
|
|
### 6. Formular-Standard (VERBINDLICH ab 2026-04-22)
|
|
|
|
|
|
**Alle neuen Formulare verwenden den Standard-Stil:**
|
|
|
|
|
|
|
|
|
|
|
|
```jsx
|
|
|
|
|
|
// ✅ Standard: Label oben, volle Breite, linksbündig
|
|
|
|
|
|
<div className="form-row">
|
|
|
|
|
|
<label className="form-label">Feldname *</label>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
className="form-input"
|
|
|
|
|
|
value={formData.field}
|
|
|
|
|
|
onChange={(e) => updateFormField('field', e.target.value)}
|
|
|
|
|
|
required
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ Textarea
|
|
|
|
|
|
<div className="form-row">
|
|
|
|
|
|
<label className="form-label">Beschreibung</label>
|
|
|
|
|
|
<textarea
|
|
|
|
|
|
className="form-input"
|
|
|
|
|
|
rows={3}
|
|
|
|
|
|
value={formData.description}
|
|
|
|
|
|
onChange={(e) => updateFormField('description', e.target.value)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ Select
|
|
|
|
|
|
<div className="form-row">
|
|
|
|
|
|
<label className="form-label">Status</label>
|
|
|
|
|
|
<select
|
|
|
|
|
|
className="form-input"
|
|
|
|
|
|
value={formData.status}
|
|
|
|
|
|
onChange={(e) => updateFormField('status', e.target.value)}
|
|
|
|
|
|
>
|
|
|
|
|
|
<option value="active">Aktiv</option>
|
|
|
|
|
|
<option value="inactive">Inaktiv</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
// ❌ NICHT: Inline-Layout mit Label links
|
|
|
|
|
|
// Nur für Ausnahmen (kurze Werte mit Einheit) nutzen:
|
|
|
|
|
|
<div className="form-row--inline">
|
|
|
|
|
|
<label className="form-label">Dauer</label>
|
|
|
|
|
|
<input type="number" className="form-input" value={duration} />
|
|
|
|
|
|
<span className="form-unit">min</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**Regeln:**
|
|
|
|
|
|
- Label ist `<label>` mit Klasse `.form-label` (nicht `<div>`)
|
|
|
|
|
|
- Input/Textarea/Select nutzen `.form-input`
|
|
|
|
|
|
- Volle Breite (100%), linksbündig
|
|
|
|
|
|
- Pflichtfelder mit `*` im Label kennzeichnen
|
|
|
|
|
|
- Inline-Variante (`.form-row--inline`) nur für Ausnahmen (Zahlen mit Einheit)
|
|
|
|
|
|
|
feat: Complete MVP setup - Docker, Frontend, Migrations, CI/CD
Docker & Deployment:
- docker-compose.yml (Prod: Port 3003/8003)
- docker-compose.dev-env.yml (Dev: Port 3098/8098)
- Backend Dockerfile (Python 3.12-slim)
- Frontend Dockerfile (Node 20 + Nginx)
- Gitea Actions (deploy-dev.yml, deploy-prod.yml)
Frontend:
- React 18 + Vite setup
- package.json, vite.config.js, index.html
- App.jsx (minimal with version display)
- api.js (complete API client)
- app.css + AuthContext from Mitai
- main.jsx entry point
Backend Migrations:
- 001_auth_membership.sql (Auth + Features + Tier Limits)
- 002_organization.sql (Clubs, Divisions, Training Groups)
- 003_catalogs.sql (Skills + Methods with sample data)
Documentation:
- .claude/rules/ (ARCHITECTURE, CODING_RULES, etc.)
- SHINKAN_PROJECT_SETUP.md (technical setup guide)
Server:
- Directories created on Pi: /home/lars/docker/shinkan[-dev]
- Gitea Runner configured and running
Ready for first deployment to dev.shinkan.jinkendo.de
version: 0.1.0
date: 2026-04-21
2026-04-21 14:36:52 +02:00
|
|
|
|
## Git & Deployment
|
|
|
|
|
|
|
|
|
|
|
|
### 1. Nie direkt auf main pushen
|
|
|
|
|
|
Immer über Pull Request in Gitea: develop → main
|
|
|
|
|
|
|
|
|
|
|
|
### 2. develop Branch nie löschen
|
|
|
|
|
|
Er ist permanent – nicht nach Merge löschen.
|
|
|
|
|
|
|
|
|
|
|
|
### 3. .env nie committen
|
|
|
|
|
|
Steht in .gitignore – nie entfernen.
|
|
|
|
|
|
|
|
|
|
|
|
### 4. Commit-Message Format
|
|
|
|
|
|
```
|
|
|
|
|
|
feat: neues Feature
|
|
|
|
|
|
fix: Bugfix
|
|
|
|
|
|
refactor: Umbau ohne Funktionsänderung
|
|
|
|
|
|
docs: Dokumentation
|
|
|
|
|
|
ci: CI/CD Änderungen
|
|
|
|
|
|
chore: Maintenance
|
|
|
|
|
|
```
|