- 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>
17 KiB
Medien-Assets, Archiv & Lebenszyklus (Single Source of Truth)
Status: verbindlich für Design, API und DB-Migrationen zum Thema Medien
Stand: 2026-05-07 (Ist-Changelog Medien bis 0.8.59; §11 Inline geplant)
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.
1. Zweck und Abgrenzung
1.1 Zweck
- Ein physisches Medium („Datei-Asset“) wird einmal gespeichert und mehrfach mit Übungen verknüpft (Wiederverwendung, keine Dubletten auf der Platte).
- Sichtbarkeit von Datei-Assets folgt derselben Semantik wie bei Übungen (
private|club|official– ggf. spätere Enum-Erweiterungen nur gemeinsam mit Migration + ACCESS_LAYER-Doku). - Superadmin kann Medien global freigeben (fachlich: Stufe wie „offiziell“ / plattformweit lesbar gemäß
library_content_*-Regeln). - Copyright: pro Asset sind Vermerke vorgesehen und an
official/ Vereinsnutzung anzubinden (Validierungsstufen in Umsetzung festlegen). - Externer Speicher: Speicherung darf lokal/NAS (Filesystem) oder extern (z. B. S3-kompatibler Dienst) erfolgen – die App arbeitet über eine Speicher-Abstraktion (Interface), nicht mit verstreuten
Path-Annahmen. - Papierkorb & Retention: mehrstufiger Lebenszyklus mit automatischen und manuellen Übergängen, siehe §5.
1.2 Abgrenzung
- Embeds (reine externe URL, kein Upload) bleiben pro Verknüpfung zur Übung möglich (
embed_urlo. Ä.); sie haben keinen physischen Lebenszyklus wie Datei-Assets (kein Papierkorb „Stufe 3 physische Löschung“). Trotzdem: UI kann „Link ungültig“ o. Ä. separat abbilden. - Übungs-Governance (wer eine Übung bearbeiten darf) bleibt maßgeblich für Anlegen/Entfernen/Umsortieren von Medien in dieser Übung; Medien erzwingen keine schärfere Edit-Policy als die Übung selbst.
2. Begriffe
| Begriff | Bedeutung |
|---|---|
| Media Asset | Logische Entität für eine hochgeladene Datei inkl. Metadaten, Sichtbarkeit, Copyright, Speicherreferenz, Lifecycle-Status. |
| Übungs-Medium / Attachment | Verknüpfung Übung ↔ Asset oder reines Embed; enthält u. a. context (Sektion), sort_order, übungsspezifischen Titel/Beschreibung, is_primary. |
| Medienmanager / Archiv | Verwaltungs-UI/API für Assets (Suche, Metadaten, Lifecycle, ggf. Superadmin-global). |
3. Zieldatenmodell (Überblick)
Konkrete Tabellennamen/Migrationen beim Implementierungsstart festziehen; dieses Kapitel ist die fachliche Norm.
3.1 Tabelle media_assets (oder gleichwertiger Name)
Pflichtidee:
- Identität, technische Metadaten:
id,mime_type,byte_size,sha256(vollständig),original_filename,created_at,updated_at. - Mandant & Governance:
visibility,club_id(nullable je nach Semantik wie bei Übungen),uploaded_by_profile_id. - Copyright: mindestens
copyright_notice(TEXT); optionallicense,attribution,source_url. - Speicher:
storage_backend(Enum/String: z. B.local,s3, …),storage_key(relativer oder bucket-key), optionalstorage_urlnur für interne Zwecke – keine öffentlichen Secrets in der DB. - Lifecycle:
lifecycle_state(siehe §5), Timestamps für Übergänge und geplante Purge-Termine.
Deduplizierung: Innerhalb sinnvoller Grenze (z. B. pro Verein bei club, global nur mit Superadmin-Policy) über sha256 + club_id/visibility; Konflikt bei Upload → bestehendes Asset verknüpfen oder expliziter Nutzerdialog (Umsetzungsdetail).
3.2 Verknüpfung (heute exercise_media)
- Erweiterung um
media_asset_id(FK, nullable wenn reines Embed). - Embed-Zeilen:
media_asset_id IS NULL,embed_url/embed_platformgesetzt wie heute. - Übungsspezifische Felder:
context(ablauf|detail|trainer_hint),sort_order,title,description,is_primary.
3.3 Referenzen & physisches Löschen
- Vor physischem Löschen muss die Referenzanzahl aller aktiven/nicht-purged Links 0 sein (oder Quarantäne-Policy); siehe §5.3.
4. Sichtbarkeit, Lesen und Übungs-Promotion
4.1 Leseregel Datei-Asset
- Zentrale Entscheidung analog
library_content_visible_to_profile/ TenantContext: Profil darf Asset lesen nur wenn Visibility+club_id+created_byzum Objekt passen (gleiche Philosophie wie Übungen). - GET Download / Stream: muss Asset-Governance prüfen; wenn der Aufruf im Kontext einer Übung erfolgt, zusätzlich (oder als Schnittmenge) Übung lesbar – verbindlich ohne Seitenkanal (kein Download nur mit Übungs-ID, wenn Asset für Nutzer unsichtbar wäre).
4.2 Promotion Übung → official (bzw. global)
Beim Speichern/Promoten einer Übung auf official (oder vergleichbare globale Stufe):
- Pflicht-Dialog: Hinweis, dass alle zugeordneten Datei-Assets, die noch nicht diese Sichtbarkeit haben, mit angehoben werden (oder die Aktion schlägt fehl / erfordert Anpassung).
- Abbruch: Nutzer kann abbrechen oder einzelne Assets vor Freigabe anpassen / entkoppeln.
- Copyright: Für
officialsind leere oder unzureichende Copyright-Felder nicht akzeptabel (genaue Regel in Umsetzung + Validierung).
Superadmin kann Medien explizit global/offiziell machen (Archiv-Pfad), unabhängig von einer einzelnen Übung – konsistent zur Rolle.
4.3 Bearbeitung Medien in der Übung
Wer die Übung bearbeiten darf (bestehende oder künftig erweiterte Regel), darf für diese Übung:
- Medien hinzufügen (Upload neu oder Verknüpfung aus Archiv),
- Reihenfolge / Sektion / Titel ändern,
- Verknüpfung entfernen (Asset bleibt im Archiv, sofern nicht anderweitig gelöscht),
- Embeds pflegen.
Das widerspricht nicht dem Papierkorb: wer kein Recht hat, ein Asset global zu Trash/ Purge zu bewegen, darf trotzdem die Verknüpfung in „seiner“ Übung lösen, solange die Übungs-Edit-Policy das erlaubt.
5. Lebenszyklus (Papierkorb) – Norm
5.1 Zustände
| Zustand | Code (Beispiel) | Neue Zuordnung zu Übungen | Anzeige in Übung (Lesemodus) | Anzeige Bearbeiten |
|---|---|---|---|---|
| Aktiv | active |
ja | normal | normal |
| Stufe 1 – Papierkorb | trash_soft |
nein | ja + Warnhinweis („wird abgeschafft“) | ja + Warnhinweis |
| Stufe 2 – Ausblendung | trash_hidden |
nein | nein (Platzhalter/„nicht verfügbar“) | Recovery-Aktion sichtbar |
| Stufe 3 – purgiert | purged / Zeile entfernt |
nein | nein | nur noch historischer Hinweis nach Policy |
Default-Retention (automatisch):
- Stufe 1 → Stufe 2: ca. 30 Tage nach Eintritt in Stufe 1 (konfigurierbar).
- Stufe 2 → Stufe 3 physisches Löschen: ca. 90 Tage (3 Monate) nach Eintritt in Stufe 2 (konfigurierbar).
Manuell: Berechtigte Rollen dürfen Übergänge vorziehen (Stufe 1 erzwingen, Stufe 2 erzwingen, Purge erzwingen) gemäß §5.2 – ggf. Tier-Gates (§6).
5.2 Wer darf Lifecycle-Transitionen?
| Aktion | Vereins-Asset (club, Owner-Verein) |
Privates Asset (private, Uploader) |
official / plattformweit |
|---|---|---|---|
| Stufe 1 (Papierkorb) | Vereinsadmin + Superadmin | Uploader + Superadmin | Superadmin |
| Stufe 2 vorziehen / erzwingen | Vereinsadmin + Superadmin | Uploader + Superadmin | Superadmin |
| Recovery Stufe 2 → 1 | wie Stufe 1-Eintritt, ggf. Tier | wie links | Superadmin |
| Physisches Löschen (3) | Vereinsadmin + Superadmin; Systemjob nach Frist | Uploader + Superadmin | Superadmin |
Superadmin / Systemadmin ist immer Override (Klarstellung zur „systemadmin“-Formulierung).
5.3 Konsistenz mit bestehenden Übungen
- In Stufe 1 bleiben Verknüpfungen funktional für Anzeige bestehen; Nutzer sehen Warnung.
- In Stufe 2 wird das Medium in der öffentlichen Übungsansicht nicht gerendert; im Bearbeitungsmodus gibt es einen Recovery-Link, der das Asset zurück auf Stufe 1 setzt (Policy: nur wer nach §5.2 Stufe 1 setzen darf, oder erweiterte Regel – bei Konflikt ACCESS_LAYER + dieses Dokument anpassen).
- Physisches Löschen: nur nach Stufe 2-Frist oder manueller Purge-Aktion; Backend entfernt Datei über Speicher-Backend und markiert Asset endgültig.
5.4 Hintergrundjobs
- Geplanter Job (täglich o. Ä.): Transitionen 1→2 und 2→3 gemäß Timestamps.
- Alle Zeiten konfigurierbar (Umgebungsvariablen oder DB-Config), Defaults wie §5.1.
6. Tier & Features
- Tier kann erlauben oder verbieten: manuelles Vorziehen von Stufe 2 oder Purge, zusätzliche Speicherquoten, externes Backend, etc.
- Tier ändert nicht die Übungs-Sichtbarkeit an sich; es limitiert nur Medien-spezifische Aktionen (Löschstufen, Archiv-Größe, …). Konkrete Feature-Keys in Umsetzung in
features/tier_limitsergänzen und hier im Changelog dieses Dokuments verlinken.
7. Externe Speicherung (Server / S3)
- Abstraktion StorageAdapter:
put,get,delete, ggf.exists. - Konfiguration über ENV (z. B. Endpoint, Bucket, Credentials) – nie im Frontend.
- NAS / anderer Rechner als App-Host: In der Praxis
MEDIA_ROOTin Compose/ENV auf den Mount-Punkt des NAS (oder NFS/SMB) setzen – die App läuft z. B. auf dem Raspberry, die Bytes liegen auf dem Speichersystem. Kein »Mediaserver« mit eigener Geschäftslogik nötig. - Pfadkonvention auf der Platte z. B.
clubs/{club_id}/…für Mandantentrennung (Umsetzung schrittweise; neue Uploads können zuerst unterexercises/{sha256}{ext}liegen).
7.1 Konfiguration: Bootstrap vs. Superadmin (Laufzeit)
| Ebene | Inhalt | Wer / Wo |
|---|---|---|
| Bootstrap | Basis-Verzeichnis MEDIA_ROOT (Container/Host), ggf. S3-Secrets |
Deployment (.env, Docker Compose) – Container muss ohne UI starten können |
| Laufzeit (nicht-geheim) | Zusätzlicher relativer Unterpfad unter MEDIA_ROOT (local_relative_root), später: aktives Backend local | s3, öffentlicher Endpoint/Bucket-Name |
Superadmin über Tabelle platform_media_storage + API; keine Secrets für S3 im Klartext in der DB (Keys nur ENV/Vault) |
| Effektives Wurzelverzeichnis | Path(MEDIA_ROOT) / local_relative_root nach Normalisierung (kein .., kein absoluter Pfad im relativen Segment) |
berechnet im Backend bei jedem Zugriff |
Hinweis Beta: Primär ein Verein, Videos auf separatem physischen System → typischerweise ein NAS-Mount als MEDIA_ROOT; Superadmin kann bei Bedarf einen Unterordner setzen, ohne neues Image zu bauen.
Drift: Jede Änderung an Speicher-Konfiguration oder Asset-Schema → Migration + Eintrag §10 Changelog + bei neuen Endpoints ACCESS_LAYER_ENDPOINT_AUDIT.md. Änderungen an Inline-Platzhaltern (§11) → ebenfalls Changelog + ggf. Frontend-Sanitize-Regeln dokumentieren.
8. Embeds & Streaming-Plattformen
- Embeds bleiben erste Klasse; bestehende Plattform-Erkennung erweiterbar (
embed_platform). - Roadmap: weitere Hosts (Player-Komponente, oEmbed, CSP) – ohne Änderung am Asset-Lifecycle-Modell.
9. Pflichten zur Drift-Vermeidung
| Änderung an … | Pflegepflicht |
|---|---|
| DB-Schema Medien | Neue Migration + Abschnitt/Changelog in diesem Dokument (Datum, Kurzbeschreibung) |
| Sichtbarkeit / Enums | Zuerst ACCESS_LAYER_AND_GOVERNANCE_PLAN.md, dann dieses Dokument synchron halten |
| Neue Endpoints Medien/Archiv | ACCESS_LAYER_ENDPOINT_AUDIT.md + get_tenant_context/Governance |
| Inline-Referenzen in Übungstexten (§11) | Renderer/Sanitizer-Regeln + dieses Dokument §11; kein zweites Rechtemodell |
| Implementierungs-Meilenstein | backend/version.py (MODULE_VERSIONS / Schema-Kommentar) und bei größerem Release PROJECT_STATUS.md |
10. Changelog
| Datum | Änderung |
|---|---|
| 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). |
11. Inline-Medien im Fließtext (Planung, Leitplanken)
Status: nicht implementiert; verbindlich nur als Richtschnur, damit später kein Big-Bang-Refactor nötig ist.
11.1 Ziel
- Medien (Player, Bild) sollen an definierter Stelle in Feldern wie Ablauf / Ziel / Notizen erscheinen können – zusätzlich oder statt reiner Zuordnung zu den Sektionen
ablauf/detail/trainer_hint. - Keine zweite Sichtbarkeit: Inline verweist immer auf dieselbe Übungs-Medium-Zeile (
exercise_media.id) bzw. indirekt auf das gleiche Asset wie die Medienliste; Lesen/Ausliefern nur nach bestehender Übungs- + Medien-Governance (§4.1).
11.2 Platzhalter-Konvention (Vorschlag für spätere Umsetzung)
- Beim Speichern im Rich-Text: markierter Verweis, z. B.
data-shinkan-exercise-media="<numerische exercise_media.id>"auf einem neutralen Element (span/figure), oder eine interne Kurzsyntax ({{exerciseMedia:123}}), die der Server beim Speichern in eine kanonische HTML-Form überführt. - Final festlegen beim Start der Implementierung (ein Format, nicht mehrere parallele).
11.3 Rendering & Sicherheit
- Ein zentraler Pfad „Übungstext für Anzeige aufbereiten“: HTML sanitizen (Allowlist), erlaubte Platzhalter auflösen, ID gehört zur aktuellen Übung und Medium ist für den Nutzer sichtbar – sonst Platzhalter mit neutralem Hinweis oder ausblenden.
- XSS/CSP: keine rohen
iframe/Skripte aus Nutzer-HTML ohne Kontrolle; eingebettete Player nur über kontrollierte Komponenten.
11.4 Koexistenz mit Sektions-Medien
- Liste + Inline dürfen dasselbe
exercise_mediareferenzieren (ein Player an zwei Stellen) – Produktentscheidung: später optional „Duplikat vermeiden“-Hinweis in der UI. - Import/Wiki: vor großen Content-Migrationen Syntax festlegen, damit nicht irreversibel „falsches“ HTML importiert wird.
11.5 Wann umsetzen (Reihenfolge)
Erledigt (Basis): Medien-Archiv,media_assets, Upload/Dedupe, Speicherpfad, Papierkorb, Bibliothek/media, Verknüpfungfrom-asset, Governanceofficial/Copyright.- 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.
- Editor: kein Zwang zum vollen Block-Editor vorab; Platzhalter im bestehenden RTE ist der vorgesehene schlanke Einstieg.
11.6 Refactor-Vermeidung (jetzt schon)
- Neue Features nicht so bauen, dass HTML aus Übungstexten an vielen Stellen „roh“ gerendert wird – ein wiederverwendbarer Renderer vorbereiten (schrittweise einziehen).
- Medien immer über stabile IDs anbinden, nicht nur über Datei-URLs im Text.
12. Referenzen
.claude/docs/technical/ACCESS_LAYER_AND_GOVERNANCE_PLAN.md.claude/docs/technical/MEDIA_UPLOAD_SPEC.md(Limits, MIME, Embed-Typen im aktuellen Backend).claude/docs/working/ACCESS_LAYER_ENDPOINT_AUDIT.mdbackend/routers/exercises.py– Ist-Zustandexercise_mediabis Refactor