feat(exercises): update to version 0.8.64 and enhance inline media functionality #25

Merged
Lars merged 1 commits from develop into main 2026-05-08 13:28:30 +02:00
7 changed files with 102 additions and 60 deletions

View File

@ -1,7 +1,7 @@
# Shinkan Jinkendo - Projekt-Status
**Stand:** 2026-05-07
**Version (Code):** 0.8.59 (`backend/version.py`, APP_VERSION)
**Stand:** 2026-05-08
**Version (Code):** 0.8.64 (`backend/version.py`, APP_VERSION)
**DB-Schema-Version:** `20260508049` (`backend/version.py`, DB_SCHEMA_VERSION)
**Branch:** develop
@ -9,21 +9,21 @@
## Executive Summary
**Aktueller Meilenstein (Medien):** Das **Medien-Archiv** (`media_assets` + `exercise_media.media_asset_id`) ist **produktiv nutzbar**: zentrale Bibliothek **`/media`** (Kacheln/Liste, Filter inkl. Lifecycle, Suche/Tags, Copyright, Bulk-Lifecycle und Bulk-PATCH), **Verknüpfung aus dem Archiv** in der Übungsbearbeitung (`POST …/media/from-asset`), **deduplizierter Speicher** unter **`library/…`** (Vereinsordner aus Name, Medienkind-Unterordner, Governance-Umzug bei Sichtbarkeitsänderung), **Papierkorb & Lifecycle** (Reaktivierung, Soft-Trash, Superadmin-Purge). **Governance:** Sichtbarkeit **`official`** nur noch **Superadmin** (Übungen und Medien); Plattform-Admin wie Trainer für Vereins-/Private-Inhalte. **Vereinsübungen** mit Datei-Assets: **Copyright-Pflicht** (API/UI). **Aktiver Verein:** Dropdown, Profilfeld `active_club_id`, Header `X-Active-Club-Id` und `effective_club_id` sind nach **0.8.59** synchronisiert (inkl. Plattform-Admin ohne Header beim ersten Request).
**Aktueller Meilenstein (Medien):** Das **Medien-Archiv** (`media_assets` + `exercise_media.media_asset_id`) ist **produktiv nutzbar**: zentrale Bibliothek **`/media`** (Kacheln/Liste, Filter inkl. Lifecycle, Suche/Tags, Copyright, Bulk-Lifecycle und Bulk-PATCH), **Verknüpfung aus dem Archiv** in der Übungsbearbeitung (`POST …/media/from-asset`), **deduplizierter Speicher** unter **`library/…`** (Vereinsordner aus Name, Medienkind-Unterordner, Governance-Umzug bei Sichtbarkeitsänderung), **Papierkorb & Lifecycle** (Reaktivierung, Soft-Trash, Superadmin-Purge), plus **Inline-Medien im Rich-Text** (Modal-Picker, Größenwahl, Drag&Drop mit Auto-Scroll, Vorschau-/Rückweg-UX). **Governance:** Sichtbarkeit **`official`** nur noch **Superadmin** (Übungen und Medien); Plattform-Admin wie Trainer für Vereins-/Private-Inhalte. **Vereinsübungen** mit Datei-Assets: **Copyright-Pflicht** (API/UI). **Aktiver Verein:** Dropdown, Profilfeld `active_club_id`, Header `X-Active-Club-Id` und `effective_club_id` sind nach **0.8.59** synchronisiert (inkl. Plattform-Admin ohne Header beim ersten Request).
**Parallel weiter relevant:** **Trainingsrahmenprogramm** (036037), **Progressionsgraph** (032034) — siehe **`TRAINING_FRAMEWORK_SPEC.md`**.
**Referenz:** [`library/FEATURES_DELIVERED_2026-Q2.md`](library/FEATURES_DELIVERED_2026-Q2.md) §12 · Medien-Norm: [`technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md`](technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md) (inkl. **§11 Inline-Medien**, Planung)
**Referenz:** [`library/FEATURES_DELIVERED_2026-Q2.md`](library/FEATURES_DELIVERED_2026-Q2.md) §12 · Medien-Norm: [`technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md`](technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md) (inkl. **§11 Inline-Medien**, umgesetzt)
**Nächste Schritte — Medien & Archiv** (Stand 2026-05-07, für **neue Session**):
**Nächste Schritte — Medien & Archiv** (Stand 2026-05-08, für **neue Session**):
1. ~~**Übung → `official` Promotion** inkl. Medien-Anhebung + **Copyright-Pflicht** bei `official` (Spec §4.2)~~ — umgesetzt (0.8.47).
2. ~~**Eigenständige Medienmanager-Seite**~~**Basis umgesetzt** (`/media`); Ausbau nach Bedarf: Quotas, feinere Bulk-Workflows, Sichtbarkeits-PATCH in der UI vereinheitlichen.
3. **Tests & Observability:** gezielte pytest-Abdeckung für Archiv/Verknüpfen/Lifecycle; Retention-Job (`scripts/media_retention_job.py`) in Betrieb/Doku verankern.
4. **S3 / externes Backend** hinter Speicher-Abstraktion (Spec §7) — nach stabiler Nutzung lokaler/NAS-Pfade.
5. **Inline-Medien im Fließtext** (Spec **§11**) — nächste inhaltliche Ausbaustufe: Platzhalter-Syntax, ein zentraler Renderer, keine zweite Governance; Archiv-Basis ist gelegt.
5. **Inline-Medien im Fließtext (Spec §11)** — **Basis umgesetzt (0.8.600.8.64)**: Platzhalter-Syntax, zentraler Renderer, Modal-Picker, Drag&Drop + Auto-Scroll; offen: weitere UX-Politur und ggf. strategischer Umbau auf reine Asset-Referenz (separat zu entscheiden).
**Inline:** verbindliche Leitplanken **`MEDIA_ASSETS_AND_ARCHIVE_SPEC.md` §11**; Umsetzung bewusst nach stabiler Archiv-/Governance-Basis.
**Inline:** verbindliche Leitplanken **`MEDIA_ASSETS_AND_ARCHIVE_SPEC.md` §11**; Umsetzung aktiv im Produktpfad (RTE + Anzeige).
---
@ -107,7 +107,7 @@ Die exakten Zahlen hängen von der Umgebung ab (siehe Admin/DB). Die Skills/Übu
- [x] **Medien:** Promotion Übung↔Medien + Copyright-Pflicht `official` / Vereins-Copyright-Regeln (Spec §4.2, Übungen+Assets)
- [x] **Medien:** Medienbibliothek `/media` (Filter, Tags, Copyright, Bulk-Lifecycle/PATCH, Lesemodus für eingeschränkte Rollen bei `official`)
- [x] **Medien:** Speicherpfad-Konvention `library/…`, Plattform-Speicher-Konfiguration (`platform_media_storage`), Mandanten-/Governance-Umzug bei Asset-Änderungen
- [ ] **Medien:** Inline im Fließtext — Spec §11 (nächste Session / Backlog)
- [x] **Medien:** Inline im Fließtext — Spec §11 (Platzhalter, Modal-Picker, Größenwahl, Drag&Drop + Auto-Scroll, Rückweg aus Vorschau)
- [ ] Admin-UI für Skill-Kategorien (CRUD) falls noch offen
- [ ] Responsive Design / Dark Mode / PWA
- [ ] KI-Suche (`ai_search`) über reine Volltextsuche hinaus
@ -150,17 +150,17 @@ Deployment der oben genannten Migrationen und Datenabgleich nach internem Prozes
| Dokument | Pfad | Stand | Status |
|----------|------|-------|--------|
| Lieferliste Q2 2026 | `library/FEATURES_DELIVERED_2026-Q2.md` | 2026-05-07 | ✅ Aktualisiert (§12 Medien 0.8.410.8.59) |
| Lieferliste Q2 2026 | `library/FEATURES_DELIVERED_2026-Q2.md` | 2026-05-08 | ✅ Aktualisiert (§12 Medien inkl. Inline 0.8.600.8.64) |
| Trainingsrahmen + Graph | `technical/TRAINING_FRAMEWORK_SPEC.md` | 2026-05-05 | ✅ §2 Blueprint |
| Anforderungen (Index) | `functional/SHINKAN_REQUIREMENTS.md` | 2026-04-27 | ✅ Neu |
| Database Schema | `technical/DATABASE_SCHEMA.md` | 2026-05-07 | ✅ Hinweis 040046 Medien (Kurz) |
| Domain Model | `functional/DOMAIN_MODEL.md` | 2026-05-07 | ✅ Abschnitt Medien-Archiv |
| API Übungen | `technical/EXERCISES_API_SPEC.md` | 2026-04-30 | ✅ Ergänzt Progressions-API |
| API Übungen | `technical/EXERCISES_API_SPEC.md` | 2026-05-08 | ✅ Medien/Inline-Workflow ergänzt |
| Frontend Routing | `technical/EXERCISES_FRONTEND_ROUTING.md` | 2026-04-30 | ✅ Ergänzt UI-Hinweise |
| Search & Filter | `technical/SEARCH_FILTER_SPEC.md` | 2026-04-27 | ✅ Aktualisiert (Liste UX) |
| Media Upload | `technical/MEDIA_UPLOAD_SPEC.md` | 2026-05-07 | ✅ Verweis Archiv/Inline |
| Medien-Archiv & Lifecycle | `technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md` | 2026-05-07 | ✅ Ist-Changelog + §11 Inline geplant |
| Projektstatus | `PROJECT_STATUS.md` | 2026-05-07 | ✅ Medien + Tenant-Dropdown |
| Medien-Archiv & Lifecycle | `technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md` | 2026-05-08 | ✅ Ist-Changelog + §11 Inline erweitert |
| Projektstatus | `PROJECT_STATUS.md` | 2026-05-08 | ✅ auf 0.8.64 aktualisiert |
---
@ -171,4 +171,4 @@ Deployment der oben genannten Migrationen und Datenabgleich nach internem Prozes
---
**Letzte Aktualisierung:** 2026-05-07 (Medien-Doku abgestimmt, §12 FEATURES, Handover)
**Letzte Aktualisierung:** 2026-05-08 (Inline-/Medien-Workflow 0.8.600.8.64 konsolidiert)

