diff --git a/.claude/docs/PROJECT_STATUS.md b/.claude/docs/PROJECT_STATUS.md index c0333bc..0087c04 100644 --- a/.claude/docs/PROJECT_STATUS.md +++ b/.claude/docs/PROJECT_STATUS.md @@ -1,29 +1,29 @@ # Shinkan Jinkendo - Projekt-Status **Stand:** 2026-05-07 -**Version (Code):** 0.8.48 (`backend/version.py`, APP_VERSION) -**DB-Schema-Version:** `20260507045` (u. a. `media_assets`, `platform_media_storage`) +**Version (Code):** 0.8.59 (`backend/version.py`, APP_VERSION) +**DB-Schema-Version:** `20260508049` (`backend/version.py`, DB_SCHEMA_VERSION) **Branch:** develop --- ## Executive Summary -**Aktueller Meilenstein (Medien):** **Medienbibliothek `/media`** ergänzt den Archiv-Picker: **Lifecycle-Filter** (aktiv / Papierkorb / ausgeblendet), **Copyright bearbeiten** (`PATCH`), **Vorschau** inkl. Papierkorb bei Verwaltungsrecht; API-Liste `copyright_notice`; zuvor **§4.2 Promotion official** (Übung + Medien). +**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). **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) · Medien-Norm: [`technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md`](technical/MEDIA_ASSETS_AND_ARCHIVE_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) -**Nächste Schritte — Medien & Archiv** (neu priorisiert, Stand 2026-05-07): +**Nächste Schritte — Medien & Archiv** (Stand 2026-05-07, 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`): Filter, Copyright, Lifecycle-Aktionen; Ausbau: Sichtbarkeit bearbeiten, Bulk, Quotas. -3. **Tests & Observability:** gezielte pytest-Abdeckung für Archiv/Verknüpfen; optional Retention-Job-Dry-Run dokumentieren. +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) — bewusst **nach** Promotion/Copyright und tragfähigem Archiv-Workflow. +5. **Inline-Medien im Fließtext** (Spec **§11**) — nächste inhaltliche Ausbaustufe: Platzhalter-Syntax, ein zentraler Renderer, keine zweite Governance; Archiv-Basis ist gelegt. -**Inline:** Leitplanken in **`MEDIA_ASSETS_AND_ARCHIVE_SPEC.md` §11**; kein Big-Bang vor stabiler Archiv-/Governance-Basis. +**Inline:** verbindliche Leitplanken **`MEDIA_ASSETS_AND_ARCHIVE_SPEC.md` §11**; Umsetzung bewusst nach stabiler Archiv-/Governance-Basis. --- @@ -50,7 +50,7 @@ | **030** | **training_unit_exercises.exercise_variant_id** | ✅ | 🔲 | | **032–034** | **Progressionsgraph Übung→Übung** | ✅ | 🔲 | | **035–037** | **Rahmenprogramm, Bibliothek‑Kopf, Slot‑Blueprint‑Units** | ✅ | 🔲 | -| **040–045** | **u. a. Mitgliedschaften, Übungs-Governance, `media_assets`, Plattform-Speicherpfad** | ✅ | 🔲 | +| **040–046** | **u. a. Mitgliedschaften, Übungs-Governance, `media_assets` (046 z. B. Tags/GIN), Plattform-Speicherpfad** | ✅ Dev | 🔲 Prod | --- @@ -103,10 +103,11 @@ Die exakten Zahlen hängen von der Umgebung ab (siehe Admin/DB). Die Skills/Übu ### 🔲 In Arbeit / Backlog -- [x] **Medien:** Papierkorb (§5), Retention-Job, Archiv-API, „Aus Archiv verknüpfen“, Picker/Vorschau in Übungsbearbeitung (Release 0.8.42 ff.) -- [x] **Medien:** Promotion Übung↔Medien + Copyright-Pflicht `official` (Spec §4.2) — 0.8.47 -- [x] **Medien:** Medienbibliothek `/media` (Lifecycle-Filter, Copyright PATCH, Vorschau); Ausbau Manager/Bulk/S3 — Roadmap -- [ ] **Medien:** Inline im Fließtext — nach Spec §11, nach Promotion/Archiv-Reife +- [x] **Medien:** Papierkorb (§5), Retention-Job, Archiv-API, „Aus Archiv verknüpfen“, Picker/Vorschau in Übungsbearbeitung (0.8.42 ff.) +- [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) - [ ] Admin-UI für Skill-Kategorien (CRUD) – falls noch offen - [ ] Responsive Design / Dark Mode / PWA - [ ] KI-Suche (`ai_search`) über reine Volltextsuche hinaus @@ -137,7 +138,7 @@ Die exakten Zahlen hängen von der Umgebung ab (siehe Admin/DB). Die Skills/Übu ### Dev -Branch `develop`; Migrations bis mindestens **045** auf dem aktuellen Entwicklungsstand; Details in `backend/version.py`. +Branch `develop`; Migrations bis mindestens **046** auf dem aktuellen Entwicklungsstand; Details in `backend/version.py`. ### Prod @@ -149,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-05 | ✅ Aktualisiert (u. a. 036–037) | +| Lieferliste Q2 2026 | `library/FEATURES_DELIVERED_2026-Q2.md` | 2026-05-07 | ✅ Aktualisiert (§12 Medien 0.8.41–0.8.59) | | 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-05 | ✅ Aktualisiert (037) | -| Domain Model | `functional/DOMAIN_MODEL.md` | 2026-05-05 | ✅ Aktualisiert | +| 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 | | 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-04-27 | ✅ Aktualisiert (Limits) | -| Medien-Archiv & Lifecycle | `technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md` | 2026-05-07 | ✅ §11 Inline-Plan, Drift-Tab | -| Projektstatus | `PROJECT_STATUS.md` | 2026-05-07 | ✅ Medien-Meilenstein aktualisiert | +| 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 | --- @@ -170,4 +171,4 @@ Deployment der oben genannten Migrationen und Datenabgleich nach internem Prozes --- -**Letzte Aktualisierung:** 2026-05-07 +**Letzte Aktualisierung:** 2026-05-07 (Medien-Doku abgestimmt, §12 FEATURES, Handover) diff --git a/.claude/docs/functional/DOMAIN_MODEL.md b/.claude/docs/functional/DOMAIN_MODEL.md index fa96c38..a7179f7 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.3 -**Stand:** 2026-05-05 (Migration **036–037:** Rahmen nur Bibliothek; Slot‑Inhalt über Blueprint‑`training_units` + Sektionen/Items wie Planung — siehe `TRAINING_FRAMEWORK_SPEC.md` §2) +**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) **Basis:** `shinkan_anforderungsdokument_entwurf.md` + Fähigkeitsmatrix --- @@ -476,6 +476,16 @@ skill_level_definitions ( --- +## Medien-Archiv & Übungs-Anhänge (Stand 2026-05-07) + +- **`media_assets`:** Zentrale Datei-/Asset-Zeile (technisch u. a. SHA‑Dedupe, Sichtbarkeit, `club_id`, Lifecycle, Copyright, Speicherreferenz unter `library/…`). Siehe **`DATABASE_SCHEMA.md`**, **`MEDIA_ASSETS_AND_ARCHIVE_SPEC.md`**. +- **`exercise_media`:** Verknüpfung **Übung ↔ Asset** (`media_asset_id`) oder **Embed** ohne Asset; Felder wie `context` (`ablauf` \| `detail` \| `trainer_hint`), Sortierung, Primär-Medium. +- **`platform_media_storage`:** Konfiguration effektiver Medienwurzel (Superadmin, relativ zu `MEDIA_ROOT`). +- **Produkt:** Medienbibliothek **`/media`**; in der Übungsbearbeitung Upload, Entfernen der Verknüpfung, **Aus Archiv verknüpfen**; Governance **`official`** und Copyright-Regeln wie in der Norm beschrieben. +- **Geplant:** **Inline-Verweise** in Fließtextfeldern auf dieselbe Verknüpfung (`exercise_media.id`) — **`MEDIA_ASSETS_AND_ARCHIVE_SPEC.md` §11**, **`docs/HANDOVER.md`** §5. + +--- + ## Methodenbezug (§11.5) **Prinzip:** Genau EINE Hauptmethode, optional weitere Nebenmethoden. diff --git a/.claude/docs/functional/SHINKAN_REQUIREMENTS.md b/.claude/docs/functional/SHINKAN_REQUIREMENTS.md index 73e0c34..01d05b0 100644 --- a/.claude/docs/functional/SHINKAN_REQUIREMENTS.md +++ b/.claude/docs/functional/SHINKAN_REQUIREMENTS.md @@ -5,9 +5,10 @@ Ausführliche fachliche Inhalte: | Dokument | Inhalt | |----------|--------| | [shinkan_anforderungsdokument_entwurf.md](./shinkan_anforderungsdokument_entwurf.md) | Gesamtentwurf Anforderungen | -| [DOMAIN_MODEL.md](./DOMAIN_MODEL.md) | Domänenmodell, Variantenlogik (Abschnitt 11.2) | +| [DOMAIN_MODEL.md](./DOMAIN_MODEL.md) | Domänenmodell, Variantenlogik (§11.2), **Medien-Archiv** (Abschnitt 2026-05) | +| [MEDIA_ASSETS_AND_ARCHIVE_SPEC.md](../technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md) | Medien-Archiv, Lifecycle, **geplante Inline-Medien §11** | | [MULTI_TENANCY_RBAC_ARCHITECTURE.md](../technical/MULTI_TENANCY_RBAC_ARCHITECTURE.md) | Zielarchitektur Mandanten/Rollen/Membership & Umsetzungsplan | -**Lieferstand & Umsetzung (Stand Code):** siehe [`../PROJECT_STATUS.md`](../PROJECT_STATUS.md) und [`../library/FEATURES_DELIVERED_2026-Q2.md`](../library/FEATURES_DELIVERED_2026-Q2.md). +**Lieferstand & Umsetzung (Stand Code):** [`../PROJECT_STATUS.md`](../PROJECT_STATUS.md), [`../library/FEATURES_DELIVERED_2026-Q2.md`](../library/FEATURES_DELIVERED_2026-Q2.md) §12, Repo-Root **`docs/HANDOVER.md`**. `CLAUDE.md` (Repo-Root) verweist hierher als Einstieg. diff --git a/.claude/docs/library/FEATURES_DELIVERED_2026-Q2.md b/.claude/docs/library/FEATURES_DELIVERED_2026-Q2.md index 90045d9..8ae50f6 100644 --- a/.claude/docs/library/FEATURES_DELIVERED_2026-Q2.md +++ b/.claude/docs/library/FEATURES_DELIVERED_2026-Q2.md @@ -1,9 +1,9 @@ # Gelieferte Features & technische Basis (Q2 2026) -**Stand:** 2026-05-05 -**Referenz:** `backend/version.py` — **APP_VERSION 0.8.10**, **DB_SCHEMA_VERSION 20260505037** +**Stand:** 2026-05-07 +**Referenz:** `backend/version.py` — **APP_VERSION 0.8.59**, **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**. Detail-Spezifikationen bleiben in den verlinkten Pfaden unter `.claude/docs/technical/` und `.claude/docs/functional/`. +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/`. --- @@ -17,7 +17,7 @@ Dieses Dokument bündelt die in der Entwicklungsphase erreichten **lieferbaren** | **030** | `training_unit_exercises.exercise_variant_id` → FK `exercise_variants(id)` ON DELETE SET NULL | | **035** | **`training_framework_programs`** + Ziele, Slots (+ frühere Slot‑Übungstabelle, heute entfallen nach **037**); **`training_plan_templates.visibility`** | | **036** | Rahmen nur Bibliothek: Kontext + M:N Trainingsarten/Zielgruppen; keine Modus-Spalten / keine Kopf‑`group_id` | -| **037** | **`training_units.framework_slot_id`**, strukturierter Ablauf wie Planung; Entfall **`training_framework_slot_exercises`**; **`origin_framework_slot_id`** | +| **045–046** | **`media_assets`**, `platform_media_storage`, `exercise_media.media_asset_id`, ggf. **Tags/GIN** — siehe `MEDIA_ASSETS_AND_ARCHIVE_SPEC.md` | --- @@ -79,7 +79,7 @@ Logik: `_upload_limit_bytes(session)` vor `read()`-Prüfung. ## 6. Frontend – Übung bearbeiten (`ExerciseFormPage.jsx`) - **Varianten-Editor**: eingeklappter Bereich (`
`), **eine Variante zur Zeit** über Dropdown oder „Neue Variante“; Felder über **`ExerciseVariantFields`**; Reihenfolge Nach oben/unten; Speichern/Löschen pro Variante. -- **Medien** wie zuvor (Formularteil). +- **Medien:** Upload/Embed, **Archiv verknüpfen** (`from-asset`), Medienliste mit Vorschau, Reaktivierung bei Archiv-Konflikt — Details **§12**. - Block **Progressionsgraph** (Edit): Kanten mit Bezug zur aktuellen Übung. Hinweis: Es gibt **keine** separaten Routen `/exercises/:id/variants/...` — Bearbeitung erfolgt unter **`/exercises/:id/edit`** (Routing-Doku ggf. anpassen). @@ -123,16 +123,40 @@ Hinweis: Es gibt **keine** separaten Routen `/exercises/:id/variants/...` — Be --- -## 12. Nächste sinnvolle Schritte (nicht Lieferstand) +## 12. Medien-Archiv & Medienbibliothek (Migration **045** ff., App ca. **0.8.41–0.8.59**) + +Einzelnorm: **`technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md`**. Kurzüberblick geliefert: + +### 12.1 Datenbank & Router + +- **`media_assets`**, **`platform_media_storage`**, **`exercise_media.media_asset_id`** (Dedupe `sha256` + Sichtbarkeit + `club_id` je nach Policy). +- Router **`media_assets.py`**: Listen, PATCH, Lifecycle, Dateiauslieferung, Bulk; Integration in **`exercises.py`** (Upload, `from-asset`, Promotion/Copyright-Regeln bei `official` / Vereinsübungen). + +### 12.2 Funktional (Ist) + +- Zentrale **Medienbibliothek** Frontend **`/media`**: Filter (Lifecycle, Medientyp, Verein für Admins), Suche, Tags, Copyright bearbeiten (rollengerecht), Kacheln/Liste, Nutzungs-Hinweise (Übungen/Planung wo implementiert). +- **Papierkorb** mehrstufig (`trash_soft` / `trash_hidden`), Recovery, Superadmin-Purge; strukturierte Fehlercodes bei Governance (u. a. Übung **`CLUB_MEDIA_*`**). +- **Speicher:** Pfadkonvention **`library/{vereinssegment}/…`** mit Medienkind-Unterordnern; Umzug bei Sichtbarkeits-/Vereinsänderung; effektives Wurzelverzeichnis `MEDIA_ROOT` + Superadmin-`local_relative_root`. +- **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) + +- **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**. + +--- + +## 13. Nächste sinnvolle Schritte (nicht Lieferstand) - Trainingsplanung: Kalender‑UI‑Anbindung **„aus Rahmen übernehmen“**; Visibility/Policies für geteilte Rahmen (**CURR‑004** später). - 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). --- -## 13. Verweise +## 14. Verweise | Thema | Dokument | |--------|----------| @@ -140,5 +164,6 @@ Hinweis: Es gibt **keine** separaten Routen `/exercises/:id/variants/...` — Be | API Übungen | `technical/EXERCISES_API_SPEC.md` | | Domänenmodell | `functional/DOMAIN_MODEL.md` | | Datenbank Überblick | `technical/DATABASE_SCHEMA.md` | -| Upload formal | `technical/MEDIA_UPLOAD_SPEC.md` | +| Medien Upload (Limits, MIME) | `technical/MEDIA_UPLOAD_SPEC.md` | +| Medien-Archiv & Lifecycle | `technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md` | | Projektstatus-Kachel | `../PROJECT_STATUS.md` | diff --git a/.claude/docs/technical/DATABASE_SCHEMA.md b/.claude/docs/technical/DATABASE_SCHEMA.md index 58a3668..2531f54 100644 --- a/.claude/docs/technical/DATABASE_SCHEMA.md +++ b/.claude/docs/technical/DATABASE_SCHEMA.md @@ -1,8 +1,8 @@ # Shinkan Jinkendo - Datenbank-Schema (Technisch) -**Version:** 0.5.2 -**Stand:** 2026-05-05 -**Hinweis:** Produktiver Deploy sollte mindestens bis Migration **037** (Rahmen‑Slot‑Blueprints in `training_units`; Entfall `training_framework_slot_exercises`) geführt sein — Details siehe `backend/version.py` (`DB_SCHEMA_VERSION`). +**Version:** 0.5.3 +**Stand:** 2026-05-07 +**Hinweis:** Produktiver Deploy sollte mindestens bis Migration **037** (Rahmen‑Slot‑Blueprints) 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`**. --- @@ -47,8 +47,7 @@ Dieses Dokument beschreibt die **technische Datenbankstruktur** von Shinkan Jink | **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** | **Slot‑Blueprint:** `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`** | ✅ | - ---- +| **040–046** | **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) @@ -267,9 +266,11 @@ exercise_skills ( UNIQUE (exercise_id, skill_id) -- Migration 020 ) --- Varianten & Medien +-- Varianten & Medien (028+ Embed/Datei; 045+ optional media_asset_id → media_assets) exercise_variants (id, exercise_id, name, description, ...) -exercise_media (id, exercise_id, type, url, title, 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 **035–036**) diff --git a/.claude/docs/technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md b/.claude/docs/technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md index 5aa91d8..4ca0d1d 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 (§11 Inline-Plan ergänzt) +**Stand:** 2026-05-07 (Ist-Changelog Medien bis 0.8.59; §11 Inline geplant) **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,8 +186,12 @@ Das widerspricht **nicht** dem Papierkorb: wer kein Recht hat, ein Asset **globa | Datum | Änderung | |-------|----------| -| 2026-05-07 | Erstfassung als Single Source of Truth (verbindlich); Abstimmung mit Stakeholder: Promotion Übung↔Medien, Copyright, Papierkorb 3-stufig, externe Speicher, Embeds getrennt vom Asset-Lifecycle. | -| 2026-05-07 | §11 **Inline-Medien im Fließtext**: Leitplanken (Anker `exercise_media.id`, einheitlicher Render-Pfad, keine zweite Governance); Zeitpunkt der Umsetzung; Drift-Vermeidung ohne jetzigen Vollbau. | +| 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`. | +| 2026-05-07 | **0.8.47–0.8.57 (Auszug):** Übung **`official`** nur Superadmin; Vereinsübungen mit File-Assets: **Copyright-Pflicht**; Speicherpfade **`library/`** mit Vereinsordner (Name+c{id}), Medienkind-Unterordner, Governance-Umzug bei Sichtbarkeit; Bibliothek-GET mit Filtern/Tags/Nutzungs-Anzeige; Bulk-Lifecycle/PATCH; Lesemodus/Kacheln; Konflikt **409** bei Upload-Dedupe vs. Papierkorb + UI-Reaktivierung. | +| 2026-05-07 | **0.8.42–0.8.43:** Papierkorb-Lifecycle API, Retention-Skript; **`POST …/exercises/{id}/media/from-asset`**; Übungs-UI Archiv verknüpfen / Vorschau. | +| 2026-05-07 | Erstfassung als Single Source of Truth (verbindlich); Abstimmung Stakeholder: Promotion Übung↔Medien, Copyright, Papierkorb 3-stufig, externe Speicher, Embeds getrennt vom Asset-Lifecycle. | +| 2026-05-07 | §11 **Inline-Medien im Fließtext**: Leitplanken (Anker `exercise_media.id`, einheitlicher Render-Pfad, keine zweite Governance); Umsetzung **nach** tragfähigem Archiv — siehe **`docs/HANDOVER.md`** §5. | | 2026-05-07 | **Medienmanager (Basis):** `GET /api/media-assets?lifecycle=…`, `copyright_notice` in Response; `PATCH` Copyright; `GET …/file` für Papierkorb-Zeilen bei Verwaltungsrecht; UI-Route `/media` (Medienbibliothek). | --- @@ -219,9 +223,9 @@ Das widerspricht **nicht** dem Papierkorb: wer kein Recht hat, ein Asset **globa ### 11.5 Wann umsetzen (Reihenfolge) -1. **Vorher:** Medien-Archiv, `media_assets`, Upload/Dedupe, Speicherpfad, Basis-Papierkorb (§5) – jeweils stabil. -2. **Danach:** Inline implementieren, sobald Trainer-Feedback oder Content-Menge den Bedarf **konkret** bestätigt (typisch nach 1–2 Beta-Zyklen). -3. **Nicht nötig:** vorher kompletten Block-Editor einführen; **Platzhalter im bestehenden RTE** ist der vorgesehene **schlanke** Einstieg. +1. ~~**Erledigt (Basis):** Medien-Archiv, `media_assets`, Upload/Dedupe, Speicherpfad, Papierkorb, Bibliothek `/media`, Verknüpfung `from-asset`, Governance `official`/Copyright.~~ +2. **Als Nächstes (geplant):** Inline implementieren gemäß §11.1–11.4 — Trainer-Feedback/Content-Menge kann Priorität schärfen; technische Leitplanken hier sind verbindlich. +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/docs/technical/MEDIA_UPLOAD_SPEC.md b/.claude/docs/technical/MEDIA_UPLOAD_SPEC.md index 3707e94..397e4c0 100644 --- a/.claude/docs/technical/MEDIA_UPLOAD_SPEC.md +++ b/.claude/docs/technical/MEDIA_UPLOAD_SPEC.md @@ -1,10 +1,8 @@ # Media Upload & Embed Specification -**Version:** 1.1 -**Datum:** 2026-04-27 -**Status:** DRAFT - Awaiting Review -**Autor:** Claude Code -**Änderungen v1.1:** Rollenbasierte Server-Limits (`EXERCISE_MEDIA_*_MB`) +**Version:** 1.2 +**Datum:** 2026-05-07 +**Status:** Aktuell für Upload-Limits, MIME, Embed — **zentraler Medien-Ist-Stand** inkl. Archiv, Lifecycle, Pfadkonvention: **`MEDIA_ASSETS_AND_ARCHIVE_SPEC.md`** > **Zielbild Medien-Archiv, Wiederverwendung, Papierkorb, Copyright, externe Speicherung, später Inline im Fließtext:** > Verbindliche Single Source of Truth: **`MEDIA_ASSETS_AND_ARCHIVE_SPEC.md`** (§11 Leitplanken Inline, ohne Umsetzung). diff --git a/.claude/docs/working/HANDOVER_NEXT_SESSION.md b/.claude/docs/working/HANDOVER_NEXT_SESSION.md index 2597020..bb50075 100644 --- a/.claude/docs/working/HANDOVER_NEXT_SESSION.md +++ b/.claude/docs/working/HANDOVER_NEXT_SESSION.md @@ -1,11 +1,14 @@ # Session Handover (Verweis) -**Dieses Dokument ist veraltet.** Der aktuelle Entwicklungsstand und die Handover-Basis stehen hier: +**Aktuelle Handover-Basis (Stand Implementierung + Medien):** -👉 **`docs/HANDOVER.md`** (Projektroot: `c:\Dev\shinkan-jinkendo\docs\HANDOVER.md`) +👉 **`docs/HANDOVER.md`** (Projektroot) -Dort: Fähigkeits-/Reifegrad-Stand (Bindings, Resolve, Export/Import, Matrix-Stack), Verweise auf `.claude/docs` für Anforderungen, und **nächste Priorität Übungen (UI/CRUD/Medien)**. +Dort: **Medien-Archiv/Medienbibliothek (Ist)**, **geplante Inline-Medienverlinkung (§11 Spec)**, Reifegrad/Matrix, Rahmenprogramm, nächste sinnvolle Arbeitspakete für neue Sessions. + +**Projektstatus-Kachel:** `.claude/docs/PROJECT_STATUS.md` +**Medien Single Source of Truth:** `.claude/docs/technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md` --- -*Historischer Inhalt aus April 2026 wurde durch `docs/HANDOVER.md` ersetzt.* +*Die historische „Übungen Lücke“-Fassung (2026-04) ist überholt; Medien-UI und Archiv sind seit 0.8.41+ weitgehend umgesetzt — Details `docs/HANDOVER.md` §4.* diff --git a/.claude/rules/ARCHITECTURE.md b/.claude/rules/ARCHITECTURE.md index 7a7c853..f3559b9 100644 --- a/.claude/rules/ARCHITECTURE.md +++ b/.claude/rules/ARCHITECTURE.md @@ -58,7 +58,8 @@ return {"message": "Fehler", "success": False} ### 1.4 Mandanten & Zugriffsschicht (Shinkan / ACCESS_LAYER) **Verbindlicher Rahmen:** `.claude/docs/technical/ACCESS_LAYER_AND_GOVERNANCE_PLAN.md` -**Medien-Assets (Archiv, Papierkorb, Promotion, Copyright, externer Speicher):** `.claude/docs/technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md` +**Medien-Assets (Archiv, Papierkorb, Promotion, Copyright, externer Speicher, geplante Inline-Verlinkung §11):** `.claude/docs/technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md` +**Session-Handover (Ist + nächste Schritte):** `docs/HANDOVER.md` **Fortlaufendes Inventar:** `.claude/docs/working/ACCESS_LAYER_ENDPOINT_AUDIT.md` **Definition of Done für neue oder geänderte geschützte APIs**, sobald Daten **Verein**, **Sichtbarkeit** oder **mandantenbezogene Listen** betreffen: diff --git a/.claude/rules/DOCUMENTATION.md b/.claude/rules/DOCUMENTATION.md index f760324..3de2325 100644 --- a/.claude/rules/DOCUMENTATION.md +++ b/.claude/rules/DOCUMENTATION.md @@ -10,7 +10,8 @@ 1. Repo-Root: `CLAUDE.md` (Kontext, Links, Pflicht-Dokus) 2. Agent-Übersicht: **`.claude/README.md`** (Baum, wo was liegt) 3. Spez-Index: **`.claude/docs/README.md`** -4. Aufgaben-Tracking: **Gitea** – Übersicht lokal: **`.claude/docs/GITEA_ISSUES_INDEX.md`** (regelmäßig refreshen nach Bedarf) +4. **Session-Handover (aktuelle Prioritäten, Medien-Ist):** **`docs/HANDOVER.md`** +5. Aufgaben-Tracking: **Gitea** – Übersicht lokal: **`.claude/docs/GITEA_ISSUES_INDEX.md`** (regelmäßig refreshen nach Bedarf) --- diff --git a/CLAUDE.md b/CLAUDE.md index a69a599..10ee6fb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -11,7 +11,8 @@ > | Lessons Learned | `.claude/rules/LESSONS_LEARNED.md` | > | Setup-Dokument | `.claude/docs/working/SHINKAN_PROJECT_SETUP.md` | > | Anforderungen | `.claude/docs/functional/SHINKAN_REQUIREMENTS.md` | -> | Medien-Archiv, Lifecycle, Promotion | `.claude/docs/technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md` | +> | Medien-Archiv, Lifecycle, Inline (Plan §11) | `.claude/docs/technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md` | +> | Handover / nächste Session | **`docs/HANDOVER.md`** | ## Projekt-Übersicht @@ -57,7 +58,7 @@ backend/ └── routers/ # Router-Module auth · profiles · clubs · groups · skills · methods exercises · exercise_progression_graphs · training_units · training_programs - planning · import_wiki · admin · membership + planning · import_wiki · admin · membership · media_assets frontend/src/ ├── App.jsx # Root, Auth-Gates, Navigation @@ -82,10 +83,11 @@ frontend/src/ **Siehe:** `backend/version.py` (`APP_VERSION`, `DB_SCHEMA_VERSION`, `MODULE_VERSIONS`) und `.claude/docs/PROJECT_STATUS.md`. -Kurz (Stand 2026-05-05): App **0.8.10**, DB‑Schema‑Version **`20260505037`**; Kern: Übungen, Varianten, Medien, Planung mit Sektionen, **Trainingsrahmen Bibliothek + Slot‑Blueprint** (036–037), Progressionsgraph, Reifegrad/Matrix‑Stack — Details `PROJECT_STATUS.md` und `TRAINING_FRAMEWORK_SPEC.md` §2. +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**). ### Log (Auszug) +- 2026-05-07: **Medien** — zentrales Archiv (`media_assets`), Bibliothek-UI, Lifecycle/Papierkorb, `from-asset`, Speicherpfade `library/…`, Governance `official`/Copyright; **0.8.59** aktiver Verein UI/API-Sync — siehe `.claude/docs/library/FEATURES_DELIVERED_2026-Q2.md` §12, `docs/HANDOVER.md`. - 2026-05-05: Rahmen nur Bibliothek (**036**), Slot‑Ablauf = `training_units` + Sektionen (**037**), `POST /api/training-units/from-framework-slot`, keine `training_framework_slot_exercises` mehr — siehe `DATABASE_SCHEMA.md` / `FEATURES_DELIVERED_2026-Q2.md`. - 2026-04-27: Übungsvarianten API/UI, Migration 030, Listen-UX-Suche, Admin-Upload-Limits — siehe `PROJECT_STATUS.md` und `docs/library/FEATURES_DELIVERED_2026-Q2.md`. diff --git a/README.md b/README.md index f49ed9d..0526a88 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ Shinkan ist eine moderne Web- und Mobile-App für Kampfsport-Trainer und Vereine - **Kataloge:** Fähigkeiten und Trainingsmethoden strukturiert verwalten - **Standardisierung:** Vereinsstandards und wiederverwendbare Vorlagen - **Freigabe:** Gesteuerte Veröffentlichung von Inhalten +- **Medien:** Zentrale **Medienbibliothek** (`/media`), Archiv mit Lifecycle/Papierkorb, Verknüpfung in Übungen — Norm: `.claude/docs/technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md` · Einstieg: `docs/HANDOVER.md` ## Nicht in Shinkan diff --git a/docs/HANDOVER.md b/docs/HANDOVER.md index 8ffbc8c..1be7643 100644 --- a/docs/HANDOVER.md +++ b/docs/HANDOVER.md @@ -1,9 +1,9 @@ # Shinkan Jinkendo – Entwicklungsstand & Handover -**Stand:** 2026-05-05 -**App-Version / DB-Schema:** siehe `backend/version.py` +**Stand:** 2026-05-07 +**App-Version / DB-Schema:** App **0.8.59**, DB-Schema siehe `backend/version.py` (`DB_SCHEMA_VERSION`) -Diese Datei ist die **Einstiegs-Doku für neue Chat-Sessions**: Anforderungen im Detail stehen in `.claude/docs/` (siehe unten); hier der **implementierte Stand** und **nächste Baustellen**. +Diese Datei ist die **Einstiegs-Doku für neue Chat-Sessions**: Anforderungen im Detail stehen in `.claude/docs/` (siehe unten); hier der **implementierte Stand**, **Medien-Meilenstein** und **sinnvolle nächste Schritte**. ### Produktion: `relation … does not exist` (z. B. `skill_main_categories`) @@ -21,11 +21,17 @@ Das Schema ist gegenüber dem Code zurück: Migration **`022_skills_schema_compl | Thema | Pfad | |--------|------| | Projekt-Setup, Domain grob | `.claude/docs/working/SHINKAN_PROJECT_SETUP.md` | -| Projekt-Status (Skills, Wiki, Stats) | `.claude/docs/PROJECT_STATUS.md` | +| **Projekt-Status (aktuell)** | `.claude/docs/PROJECT_STATUS.md` | +| **Medien-Archiv, Lifecycle, Inline-Plan (§11)** | `.claude/docs/technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md` | | Übungen: API, DB, Architektur, Routing | `.claude/docs/technical/EXERCISES_API_SPEC.md`, `EXERCISES_DATABASE_FINAL.md`, `EXERCISES_ARCHITECTURE.md`, `EXERCISES_FRONTEND_ROUTING.md` | -| Media / Upload | `.claude/docs/technical/MEDIA_UPLOAD_SPEC.md` | +| Media / Upload-Limits / Embed | `.claude/docs/technical/MEDIA_UPLOAD_SPEC.md` | | MediaWiki-Import | `.claude/docs/technical/MEDIAWIKI_IMPORT_SPEC.md` | -| Rahmenprogramm · Planung (`training_units` Blueprints), Progressionsgraph | `.claude/docs/technical/TRAINING_FRAMEWORK_SPEC.md`; Überblick DB → `.claude/docs/technical/DATABASE_SCHEMA.md`; Domäne → `.claude/docs/functional/DOMAIN_MODEL.md` | +| Zugriffsschicht, Mandant, Governance | `.claude/docs/technical/ACCESS_LAYER_AND_GOVERNANCE_PLAN.md` | +| Tenant-Endpoints (Audit) | `.claude/docs/working/ACCESS_LAYER_ENDPOINT_AUDIT.md` | +| Rahmenprogramm · Planung | `.claude/docs/technical/TRAINING_FRAMEWORK_SPEC.md` | +| Überblick DB | `.claude/docs/technical/DATABASE_SCHEMA.md` | +| Domäne | `.claude/docs/functional/DOMAIN_MODEL.md` | +| **Lieferliste inkl. Medien** | `.claude/docs/library/FEATURES_DELIVERED_2026-Q2.md` §12 | --- @@ -36,73 +42,108 @@ Das Schema ist gegenüber dem Code zurück: Migration **`022_skills_schema_compl - **`maturity_models`**, **`model_levels`**, **`model_skills`**, **`model_skill_levels`**: Matrix-Inhalt pro Modell. - **Kontext am Modell (Legacy, M:N):** `maturity_model_focus_areas`, `maturity_model_style_directions`, `maturity_model_target_groups` (Migration 025). - **Hierarchische Kontext-Zuordnung (Resolve):** `maturity_model_context_bindings` mit optional `style_direction_id`, `training_type_id` (Migration 026, 027). -- **027:** u. a. `Fokus + Trainingsstil` ohne Stilrichtung (partielle Unique-Indizes). +- **027:** u. a. `Fokus + Trainingsstil` ohne Stilrichtung (partielle Unique-Indizes). ### 2.2 Resolve-Logik (Backend) - **`GET /api/maturity-models/resolve`**: Bindings zum Fokus, die zur Anfrage passen; Merge nach Spezifität (weniger spezifisch zuerst); spezifischere Zeilen überschreiben Zelltexte. -- **Matching:** Gesetzte Spalten einer Binding-Zeile müssen mit der Anfrage übereinstimmen; `NULL` in der Zeile = Wildcard (z. B. Fokus+Trainingsstil gilt für alle Stilrichtungen, aber nur für diesen Trainingsstil). -- **Legacy-Fallback:** Nur wenn für den **Fokus keine einzige** Zeile in `maturity_model_context_bindings` existiert. Sonst bei fehlendem Treffer **`null`** (kein stilles Legacy mit falschem Trainingsstil). +- **Matching:** Gesetzte Spalten einer Binding-Zeile müssen mit der Anfrage übereinstimmen; `NULL` in der Zeile = Wildcard. +- **Legacy-Fallback:** Nur wenn für den **Fokus keine einzige** Zeile in `maturity_model_context_bindings` existiert. ### 2.3 Export / Import (einzelnes Modell & aufgelöst) - **`GET /api/maturity-models/{id}/export`**: `shinkan.maturity_model.v1` inkl. `context_bindings_for_model` (IDs). - **`GET /api/maturity-models/export-resolved`**: `shinkan.maturity_matrix_resolved.v1` (Query: `focus_area_id`, optional `style_direction_id`, `training_type_id`). -- **`POST /api/maturity-models/import`**: `create` | `replace`, optional `import_bindings` (nur bei `maturity_model.v1`). +- **`POST /api/maturity-models/import`**: `create` | `replace`, optional `import_bindings`. ### 2.4 Komplett-Stack Test → Prod -- **`GET /api/admin/matrix-stack/export`**: `shinkan.matrix_stack.v1` – Katalog (`skill_main_categories`, `skill_categories`, `skills`, `skill_level_definitions`) + alle Reifegradmodelle + Bindings mit **Namen** (Fokus/Stil/Trainingsstil). -- **`POST /api/admin/matrix-stack/import`**: Upsert Katalog per Slug; Skills per Kategorie+Name; Modelle neu anlegen; Bindings per Katalognamen. Optional **`replace_all_maturity_models`** + **`confirm_replace_all: "DELETE_MATURITY_STACK"`** (nur Superadmin). -- Router: `backend/routers/matrix_stack_bundle.py`, in `main.py` registriert. +- **`GET /api/admin/matrix-stack/export`**, **`POST /api/admin/matrix-stack/import`** — siehe `matrix_stack_bundle.py`. ### 2.5 Frontend (Admin) -- **`frontend/src/pages/AdminMaturityModelsPage.jsx`**: Tabs u. a. Katalog, Modelle, Kontext-Zuordnung, **Matrix-Ansicht und Export**. -- **`MaturityModelBindingsAdmin.jsx`**: Bindings CRUD, Erklärung Merge/Legacy. -- **`MaturityMatrixToolsAdmin.jsx`**: Kontext auflösen, hierarchische Matrix-Ansicht, Export einzelnes Modell / aufgelöst, Einzelmodell-Import; **Komplett-Stack** mit eigenem Export-Button und **eigenem Dateifeld für Stack-Import** (`POST /api/admin/matrix-stack/import`). -- **`frontend/src/utils/api.js`**: u. a. `exportMatrixStackBundle`, `importMatrixStackBundle`, Reifegrad-APIs. +- **`AdminMaturityModelsPage.jsx`**, **`MaturityModelBindingsAdmin.jsx`**, **`MaturityMatrixToolsAdmin.jsx`**; APIs in `api.js`. --- ## 3. Trainingsrahmenprogramm & Planungs‑Blueprint (kurz) -- **Migration 036:** Rahmenkopf nur Bibliothek (Kontext: Fokusbereich, Stilrichtung; M:N Trainingsarten/Zielgruppen); keine `plan_mode`/keine Kopf‑`group_id`. -- **Migration 037:** Pro Rahmen‑Slot eine **`training_units`‑Zeile mit `framework_slot_id`**; strukturierter Ablauf wie echte Einheiten (`training_unit_sections` / `training_unit_section_items`). Tabelle **`training_framework_slot_exercises`** entfällt. -- **API:** Rahmen unter **`/api/training-framework-programs`** (Slots liefern u. a. **`blueprint_training_unit_id`**, **`sections[]`**, **`exercises[]`**); Kalenderliste **`GET /api/training-units`** ohne Blueprints; Übernahme **`POST /api/training-units/from-framework-slot`**. -- **Code:** `backend/routers/training_framework_programs.py`, `training_planning.py`; Frontend **`TrainingFrameworkProgramEditPage.jsx`**; **`createTrainingUnitFromFrameworkSlot`** in `api.js`. +- **036 / 037:** Bibliotheks-Rahmen, Slot-Inhalt als **`training_units`** mit **`framework_slot_id`**; **`POST /api/training-units/from-framework-slot`**. +- **Code:** `training_framework_programs.py`, `training_planning.py`; Frontend **`TrainingFrameworkProgramEditPage.jsx`**, **`createTrainingUnitFromFrameworkSlot`** in `api.js`. --- -## 4. Stand: Übungen (Lücke für nächste Session) +## 4. Stand: Medien-Management (Ist, 2026-05-07) -**Ist (laut Projektdoku und aktuellem Produktziel):** +**Datenmodell (Kurz):** -- Backend: Übungen-CRUD, M:N, Suche, Blöcke, Medien-Struktur u. a. sind in `PROJECT_STATUS.md` als umgesetzt geführt; viele Übungen stammen aus **MediaWiki-Import** (Wiki-Tracking-Tabellen). -- **Soll / Nutzerfeedback:** In der Praxis fehlt oder ist unzureichend: **stabile Liste**, **gerenderte Detailansicht**, **Bearbeiten/Anlegen**, **Medien zuweisen/Upload** – konkrete Fehler (404, leere Liste, falsche Route) sind vor Ort zu verifizieren. +- **`media_assets`:** Physische oder logische Archiv-Datei (u. a. `sha256`, `visibility`, `club_id`, `lifecycle_state`, `copyright_notice`, Speicherpfad/`storage_key`, `tags` ab passender Migration). +- **`exercise_media`:** optional **`media_asset_id`**; weiterhin Embeds ohne Asset; Kontext/Sortierung/Titel wie bisher. +- **`platform_media_storage`:** Superadmin — relativer Unterpfad unter `MEDIA_ROOT`. -**Nächste Session sollte:** +**API (Auszug):** -1. Aktuelle Routen und Seiten prüfen (`App.jsx`, `EXERCISES_FRONTEND_ROUTING.md`). -2. `GET /api/exercises` (Filter, Auth) und eine Beispiel-Übung gegen die Dev-DB testen. -3. UI schrittweise: Liste → Detail → Formular → Medien (an Specs in `.claude/docs/technical/` ausrichten). +- **`GET /api/media-assets`**, Filter (Lifecycle, `media_kind`, Verein, Suche, Tags), Berechtigungen pro Zeile. +- **`PATCH /api/media-assets/{id}`** / Bulk-PATCH, **Lifecycle** (`POST …/lifecycle`, Bulk). +- **`GET …/media-assets/{id}/file`** (inkl. Kontext `ssetoken` wo vorgesehen). +- **`POST /api/exercises/{id}/media/from-asset`:** bestehendes Asset verknüpfen. +- Übungs-Uploads erzeugen/verknüpfen **`media_assets`**; Governance wie **`library_content_*`** / TenantContext. + +**Frontend:** + +- Route **`/media`** (Medienbibliothek): Kacheln/Liste, Filter, Copyright, Lifecycle-Actions (Rollen gemäß Spec; **`official`:** bearbeiten/Lifecycle im Wesentlichen **Superadmin**, andere Rollen **Lesemodus** im Bearbeiten-Dialog). +- Übungsformular: Archiv-Picker, Vorschau, Reaktivierung bei Dedupe-Konflikt (Papierkorb). + +**Speicher & Pfade:** + +- Struktur unter **`library/`** mit Vereinssegment (aus Vereinsname abgeleitet + `c{id}`), Unterordner nach Medienkind (`image`/`video`/`pdf`/`other`), Dateiname z. B. SHA-basiert — Details und Drift-Vermeidung in **`MEDIA_ASSETS_AND_ARCHIVE_SPEC.md`**. + +**Governance & Mandant:** + +- **`visibility=official`** für Übungen: nur **Superadmin**; Medien **`official`:** Lifecycle/PATCH schwerpunktmäßig Superadmin (Plattform-Admin nicht gleich Superadmin). +- **Aktiver Verein:** `profiles.active_club_id`, Header **`X-Active-Club-Id`**, Response **`effective_club_id`** — nach **0.8.59** konsistent inkl. Plattform-Admin beim Reload (Backend **`tenant_context`**, Frontend **`getResolvedActiveClubIdForUi`** + Profil-Refresh nach Vereinswechsel). --- -## 5. Technische Referenz (kurz) +## 5. Geplant: Inline-Medienverlinkung (nicht umgesetzt) + +**Ziel:** Mediendarstellung **innerhalb** von Fließtext-Feldern (Ablauf, Ziele, Trainerhinweise), konsistent mit derselben **`exercise_media`‑** bzw. Asset-Governance wie die Medienliste. + +**Norm:** **`MEDIA_ASSETS_AND_ARCHIVE_SPEC.md` §11** — u. a.: + +- Verweis auf **`exercise_media.id`** (oder kanonisch übersetzte Markup-Syntax), **keine** zweite Sichtbarkeitslogik. +- **Ein** zentraler Render-/Sanitize-Pfad für Übungstexte; keine verstreuten „roh `dangerouslySetInnerHTML`“-Pfade. +- XSS/CSP: nur Allowlist-HTML und kontrollierte Player-Komponenten. + +**Reihenfolge:** Archiv & aktuelle Governance gelten als Basis; Inline ist die **nächste** inhaltliche Ausbaustufe für Medien (siehe **`PROJECT_STATUS.md`** Nächste Schritte). + +--- + +## 6. Nächste Session — sinnvolle Arbeitspakete + +1. **Inline §11:** Syntax festlegen (z. B. `{{exerciseMedia:id}}` → kanonisches HTML), Server normalisieren bei Speichern, einen **`renderExerciseRichText()`**-Pfad im Frontend. +2. **Tests:** pytest für `media_assets`-Router (Leserechte, Lifecycle, `from-asset`); ggf. Snapshot der Pfad-Umzug-Logik. +3. **Retention:** Job-Dokumentation + Betrieb (ENV, Intervall); Dry-Run beschreiben. +4. **S3/Adapter:** Speicher-Abstraktion §7 — wenn Produkt es verlangt. +5. **Rahmen/UI:** Kalender „aus Rahmen übernehmen“ weiter anbinden (parallel, unabhängig von Medien). + +--- + +## 7. Technische Referenz (kurz) | Bereich | Einstieg | |---------|----------| -| Backend API | `backend/main.py`; Router u. a. **`training_framework_programs.py`**, **`training_planning.py`**, `maturity_models.py`, `matrix_stack_bundle.py`, `exercises.py`, `catalogs.py`, `skills.py` | -| Migrationen | `backend/migrations/` (u. a. 024–027 Reifegrad/Bindings; **035–037** Rahmenprogramm / Slot‑Blueprint) | +| Backend API | `backend/main.py`; u. a. **`media_assets.py`**, **`exercises.py`**, **`profiles.py`**, **`training_framework_programs.py`**, `tenant_context.py` | +| Migrationen | `backend/migrations/` (040+ Mitgliedschaft/Governance; **045+** Medien-Stack) | | Frontend API | `frontend/src/utils/api.js` | +| Aktiver Verein (UI) | `frontend/src/utils/activeClub.js`, `AuthContext.jsx` | | Version / Changelog | `backend/version.py` | --- -## 6. Veraltete Hinweise +## 8. Veraltete Hinweise -Die Datei `.claude/docs/working/HANDOVER_NEXT_SESSION.md` (2026-04-22) ist **historisch**; für den aktuellen Stand gilt **`docs/HANDOVER.md`**. +`.claude/docs/working/HANDOVER_NEXT_SESSION.md` verweist auf **dieses** Dokument (`docs/HANDOVER.md`) als aktuelle Basis. --- diff --git a/frontend/nginx.conf b/frontend/nginx.conf index 4cf0529..2a0cc31 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -13,9 +13,9 @@ server { # — verringert sporadische 502, wenn sich nur die Backend-Container-IP geändert hat. resolver 127.0.0.11 valid=10s ipv6=off; - # Uploads (Übungsmedien) und API erreichen Clients unter derselben Host-URL wie die SPA — - # dafür muss Nginx zur FastAPI-Instanz im Compose-Netz weiterleiten. - client_max_body_size 64m; + # Uploads (Übungsmedien, Medienarchiv-Bulk) — Limit muss >= größte Einzeldatei und + # bei multipart ggf. Summe mehrerer Dateien sein (Backend praxis: bis 1024 MB Admin). + client_max_body_size 1024m; location ^~ /api/ { set $docker_backend_svc backend; diff --git a/frontend/src/pages/ExerciseFormPage.jsx b/frontend/src/pages/ExerciseFormPage.jsx index 088da0e..b5e99bf 100644 --- a/frontend/src/pages/ExerciseFormPage.jsx +++ b/frontend/src/pages/ExerciseFormPage.jsx @@ -7,6 +7,20 @@ import ExerciseProgressionGraphPanel from '../components/ExerciseProgressionGrap import { SKILL_LEVEL_OPTIONS, normalizeSkillLevelSlug } from '../constants/skillLevels' import { useAuth } from '../context/AuthContext' +/** MIME/Dateiname → Übungs-media_type; null → Dropdown-Fallback. */ +function inferExerciseMediaType(file) { + if (!file) return null + const mime = (file.type || '').toLowerCase() + if (mime.startsWith('image/')) return 'image' + if (mime.startsWith('video/')) return 'video' + if (mime === 'application/pdf' || mime.includes('pdf')) return 'document' + const name = (file.name || '').toLowerCase() + if (/\.(mp4|webm|mov|mkv|avi|m4v|mpeg|mpg)$/.test(name)) return 'video' + if (/\.(jpg|jpeg|png|gif|webp|bmp|svg)$/.test(name)) return 'image' + if (/\.pdf$/.test(name)) return 'document' + return null +} + /** Kachelvorschau: Video nutzt ersten Frame (metadata), Bild = img. */ function ExerciseMediaThumbTile({ exerciseId, media, onOpenPreview }) { const src = !media.embed_url ? resolveExerciseMediaFileUrl(exerciseId, media) : null @@ -429,7 +443,7 @@ function ExerciseFormPage() { const [variantEditSelection, setVariantEditSelection] = useState(null) const variantsDetailsRef = useRef(null) - const [mediaFile, setMediaFile] = useState(null) + const [mediaFiles, setMediaFiles] = useState([]) const [mediaType, setMediaType] = useState('image') const [mediaTitle, setMediaTitle] = useState('') const [mediaContext, setMediaContext] = useState('ablauf') @@ -741,62 +755,63 @@ function ExerciseFormPage() { ) const handleUploadFile = async () => { - if (!exerciseId || !mediaFile) { - alert('Datei wählen') + if (!exerciseId || mediaFiles.length === 0) { + alert('Datei(en) wählen') return } - const fd = new FormData() - fd.append('file', mediaFile) - fd.append('media_type', mediaType) - fd.append('title', mediaTitle) - fd.append('description', '') - fd.append('context', mediaContext) - fd.append('is_primary', 'false') - try { - await api.uploadExerciseMedia(exerciseId, fd) - setMediaFile(null) - setMediaTitle('') - await refreshMedia() - } catch (err) { - if (err.code === 'MEDIA_ASSET_IN_TRASH' && err.payload?.media_asset_id != null) { - const aid = err.payload.media_asset_id - const nameHint = - (mediaFile && mediaFile.name) || - err.payload.original_filename || - 'diese Datei' - if ( - confirm( - `Die hochgeladene Datei ist inhaltsgleich mit einem Archiv-Medium im Papierkorb (${nameHint}). ` + - 'Soll dieses Medium wieder aktiviert und an die Übung gehängt werden? (Es wird kein zweites Exemplar auf der Platte angelegt.)', - ) - ) { - try { - await api.postMediaAssetLifecycle(aid, 'reactivate') - await api.attachExerciseMediaFromAsset(exerciseId, { - media_asset_id: aid, - title: mediaTitle || undefined, - description: '', - context: mediaContext, - is_primary: false, - }) - setMediaFile(null) - setMediaTitle('') - await refreshMedia() - } catch (e2) { - alert(e2.message || String(e2)) + const files = [...mediaFiles] + for (const file of files) { + const inferred = inferExerciseMediaType(file) || mediaType + const fd = new FormData() + fd.append('file', file) + fd.append('media_type', inferred) + fd.append('title', mediaTitle) + fd.append('description', '') + fd.append('context', mediaContext) + fd.append('is_primary', 'false') + try { + await api.uploadExerciseMedia(exerciseId, fd) + } catch (err) { + if (err.code === 'MEDIA_ASSET_IN_TRASH' && err.payload?.media_asset_id != null) { + const aid = err.payload.media_asset_id + const nameHint = file?.name || err.payload.original_filename || 'diese Datei' + if ( + confirm( + `Die hochgeladene Datei ist inhaltsgleich mit einem Archiv-Medium im Papierkorb (${nameHint}). ` + + 'Soll dieses Medium wieder aktiviert und an die Übung gehängt werden? (Es wird kein zweites Exemplar auf der Platte angelegt.)', + ) + ) { + try { + await api.postMediaAssetLifecycle(aid, 'reactivate') + await api.attachExerciseMediaFromAsset(exerciseId, { + media_asset_id: aid, + title: mediaTitle || undefined, + description: '', + context: mediaContext, + is_primary: false, + }) + } catch (e2) { + alert(e2.message || String(e2)) + return + } + } else { + return } + } else if (err.code === 'MEDIA_ASSET_UNAVAILABLE') { + alert( + (err.message || 'Archiv-Konflikt') + + ' Bitte wenden Sie sich an einen Administrator oder wählen Sie eine andere Datei.', + ) + return + } else { + alert(`Upload (${file.name}): ${err.message || String(err)}`) + return } - return } - if (err.code === 'MEDIA_ASSET_UNAVAILABLE') { - alert( - (err.message || 'Archiv-Konflikt') + - ' Bitte wenden Sie sich an einen Administrator oder wählen Sie eine andere Datei.', - ) - return - } - alert('Upload: ' + (err.message || String(err))) } + setMediaFiles([]) + setMediaTitle('') + await refreshMedia() } const handleAddEmbed = async () => { @@ -1474,17 +1489,26 @@ function ExerciseFormPage() {
- + setMediaFile(e.target.files?.[0] || null)} + onChange={(e) => { + setMediaFiles(Array.from(e.target.files || [])) + e.target.value = '' + }} /> + {mediaFiles.length > 0 && ( +
+ {mediaFiles.length} Datei(en): {mediaFiles.map((f) => f.name).join(', ')} +
+ )}
({ detail: 'Unknown error' })) - const d = err.detail + const text = await response.text() + let parsed = null + try { + parsed = text ? JSON.parse(text) : null + } catch { + parsed = null + } + const d = parsed?.detail if ( response.status === 409 && d && @@ -489,6 +495,14 @@ export async function uploadExerciseMedia(exerciseId, formData) { e.payload = d throw e } + if (response.status === 413) { + const nginx = (text || '').toLowerCase().includes('nginx') + throw new Error( + nginx + ? 'Die Anfrage ist zu groß (413). Häufig: nginx „client_max_body_size“ — z. B. große/r mehrere Videos oder Bulk-Upload. Dateien kleiner aufteilen oder Server-Limit erhöhen (Frontend-Image Neu bauen).' + : 'Die Anfrage ist zu groß (413). Dateigröße oder Server-Limit prüfen.', + ) + } const msg = typeof d === 'string' ? d @@ -496,7 +510,9 @@ export async function uploadExerciseMedia(exerciseId, formData) { ? d.message : d != null ? JSON.stringify(d) - : `HTTP ${response.status}` + : text && text.length < 400 && !/^\s*