shinkan-jinkendo/.claude/docs/technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md
Lars e2964a077d
All checks were successful
Deploy Development / deploy (push) Successful in 38s
Test Suite / pytest-backend (push) Successful in 24s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 7s
Test Suite / playwright-tests (push) Successful in 27s
feat: update media management and project status documentation
- Updated project status to reflect the latest media management milestones and version increment to 0.8.44.
- Enhanced MEDIA_ASSETS_AND_ARCHIVE_SPEC.md with new API details for media asset lifecycle and inline media integration.
- Improved exercise media handling in the frontend, including new preview features and user prompts for media deletion.
- Adjusted backend API to ensure proper handling of media asset deletions without removing files, maintaining governance and user experience.
2026-05-07 13:10:37 +02:00

16 KiB
Raw Blame History

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)
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_url o. Ä.); 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); optional license, attribution, source_url.
  • Speicher: storage_backend (Enum/String: z.B. local, s3, …), storage_key (relativer oder bucket-key), optional storage_url nur 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_platform gesetzt 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_by zum 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):

  1. 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).
  2. Abbruch: Nutzer kann abbrechen oder einzelne Assets vor Freigabe anpassen / entkoppeln.
  3. Copyright: Für official sind 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_limits ergä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_ROOT in 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 unter exercises/{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 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 Archiv-API: GET /api/media-assets, GET /api/media-assets/{id}/file; POST /api/exercises/{id}/media/from-asset; UI Picker/Vorschau. Papierkorb/Retention wie vorhergehende Releases.

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_media referenzieren (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)

  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 12 Beta-Zyklen).
  3. Nicht nötig: vorher kompletten Block-Editor einführen; 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.md
  • backend/routers/exercises.py Ist-Zustand exercise_media bis Refactor