diff --git a/.claude/docs/PROJECT_STATUS.md b/.claude/docs/PROJECT_STATUS.md index 0087c04..bf53926 100644 --- a/.claude/docs/PROJECT_STATUS.md +++ b/.claude/docs/PROJECT_STATUS.md @@ -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** (036–037), **Progressionsgraph** (032–034) — 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.60–0.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.41–0.8.59) | +| Lieferliste Q2 2026 | `library/FEATURES_DELIVERED_2026-Q2.md` | 2026-05-08 | ✅ Aktualisiert (§12 Medien inkl. Inline 0.8.60–0.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 040–046 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.60–0.8.64 konsolidiert) diff --git a/.claude/docs/functional/DOMAIN_MODEL.md b/.claude/docs/functional/DOMAIN_MODEL.md index a7179f7..0c19ec9 100644 --- a/.claude/docs/functional/DOMAIN_MODEL.md +++ b/.claude/docs/functional/DOMAIN_MODEL.md @@ -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 --- diff --git a/.claude/docs/library/FEATURES_DELIVERED_2026-Q2.md b/.claude/docs/library/FEATURES_DELIVERED_2026-Q2.md index 8ae50f6..118bbff 100644 --- a/.claude/docs/library/FEATURES_DELIVERED_2026-Q2.md +++ b/.claude/docs/library/FEATURES_DELIVERED_2026-Q2.md @@ -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**. Trainingsrahmen‑Bibliothek + Slot‑Blueprint: **`technical/TRAINING_FRAMEWORK_SPEC.md`** §2. **Progressionsgraph zwischen Übungen** (Zwischenstand, Grenzen): **§§3–4**. **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.41–0.8.59**) +## 12. Medien-Archiv & Medienbibliothek (Migration **045** ff., App ca. **0.8.41–0.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.60–0.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“. --- diff --git a/.claude/docs/technical/EXERCISES_API_SPEC.md b/.claude/docs/technical/EXERCISES_API_SPEC.md index 9572d7d..c99b779 100644 --- a/.claude/docs/technical/EXERCISES_API_SPEC.md +++ b/.claude/docs/technical/EXERCISES_API_SPEC.md @@ -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=""`; 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 diff --git a/.claude/docs/technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md b/.claude/docs/technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md index 46736e3..d30a1eb 100644 --- a/.claude/docs/technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md +++ b/.claude/docs/technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md @@ -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:** ``. +- **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.1–11.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.1–11.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) diff --git a/CLAUDE.md b/CLAUDE.md index 10ee6fb..6e926ba 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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**, DB‑Schema‑Version siehe **`backend/version.py`**; Kern: Übungen, Varianten, **Medien-Archiv & Bibliothek (`/media`)**, Mandanten-Sync aktiver Verein, Planung mit Sektionen, **Trainingsrahmen Bibliothek + Slot‑Blueprint** (036–037), Progressionsgraph, Reifegrad/Matrix‑Stack — 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**, DB‑Schema‑Version 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 + Slot‑Blueprint** (036–037), Progressionsgraph, Reifegrad/Matrix‑Stack — 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**, DB‑Schema‑Version 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**) diff --git a/backend/routers/clubs.py b/backend/routers/clubs.py index 2ea9c93..7507709 100644 --- a/backend/routers/clubs.py +++ b/backend/routers/clubs.py @@ -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, ), )