Merge pull request 'feat(exercises): update to version 0.8.64 and enhance inline media functionality' (#25) from develop into main
All checks were successful
Deploy Production / deploy (push) Successful in 36s
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 31s
All checks were successful
Deploy Production / deploy (push) Successful in 36s
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 31s
Reviewed-on: #25
This commit is contained in:
commit
d82b805ffa
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
---
|
||||
|
|
|
|||
|
|
@ -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“.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.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)
|
||||
|
|
|
|||
|
|
@ -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**)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user