diff --git a/.claude/docs/working/SHINKAN_PROJECT_SETUP.md b/.claude/docs/working/SHINKAN_PROJECT_SETUP.md new file mode 100644 index 0000000..0af3a3e --- /dev/null +++ b/.claude/docs/working/SHINKAN_PROJECT_SETUP.md @@ -0,0 +1,1202 @@ +# Shinkan Jinkendo – Technisches Setup + +> **Trainer- und Vereinsplattform für Kampfsport-Trainingsplanung** +> Basis: Mitai Jinkendo Ökosystem +> Domain: shinkan.jinkendo.de (真観 - "Wahre Beobachtung") + +--- + +## 1. Fachliche Einordnung + +**Shinkan ist KEINE persönliche Tracking-App.** + +Shinkan ist eine **trainer- und vereinszentrierte Planungs- und Inhaltsplattform** mit Fokus auf: +- Übungsverwaltung und -suche +- Trainingsplanung für Gruppen +- Standardisierung und Wiederverwendung +- Freigabe und Governance von Inhalten +- Import aus bestehendem MediaWiki + +**Primäre Nutzer in MVP:** Trainer, Vereinsadmins, Redakteure +**Nicht in MVP:** Individuelle Sportler-Apps, persönliches Tracking, Gürtel-Tracking + +**Abgrenzung zu Mitai:** +- Mitai = persönliches Körper- und Trainings-Tracking +- Shinkan = Trainer-Arbeit, Übungen, Trainingsplanung, Vereinsstandards + +--- + +## 2. Übernommene Infrastruktur von Mitai + +| Komponente | Status | Verwendung | +|-----------|---------|------------| +| Auth-System | ✅ 1:1 übernehmen | Token + bcrypt | +| Membership-Basis | ✅ Übernehmen | Tiers, Features (vereinfacht) | +| User-Profiles | ✅ Übernehmen | Multi-Profile pro Account | +| Tech-Stack | ✅ 1:1 | React 18 + FastAPI + PostgreSQL 16 | +| Docker/Compose | ✅ 1:1 | Dev + Prod Umgebungen | +| PWA | ✅ 1:1 | Mobile + Desktop | +| Design-System | ✅ Basis | CSS-Variablen, mobile-first | +| Versioning | ✅ 1:1 | version.py + Migrations | +| Gitea CI/CD | ✅ 1:1 | Auto-Deploy dev/prod | + +**Ports:** +- Prod: 3003/8003 (Frontend/Backend) +- Dev: 3098/8098 (Frontend/Backend) + +--- + +## 3. Technische Basis (identisch zu Mitai) + +``` +Frontend: React 18 + Vite + PWA (Node 20) +Backend: FastAPI Python 3.12 +Datenbank: PostgreSQL 16 Alpine +Container: Docker + Docker Compose +Auth: Token-basiert + bcrypt +KI: OpenRouter API (optional, nicht MVP-kritisch) +``` + +--- + +## 4. Verzeichnisstruktur (angepasst) + +``` +shinkan-jinkendo/ +├── backend/ +│ ├── main.py # App-Setup + Router-Registration +│ ├── db.py # PostgreSQL Connection Pool +│ ├── db_init.py # DB-Init + Migrations +│ ├── auth.py # Hash, Verify, Sessions, Feature-Access +│ ├── models.py # Pydantic Models +│ ├── version.py # Versionskontrolle +│ ├── migrations/ # SQL-Migrationen (001_*.sql) +│ └── routers/ +│ ├── auth.py # Login, Register, Sessions +│ ├── profiles.py # User-Profiles +│ ├── clubs.py # Vereine / Sparten +│ ├── groups.py # Trainingsgruppen +│ ├── skills.py # Fähigkeiten (Admin + CRUD) +│ ├── methods.py # Trainingsmethoden (Admin + CRUD) +│ ├── exercises.py # Übungen (Kern-Modul) +│ ├── training_units.py # Trainingseinheiten +│ ├── training_programs.py # Trainingsprogramme +│ ├── planning.py # Planungsansichten +│ ├── import_wiki.py # MediaWiki-Import +│ ├── admin.py # Admin-Panel +│ └── membership.py # Subscription (vereinfacht) +│ +├── frontend/ +│ ├── src/ +│ │ ├── App.jsx # Root, Auth-Gates, Navigation +│ │ ├── app.css # CSS-Variablen + Styles +│ │ ├── version.js # Frontend-Versionierung +│ │ ├── config/ +│ │ │ ├── appNav.js # Hauptnavigation +│ │ │ └── adminNav.js # Admin-Navigation +│ │ ├── context/ +│ │ │ ├── AuthContext.jsx +│ │ │ └── ProfileContext.jsx +│ │ ├── pages/ +│ │ │ ├── Dashboard.jsx +│ │ │ ├── ExercisesPage.jsx # Übungssuche + CRUD +│ │ │ ├── PlanningPage.jsx # Trainingsplanung +│ │ │ ├── GroupsPage.jsx # Gruppenverwaltung +│ │ │ ├── SkillsPage.jsx # Fähigkeiten (Admin) +│ │ │ ├── MethodsPage.jsx # Methoden (Admin) +│ │ │ ├── ImportPage.jsx # MediaWiki-Import +│ │ │ └── SettingsPage.jsx +│ │ └── utils/ +│ │ ├── api.js # ALLE API-Calls +│ │ └── planning.js # Planungshelfer +│ └── public/ +│ ├── manifest.json # PWA-Manifest +│ └── icons/ +│ +├── .claude/ +│ ├── commands/ # Slash-Commands +│ ├── docs/ +│ │ ├── functional/ +│ │ │ └── SHINKAN_REQUIREMENTS.md # Anforderungsdokument +│ │ ├── technical/ +│ │ │ └── SHINKAN_DOMAIN_MODEL.md # Domänenmodell +│ │ └── rules/ # Architektur-Regeln (wie Mitai) +│ └── library/ +│ +├── docker-compose.yml # Production +├── docker-compose.dev-env.yml # Development +├── .env +├── .gitignore +└── CLAUDE.md # Agent-Einstieg +``` + +--- + +## 5. Domänenmodell (MVP Core) + +### 5.1 Organisationsstruktur + +```sql +-- Vereine +clubs ( + id SERIAL PRIMARY KEY, + name VARCHAR(200) NOT NULL, + abbreviation VARCHAR(50), + description TEXT, + status VARCHAR(50) DEFAULT 'active', + created_at TIMESTAMP DEFAULT NOW() +) + +-- Sparten (optional, kann auch später) +divisions ( + id SERIAL PRIMARY KEY, + club_id INT REFERENCES clubs(id), + name VARCHAR(200) NOT NULL, + focus_area VARCHAR(100), -- karate, selbstverteidigung, gewaltschutz + created_at TIMESTAMP DEFAULT NOW() +) + +-- Trainingsgruppen +training_groups ( + id SERIAL PRIMARY KEY, + club_id INT REFERENCES clubs(id), + division_id INT REFERENCES divisions(id), + name VARCHAR(200) NOT NULL, + focus VARCHAR(100), + level VARCHAR(50), + age_group VARCHAR(50), + weekday VARCHAR(20), + time_start TIME, + time_end TIME, + location VARCHAR(200), + trainer_id INT REFERENCES profiles(id), + co_trainer_ids JSONB, -- [1, 2, 3] + status VARCHAR(50) DEFAULT 'active', + created_at TIMESTAMP DEFAULT NOW() +) +``` + +### 5.2 Fähigkeiten und Methoden (Kataloge) + +```sql +-- Fähigkeiten (global) +skills ( + id SERIAL PRIMARY KEY, + name VARCHAR(200) NOT NULL, + category VARCHAR(100), -- kihon, kumite, kata, selbstverteidigung, fitness + description TEXT, + importance INT, -- 1-5 + keywords JSONB, + status VARCHAR(50) DEFAULT 'active', + created_at TIMESTAMP DEFAULT NOW() +) + +-- Trainingsmethoden (global) +training_methods ( + id SERIAL PRIMARY KEY, + name VARCHAR(200) NOT NULL, + abbreviation VARCHAR(20), + category VARCHAR(100), -- intervall, rollenspiel, zirkel, koordination, etc. + description TEXT, + typical_duration INT, -- in Minuten + typical_group_size VARCHAR(50), + related_skills JSONB, -- [skill_id, skill_id, ...] + keywords JSONB, + status VARCHAR(50) DEFAULT 'active', + created_at TIMESTAMP DEFAULT NOW() +) +``` + +### 5.3 Übungen (Kernobjekt) + +```sql +-- Übungen +exercises ( + id SERIAL PRIMARY KEY, + title VARCHAR(300) NOT NULL, + summary TEXT, -- Kurzbeschreibung + goal TEXT NOT NULL, -- Ziel der Übung + execution TEXT NOT NULL, -- Durchführung + preparation TEXT, -- Vorbereitung / Aufbau + trainer_notes TEXT, -- Hinweise für Trainer + equipment JSONB, -- ["Matten", "Pratzen", "Bälle"] + + -- Metadaten + duration_min INT, + duration_max INT, + group_size_min INT, + group_size_max INT, + age_groups JSONB, -- ["minis", "kinder", "erwachsene"] + + -- Fachliche Zuordnung + focus_area VARCHAR(100), -- karate, selbstverteidigung, gewaltschutz + secondary_areas JSONB, + training_character VARCHAR(50), -- grundlage, aufbau, vertiefung, festigung, diagnose + + -- Methode + primary_method_id INT REFERENCES training_methods(id), + secondary_method_ids JSONB, + + -- Freigabe + visibility VARCHAR(50) DEFAULT 'private', -- private, club, official + status VARCHAR(50) DEFAULT 'draft', -- draft, in_review, approved, archived + created_by INT REFERENCES profiles(id), + club_id INT REFERENCES clubs(id), + + -- Import + import_source VARCHAR(50), -- mediawiki, manual + import_id VARCHAR(200), + + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +) + +-- Übungs-Fähigkeiten (M:N) +exercise_skills ( + id SERIAL PRIMARY KEY, + exercise_id INT REFERENCES exercises(id) ON DELETE CASCADE, + skill_id INT REFERENCES skills(id), + is_primary BOOLEAN DEFAULT false, + intensity INT, -- 1-5 + development_contribution VARCHAR(50), -- low, medium, high + required_level INT, -- Eingangs-Niveau + target_level INT, -- Ziel-Niveau + created_at TIMESTAMP DEFAULT NOW() +) + +-- Übungsvarianten +exercise_variants ( + id SERIAL PRIMARY KEY, + exercise_id INT REFERENCES exercises(id) ON DELETE CASCADE, + variant_name VARCHAR(200) NOT NULL, + description TEXT, + execution_changes TEXT, + duration_min INT, + duration_max INT, + equipment_changes JSONB, + difficulty_adjustment VARCHAR(50), -- easier, harder, adapted + age_group_override JSONB, + skill_focus_override JSONB, + created_at TIMESTAMP DEFAULT NOW() +) + +-- Medien (1:N) +exercise_media ( + id SERIAL PRIMARY KEY, + exercise_id INT REFERENCES exercises(id) ON DELETE CASCADE, + media_type VARCHAR(50), -- image, video, document, sketch + file_path TEXT, + title VARCHAR(200), + description TEXT, + sort_order INT, + is_primary BOOLEAN DEFAULT false, + context VARCHAR(100), -- ablauf, detail, trainer_hint + created_at TIMESTAMP DEFAULT NOW() +) +``` + +### 5.4 Trainingsplanung + +```sql +-- Training Templates / Standards (vereinbar) +training_templates ( + id SERIAL PRIMARY KEY, + name VARCHAR(300) NOT NULL, + type VARCHAR(50), -- template, standard + club_id INT REFERENCES clubs(id), + division_id INT REFERENCES divisions(id), + + goal TEXT, + focus_areas JSONB, + duration_total INT, + + visibility VARCHAR(50) DEFAULT 'private', + status VARCHAR(50) DEFAULT 'active', + version INT DEFAULT 1, + created_by INT REFERENCES profiles(id), + + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +) + +-- Trainingsabschnitte (für Templates + Units) +training_sections ( + id SERIAL PRIMARY KEY, + parent_type VARCHAR(50), -- template, unit + parent_id INT, + + title VARCHAR(200) NOT NULL, + section_type VARCHAR(100), -- aufwaermen, kihon, kumite, kata, abschluss, etc. + sort_order INT, + duration_planned INT, + goal TEXT, + notes TEXT, + + is_combination BOOLEAN DEFAULT false, -- Übungen als Komplex + combination_method_id INT REFERENCES training_methods(id), + + created_at TIMESTAMP DEFAULT NOW() +) + +-- Übungen in Abschnitten +section_exercises ( + id SERIAL PRIMARY KEY, + section_id INT REFERENCES training_sections(id) ON DELETE CASCADE, + exercise_id INT REFERENCES exercises(id), + variant_id INT REFERENCES exercise_variants(id), + sort_order INT, + duration_planned INT, + notes TEXT, + created_at TIMESTAMP DEFAULT NOW() +) + +-- Konkrete Trainingseinheiten +training_units ( + id SERIAL PRIMARY KEY, + group_id INT REFERENCES training_groups(id), + date DATE NOT NULL, + time_start TIME, + time_end TIME, + + derived_from_template_id INT REFERENCES training_templates(id), + derived_from_unit_id INT REFERENCES training_units(id), + + title VARCHAR(300), + goal TEXT, + focus_areas JSONB, + + -- Durchführung + actual_time_start TIME, + actual_time_end TIME, + completion_status VARCHAR(50), -- planned, in_progress, completed + + -- Reflexion + reflection_text TEXT, + what_worked_well TEXT, + what_to_improve TEXT, + + created_by INT REFERENCES profiles(id), + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +) + +-- Trainingsprogramme +training_programs ( + id SERIAL PRIMARY KEY, + club_id INT REFERENCES clubs(id), + division_id INT REFERENCES divisions(id), + + name VARCHAR(300) NOT NULL, + description TEXT, + goal TEXT, + duration_weeks INT, + focus_areas JSONB, + + visibility VARCHAR(50) DEFAULT 'private', + status VARCHAR(50) DEFAULT 'active', + created_by INT REFERENCES profiles(id), + + created_at TIMESTAMP DEFAULT NOW() +) + +-- Programm-Einheiten (Reihenfolge) +program_units ( + id SERIAL PRIMARY KEY, + program_id INT REFERENCES training_programs(id) ON DELETE CASCADE, + unit_order INT, + template_id INT REFERENCES training_templates(id), + week_number INT, + notes TEXT, + created_at TIMESTAMP DEFAULT NOW() +) +``` + +### 5.5 Governance + +```sql +-- Änderungsanfragen (für geschützte Inhalte) +content_change_requests ( + id SERIAL PRIMARY KEY, + content_type VARCHAR(50), -- exercise, template, skill, method + content_id INT, + requested_by INT REFERENCES profiles(id), + change_description TEXT, + change_details JSONB, + status VARCHAR(50) DEFAULT 'pending', -- pending, approved, rejected + reviewed_by INT REFERENCES profiles(id), + review_notes TEXT, + created_at TIMESTAMP DEFAULT NOW(), + reviewed_at TIMESTAMP +) +``` + +### 5.6 MediaWiki-Import + +```sql +-- Import-Tracking +wiki_import_log ( + id SERIAL PRIMARY KEY, + import_type VARCHAR(50), -- skill, method, exercise + import_status VARCHAR(50), -- success, partial, failed + items_total INT, + items_imported INT, + items_failed INT, + error_log JSONB, + imported_by INT REFERENCES profiles(id), + created_at TIMESTAMP DEFAULT NOW() +) + +-- Import-Referenzen (für Re-Import-Schutz) +wiki_import_references ( + id SERIAL PRIMARY KEY, + wiki_page_title VARCHAR(300), + wiki_page_id VARCHAR(100), + content_type VARCHAR(50), -- skill, method, exercise + local_id INT, + last_imported TIMESTAMP, + created_at TIMESTAMP DEFAULT NOW(), + UNIQUE(wiki_page_title, content_type) +) +``` + +--- + +## 6. MVP Core-Features (Release 1) + +### 6.1 Pflicht in MVP + +**Organisationsstruktur:** +- ✅ Verein anlegen/verwalten +- ✅ Trainingsgruppen anlegen/verwalten +- ✅ Trainer zuordnen + +**Kataloge:** +- ✅ Fähigkeiten-Katalog (Admin CRUD) +- ✅ Methoden-Katalog (Admin CRUD) + +**Übungen:** +- ✅ Übung anlegen (minimal: Titel, Ziel, Durchführung) +- ✅ Übung vollständig beschreiben (alle Felder) +- ✅ Übungsvarianten anlegen +- ✅ Fähigkeiten zuordnen (Haupt/Neben, Intensität) +- ✅ Methoden zuordnen (Haupt/Neben) +- ✅ Medien hochladen (Bilder, Videos) +- ✅ Übung suchen und filtern + - Schnellsuche (Titel, Schlagworte) + - Filter (Bereich, Zielgruppe, Fähigkeit, Dauer, Gruppengröße) +- ✅ Übung drucken +- ✅ Freigabelogik (privat / Verein / offiziell) + +**Trainingsplanung:** +- ✅ Trainingseinheit für Gruppe + Termin planen +- ✅ Training aus Vorlage ableiten +- ✅ Training aus altem Training kopieren +- ✅ Trainingsabschnitte verwalten +- ✅ Übungen zu Abschnitten zuordnen +- ✅ Kombinations-Flag für Abschnitte +- ✅ Kalenderansicht (Gruppe, Datum) +- ✅ Druckansicht +- ✅ Mobile Durchführungssicht +- ✅ Notizen zu Training + Übungen +- ✅ Reflexion nach Training + +**Import:** +- ✅ Einseitiger MediaWiki-Import + - Fähigkeiten + - Methoden + - Übungen +- ✅ Import-Tracking (Erfolg/Fehler) +- ✅ Duplikat-Erkennung (Wiki-Referenz) + +### 6.2 Bewusst NICHT in MVP + +**Kern-Features, die warten:** +- ❌ KI-Trainingsplanung +- ❌ KI-Suche mit externen Calls +- ❌ Sportler-Tracking (individuelle Entwicklung) +- ❌ Gürtel/Graduierungs-System +- ❌ Technik-Mastery-Tracking +- ❌ Reifegradmodell (vollständig) +- ❌ Turnier-/Wettkampfbegleitung +- ❌ Community/Marktplatz +- ❌ Komplexe Membership-Matrix +- ❌ Vollhistorisierung jeder Änderung +- ❌ Bidirektionale Wiki-Sync +- ❌ Offline-Sync (vollständig) + +**Rollen/Governance (vereinfacht):** +- ❌ Feingranulare Rechte-Matrix +- ❌ Workflow-basierte Freigabe +- ❌ Multi-Tenant-Administration + +--- + +## 7. Initiale Migrationen (MVP) + +### 7.1 001_auth_membership.sql + +```sql +-- Von Mitai übernehmen: +-- profiles, sessions, features, tier_limits, subscriptions +-- (siehe Mitai 001_*.sql + 002_*.sql) +``` + +### 7.2 002_organization.sql + +```sql +-- Vereine +CREATE TABLE clubs ( + id SERIAL PRIMARY KEY, + name VARCHAR(200) NOT NULL, + abbreviation VARCHAR(50), + description TEXT, + status VARCHAR(50) DEFAULT 'active', + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); + +-- Sparten (optional, kann später) +CREATE TABLE divisions ( + id SERIAL PRIMARY KEY, + club_id INT REFERENCES clubs(id) ON DELETE CASCADE, + name VARCHAR(200) NOT NULL, + focus_area VARCHAR(100), + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); + +-- Trainingsgruppen +CREATE TABLE training_groups ( + id SERIAL PRIMARY KEY, + club_id INT REFERENCES clubs(id) ON DELETE CASCADE, + division_id INT REFERENCES divisions(id), + name VARCHAR(200) NOT NULL, + focus VARCHAR(100), + level VARCHAR(50), + age_group VARCHAR(50), + weekday VARCHAR(20), + time_start TIME, + time_end TIME, + location VARCHAR(200), + trainer_id INT REFERENCES profiles(id), + co_trainer_ids JSONB, + status VARCHAR(50) DEFAULT 'active', + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); + +CREATE INDEX idx_groups_club ON training_groups(club_id); +CREATE INDEX idx_groups_trainer ON training_groups(trainer_id); +``` + +### 7.3 003_catalogs.sql + +```sql +-- Fähigkeiten +CREATE TABLE skills ( + id SERIAL PRIMARY KEY, + name VARCHAR(200) NOT NULL, + category VARCHAR(100), + description TEXT, + importance INT CHECK (importance BETWEEN 1 AND 5), + keywords JSONB, + status VARCHAR(50) DEFAULT 'active', + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); + +-- Trainingsmethoden +CREATE TABLE training_methods ( + id SERIAL PRIMARY KEY, + name VARCHAR(200) NOT NULL, + abbreviation VARCHAR(20), + category VARCHAR(100), + description TEXT, + typical_duration INT, + typical_group_size VARCHAR(50), + related_skills JSONB, + keywords JSONB, + status VARCHAR(50) DEFAULT 'active', + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); + +CREATE INDEX idx_skills_category ON skills(category); +CREATE INDEX idx_methods_category ON training_methods(category); +``` + +### 7.4 004_exercises.sql + +```sql +-- Übungen +CREATE TABLE exercises ( + id SERIAL PRIMARY KEY, + title VARCHAR(300) NOT NULL, + summary TEXT, + goal TEXT NOT NULL, + execution TEXT NOT NULL, + preparation TEXT, + trainer_notes TEXT, + equipment JSONB, + + duration_min INT, + duration_max INT, + group_size_min INT, + group_size_max INT, + age_groups JSONB, + + focus_area VARCHAR(100), + secondary_areas JSONB, + training_character VARCHAR(50), + + primary_method_id INT REFERENCES training_methods(id), + secondary_method_ids JSONB, + + visibility VARCHAR(50) DEFAULT 'private', + status VARCHAR(50) DEFAULT 'draft', + created_by INT REFERENCES profiles(id), + club_id INT REFERENCES clubs(id), + + import_source VARCHAR(50), + import_id VARCHAR(200), + + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); + +-- Übungs-Fähigkeiten +CREATE TABLE exercise_skills ( + id SERIAL PRIMARY KEY, + exercise_id INT REFERENCES exercises(id) ON DELETE CASCADE, + skill_id INT REFERENCES skills(id), + is_primary BOOLEAN DEFAULT false, + intensity INT CHECK (intensity BETWEEN 1 AND 5), + development_contribution VARCHAR(50), + required_level INT, + target_level INT, + created_at TIMESTAMP DEFAULT NOW() +); + +-- Varianten +CREATE TABLE exercise_variants ( + id SERIAL PRIMARY KEY, + exercise_id INT REFERENCES exercises(id) ON DELETE CASCADE, + variant_name VARCHAR(200) NOT NULL, + description TEXT, + execution_changes TEXT, + duration_min INT, + duration_max INT, + equipment_changes JSONB, + difficulty_adjustment VARCHAR(50), + age_group_override JSONB, + skill_focus_override JSONB, + created_at TIMESTAMP DEFAULT NOW() +); + +-- Medien +CREATE TABLE exercise_media ( + id SERIAL PRIMARY KEY, + exercise_id INT REFERENCES exercises(id) ON DELETE CASCADE, + media_type VARCHAR(50), + file_path TEXT, + title VARCHAR(200), + description TEXT, + sort_order INT, + is_primary BOOLEAN DEFAULT false, + context VARCHAR(100), + created_at TIMESTAMP DEFAULT NOW() +); + +CREATE INDEX idx_exercises_created_by ON exercises(created_by); +CREATE INDEX idx_exercises_club ON exercises(club_id); +CREATE INDEX idx_exercises_visibility ON exercises(visibility); +CREATE INDEX idx_exercise_skills_skill ON exercise_skills(skill_id); +``` + +### 7.5 005_training_planning.sql + +```sql +-- Templates / Standards +CREATE TABLE training_templates ( + id SERIAL PRIMARY KEY, + name VARCHAR(300) NOT NULL, + type VARCHAR(50), + club_id INT REFERENCES clubs(id), + division_id INT REFERENCES divisions(id), + goal TEXT, + focus_areas JSONB, + duration_total INT, + visibility VARCHAR(50) DEFAULT 'private', + status VARCHAR(50) DEFAULT 'active', + version INT DEFAULT 1, + created_by INT REFERENCES profiles(id), + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); + +-- Abschnitte +CREATE TABLE training_sections ( + id SERIAL PRIMARY KEY, + parent_type VARCHAR(50), + parent_id INT, + title VARCHAR(200) NOT NULL, + section_type VARCHAR(100), + sort_order INT, + duration_planned INT, + goal TEXT, + notes TEXT, + is_combination BOOLEAN DEFAULT false, + combination_method_id INT REFERENCES training_methods(id), + created_at TIMESTAMP DEFAULT NOW() +); + +-- Übungen in Abschnitten +CREATE TABLE section_exercises ( + id SERIAL PRIMARY KEY, + section_id INT REFERENCES training_sections(id) ON DELETE CASCADE, + exercise_id INT REFERENCES exercises(id), + variant_id INT REFERENCES exercise_variants(id), + sort_order INT, + duration_planned INT, + notes TEXT, + created_at TIMESTAMP DEFAULT NOW() +); + +-- Trainingseinheiten +CREATE TABLE training_units ( + id SERIAL PRIMARY KEY, + group_id INT REFERENCES training_groups(id) ON DELETE CASCADE, + date DATE NOT NULL, + time_start TIME, + time_end TIME, + derived_from_template_id INT REFERENCES training_templates(id), + derived_from_unit_id INT REFERENCES training_units(id), + title VARCHAR(300), + goal TEXT, + focus_areas JSONB, + actual_time_start TIME, + actual_time_end TIME, + completion_status VARCHAR(50), + reflection_text TEXT, + what_worked_well TEXT, + what_to_improve TEXT, + created_by INT REFERENCES profiles(id), + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); + +-- Programme +CREATE TABLE training_programs ( + id SERIAL PRIMARY KEY, + club_id INT REFERENCES clubs(id), + division_id INT REFERENCES divisions(id), + name VARCHAR(300) NOT NULL, + description TEXT, + goal TEXT, + duration_weeks INT, + focus_areas JSONB, + visibility VARCHAR(50) DEFAULT 'private', + status VARCHAR(50) DEFAULT 'active', + created_by INT REFERENCES profiles(id), + created_at TIMESTAMP DEFAULT NOW() +); + +CREATE TABLE program_units ( + id SERIAL PRIMARY KEY, + program_id INT REFERENCES training_programs(id) ON DELETE CASCADE, + unit_order INT, + template_id INT REFERENCES training_templates(id), + week_number INT, + notes TEXT, + created_at TIMESTAMP DEFAULT NOW() +); + +CREATE INDEX idx_units_group_date ON training_units(group_id, date); +CREATE INDEX idx_sections_parent ON training_sections(parent_type, parent_id); +``` + +### 7.6 006_governance.sql + +```sql +-- Änderungsanfragen +CREATE TABLE content_change_requests ( + id SERIAL PRIMARY KEY, + content_type VARCHAR(50), + content_id INT, + requested_by INT REFERENCES profiles(id), + change_description TEXT, + change_details JSONB, + status VARCHAR(50) DEFAULT 'pending', + reviewed_by INT REFERENCES profiles(id), + review_notes TEXT, + created_at TIMESTAMP DEFAULT NOW(), + reviewed_at TIMESTAMP +); + +CREATE INDEX idx_change_requests_status ON content_change_requests(status); +``` + +### 7.7 007_wiki_import.sql + +```sql +-- Import-Log +CREATE TABLE wiki_import_log ( + id SERIAL PRIMARY KEY, + import_type VARCHAR(50), + import_status VARCHAR(50), + items_total INT, + items_imported INT, + items_failed INT, + error_log JSONB, + imported_by INT REFERENCES profiles(id), + created_at TIMESTAMP DEFAULT NOW() +); + +-- Import-Referenzen +CREATE TABLE wiki_import_references ( + id SERIAL PRIMARY KEY, + wiki_page_title VARCHAR(300), + wiki_page_id VARCHAR(100), + content_type VARCHAR(50), + local_id INT, + last_imported TIMESTAMP, + created_at TIMESTAMP DEFAULT NOW(), + UNIQUE(wiki_page_title, content_type) +); + +CREATE INDEX idx_wiki_refs_page ON wiki_import_references(wiki_page_title); +``` + +--- + +## 8. MediaWiki-Import-Strategie + +### 8.1 Grundprinzip + +**Einseitig, einmalig, dann Shinkan-nativ weiter** + +- ✅ Import aus MediaWiki nach Shinkan +- ❌ KEINE bidirektionale Synchronisation +- ❌ KEINE Live-Kopplung +- ❌ KEINE kontinuierliche Aktualisierung + +### 8.2 Import-Reihenfolge + +1. **Fähigkeiten** (skills) - Basis-Katalog +2. **Methoden** (training_methods) - Methodenkatalog +3. **Übungen** (exercises) - Hauptinhalt + +**Programme/Trainings:** Nur wenn Datenqualität ausreicht + +### 8.3 Import-Logik + +```python +# Pseudo-Code Import-Workflow +for wiki_page in wiki_pages: + # Duplikat-Check + existing = check_wiki_reference(wiki_page.title, content_type) + if existing: + log_skip(wiki_page) + continue + + # Parse + Transform + data = parse_wiki_page(wiki_page) + + # Validate + if not validate_minimal_fields(data): + log_error(wiki_page, "missing required fields") + continue + + # Import + try: + local_id = create_content(data) + create_wiki_reference(wiki_page, local_id) + log_success(wiki_page, local_id) + except Exception as e: + log_error(wiki_page, str(e)) +``` + +### 8.4 Import-Metadaten + +Jeder importierte Inhalt erhält: +- `import_source = 'mediawiki'` +- `import_id = wiki_page_id` +- Referenz in `wiki_import_references` +- Status = `draft` (muss nachbearbeitet werden) + +### 8.5 Nach-Import-Workflow + +Nach Import müssen Trainer/Admins: +- Importierte Inhalte prüfen +- Unvollständige Felder ergänzen +- Fähigkeiten/Methoden zuordnen +- Status auf `approved` setzen + +--- + +## 9. Docker-Setup (wie Mitai) + +### 9.1 docker-compose.yml (Production) + +```yaml +version: '3.8' + +services: + postgres: + image: postgres:16-alpine + container_name: shinkan-db-prod + environment: + POSTGRES_DB: shinkan + POSTGRES_USER: shinkan_user + POSTGRES_PASSWORD: ${DB_PASSWORD} + volumes: + - shinkan-db-data:/var/lib/postgresql/data + ports: + - "5434:5432" + restart: unless-stopped + + backend: + build: + context: ./backend + dockerfile: Dockerfile + container_name: shinkan-api + environment: + DB_HOST: postgres + DB_PORT: 5432 + DB_NAME: shinkan + DB_USER: shinkan_user + DB_PASSWORD: ${DB_PASSWORD} + APP_URL: https://shinkan.jinkendo.de + ALLOWED_ORIGINS: https://shinkan.jinkendo.de + volumes: + - shinkan-media:/app/media + ports: + - "8003:8000" + depends_on: + - postgres + restart: unless-stopped + + frontend: + build: + context: ./frontend + dockerfile: Dockerfile + args: + VITE_API_URL: https://shinkan.jinkendo.de + container_name: shinkan-ui + ports: + - "3003:80" + restart: unless-stopped + +volumes: + shinkan-db-data: + shinkan-media: +``` + +### 9.2 docker-compose.dev-env.yml (Development) + +```yaml +version: '3.8' + +services: + postgres: + image: postgres:16-alpine + container_name: dev-shinkan-postgres + environment: + POSTGRES_DB: shinkan_dev + POSTGRES_USER: shinkan_dev + POSTGRES_PASSWORD: dev_password + volumes: + - dev-shinkan-db-data:/var/lib/postgresql/data + ports: + - "5435:5432" + restart: unless-stopped + + backend: + build: + context: ./backend + dockerfile: Dockerfile + container_name: dev-shinkan-api + environment: + DB_HOST: postgres + DB_PORT: 5432 + DB_NAME: shinkan_dev + DB_USER: shinkan_dev + DB_PASSWORD: dev_password + APP_URL: https://dev.shinkan.jinkendo.de + ALLOWED_ORIGINS: https://dev.shinkan.jinkendo.de + volumes: + - dev-shinkan-media:/app/media + ports: + - "8098:8000" + depends_on: + - postgres + restart: unless-stopped + + frontend: + build: + context: ./frontend + dockerfile: Dockerfile + args: + VITE_API_URL: https://dev.shinkan.jinkendo.de + container_name: dev-shinkan-ui + ports: + - "3098:80" + restart: unless-stopped + +volumes: + dev-shinkan-db-data: + dev-shinkan-media: +``` + +--- + +## 10. Initiale Version + +```python +# backend/version.py + +APP_VERSION = "0.1.0" +BUILD_DATE = "2026-04-21" +DB_SCHEMA_VERSION = "20260421" + +MODULE_VERSIONS = { + "auth": "1.0.0", + "profiles": "1.0.0", + "clubs": "0.1.0", + "groups": "0.1.0", + "skills": "0.1.0", + "methods": "0.1.0", + "exercises": "0.1.0", + "training_units": "0.1.0", + "training_programs": "0.1.0", + "planning": "0.1.0", + "import_wiki": "0.1.0", + "admin": "1.0.0", + "membership": "1.0.0", +} + +CHANGELOG = [ + { + "version": "0.1.0", + "date": "2026-04-21", + "changes": [ + "Initial MVP Setup", + "Feature: Übungsverwaltung (Kern-Modul)", + "Feature: Fähigkeiten- und Methodenkataloge", + "Feature: Trainingsplanung für Gruppen", + "Feature: Trainingsabschnitte mit Kombinations-Flag", + "Feature: MediaWiki-Import (einseitig)", + "Feature: Freigabelogik (privat/Verein/offiziell)", + ] + } +] +``` + +--- + +## 11. Umsetzungs-Reihenfolge (Empfehlung) + +### Phase 1: Basis (2-3 Tage) +- Repository + Docker-Setup +- Auth + Membership von Mitai kopieren +- DB-Migrationen 001-003 +- Version.py anpassen + +### Phase 2: Kataloge (2 Tage) +- Skills CRUD (Admin) +- Methods CRUD (Admin) +- Frontend Admin-Pages + +### Phase 3: Übungen (5-7 Tage) +- Exercises CRUD +- Exercise-Skills M:N +- Exercise-Variants +- Exercise-Media +- Suche + Filter +- Druckansicht + +### Phase 4: Trainingsplanung (5-7 Tage) +- Training-Groups CRUD +- Training-Templates/Standards +- Training-Sections +- Training-Units +- Kalenderansicht +- Mobile Durchführungssicht + +### Phase 5: Import (3-4 Tage) +- MediaWiki-Parser +- Import-Workflow (Skills, Methods, Exercises) +- Import-Log + Duplikat-Erkennung + +### Phase 6: Governance (2-3 Tage) +- Freigabelogik +- Change-Requests (Basis) +- Eigene vs. Vereins-Inhalte + +**Geschätzte Gesamtdauer MVP:** 4-5 Wochen + +--- + +## 12. Bewusst nur vorbereitet, nicht umgesetzt (in MVP) + +Diese Elemente sollen durch das Datenmodell **nicht verbaut** werden, gehören aber **nicht in MVP**: + +**Reifegradmodelle:** +- Skill-Levels pro Modell +- Maturity-Stages +- Target-Descriptions pro Level + +**Individuelle Sportler-Entwicklung:** +- Athlete-Profiles +- Skill-Progress-Tracking +- Assessment-History + +**Erweiterte Governance:** +- Workflow-basierte Freigabe +- Review-Zyklen +- Versionierung mit Branches + +**Community:** +- Shared Content-Pool +- Bewertungen +- Kommentare + +**KI:** +- Trainingsplanung +- Übungssuche mit Semantik +- Automatische Programm-Generierung + +--- + +## 13. Offene Punkte / Entscheidungsbedarf + +``` +🤔 MediaWiki-Parser: Welches Format nutzt das aktuelle Wiki? (Semantic MediaWiki) +🤔 Medien-Upload: Lokal oder S3/Cloud? +🤔 Suche: PostgreSQL Full-Text oder ElasticSearch später? +🤔 Offline: Service Worker + IndexedDB oder nur gute Mobile-UX? +🤔 Druckformat: PDF-Export oder nur HTML-Print? +🤔 Sparten: Schon in MVP oder erst später? +``` + +--- + +## 14. Nächste konkrete Schritte + +1. **Repository erstellen** auf Gitea: `shinkan-jinkendo` +2. **Basis-Dateien kopieren** aus Mitai: + - `backend/auth.py`, `db.py`, `db_init.py` + - `frontend/src/context/`, `utils/api.js` +3. **Migrationen schreiben** (001-007) +4. **Version.py anpassen** +5. **CLAUDE.md erstellen** für Shinkan +6. **Erste Router implementieren:** + - `skills.py` + - `methods.py` + - `exercises.py` + +--- + +**Version:** 2.0 (komplett überarbeitet) +**Stand:** 21.04.2026 +**Autor:** Claude Code (basierend auf Anforderungsdokument) diff --git a/backend/dashboard_widget_config.py b/backend/dashboard_widget_config.py index 729c1ea..d84e9cf 100644 --- a/backend/dashboard_widget_config.py +++ b/backend/dashboard_widget_config.py @@ -14,6 +14,7 @@ MAX_WIDGET_CONFIG_JSON_BYTES = 3072 WIDGETS_ALLOWING_CONFIG: frozenset[str] = frozenset({ "body_overview", + "body_history_viz", "activity_overview", "kpi_board", "quick_capture", @@ -52,6 +53,8 @@ def validate_widget_entry_config(widget_id: str, raw: Any) -> dict[str, Any]: if widget_id == "body_overview": return _validate_chart_days_only(raw, label="body_overview") + if widget_id == "body_history_viz": + return _validate_chart_days_only(raw, label="body_history_viz") if widget_id == "activity_overview": return _validate_chart_days_only(raw, label="activity_overview") if widget_id == "kpi_board": diff --git a/backend/tests/test_dashboard_widget_config.py b/backend/tests/test_dashboard_widget_config.py index 9f30f19..e3bb2f1 100644 --- a/backend/tests/test_dashboard_widget_config.py +++ b/backend/tests/test_dashboard_widget_config.py @@ -14,6 +14,13 @@ def test_body_chart_days_bounds(): validate_widget_entry_config("body_overview", {"chart_days": 91}) +def test_body_history_viz_chart_days(): + assert validate_widget_entry_config("body_history_viz", {}) == {} + assert validate_widget_entry_config("body_history_viz", {"chart_days": 60}) == {"chart_days": 60} + with pytest.raises(ValueError): + validate_widget_entry_config("body_history_viz", {"chart_days": 5}) + + def test_welcome_config_rejected_unknown_key(): with pytest.raises(ValueError): validate_widget_entry_config("welcome", {"x": 1}) diff --git a/backend/version.py b/backend/version.py index 7393c96..98586bd 100644 --- a/backend/version.py +++ b/backend/version.py @@ -30,7 +30,7 @@ MODULE_VERSIONS = { "importdata": "1.0.0", "membership": "2.1.0", "workflow": "0.7.0", # Part 3: Inline Prompts (reference + inline mode) - "app_dashboard": "1.11.0", # Entitlements: DB-Override widget→features (AND), sonst Katalog + "app_dashboard": "1.12.0", # Widget body_history_viz (Verlauf body-history-viz Bundle) "csv_import": "0.3.2", # Import-Fehler: enrich_row_error / freundlichere 500-Hinweise "admin_csv_templates": "0.3.0", # POST /validate + Speichern nur bei valid (422 + warnings in Response) } diff --git a/backend/widget_catalog.py b/backend/widget_catalog.py index f89a908..f94f75e 100644 --- a/backend/widget_catalog.py +++ b/backend/widget_catalog.py @@ -42,6 +42,12 @@ WIDGET_CATALOG: list[WidgetCatalogEntry] = [ "description": "Gewicht & Kennzahlen (optional: config chart_days 7–90); Feature weight_entries", "requires_feature": "weight_entries", }, + { + "id": "body_history_viz", + "title": "Körper (Verlauf-Bundle)", + "description": "Wie Verlauf → Körper: GET /charts/body-history-viz (optional chart_days 7–90); Feature weight_entries", + "requires_feature": "weight_entries", + }, { "id": "activity_overview", "title": "Aktivität", diff --git a/frontend/src/components/dashboard-widgets/BodyHistoryVizWidget.jsx b/frontend/src/components/dashboard-widgets/BodyHistoryVizWidget.jsx new file mode 100644 index 0000000..f7d2c04 --- /dev/null +++ b/frontend/src/components/dashboard-widgets/BodyHistoryVizWidget.jsx @@ -0,0 +1,29 @@ +import { useNavigate } from 'react-router-dom' +import BodyHistoryVizSection from '../history/BodyHistoryVizSection' +import { useProfile } from '../../context/ProfileContext' +import { BODY_CHART_DAYS_DEFAULT, normalizeBodyChartDays } from '../../widgetSystem/bodyChartDays' + +/** + * Verlauf → Körper als Dashboard-Widget: GET /charts/body-history-viz (Layer 2b), optional chart_days 7–90. + * @param {{ refreshTick?: number, chartDays?: number }} props + */ +export default function BodyHistoryVizWidget({ refreshTick = 0, chartDays }) { + const nav = useNavigate() + const { activeProfile } = useProfile() + const days = chartDays != null ? normalizeBodyChartDays(chartDays) : BODY_CHART_DAYS_DEFAULT + + return ( +
+ Daten und Kennzahlen aus dem Backend-Bundle (gleiche Quelle wie Platzhalter). Training: Verlauf → Fitness. +
+ + {viz?.meta?.layer_2a_alignment && ( +- Daten und Kennzahlen aus dem Backend-Bundle (gleiche Quelle wie Platzhalter). Training: Verlauf → Fitness. -
- - {viz?.meta?.layer_2a_alignment && ( -