View File

@ -1,7 +1,7 @@
# Shinkan Jinkendo - Fachliches Domänenmodell
**Version:** 0.4.4
**Stand:** 2026-05-07 (Medien-Archiv **`media_assets`** / Bibliothek **`/media`** im Ist; **Inline-Medien** Fließtext geplant — `MEDIA_ASSETS_AND_ARCHIVE_SPEC.md` §11)
**Stand:** 2026-05-08 (Medien-Archiv **`media_assets`** / Bibliothek **`/media`** im Ist; **Inline-Medien** im Fließtext umgesetzt — `MEDIA_ASSETS_AND_ARCHIVE_SPEC.md` §11)
**Basis:** `shinkan_anforderungsdokument_entwurf.md` + Fähigkeitsmatrix
---

View File

@ -1,7 +1,7 @@
# Gelieferte Features & technische Basis (Q2 2026)
**Stand:** 2026-05-07
**Referenz:** `backend/version.py`**APP_VERSION 0.8.59**, **DB_SCHEMA_VERSION** siehe dort
**Stand:** 2026-05-08
**Referenz:** `backend/version.py`**APP_VERSION 0.8.64**, **DB_SCHEMA_VERSION** siehe dort
Dieses Dokument bündelt die in der Entwicklungsphase erreichten **lieferbaren** Funktionen und die zugehörigen **technischen Artefakte**. TrainingsrahmenBibliothek + SlotBlueprint: **`technical/TRAINING_FRAMEWORK_SPEC.md`** §2. **Progressionsgraph zwischen Übungen** (Zwischenstand, Grenzen): **§§34**. **Medien-Archiv & Bibliothek:** Abschnitt **12** unten + **`MEDIA_ASSETS_AND_ARCHIVE_SPEC.md`**. Detail-Spezifikationen bleiben in den verlinkten Pfaden unter `.claude/docs/technical/` und `.claude/docs/functional/`.
@ -123,7 +123,7 @@ Hinweis: Es gibt **keine** separaten Routen `/exercises/:id/variants/...` — Be
---
## 12. Medien-Archiv & Medienbibliothek (Migration **045** ff., App ca. **0.8.410.8.59**)
## 12. Medien-Archiv & Medienbibliothek (Migration **045** ff., App ca. **0.8.410.8.64**)
Einzelnorm: **`technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md`**. Kurzüberblick geliefert:
@ -140,9 +140,13 @@ Einzelnorm: **`technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md`**. Kurzüberblick gel
- **Governance:** **`visibility=official`** für Übungen und schützenswerte Medien-Operationen im Wesentlichen **Superadmin**; Plattform-Admin entspricht nicht automatisch Superadmin.
- **Mandant:** aktiver Verein über Profil + **`X-Active-Club-Id`**; Sync UI nach **0.8.59** (siehe `tenant_context` / `AuthContext` / `activeClub.js`).
### 12.3 Geplant (nicht geliefert)
### 12.3 Inline-Medien im Fließtext (geliefert in 0.8.600.8.64)
- **Inline-Medien** in Fließtextfeldern gemäß **`MEDIA_ASSETS_AND_ARCHIVE_SPEC.md` §11** — Verweis auf `exercise_media.id`, zentraler Renderer; siehe **`docs/HANDOVER.md` §5**.
- Platzhalter-Syntax: `{{exerciseMedia:id}}` (normalisiert auf `data-shinkan-exercise-media`), Validierung gegen `exercise_media` der aktuellen Übung.
- Zentraler Anzeigepfad über `ExerciseRichTextBlock` inkl. Sanitize und Lifecycle-Hinweisen.
- RTE-Workflow: Modal „Medien im Text“ (Mediathek + Upload), separates „Embed im Text“-Modal, Größenwahl (`small|medium|full`) und sprechende Platzhalter-Captions.
- Bearbeiten: kompakte Medien-Kacheln + Drag&Drop in Textfelder; Auto-Scroll beim Drag aktiv.
- UX-Schutz: Warnung bei Wechsel „Ansehen“ mit ungespeicherten Änderungen; Detailseite bietet Rückweg zur Bearbeitung.
---
@ -152,7 +156,7 @@ Einzelnorm: **`technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md`**. Kurzüberblick gel
- Progressions-Serien als **Blöcke** (angekündigt; Voraussetzung: `prerequisite_variant_id` / `progression_level` vorhanden).
- Serverseitige **Suchvorschläge** (Autocomplete-Endpoint), falls datalist nicht reicht.
- Optional: Streaming/chunked Upload für sehr große Videos (RAM-Thema).
- **Medien:** Inline Fließtext §11; Retention-Job-Betrieb; pytest-Abdeckung Archiv; S3-Adapter (Spec §7).
- **Medien:** Retention-Job-Betrieb; pytest-Abdeckung Archiv; S3-Adapter (Spec §7); ggf. strategischer Entscheid „exercise_media-Verknüpfung vs. reine Asset-Referenz“.
---

