# Shinkan Jinkendo - Datenbank-Schema (Technisch) **Version:** 0.3.4 **Stand:** 2026-04-23 **Aktuell deployed:** Migration 009 (Target Groups M:N Refactoring) --- ## Übersicht Dieses Dokument beschreibt die **technische Datenbankstruktur** von Shinkan Jinkendo. **Zuständig für fachliche Modellierung:** siehe `DOMAIN_MODEL.md` --- ## Migrations-Historie | Nr. | Datum | Beschreibung | Status | |-----|-------|--------------|--------| | 001 | 2026-04-20 | Initial Schema (Auth, Profiles) | ✅ Deployed | | 002 | 2026-04-20 | Clubs, Divisions, Groups | ✅ Deployed | | 003 | 2026-04-20 | Skills, Methods | ✅ Deployed | | 004 | 2026-04-21 | Training Types | ✅ Deployed | | 005 | 2026-04-21 | Exercises (Basis) | ✅ Deployed | | 006 | 2026-04-21 | Training Planning | ✅ Deployed | | 007 | 2026-04-22 | Exercise Catalogs (idempotent) | ✅ Deployed | | 008 | 2026-04-23 | M:N Exercise Relations + Hierarchical Catalogs | ✅ Deployed | | 009 | 2026-04-23 | **Target Groups M:N Refactoring** (BREAKING) | ✅ Deployed | --- ## Migration 009: Target Groups M:N Refactoring (BREAKING CHANGE) **Problem:** Zielgruppen waren hierarchisch an Trainingsstile gebunden (1:N via FK `training_style_id`). **Lösung:** M:N Beziehung über Junction-Tabelle `training_style_target_groups`. **Änderungen:** - ❌ **Entfernt:** `target_groups.training_style_id` (FK constraint + column) - ✅ **Neu:** `training_style_target_groups` Junction-Tabelle - ✅ **Migriert:** Alte Zuordnungen zu M:N (alle mit `is_primary = true`) - ✅ **Architektur:** Eine Zielgruppe kann mehreren Stilen zugeordnet sein **Neue Struktur:** ```sql -- Global unabhängige Zielgruppen (KEINE training_style_id mehr) target_groups ( id, name, description, min_age, max_age, sort_order, status, created_at, updated_at ) -- M:N Junction-Tabelle training_style_target_groups ( id SERIAL PRIMARY KEY, training_style_id INT REFERENCES training_styles(id) ON DELETE CASCADE, target_group_id INT REFERENCES target_groups(id) ON DELETE CASCADE, is_primary BOOLEAN DEFAULT false, created_at TIMESTAMP DEFAULT NOW(), UNIQUE(training_style_id, target_group_id) ) ``` **Backend API:** 5 neue Endpoints: - `GET /api/training-style-target-groups` (Liste mit Filtern + Enrichment) - `POST /api/training-style-target-groups` (Upsert-Logik) - `PUT /api/training-style-target-groups/{id}` (is_primary Flag) - `DELETE /api/training-style-target-groups/{id}` - `GET /api/training-styles/hierarchy` (Hierarchische Struktur für Tree-View) **Frontend:** - Admin-UI Tab "Hierarchie" (Tree-View: Fokusbereich → Stil → Zielgruppen) - Admin-UI Tab "Zuordnungen" (Checkbox-Matrix für M:N) - Tab "Zielgruppen" überarbeitet (global, ohne training_style_id Dropdown) --- ## Kern-Entitäten (Stand Migration 007) ### Auth & Profile ```sql profiles (id, name, email, password_hash, role, created_at) sessions (id, profile_id, token, created_at, expires_at) ``` ### Organisation ```sql clubs (id, name, abbreviation, description, logo_url, ...) divisions (id, club_id, name, description, ...) training_groups (id, division_id, name, description, focus_area, level, ...) ``` ### Kataloge (Einfache FK-Struktur - zu refactoren) **ACHTUNG:** Aktuell 1:1 Beziehungen - fachlich inkorrekt! ```sql focus_areas (id, name, abbreviation, description, color, icon, ...) training_styles (id, name, abbreviation, description, parent_style_id, ...) training_characters (id, name, description, ...) skill_categories (id, name, description, parent_category_id, ...) ``` ### Übungen (Aktuell - zu refactoren) ```sql exercises ( id, title, summary, goal, execution, preparation, trainer_notes, focus_area_id, -- ❌ 1:1 - sollte M:N sein training_style_id, -- ❌ 1:1 - sollte M:N sein training_character_id, -- ❌ 1:1 - sollte M:N sein visibility, status, created_by, club_id, ... ) exercise_skills (exercise_id, skill_id, is_primary, intensity, ...) exercise_variants (id, exercise_id, name, description, ...) exercise_media (id, exercise_id, type, url, title, description, ...) ``` ### Training Planning ```sql training_units (id, group_id, date, title, description, ...) training_unit_exercises (training_unit_id, exercise_id, sort_order, ...) ``` --- ## Geplante Änderungen (Migration 008) ### Hierarchische Katalog-Struktur **Fachliche Anforderung (§8.1 Anforderungsdokument):** ``` Fokusbereich → Stil → Zielgruppe ``` **Neue Struktur:** ```sql -- Level 1: Fokusbereiche (Root) focus_areas ( id SERIAL PRIMARY KEY, name VARCHAR(100) UNIQUE NOT NULL, abbreviation VARCHAR(20), description TEXT, color VARCHAR(20), icon VARCHAR(50), sort_order INT, status VARCHAR(50) DEFAULT 'active' ) -- Level 2: Trainingsstile (untergeordnet zu Fokusbereich) training_styles ( id SERIAL PRIMARY KEY, focus_area_id INT REFERENCES focus_areas(id), -- Hierarchie-Link name VARCHAR(100) NOT NULL, abbreviation VARCHAR(20), description TEXT, parent_style_id INT REFERENCES training_styles(id), -- Optional: Sub-Stile sort_order INT, status VARCHAR(50) DEFAULT 'active' ) -- Level 3: Zielgruppen (untergeordnet zu Stil) target_groups ( id SERIAL PRIMARY KEY, training_style_id INT REFERENCES training_styles(id), -- Hierarchie-Link name VARCHAR(100) NOT NULL, description TEXT, min_age INT, max_age INT, sort_order INT, status VARCHAR(50) DEFAULT 'active' ) ``` ### M:N Übungs-Zuordnungen ```sql -- Übung → Fokusbereiche (M:N) exercise_focus_areas ( id SERIAL PRIMARY KEY, exercise_id INT REFERENCES exercises(id) ON DELETE CASCADE, focus_area_id INT REFERENCES focus_areas(id), is_primary BOOLEAN DEFAULT false, UNIQUE(exercise_id, focus_area_id) ) -- Übung → Stile (M:N) exercise_styles ( id SERIAL PRIMARY KEY, exercise_id INT REFERENCES exercises(id) ON DELETE CASCADE, training_style_id INT REFERENCES training_styles(id), is_primary BOOLEAN DEFAULT false, UNIQUE(exercise_id, training_style_id) ) -- Übung → Zielgruppen (M:N) exercise_target_groups ( id SERIAL PRIMARY KEY, exercise_id INT REFERENCES exercises(id) ON DELETE CASCADE, target_group_id INT REFERENCES target_groups(id), is_primary BOOLEAN DEFAULT false, UNIQUE(exercise_id, target_group_id) ) -- Übung → Altersgruppen (M:N - separate Dimension) exercise_age_groups ( id SERIAL PRIMARY KEY, exercise_id INT REFERENCES exercises(id) ON DELETE CASCADE, age_group VARCHAR(50) NOT NULL, -- 'Minis', 'Kinder', 'Schüler', 'Teenager', 'Erwachsene' UNIQUE(exercise_id, age_group) ) ``` --- ## Kaskadier-Regeln (Geplant) ### Fokusbereich löschen **Verhalten:** `RESTRICT` (Löschen verweigern wenn verwendet) **Alternativen:** 1. **Umrouten:** Alle abhängigen Stile auf neuen Fokusbereich setzen 2. **Archivieren:** Status auf 'archived' setzen statt löschen **Implementierung:** ```sql -- Prüfung vor Löschen: SELECT COUNT(*) FROM training_styles WHERE focus_area_id = :id SELECT COUNT(*) FROM exercise_focus_areas WHERE focus_area_id = :id -- Falls verwendet → HTTP 409 Conflict mit Hinweis ``` ### Stil löschen **Verhalten:** `RESTRICT` wenn Zielgruppen oder Übungen zugeordnet **Umrouten-Workflow:** 1. Admin wählt Ziel-Stil 2. System aktualisiert: - `target_groups.training_style_id` - `exercise_styles.training_style_id` 3. Löschen erlaubt ### Zielgruppe löschen **Verhalten:** `SET NULL` oder `CASCADE` in `exercise_target_groups` --- ## Indizes (Geplant) ```sql -- Hierarchie-Lookups CREATE INDEX idx_training_styles_focus_area ON training_styles(focus_area_id); CREATE INDEX idx_target_groups_style ON target_groups(training_style_id); -- M:N Joins CREATE INDEX idx_exercise_focus_areas_exercise ON exercise_focus_areas(exercise_id); CREATE INDEX idx_exercise_focus_areas_focus ON exercise_focus_areas(focus_area_id); CREATE INDEX idx_exercise_styles_exercise ON exercise_styles(exercise_id); CREATE INDEX idx_exercise_styles_style ON exercise_styles(training_style_id); CREATE INDEX idx_exercise_target_groups_exercise ON exercise_target_groups(exercise_id); CREATE INDEX idx_exercise_target_groups_target ON exercise_target_groups(target_group_id); ``` --- ## Deprecation-Pfad (Migration 008) **Legacy-Spalten in `exercises`:** ```sql -- Werden in Migration 008 deprecated: exercises.focus_area_id → Migriert zu exercise_focus_areas (is_primary=true) exercises.training_style_id → Migriert zu exercise_styles (is_primary=true) exercises.training_character_id → Bleibt vorerst (separate Dimension) -- Spalten bleiben zur Rückwärtskompatibilität, aber: -- 1. Daten migriert -- 2. Neue Übungen nutzen M:N-Tabellen -- 3. API liefert enriched data aus M:N -- 4. Später: Spalten droppen nach Stabilisierung ``` --- ## Nächste Schritte - [ ] Migration 008 Entwurf erstellen - [ ] Migrations-Strategie für Altdaten dokumentieren - [ ] Admin-UI für Baum-Verwaltung planen - [ ] API-Endpoints für hierarchische Navigation - [ ] Cascade-Logik implementieren - [ ] Tests für Löschen/Umrouten --- **Letzte Aktualisierung:** 2026-04-23 **Verantwortlich:** Claude Code **Review:** Pending