shinkan-jinkendo/.claude/docs/technical/DATABASE_SCHEMA.md
Lars b6de1f15ea
All checks were successful
Deploy Development / deploy (push) Successful in 34s
Test Suite / pytest-backend (push) Successful in 25s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 7s
Test Suite / playwright-tests (push) Successful in 23s
feat(media): implement centralized media archive and inline media linking
- Introduced a centralized media archive (`/media`) with lifecycle management, including soft delete and recovery options.
- Enhanced media upload functionality to support multiple files and automatic type inference.
- Updated documentation to reflect the new media architecture and inline media linking specifications.
- Version bump to 0.8.59 to accommodate changes in media handling and database schema.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-08 10:56:43 +02:00

457 lines
18 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Shinkan Jinkendo - Datenbank-Schema (Technisch)
**Version:** 0.5.3
**Stand:** 2026-05-07
**Hinweis:** Produktiver Deploy sollte mindestens bis Migration **037** (RahmenSlotBlueprints) und für Medien-Archiv bis **045+** (`media_assets`, …) geführt sein — Details siehe `backend/version.py` (`DB_SCHEMA_VERSION`) und **`MEDIA_ASSETS_AND_ARCHIVE_SPEC.md`**.
---
## Ü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 |
| 010 | 2026-04-23 | Rename training_styles to style_directions | ✅ Deployed |
| 011 | 2026-04-23 | Create Training Types | ✅ Deployed |
| 012 | 2026-04-23 | Exercise Training Characters + Trainer Contexts | ✅ Deployed |
| 013 | 2026-04-23 | Training Types Focus Area | ✅ Deployed |
| 014 | 2026-04-24 | Variant Progression Search Legacy | ✅ Deployed |
| 016 | 2026-04-24 | Saved Searches | ✅ Deployed |
| 017 | 2026-04-24 | Exercise Blocks | ✅ Deployed |
| 018 | 2026-04-24 | Wiki Import Tracking | ✅ Deployed |
| 019 | 2026-04-24 | Exercises Optional Fields (goal/execution nullable) | ✅ Deployed |
| 020 | 2026-04-27 | Exercise Skills UNIQUE Constraint | ✅ Deployed |
| 021 | 2026-04-27 | ~~Import Skills from Matrix~~ (DEPRECATED) | ⚠️ Faulty |
| **022** | **2026-04-27** | **Skills Schema Complete (BREAKING)** | ✅ Deployed |
| **023** | **2026-04-27** | **Skills Complete Import (69 Skills)** | ✅ Deployed |
| 024031 | *versch.* | Reifegradmodelle, Medien, Planvorlagen/Sektionen u.a. — siehe `backend/migrations/` | ✅ je Umgebung |
| **032** | **2026-04-30** | **Progressionsgraph Übung→Übung:** `exercise_progression_graphs`, `exercise_progression_edges` | ✅ |
| **033** | **2026-04-30** | **`exercise_progression_edges.notes`** | ✅ |
| **034** | **2026-04-30** | **Kanten-Endpunkte optional `exercise_variants`; UNIQUE/CHECK** | ✅ |
| **035** | **2026-05-05** | **Rahmenprogramm:** `training_framework_programs` (+ Ziele, Slots, früher `training_framework_slot_exercises`); **`training_plan_templates.visibility`** (Backfill `club`) — siehe `TRAINING_FRAMEWORK_SPEC.md` | ✅ |
| **036** | **2026-05-05** | **Rahmen nur Bibliothek:** Kopf mit `focus_area_id`, `style_direction_id`, M:N Trainingsarten/Zielgruppen; Entfall `plan_mode`, `group_id`; Slot`training_unit_id` geleert — siehe `036_framework_program_context_only_library.sql` | ✅ |
| **037** | **2026-05-05** | **SlotBlueprint:** `training_units.framework_slot_id` (+ CHECK Blueprint vs. Kalender), `origin_framework_slot_id`; Migration SlotÜbungen → `training_unit_sections`/`training_unit_section_items`; **`DROP training_framework_slot_exercises`** | ✅ |
| **040046** | **2026-05** | **Mitgliedschaft/Anträge, Übungs-Governance-Erweiterungen, `media_assets`, `platform_media_storage`, `exercise_media.media_asset_id`, Tags/GIN u.a.** — exakte Nummern: `backend/migrations/`; fachliche Norm Medien: **`MEDIA_ASSETS_AND_ARCHIVE_SPEC.md`** | ✅ je Umgebung |
## Migration 022: Skills Schema Complete (BREAKING CHANGE)
**Problem:** Skills hatten keine Kategorisierung (Haupt-/Unterkategorie, Fokusbereich).
**Lösung:** Vollständiges hierarchisches Schema für produktionsreifen Import.
**Neue Tabellen:**
```sql
-- Haupt-Kategorien (KARATE / ALLGEMEINE)
skill_main_categories (
id SERIAL PRIMARY KEY,
name VARCHAR(200) UNIQUE NOT NULL, -- "KARATE Fähigkeiten" / "ALLGEMEINE sportliche Fähigkeiten"
slug VARCHAR(50) UNIQUE NOT NULL, -- "karate" / "allgemeine"
description TEXT,
sort_order INT,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
)
-- Level-Definitionen (1-5 Beschreibungen aus Fähigkeitsmatrix)
skill_level_definitions (
id SERIAL PRIMARY KEY,
skill_id INT NOT NULL REFERENCES skills(id) ON DELETE CASCADE,
level INT NOT NULL CHECK (level BETWEEN 1 AND 5),
description TEXT NOT NULL,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
UNIQUE (skill_id, level)
)
```
**Erweiterte Tabellen:**
```sql
-- skill_categories erweitert
ALTER TABLE skill_categories ADD COLUMN slug VARCHAR(50);
ALTER TABLE skill_categories ADD COLUMN main_category_id INT REFERENCES skill_main_categories(id);
ALTER TABLE skill_categories ADD CONSTRAINT skill_categories_slug_unique UNIQUE (slug);
-- skills erweitert
ALTER TABLE skills ADD COLUMN main_category_id INT REFERENCES skill_main_categories(id);
ALTER TABLE skills ADD COLUMN focus_areas JSONB DEFAULT '[]'::jsonb;
```
**Indizes:**
- `idx_skills_main_category_id`
- `idx_skills_category_id`
- `idx_skill_categories_main_category_id`
- `idx_skill_level_definitions_skill_id`
**Struktur:**
```
skill_main_categories (Hauptkategorie)
└─ skill_categories (Unterkategorie)
└─ skills (Fähigkeit)
└─ skill_level_definitions (Level 1-5 Beschreibungen)
```
---
## Migration 023: Skills Complete Import
**Quelle:** Fähigkeitsmatrix (https://karatetrainer.net/index.php?title=Fähigkeitsmatrix)
**Importiert:**
- **69 Skills** mit vollständiger Kategorisierung
- **2 Haupt-Kategorien:** KARATE Fähigkeiten, ALLGEMEINE sportliche Fähigkeiten
- **9 Unterkategorien:** Kihon, Kumite, Kata, Selbstverteidigung, Koordination, Kondition, Kognition, Soziale Fähigkeiten, Psychische Fähigkeiten
**Skills-Verteilung:**
```
KARATE Fähigkeiten (32 Skills, focus_areas: ["karate"]):
├─ Kata (8): Technik Kombination, Kata Ablauf, Bunkai, Oyo, Henka, Kakushi, Kata Atmung, Kata Rhythmus
├─ Kihon (10): Dachi Waza, Uke Waza, Zuki Waza, Uchi Waza, Geri Waza, Nage Waza, Nukite Waza, Ken Waza, Hüfteinsatz, Kime
├─ Kumite (10): Beinarbeit, Distanzkontrolle, Angriff, Abwehr Konter, Präzision, Antizipation, Timing, Taktik, Fokus, Mentale Stärke
└─ Selbstverteidigung (4): Gefahrenbewustsein, Selbstbehauptung, Selbstschutz, Gefahrenabwehr
ALLGEMEINE sportliche Fähigkeiten (37 Skills, focus_areas: ["universal"]):
├─ Kognition (5): Aufmerksamkeit, Wahrnehmung, Urteilsvermögen, Merkfähigkeit, Lernfähigkeit
├─ Kondition (15): Maximalkraft, Schnellkraft, Reaktivkraft, Kraftausdauer, Muskelaufbau,
│ Reaktionsschnelligkeit, Bewegungsschnelligkeit, Handlungsschnelligkeit,
│ Schnelligkeitsausdauer, Grundlagenausdauer, Aerobe Ausdauer, Anaerobe Ausdauer,
│ Regenerationsfähigkeit, Ermüdungswiderstandsfähigkeit, Flexibilität
├─ Koordination (7): Orientierung, Differenzierung, Kopplung, Gleichgewicht, Rhythmisierung, Reaktion, Umstellung
├─ Psychische Fähigkeiten (6): Selbstvertrauen, Konzentration, Emotionale Kontrolle, Motivation, Stressresistenz, Stressregulation
└─ Soziale Fähigkeiten (4): Deeskalation, Selbstdisziplin, Toleranz, Fairness
```
**Duplikat-Handling:**
Einige Skills kommen in der Matrix doppelt vor (Kumite + Kondition/Koordination):
- **Behalten in Kondition:** Anaerobe Ausdauer, Bewegungsschnelligkeit, Flexibilität, Reaktionsschnelligkeit, Schnelligkeitsausdauer (konditionelle Primärfähigkeiten)
- **Behalten in Kumite:** Antizipation, Timing (kampfspezifisch)
**Cleanup:**
- DELETE FROM exercise_skills (M:N Beziehungen)
- DELETE FROM skills (alte Daten)
- DELETE FROM skill_categories
- DELETE FROM skill_main_categories
**Verifikation:**
```sql
-- Sollte 69 ergeben
SELECT COUNT(*) FROM skills;
-- Zeigt Verteilung
SELECT
mc.name AS hauptkategorie,
sc.name AS unterkategorie,
COUNT(s.id) AS anzahl_skills
FROM skills s
JOIN skill_categories sc ON s.category_id = sc.id
JOIN skill_main_categories mc ON s.main_category_id = mc.id
GROUP BY mc.name, sc.name, mc.sort_order, sc.sort_order
ORDER BY mc.sort_order, sc.sort_order;
```
---
## Kern-Entitäten
### 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
#### Fokusbereiche & Stile (M:N mit Zielgruppen)
```sql
focus_areas (id, name, abbreviation, description, color, icon, ...)
style_directions (id, focus_area_id, name, abbreviation, description, parent_style_id, ...)
target_groups (id, name, description, min_age, max_age, ...) -- Global, NICHT gebunden an Stil
training_style_target_groups (style_direction_id, target_group_id, is_primary) -- M:N Junction
```
#### Fähigkeiten (Hierarchisches Schema ab Migration 022)
```sql
-- Haupt-Kategorien (2: KARATE, ALLGEMEINE)
skill_main_categories (
id, name, slug, description, sort_order, created_at, updated_at
)
-- Unterkategorien (9: Kihon, Kumite, Kata, Selbstverteidigung, Koordination, Kondition, ...)
skill_categories (
id, name, slug, description, sort_order,
main_category_id REFERENCES skill_main_categories(id),
parent_category_id, -- Optional: Hierarchie
created_at, updated_at
)
-- Fähigkeiten (69 Skills)
skills (
id, name, description,
category_id REFERENCES skill_categories(id),
main_category_id REFERENCES skill_main_categories(id),
focus_areas JSONB, -- ["karate"] oder ["universal"]
created_at, updated_at
)
-- Level-Definitionen (optional, noch nicht gefüllt)
skill_level_definitions (
id, skill_id, level, description,
created_at, updated_at,
UNIQUE (skill_id, level)
)
```
**Focus Areas Bedeutung:**
- `["karate"]` - Skill ist spezifisch für Karate (z.B. Kata Ablauf, Kihon)
- `["universal"]` - Skill ist universell einsetzbar (z.B. Maximalkraft, Konzentration, Deeskalation)
- Später möglich: `["karate", "selbstverteidigung"]` - Mehrfachzuordnung
#### Trainingscharakter
```sql
training_characters (id, name, description, ...)
trainer_contexts (id, name, description, ...) -- Neue Dimension (Migration 012)
```
### Übungen (M:N Beziehungen ab Migration 008)
```sql
exercises (
id, title, summary, goal, execution, preparation, trainer_notes,
duration_min, duration_max,
group_size_min, group_size_max,
equipment JSONB,
visibility, status, created_by, club_id,
import_source, import_id, wiki_page_id, -- MediaWiki Import (Migration 018)
created_at, updated_at
)
-- M:N Beziehungen
exercise_focus_areas (exercise_id, focus_area_id, is_primary)
exercise_style_directions (exercise_id, style_direction_id, is_primary)
exercise_target_groups (exercise_id, target_group_id, is_primary)
exercise_age_groups (exercise_id, age_group) -- Enum: Minis, Kinder, Schüler, Teenager, Erwachsene
-- Fähigkeiten-Zuordnung (mit Level)
exercise_skills (
exercise_id, skill_id,
is_primary, intensity,
required_level INT, -- Voraussetzung (1-5)
target_level INT, -- Ziel (1-5)
UNIQUE (exercise_id, skill_id) -- Migration 020
)
-- Varianten & Medien (028+ Embed/Datei; 045+ optional media_asset_id → media_assets)
exercise_variants (id, exercise_id, name, description, ...)
exercise_media (id, exercise_id, media_asset_id NULL FK, embed_url, file_path, )
media_assets (id, sha256, visibility, club_id, lifecycle_state, copyright_notice, storage_key, )
platform_media_storage (id, local_relative_root, )
```
### Trainingsrahmenprogramm Bibliothek (Migrationen **035036**)
Kopf ohne Gruppenbindung (`training_framework_programs`), Ziele, Slots. Slotspezifischer Ablauf liegt nach **037** nicht mehr in eigener Übungstabelle, sondern in **`training_units`** mit **`framework_slot_id`** — siehe nächster Abschnitt.
```sql
training_framework_programs ( focus_area_id, style_direction_id, visibility, club_id, created_by )
training_framework_goals (framework_program_id, sort_order, title, notes)
training_framework_slots (framework_program_id, sort_order, title, notes, training_unit_id -- ungenutzt)
training_framework_program_training_types (framework_program_id, training_type_id)
training_framework_program_target_groups (framework_program_id, target_group_id)
```
### Training Planning & RahmenBlueprint (Migrationen 006, 031, **037**)
Geplante Einheit und **RahmenSlotBlueprint** teilen sich **`training_units`** und den strukturierten Ablauf über **Sektionen** (031). BlueprintZeilen haben **`framework_slot_id`** gesetzt (genau eine Zeile pro Slot); KalenderZeilen haben **`framework_slot_id IS NULL`** und **`group_id` / `planned_date`** gesetzt. Kopien aus dem Rahmen können **`origin_framework_slot_id`** setzen.
```sql
training_units (
id,
group_id INT NULL REFERENCES training_groups(id), -- Pflicht für KalenderZeilen (CHECK)
planned_date DATE NULL, -- Pflicht für KalenderZeilen (CHECK)
planned_time_start, planned_time_end, planned_focus,
actual_date, actual_time_start, actual_time_end, attendance_count,
status, notes, trainer_notes,
created_by, plan_template_id REFERENCES training_plan_templates(id),
framework_slot_id INT NULL REFERENCES training_framework_slots(id) ON DELETE CASCADE,
origin_framework_slot_id INT NULL REFERENCES training_framework_slots(id) ON DELETE SET NULL,
)
training_unit_sections (
training_unit_id, order_index, title, guidance_notes,
source_template_section_id REFERENCES training_plan_template_sections(id)
)
training_unit_section_items (
section_id, order_index, item_type CHECK ('exercise'|'note'),
exercise_id, exercise_variant_id, planned_duration_min, actual_duration_min,
notes, modifications, note_body
)
```
**Legacy (Migration 006, für ältere Codepfade noch referenzierbar):** `training_unit_exercises`; produktiver Standardablauf liegt in **Sections/Items**.
**Trainingsvorlagen (031):** `training_plan_templates`, `training_plan_template_sections`.
```sql
exercise_blocks (id, name, description, created_by, club_id, ...) -- Migration 017
```
### Progressionsgraph Übung → Übung (Migrationen 032034)
Separater gerichteter Graph **zwischen** Übungen (nicht zu verwechseln mit Varianten-Reihen **innerhalb** einer Übung, Migration 014). Detail-DDL und REST siehe `technical/TRAINING_FRAMEWORK_SPEC.md` §3.
```sql
exercise_progression_graphs (
id, name, description, visibility, club_id, created_by,
created_at, updated_at
)
exercise_progression_edges (
id, graph_id,
from_exercise_id, to_exercise_id,
from_exercise_variant_id, -- nullable (Migration 034)
to_exercise_variant_id,
edge_type, -- z. B. next_exercise, sibling
notes, -- Migration 033
created_at
)
```
### MediaWiki Import (Migration 018)
```sql
wiki_import_log (
id, category, import_type, import_status,
items_total, items_imported, items_skipped, items_failed,
error_log JSONB,
started_at, finished_at,
imported_by
)
wiki_import_references (
id, wiki_page_id, entity_type, local_id,
created_at, updated_at
)
```
**Import-Typen:**
- `exercise` - Übungen aus `Kategorie:Übungen`
- `skill` - Fähigkeiten aus `Kategorie:Fähigkeitsbeschreibung` (veraltet, nutze Migration 023)
- `method` - Trainingsmethoden aus `Kategorie:Methodenbeschreibung`
---
## Indizes
### Performance-Indizes
```sql
-- Hierarchie-Lookups
CREATE INDEX idx_style_directions_focus_area ON style_directions(focus_area_id);
CREATE INDEX idx_skill_categories_main_category ON skill_categories(main_category_id);
CREATE INDEX idx_skills_category ON skills(category_id);
CREATE INDEX idx_skills_main_category ON skills(main_category_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_style_directions_exercise ON exercise_style_directions(exercise_id);
CREATE INDEX idx_exercise_style_directions_style ON exercise_style_directions(style_direction_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);
CREATE INDEX idx_exercise_skills_exercise ON exercise_skills(exercise_id);
CREATE INDEX idx_exercise_skills_skill ON exercise_skills(skill_id);
-- Import-Tracking
CREATE INDEX idx_wiki_import_references_wiki_page ON wiki_import_references(wiki_page_id);
CREATE INDEX idx_wiki_import_references_entity ON wiki_import_references(entity_type, local_id);
```
---
## Kaskadier-Regeln
### Fokusbereich löschen
**Verhalten:** `RESTRICT` (Löschen verweigern wenn verwendet)
**Prüfung:**
```sql
SELECT COUNT(*) FROM style_directions WHERE focus_area_id = :id
SELECT COUNT(*) FROM exercise_focus_areas WHERE focus_area_id = :id
```
**Alternativen:**
1. Umrouten: Alle abhängigen Stile auf neuen Fokusbereich setzen
2. Archivieren: Status auf 'archived' setzen statt löschen
### Stil löschen
**Verhalten:** `RESTRICT` wenn Zielgruppen oder Übungen zugeordnet
**Umrouten-Workflow:**
1. Admin wählt Ziel-Stil
2. System aktualisiert:
- `training_style_target_groups.style_direction_id`
- `exercise_style_directions.style_direction_id`
3. Löschen erlaubt
### Zielgruppe löschen
**Verhalten:** `SET NULL` oder `CASCADE` in `exercise_target_groups`
**Prüfung:**
```sql
SELECT COUNT(*) FROM training_style_target_groups WHERE target_group_id = :id
SELECT COUNT(*) FROM exercise_target_groups WHERE target_group_id = :id
```
### Skill löschen
**Verhalten:** `CASCADE` zu `skill_level_definitions`, `RESTRICT` wenn in `exercise_skills`
**Prüfung:**
```sql
SELECT COUNT(*) FROM exercise_skills WHERE skill_id = :id
```
---
## Nächste Schritte
- [ ] Level-Definitionen aus Fähigkeitsmatrix extrahieren (optional)
- [ ] Skills-Import testen mit Übungs-Import
- [ ] Migration 024: Skills-Beschreibungen aus Wiki importieren
- [ ] Admin-UI für Skill-Kategorien (CRUD)
- [ ] Skill-Filter in Übungssuche integrieren
---
**Letzte Aktualisierung:** 2026-04-27
**Verantwortlich:** Claude Code
**Review:** Pending