View File

@ -1,10 +1,11 @@
# Exercises API Specification
**Version:** 1.4
**Datum:** 2026-04-30
**Version:** 1.5
**Datum:** 2026-05-08
**Status:** Teilweise implementiert (Liste mit Filtern + Varianten + Medienlimits + Progressionsgraphen siehe Code)
**Autor:** Claude Code
**Änderungen v1.4:** Endpoints **`/exercise-progression-graphs`** inkl. Kanten, **`POST …/edges/sequence`**, **`POST …/edges/delete-batch`** — Detailtabellen siehe **`TRAINING_FRAMEWORK_SPEC.md`** §3.3
**Änderungen v1.5:** Medien-/Inline-Workflow aktualisiert (Modal-Picker, Drag&Drop UX im Frontend), Klarstellung zu `context` (legacy/optional), Hinweise zu Platzhaltern in Rich-Text-Feldern.
**Änderungen v1.3:** `GET /exercises` erweiterte Query-Parameter (`include_variants`, Multi-Filter, `ai_search`-Platzhalter); Dokumentation angepasst
**Änderungen v1.2:** KI-Assistenz Endpoints, Skill-Level-System (benannte Stufen), intensity als low/medium/high
**Änderungen v1.1:** Exercise Blocks Endpoints, Permissions dokumentiert, age_groups korrigiert
@ -335,7 +336,8 @@ Lightweight-Liste; bei `include_variants=true` zusätzlich z.B.:
### `PUT /exercises/{id}`
**Request Body:** Same as POST (all fields optional except id)
**Request Body:** Same as POST (all fields optional except id)
**Inline-Hinweis:** Rich-Text-Felder (`summary`, `goal`, `execution`, `preparation`, `trainer_notes` sowie `execution_changes` bei Varianten) unterstützen Platzhalter `{{exerciseMedia:id}}`, die serverseitig auf kanonisches Span-Markup normalisiert werden. Beim erstmaligen Anlegen ohne vorhandene `exercise_media` wird dies mit 400 abgelehnt.
**Response:** `200 OK` (full exercise object)
@ -459,7 +461,7 @@ Lightweight-Liste; bei `include_variants=true` zusätzlich z.B.:
- `media_type` (enum, required) - `image | video | document | sketch`
- `title` (string, optional)
- `description` (string, optional)
- `context` (enum, optional) - `ablauf | detail | trainer_hint`
- `context` (enum, optional, legacy UI-Feld) - `ablauf | detail | trainer_hint`
**Entweder `file` ODER `embed_url` (nicht beides).**
@ -505,11 +507,12 @@ Lightweight-Liste; bei `include_variants=true` zusätzlich z.B.:
{
"title": "Neuer Titel",
"description": "Neue Beschreibung",
"is_primary": true,
"context": "detail"
"is_primary": true
}
```
`context` bleibt backendseitig kompatibel, wird in aktuellen Bearbeitungsflüssen jedoch nicht mehr aktiv zur UI-Zuordnung verwendet (Inline-Platzhalter priorisiert).
**Response:** `200 OK` (media object)
---
@ -893,6 +896,7 @@ Trainer muss im Frontend aktiv übernehmen.
- File size: max 50MB
- Mime types: `image/jpeg, image/png, image/gif, video/mp4, application/pdf`
- Embed platforms: `youtube, instagram, vimeo`
- Inline-Rich-Text: `{{exerciseMedia:id}}` bzw. `data-shinkan-exercise-media="<id>"`; optional `data-shinkan-exercise-media-size="small|medium|full"` für Layout.
### Exercise Block
- `name`: 3-200 chars
@ -947,6 +951,6 @@ Trainer muss im Frontend aktiv übernehmen.
---
**Version:** 1.0
**Letzte Änderung:** 2026-04-24
**Status:** DRAFT - Awaiting Review
**Version:** 1.5
**Letzte Änderung:** 2026-05-08
**Status:** Living Spec

View File

@ -1,7 +1,7 @@
# Medien-Assets, Archiv & Lebenszyklus (Single Source of Truth)
**Status:** verbindlich für Design, API und DB-Migrationen zum Thema Medien
**Stand:** 2026-05-07 (Ist-Changelog Medien bis 0.8.59; §11 Inline geplant)
**Stand:** 2026-05-08 (Ist-Changelog Medien bis 0.8.64; §11 Inline umgesetzt und erweitert)
**ersetzt/ergänzt:** operatives Upload-Format/Größen siehe weiterhin `MEDIA_UPLOAD_SPEC.md`
**normative Governance:** Sichtbarkeit & Mandanten wie `ACCESS_LAYER_AND_GOVERNANCE_PLAN.md`; bei Abweichung zwischen Dokumenten hat der Zugriffsplan Vorrang, **außer** dieses Dokument präzisiert explizit nur den Medien-Domain.
@ -186,6 +186,8 @@ Das widerspricht **nicht** dem Papierkorb: wer kein Recht hat, ein Asset **globa
| Datum | Änderung |
|-------|----------|
| 2026-05-08 | **0.8.64:** Bearbeiten-UX erweitert: Drag&Drop mit Auto-Scroll in Richtung Rich-Text, Medienliste in Kachel-Grid, Warnhinweis vor Wechsel „Ansehen“ bei ungespeicherten Änderungen + Rückweg „Zurück zur Bearbeitung“, Katalogvorschau ohne separaten Anhangs-Medienstreifen. |
| 2026-05-08 | **0.8.63:** Inline-Medien-Picker als Modal (Mediathek/Upload + separates Embed-Modal), Größenwahl `small|medium|full`, Platzhalter-Caption (`data-shinkan-exercise-media-caption`), Wiederverwendung bereits verknüpfter Assets im Picker, kompakter Medienbereich mit Drag&Drop in Textfelder. |
| 2026-05-08 | **0.8.60 §11:** Inline in Übungstexten (`{{exerciseMedia:id}}` / `data-shinkan-exercise-media`); Server-Normalisierung + Validierung; Client-Sanitize und zentraler Block-Renderer (`ExerciseRichTextBlock`); CREATE ohne bestehende `exercise_media` lehnt Platzhalter ab. |
| 2026-05-07 | **0.8.59:** Dokumentation — Aktiver Verein (Profil/Header/`effective_club_id`) für Plattform-Admin und UI-Dropdown synchron; kein fachliches Archiv-Schema-Change. |
| 2026-05-07 | **0.8.58:** Medien **`official`:** Lifecycle schwerpunktmäßig **Superadmin** (nicht Plattform-Admin); Bearbeitungsdialog Bibliothek für andere Rollen **Lesemodus**; Superadmin-Upload: Vereinskontext folgt aktiv gesetztem Verein / `effective_club_id`. |
@ -209,6 +211,7 @@ Das widerspricht **nicht** dem Papierkorb: wer kein Recht hat, ein Asset **globa
### 11.2 Platzhalter-Konvention (**festgelegt**)
- **Kanonisches Markup nach Speichern:** `<span data-shinkan-exercise-media="<numerische exercise_media.id>" class="shinkan-inline-media"></span>`.
- **Erweiterung (UI/Lesbarkeit):** optionales Attribut `data-shinkan-exercise-media-size="small|medium|full"` (Default `medium`) und optional `data-shinkan-exercise-media-caption="..."` für sprechende Platzhalter-Chips im Editor.
- **Kurzsyntax (Eingabe/Import):** `{{exerciseMedia:123}}` — Server normalisiert beim Speichern (PUT Übung / Varianten) in das kanonische `span`; **CREATE** ohne bestehende `exercise_media`-Zeilen verwirft Platzhalter mit **400**.
### 11.3 Rendering & Sicherheit
@ -218,14 +221,14 @@ Das widerspricht **nicht** dem Papierkorb: wer kein Recht hat, ein Asset **globa
### 11.4 Koexistenz mit Sektions-Medien
- **Liste + Inline** dürfen dasselbe `exercise_media` referenzieren (ein Player an zwei Stellen) Produktentscheidung: später optional „Duplikat vermeiden“-Hinweis in der UI.
- **Liste + Inline** dürfen dasselbe `exercise_media` referenzieren (ein Player an zwei Stellen).
- Stand 0.8.64: Katalog-/Vorschaupfade priorisieren Inline-Darstellung; ein zusätzlicher Anhangsblock wird dort nicht mehr parallel erzwungen.
- Import/Wiki: vor großen Content-Migrationen **Syntax festlegen**, damit nicht irreversibel „falsches“ HTML importiert wird.
### 11.5 Wann umsetzen (Reihenfolge)
1. ~~**Erledigt (Basis):** Medien-Archiv, `media_assets`, Upload/Dedupe, Speicherpfad, Papierkorb, Bibliothek `/media`, Verknüpfung `from-asset`, Governance `official`/Copyright.~~
2. **Umgesetzt (Stand 0.8.60):** Platzhalter/Kurzsyntax + zentraler Frontend-Render-Pfad + Server-Validierung gemäß §11.111.4; Editor-Einstieg „Medium einfügen“ in Übungsformular (Bearbeitungsmodus).
(Optional Feinschliff: komfortablerer Medien-Picker statt Prompt bei mehreren Medien.)
2. **Umgesetzt (Stand 0.8.64):** Platzhalter/Kurzsyntax + zentraler Frontend-Render-Pfad + Server-Validierung gemäß §11.111.4; Editor-Einstieg „Medium einfügen“ inkl. Modal-Picker (Mediathek/Upload), separates Embed-Modal, Größenwahl und Drag&Drop aus der Medienliste.
3. **Editor:** kein Zwang zum vollen Block-Editor vorab; **Platzhalter im bestehenden RTE** ist der vorgesehene schlanke Einstieg.
### 11.6 Refactor-Vermeidung (jetzt schon)

View File

@ -83,7 +83,7 @@ frontend/src/
**Siehe:** `backend/version.py` (`APP_VERSION`, `DB_SCHEMA_VERSION`, `MODULE_VERSIONS`) und `.claude/docs/PROJECT_STATUS.md`.
Kurz (Stand 2026-05-07): App **0.8.59**, DBSchemaVersion siehe **`backend/version.py`**; Kern: Übungen, Varianten, **Medien-Archiv & Bibliothek (`/media`)**, Mandanten-Sync aktiver Verein, Planung mit Sektionen, **Trainingsrahmen Bibliothek + SlotBlueprint** (036037), Progressionsgraph, Reifegrad/MatrixStack — Details `PROJECT_STATUS.md`, `docs/HANDOVER.md`, `MEDIA_ASSETS_AND_ARCHIVE_SPEC.md` (§11 **Inline geplant**).
Kurz (Stand 2026-05-08): App **0.8.64**, DBSchemaVersion siehe **`backend/version.py`**; Kern: Übungen, Varianten, **Medien-Archiv & Bibliothek (`/media`)**, **Inline-Medien im Rich-Text** (Modal-Picker, Größenwahl, Drag&Drop + Auto-Scroll), Mandanten-Sync aktiver Verein, Planung mit Sektionen, **Trainingsrahmen Bibliothek + SlotBlueprint** (036037), Progressionsgraph, Reifegrad/MatrixStack — Details `PROJECT_STATUS.md`, `docs/HANDOVER.md`, `MEDIA_ASSETS_AND_ARCHIVE_SPEC.md` (§11 umgesetzt).
### Log (Auszug)
@ -108,7 +108,7 @@ Kurz (Stand 2026-05-07): App **0.8.59**, DBSchemaVersion siehe **`backend/
- `exercises` - Übungen (Kernobjekt)
- `exercise_variants` - Übungsvarianten
- `exercise_skills` - M:N Übung ↔ Fähigkeit
- `exercise_media` - Medien (Bilder, Videos); Zielbild Archiv & Lifecycle: **`MEDIA_ASSETS_AND_ARCHIVE_SPEC.md`**. **Inline im Fließtext** (Übungstexte): geplant §11 derselben Spec — Anker `exercise_media.id`, einheitlicher Render-Pfad; noch nicht implementiert.
- `exercise_media` - Medien (Bilder, Videos); Zielbild Archiv & Lifecycle: **`MEDIA_ASSETS_AND_ARCHIVE_SPEC.md`**. **Inline im Fließtext** (Übungstexte) ist implementiert (Spec §11): Anker `exercise_media.id`, einheitlicher Render-Pfad.
**Trainingsplanung / Rahmen:**
- `training_plan_templates` + Sektionsvorlagen — Mikrovorlage pro Einheit (Migration **031**)

