feat(media): implement centralized media archive and inline media linking
All checks were successful
Deploy Development / deploy (push) Successful in 34s
Test Suite / pytest-backend (push) Successful in 25s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 7s
Test Suite / playwright-tests (push) Successful in 23s
All checks were successful
Deploy Development / deploy (push) Successful in 34s
Test Suite / pytest-backend (push) Successful in 25s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 7s
Test Suite / playwright-tests (push) Successful in 23s
- Introduced a centralized media archive (`/media`) with lifecycle management, including soft delete and recovery options. - Enhanced media upload functionality to support multiple files and automatic type inference. - Updated documentation to reflect the new media architecture and inline media linking specifications. - Version bump to 0.8.59 to accommodate changes in media handling and database schema. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
3ff47779e0
commit
b6de1f15ea
|
|
@ -1,29 +1,29 @@
|
||||||
# Shinkan Jinkendo - Projekt-Status
|
# Shinkan Jinkendo - Projekt-Status
|
||||||
|
|
||||||
**Stand:** 2026-05-07
|
**Stand:** 2026-05-07
|
||||||
**Version (Code):** 0.8.48 (`backend/version.py`, APP_VERSION)
|
**Version (Code):** 0.8.59 (`backend/version.py`, APP_VERSION)
|
||||||
**DB-Schema-Version:** `20260507045` (u. a. `media_assets`, `platform_media_storage`)
|
**DB-Schema-Version:** `20260508049` (`backend/version.py`, DB_SCHEMA_VERSION)
|
||||||
**Branch:** develop
|
**Branch:** develop
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Executive Summary
|
## 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`**.
|
**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).
|
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.
|
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; optional Retention-Job-Dry-Run dokumentieren.
|
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.
|
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** | ✅ | 🔲 |
|
| **030** | **training_unit_exercises.exercise_variant_id** | ✅ | 🔲 |
|
||||||
| **032–034** | **Progressionsgraph Übung→Übung** | ✅ | 🔲 |
|
| **032–034** | **Progressionsgraph Übung→Übung** | ✅ | 🔲 |
|
||||||
| **035–037** | **Rahmenprogramm, Bibliothek‑Kopf, Slot‑Blueprint‑Units** | ✅ | 🔲 |
|
| **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
|
### 🔲 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:** 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` (Spec §4.2) — 0.8.47
|
- [x] **Medien:** Promotion Übung↔Medien + Copyright-Pflicht `official` / Vereins-Copyright-Regeln (Spec §4.2, Übungen+Assets)
|
||||||
- [x] **Medien:** Medienbibliothek `/media` (Lifecycle-Filter, Copyright PATCH, Vorschau); Ausbau Manager/Bulk/S3 — Roadmap
|
- [x] **Medien:** Medienbibliothek `/media` (Filter, Tags, Copyright, Bulk-Lifecycle/PATCH, Lesemodus für eingeschränkte Rollen bei `official`)
|
||||||
- [ ] **Medien:** Inline im Fließtext — nach Spec §11, nach Promotion/Archiv-Reife
|
- [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
|
- [ ] Admin-UI für Skill-Kategorien (CRUD) – falls noch offen
|
||||||
- [ ] Responsive Design / Dark Mode / PWA
|
- [ ] Responsive Design / Dark Mode / PWA
|
||||||
- [ ] KI-Suche (`ai_search`) über reine Volltextsuche hinaus
|
- [ ] 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
|
### 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
|
### Prod
|
||||||
|
|
||||||
|
|
@ -149,17 +150,17 @@ Deployment der oben genannten Migrationen und Datenabgleich nach internem Prozes
|
||||||
|
|
||||||
| Dokument | Pfad | Stand | Status |
|
| 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 |
|
| Trainingsrahmen + Graph | `technical/TRAINING_FRAMEWORK_SPEC.md` | 2026-05-05 | ✅ §2 Blueprint |
|
||||||
| Anforderungen (Index) | `functional/SHINKAN_REQUIREMENTS.md` | 2026-04-27 | ✅ Neu |
|
| Anforderungen (Index) | `functional/SHINKAN_REQUIREMENTS.md` | 2026-04-27 | ✅ Neu |
|
||||||
| Database Schema | `technical/DATABASE_SCHEMA.md` | 2026-05-05 | ✅ Aktualisiert (037) |
|
| Database Schema | `technical/DATABASE_SCHEMA.md` | 2026-05-07 | ✅ Hinweis 040–046 Medien (Kurz) |
|
||||||
| Domain Model | `functional/DOMAIN_MODEL.md` | 2026-05-05 | ✅ Aktualisiert |
|
| 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-04-30 | ✅ Ergänzt Progressions-API |
|
||||||
| Frontend Routing | `technical/EXERCISES_FRONTEND_ROUTING.md` | 2026-04-30 | ✅ Ergänzt UI-Hinweise |
|
| 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) |
|
| 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) |
|
| 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 | ✅ §11 Inline-Plan, Drift-Tab |
|
| 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-Meilenstein aktualisiert |
|
| 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)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# Shinkan Jinkendo - Fachliches Domänenmodell
|
# Shinkan Jinkendo - Fachliches Domänenmodell
|
||||||
|
|
||||||
**Version:** 0.4.3
|
**Version:** 0.4.4
|
||||||
**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)
|
**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
|
**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)
|
## Methodenbezug (§11.5)
|
||||||
|
|
||||||
**Prinzip:** Genau EINE Hauptmethode, optional weitere Nebenmethoden.
|
**Prinzip:** Genau EINE Hauptmethode, optional weitere Nebenmethoden.
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,10 @@ Ausführliche fachliche Inhalte:
|
||||||
| Dokument | Inhalt |
|
| Dokument | Inhalt |
|
||||||
|----------|--------|
|
|----------|--------|
|
||||||
| [shinkan_anforderungsdokument_entwurf.md](./shinkan_anforderungsdokument_entwurf.md) | Gesamtentwurf Anforderungen |
|
| [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 |
|
| [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.
|
`CLAUDE.md` (Repo-Root) verweist hierher als Einstieg.
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
# Gelieferte Features & technische Basis (Q2 2026)
|
# Gelieferte Features & technische Basis (Q2 2026)
|
||||||
|
|
||||||
**Stand:** 2026-05-05
|
**Stand:** 2026-05-07
|
||||||
**Referenz:** `backend/version.py` — **APP_VERSION 0.8.10**, **DB_SCHEMA_VERSION 20260505037**
|
**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 |
|
| **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`** |
|
| **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` |
|
| **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`)
|
## 6. Frontend – Übung bearbeiten (`ExerciseFormPage.jsx`)
|
||||||
|
|
||||||
- **Varianten-Editor**: eingeklappter Bereich (`<details>`), **eine Variante zur Zeit** über Dropdown oder „Neue Variante“; Felder über **`ExerciseVariantFields`**; Reihenfolge Nach oben/unten; Speichern/Löschen pro Variante.
|
- **Varianten-Editor**: eingeklappter Bereich (`<details>`), **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.
|
- 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).
|
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).
|
- 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).
|
- Progressions-Serien als **Blöcke** (angekündigt; Voraussetzung: `prerequisite_variant_id` / `progression_level` vorhanden).
|
||||||
- Serverseitige **Suchvorschläge** (Autocomplete-Endpoint), falls datalist nicht reicht.
|
- Serverseitige **Suchvorschläge** (Autocomplete-Endpoint), falls datalist nicht reicht.
|
||||||
- Optional: Streaming/chunked Upload für sehr große Videos (RAM-Thema).
|
- 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 |
|
| Thema | Dokument |
|
||||||
|--------|----------|
|
|--------|----------|
|
||||||
|
|
@ -140,5 +164,6 @@ Hinweis: Es gibt **keine** separaten Routen `/exercises/:id/variants/...` — Be
|
||||||
| API Übungen | `technical/EXERCISES_API_SPEC.md` |
|
| API Übungen | `technical/EXERCISES_API_SPEC.md` |
|
||||||
| Domänenmodell | `functional/DOMAIN_MODEL.md` |
|
| Domänenmodell | `functional/DOMAIN_MODEL.md` |
|
||||||
| Datenbank Überblick | `technical/DATABASE_SCHEMA.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` |
|
| Projektstatus-Kachel | `../PROJECT_STATUS.md` |
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
# Shinkan Jinkendo - Datenbank-Schema (Technisch)
|
# Shinkan Jinkendo - Datenbank-Schema (Technisch)
|
||||||
|
|
||||||
**Version:** 0.5.2
|
**Version:** 0.5.3
|
||||||
**Stand:** 2026-05-05
|
**Stand:** 2026-05-07
|
||||||
**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`).
|
**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` | ✅ |
|
| **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` | ✅ |
|
| **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`** | ✅ |
|
| **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)
|
## Migration 022: Skills Schema Complete (BREAKING CHANGE)
|
||||||
|
|
||||||
|
|
@ -267,9 +266,11 @@ exercise_skills (
|
||||||
UNIQUE (exercise_id, skill_id) -- Migration 020
|
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_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**)
|
### Trainingsrahmenprogramm Bibliothek (Migrationen **035–036**)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# Medien-Assets, Archiv & Lebenszyklus (Single Source of Truth)
|
# Medien-Assets, Archiv & Lebenszyklus (Single Source of Truth)
|
||||||
|
|
||||||
**Status:** verbindlich für Design, API und DB-Migrationen zum Thema Medien
|
**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`
|
**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.
|
**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 |
|
| 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 | **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 | §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.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). |
|
| 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)
|
### 11.5 Wann umsetzen (Reihenfolge)
|
||||||
|
|
||||||
1. **Vorher:** Medien-Archiv, `media_assets`, Upload/Dedupe, Speicherpfad, Basis-Papierkorb (§5) – jeweils stabil.
|
1. ~~**Erledigt (Basis):** Medien-Archiv, `media_assets`, Upload/Dedupe, Speicherpfad, Papierkorb, Bibliothek `/media`, Verknüpfung `from-asset`, Governance `official`/Copyright.~~
|
||||||
2. **Danach:** Inline implementieren, sobald Trainer-Feedback oder Content-Menge den Bedarf **konkret** bestätigt (typisch nach 1–2 Beta-Zyklen).
|
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. **Nicht nötig:** vorher kompletten Block-Editor einführen; **Platzhalter im bestehenden RTE** ist der vorgesehene **schlanke** Einstieg.
|
3. **Editor:** kein Zwang zum vollen Block-Editor vorab; **Platzhalter im bestehenden RTE** ist der vorgesehene schlanke Einstieg.
|
||||||
|
|
||||||
### 11.6 Refactor-Vermeidung (jetzt schon)
|
### 11.6 Refactor-Vermeidung (jetzt schon)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
# Media Upload & Embed Specification
|
# Media Upload & Embed Specification
|
||||||
|
|
||||||
**Version:** 1.1
|
**Version:** 1.2
|
||||||
**Datum:** 2026-04-27
|
**Datum:** 2026-05-07
|
||||||
**Status:** DRAFT - Awaiting Review
|
**Status:** Aktuell für Upload-Limits, MIME, Embed — **zentraler Medien-Ist-Stand** inkl. Archiv, Lifecycle, Pfadkonvention: **`MEDIA_ASSETS_AND_ARCHIVE_SPEC.md`**
|
||||||
**Autor:** Claude Code
|
|
||||||
**Änderungen v1.1:** Rollenbasierte Server-Limits (`EXERCISE_MEDIA_*_MB`)
|
|
||||||
|
|
||||||
> **Zielbild Medien-Archiv, Wiederverwendung, Papierkorb, Copyright, externe Speicherung, später Inline im Fließtext:**
|
> **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).
|
> Verbindliche Single Source of Truth: **`MEDIA_ASSETS_AND_ARCHIVE_SPEC.md`** (§11 Leitplanken Inline, ohne Umsetzung).
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,14 @@
|
||||||
# Session Handover (Verweis)
|
# 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.*
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,8 @@ return {"message": "Fehler", "success": False}
|
||||||
### 1.4 Mandanten & Zugriffsschicht (Shinkan / ACCESS_LAYER)
|
### 1.4 Mandanten & Zugriffsschicht (Shinkan / ACCESS_LAYER)
|
||||||
|
|
||||||
**Verbindlicher Rahmen:** `.claude/docs/technical/ACCESS_LAYER_AND_GOVERNANCE_PLAN.md`
|
**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`
|
**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:
|
**Definition of Done für neue oder geänderte geschützte APIs**, sobald Daten **Verein**, **Sichtbarkeit** oder **mandantenbezogene Listen** betreffen:
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,8 @@
|
||||||
1. Repo-Root: `CLAUDE.md` (Kontext, Links, Pflicht-Dokus)
|
1. Repo-Root: `CLAUDE.md` (Kontext, Links, Pflicht-Dokus)
|
||||||
2. Agent-Übersicht: **`.claude/README.md`** (Baum, wo was liegt)
|
2. Agent-Übersicht: **`.claude/README.md`** (Baum, wo was liegt)
|
||||||
3. Spez-Index: **`.claude/docs/README.md`**
|
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)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,8 @@
|
||||||
> | Lessons Learned | `.claude/rules/LESSONS_LEARNED.md` |
|
> | Lessons Learned | `.claude/rules/LESSONS_LEARNED.md` |
|
||||||
> | Setup-Dokument | `.claude/docs/working/SHINKAN_PROJECT_SETUP.md` |
|
> | Setup-Dokument | `.claude/docs/working/SHINKAN_PROJECT_SETUP.md` |
|
||||||
> | Anforderungen | `.claude/docs/functional/SHINKAN_REQUIREMENTS.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
|
## Projekt-Übersicht
|
||||||
|
|
||||||
|
|
@ -57,7 +58,7 @@ backend/
|
||||||
└── routers/ # Router-Module
|
└── routers/ # Router-Module
|
||||||
auth · profiles · clubs · groups · skills · methods
|
auth · profiles · clubs · groups · skills · methods
|
||||||
exercises · exercise_progression_graphs · training_units · training_programs
|
exercises · exercise_progression_graphs · training_units · training_programs
|
||||||
planning · import_wiki · admin · membership
|
planning · import_wiki · admin · membership · media_assets
|
||||||
|
|
||||||
frontend/src/
|
frontend/src/
|
||||||
├── App.jsx # Root, Auth-Gates, Navigation
|
├── 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`.
|
**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)
|
### 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-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`.
|
- 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`.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
- **Kataloge:** Fähigkeiten und Trainingsmethoden strukturiert verwalten
|
||||||
- **Standardisierung:** Vereinsstandards und wiederverwendbare Vorlagen
|
- **Standardisierung:** Vereinsstandards und wiederverwendbare Vorlagen
|
||||||
- **Freigabe:** Gesteuerte Veröffentlichung von Inhalten
|
- **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
|
## Nicht in Shinkan
|
||||||
|
|
||||||
|
|
|
||||||
109
docs/HANDOVER.md
109
docs/HANDOVER.md
|
|
@ -1,9 +1,9 @@
|
||||||
# Shinkan Jinkendo – Entwicklungsstand & Handover
|
# Shinkan Jinkendo – Entwicklungsstand & Handover
|
||||||
|
|
||||||
**Stand:** 2026-05-05
|
**Stand:** 2026-05-07
|
||||||
**App-Version / DB-Schema:** siehe `backend/version.py`
|
**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`)
|
### 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 |
|
| Thema | Pfad |
|
||||||
|--------|------|
|
|--------|------|
|
||||||
| Projekt-Setup, Domain grob | `.claude/docs/working/SHINKAN_PROJECT_SETUP.md` |
|
| 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` |
|
| Ü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` |
|
| 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.
|
- **`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).
|
- **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).
|
- **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)
|
### 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.
|
- **`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).
|
- **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. Sonst bei fehlendem Treffer **`null`** (kein stilles Legacy mit falschem Trainingsstil).
|
- **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)
|
### 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/{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`).
|
- **`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
|
### 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).
|
- **`GET /api/admin/matrix-stack/export`**, **`POST /api/admin/matrix-stack/import`** — siehe `matrix_stack_bundle.py`.
|
||||||
- **`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.
|
|
||||||
|
|
||||||
### 2.5 Frontend (Admin)
|
### 2.5 Frontend (Admin)
|
||||||
|
|
||||||
- **`frontend/src/pages/AdminMaturityModelsPage.jsx`**: Tabs u. a. Katalog, Modelle, Kontext-Zuordnung, **Matrix-Ansicht und Export**.
|
- **`AdminMaturityModelsPage.jsx`**, **`MaturityModelBindingsAdmin.jsx`**, **`MaturityMatrixToolsAdmin.jsx`**; APIs in `api.js`.
|
||||||
- **`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.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. Trainingsrahmenprogramm & Planungs‑Blueprint (kurz)
|
## 3. Trainingsrahmenprogramm & Planungs‑Blueprint (kurz)
|
||||||
|
|
||||||
- **Migration 036:** Rahmenkopf nur Bibliothek (Kontext: Fokusbereich, Stilrichtung; M:N Trainingsarten/Zielgruppen); keine `plan_mode`/keine Kopf‑`group_id`.
|
- **036 / 037:** Bibliotheks-Rahmen, Slot-Inhalt als **`training_units`** mit **`framework_slot_id`**; **`POST /api/training-units/from-framework-slot`**.
|
||||||
- **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.
|
- **Code:** `training_framework_programs.py`, `training_planning.py`; Frontend **`TrainingFrameworkProgramEditPage.jsx`**, **`createTrainingUnitFromFrameworkSlot`** in `api.js`.
|
||||||
- **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`.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 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).
|
- **`media_assets`:** Physische oder logische Archiv-Datei (u. a. `sha256`, `visibility`, `club_id`, `lifecycle_state`, `copyright_notice`, Speicherpfad/`storage_key`, `tags` ab passender Migration).
|
||||||
- **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.
|
- **`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`).
|
- **`GET /api/media-assets`**, Filter (Lifecycle, `media_kind`, Verein, Suche, Tags), Berechtigungen pro Zeile.
|
||||||
2. `GET /api/exercises` (Filter, Auth) und eine Beispiel-Übung gegen die Dev-DB testen.
|
- **`PATCH /api/media-assets/{id}`** / Bulk-PATCH, **Lifecycle** (`POST …/lifecycle`, Bulk).
|
||||||
3. UI schrittweise: Liste → Detail → Formular → Medien (an Specs in `.claude/docs/technical/` ausrichten).
|
- **`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 |
|
| 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` |
|
| Backend API | `backend/main.py`; u. a. **`media_assets.py`**, **`exercises.py`**, **`profiles.py`**, **`training_framework_programs.py`**, `tenant_context.py` |
|
||||||
| Migrationen | `backend/migrations/` (u. a. 024–027 Reifegrad/Bindings; **035–037** Rahmenprogramm / Slot‑Blueprint) |
|
| Migrationen | `backend/migrations/` (040+ Mitgliedschaft/Governance; **045+** Medien-Stack) |
|
||||||
| Frontend API | `frontend/src/utils/api.js` |
|
| Frontend API | `frontend/src/utils/api.js` |
|
||||||
|
| Aktiver Verein (UI) | `frontend/src/utils/activeClub.js`, `AuthContext.jsx` |
|
||||||
| Version / Changelog | `backend/version.py` |
|
| 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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,9 @@ server {
|
||||||
# — verringert sporadische 502, wenn sich nur die Backend-Container-IP geändert hat.
|
# — verringert sporadische 502, wenn sich nur die Backend-Container-IP geändert hat.
|
||||||
resolver 127.0.0.11 valid=10s ipv6=off;
|
resolver 127.0.0.11 valid=10s ipv6=off;
|
||||||
|
|
||||||
# Uploads (Übungsmedien) und API erreichen Clients unter derselben Host-URL wie die SPA —
|
# Uploads (Übungsmedien, Medienarchiv-Bulk) — Limit muss >= größte Einzeldatei und
|
||||||
# dafür muss Nginx zur FastAPI-Instanz im Compose-Netz weiterleiten.
|
# bei multipart ggf. Summe mehrerer Dateien sein (Backend praxis: bis 1024 MB Admin).
|
||||||
client_max_body_size 64m;
|
client_max_body_size 1024m;
|
||||||
|
|
||||||
location ^~ /api/ {
|
location ^~ /api/ {
|
||||||
set $docker_backend_svc backend;
|
set $docker_backend_svc backend;
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,20 @@ import ExerciseProgressionGraphPanel from '../components/ExerciseProgressionGrap
|
||||||
import { SKILL_LEVEL_OPTIONS, normalizeSkillLevelSlug } from '../constants/skillLevels'
|
import { SKILL_LEVEL_OPTIONS, normalizeSkillLevelSlug } from '../constants/skillLevels'
|
||||||
import { useAuth } from '../context/AuthContext'
|
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. */
|
/** Kachelvorschau: Video nutzt ersten Frame (metadata), Bild = img. */
|
||||||
function ExerciseMediaThumbTile({ exerciseId, media, onOpenPreview }) {
|
function ExerciseMediaThumbTile({ exerciseId, media, onOpenPreview }) {
|
||||||
const src = !media.embed_url ? resolveExerciseMediaFileUrl(exerciseId, media) : null
|
const src = !media.embed_url ? resolveExerciseMediaFileUrl(exerciseId, media) : null
|
||||||
|
|
@ -429,7 +443,7 @@ function ExerciseFormPage() {
|
||||||
const [variantEditSelection, setVariantEditSelection] = useState(null)
|
const [variantEditSelection, setVariantEditSelection] = useState(null)
|
||||||
const variantsDetailsRef = useRef(null)
|
const variantsDetailsRef = useRef(null)
|
||||||
|
|
||||||
const [mediaFile, setMediaFile] = useState(null)
|
const [mediaFiles, setMediaFiles] = useState([])
|
||||||
const [mediaType, setMediaType] = useState('image')
|
const [mediaType, setMediaType] = useState('image')
|
||||||
const [mediaTitle, setMediaTitle] = useState('')
|
const [mediaTitle, setMediaTitle] = useState('')
|
||||||
const [mediaContext, setMediaContext] = useState('ablauf')
|
const [mediaContext, setMediaContext] = useState('ablauf')
|
||||||
|
|
@ -741,62 +755,63 @@ function ExerciseFormPage() {
|
||||||
)
|
)
|
||||||
|
|
||||||
const handleUploadFile = async () => {
|
const handleUploadFile = async () => {
|
||||||
if (!exerciseId || !mediaFile) {
|
if (!exerciseId || mediaFiles.length === 0) {
|
||||||
alert('Datei wählen')
|
alert('Datei(en) wählen')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const fd = new FormData()
|
const files = [...mediaFiles]
|
||||||
fd.append('file', mediaFile)
|
for (const file of files) {
|
||||||
fd.append('media_type', mediaType)
|
const inferred = inferExerciseMediaType(file) || mediaType
|
||||||
fd.append('title', mediaTitle)
|
const fd = new FormData()
|
||||||
fd.append('description', '')
|
fd.append('file', file)
|
||||||
fd.append('context', mediaContext)
|
fd.append('media_type', inferred)
|
||||||
fd.append('is_primary', 'false')
|
fd.append('title', mediaTitle)
|
||||||
try {
|
fd.append('description', '')
|
||||||
await api.uploadExerciseMedia(exerciseId, fd)
|
fd.append('context', mediaContext)
|
||||||
setMediaFile(null)
|
fd.append('is_primary', 'false')
|
||||||
setMediaTitle('')
|
try {
|
||||||
await refreshMedia()
|
await api.uploadExerciseMedia(exerciseId, fd)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.code === 'MEDIA_ASSET_IN_TRASH' && err.payload?.media_asset_id != null) {
|
if (err.code === 'MEDIA_ASSET_IN_TRASH' && err.payload?.media_asset_id != null) {
|
||||||
const aid = err.payload.media_asset_id
|
const aid = err.payload.media_asset_id
|
||||||
const nameHint =
|
const nameHint = file?.name || err.payload.original_filename || 'diese Datei'
|
||||||
(mediaFile && mediaFile.name) ||
|
if (
|
||||||
err.payload.original_filename ||
|
confirm(
|
||||||
'diese Datei'
|
`Die hochgeladene Datei ist inhaltsgleich mit einem Archiv-Medium im Papierkorb (${nameHint}). ` +
|
||||||
if (
|
'Soll dieses Medium wieder aktiviert und an die Übung gehängt werden? (Es wird kein zweites Exemplar auf der Platte angelegt.)',
|
||||||
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, {
|
||||||
try {
|
media_asset_id: aid,
|
||||||
await api.postMediaAssetLifecycle(aid, 'reactivate')
|
title: mediaTitle || undefined,
|
||||||
await api.attachExerciseMediaFromAsset(exerciseId, {
|
description: '',
|
||||||
media_asset_id: aid,
|
context: mediaContext,
|
||||||
title: mediaTitle || undefined,
|
is_primary: false,
|
||||||
description: '',
|
})
|
||||||
context: mediaContext,
|
} catch (e2) {
|
||||||
is_primary: false,
|
alert(e2.message || String(e2))
|
||||||
})
|
return
|
||||||
setMediaFile(null)
|
}
|
||||||
setMediaTitle('')
|
} else {
|
||||||
await refreshMedia()
|
return
|
||||||
} catch (e2) {
|
|
||||||
alert(e2.message || String(e2))
|
|
||||||
}
|
}
|
||||||
|
} 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 () => {
|
const handleAddEmbed = async () => {
|
||||||
|
|
@ -1474,17 +1489,26 @@ function ExerciseFormPage() {
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'grid', gap: '12px', marginTop: '12px' }}>
|
<div style={{ display: 'grid', gap: '12px', marginTop: '12px' }}>
|
||||||
<div>
|
<div>
|
||||||
<label className="form-label">Datei</label>
|
<label className="form-label">Dateien</label>
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
|
multiple
|
||||||
accept="image/*,video/*,application/pdf"
|
accept="image/*,video/*,application/pdf"
|
||||||
onChange={(e) => setMediaFile(e.target.files?.[0] || null)}
|
onChange={(e) => {
|
||||||
|
setMediaFiles(Array.from(e.target.files || []))
|
||||||
|
e.target.value = ''
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
{mediaFiles.length > 0 && (
|
||||||
|
<div style={{ fontSize: '0.875rem', color: 'var(--text2)', marginTop: '6px' }}>
|
||||||
|
{mediaFiles.length} Datei(en): {mediaFiles.map((f) => f.name).join(', ')}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="form-row" style={{ marginTop: '8px' }}>
|
<div className="form-row" style={{ marginTop: '8px' }}>
|
||||||
<select className="form-input" value={mediaType} onChange={(e) => setMediaType(e.target.value)}>
|
<select className="form-input" value={mediaType} onChange={(e) => setMediaType(e.target.value)}>
|
||||||
<option value="image">Bild</option>
|
<option value="image">Typ-Fallback: Bild</option>
|
||||||
<option value="video">Video</option>
|
<option value="video">Typ-Fallback: Video</option>
|
||||||
<option value="document">PDF</option>
|
<option value="document">Typ-Fallback: PDF</option>
|
||||||
</select>
|
</select>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
|
|
||||||
|
|
@ -472,8 +472,14 @@ export async function uploadExerciseMedia(exerciseId, formData) {
|
||||||
body: formData,
|
body: formData,
|
||||||
})
|
})
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const err = await response.json().catch(() => ({ detail: 'Unknown error' }))
|
const text = await response.text()
|
||||||
const d = err.detail
|
let parsed = null
|
||||||
|
try {
|
||||||
|
parsed = text ? JSON.parse(text) : null
|
||||||
|
} catch {
|
||||||
|
parsed = null
|
||||||
|
}
|
||||||
|
const d = parsed?.detail
|
||||||
if (
|
if (
|
||||||
response.status === 409 &&
|
response.status === 409 &&
|
||||||
d &&
|
d &&
|
||||||
|
|
@ -489,6 +495,14 @@ export async function uploadExerciseMedia(exerciseId, formData) {
|
||||||
e.payload = d
|
e.payload = d
|
||||||
throw e
|
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 =
|
const msg =
|
||||||
typeof d === 'string'
|
typeof d === 'string'
|
||||||
? d
|
? d
|
||||||
|
|
@ -496,7 +510,9 @@ export async function uploadExerciseMedia(exerciseId, formData) {
|
||||||
? d.message
|
? d.message
|
||||||
: d != null
|
: d != null
|
||||||
? JSON.stringify(d)
|
? JSON.stringify(d)
|
||||||
: `HTTP ${response.status}`
|
: text && text.length < 400 && !/^\s*</.test(text)
|
||||||
|
? `HTTP ${response.status}: ${text.trim()}`
|
||||||
|
: `HTTP ${response.status}`
|
||||||
throw new Error(msg)
|
throw new Error(msg)
|
||||||
}
|
}
|
||||||
return response.json()
|
return response.json()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user