View File

@ -19,6 +19,26 @@ from club_tenancy import (
router = APIRouter(prefix="/api", tags=["clubs"])
def _blank_to_none(value: Any) -> Any:
"""Frontend sendet häufig '' für optionale Felder — ungültig für PostgreSQL TIME/INTEGER."""
if value is None:
return None
if isinstance(value, str) and value.strip() == "":
return None
return value
def _optional_int(value: Any) -> Optional[int]:
v = _blank_to_none(value)
if v is None:
return None
try:
i = int(v)
except (TypeError, ValueError):
return None
return i if i > 0 else None
# ── List Clubs ────────────────────────────────────────────────────────
@router.get("/clubs")
def list_clubs(
@ -581,9 +601,16 @@ def create_training_group(data: dict, tenant: TenantContext = Depends(get_tenant
if not club_id or not name:
raise HTTPException(400, "club_id und name sind Pflichtfelder")
trainer_id = data.get("trainer_id")
if trainer_id in (None, "", 0) and role in ("trainer", "user"):
trainer_id = profile_id
raw_tid = data.get("trainer_id")
if role in ("trainer", "user"):
parsed_tid = _optional_int(raw_tid)
trainer_id = parsed_tid if parsed_tid is not None else profile_id
else:
trainer_id = _optional_int(raw_tid)
division_id = _optional_int(data.get("division_id"))
co_raw = data.get("co_trainer_ids")
co_trainer_ids = [] if co_raw is None else co_raw
with get_db() as conn:
cur = get_cursor(conn)
@ -610,19 +637,19 @@ def create_training_group(data: dict, tenant: TenantContext = Depends(get_tenant
) RETURNING id
""",
(
club_id,
data.get("division_id"),
int(club_id),
division_id,
name,
data.get("focus"),
data.get("level"),
data.get("age_group"),
data.get("weekday"),
data.get("time_start"),
data.get("time_end"),
data.get("location"),
_blank_to_none(data.get("focus")),
_blank_to_none(data.get("level")),
_blank_to_none(data.get("age_group")),
_blank_to_none(data.get("weekday")),
_blank_to_none(data.get("time_start")),
_blank_to_none(data.get("time_end")),
_blank_to_none(data.get("location")),
trainer_id,
data.get("co_trainer_ids"),
data.get("status", "active"),
co_trainer_ids,
data.get("status") or "active",
),
)
@ -662,6 +689,10 @@ def update_training_group(group_id: int, data: dict, tenant: TenantContext = Dep
if not allowed:
raise HTTPException(403, "Keine Berechtigung")
co_upd = data.get("co_trainer_ids")
if co_upd is None:
co_upd = []
cur.execute(
"""
UPDATE training_groups SET
@ -682,17 +713,17 @@ def update_training_group(group_id: int, data: dict, tenant: TenantContext = Dep
""",
(
data.get("name"),
data.get("division_id"),
data.get("focus"),
data.get("level"),
data.get("age_group"),
data.get("weekday"),
data.get("time_start"),
data.get("time_end"),
data.get("location"),
data.get("trainer_id"),
data.get("co_trainer_ids"),
data.get("status"),
_optional_int(data.get("division_id")),
_blank_to_none(data.get("focus")),
_blank_to_none(data.get("level")),
_blank_to_none(data.get("age_group")),
_blank_to_none(data.get("weekday")),
_blank_to_none(data.get("time_start")),
_blank_to_none(data.get("time_end")),
_blank_to_none(data.get("location")),
_optional_int(data.get("trainer_id")),
co_upd,
data.get("status") or "active",
group_id,
),
)