shinkan-jinkendo/.claude/docs/technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md
Lars bab9b178a4
All checks were successful
Deploy Development / deploy (push) Successful in 35s
Test Suite / pytest-backend (push) Successful in 25s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 6s
Test Suite / playwright-tests (push) Successful in 23s
Test Suite / pytest-backend (pull_request) Successful in 23s
Test Suite / lint-backend (pull_request) Successful in 0s
Test Suite / build-frontend (pull_request) Successful in 14s
Test Suite / playwright-tests (pull_request) Successful in 22s
feat(exercises): update to version 0.8.64 and enhance inline media functionality
- Incremented application version to 0.8.64 and updated changelog with new features.
- Implemented inline media support in Rich Text Editor, allowing for drag-and-drop functionality and auto-scrolling.
- Enhanced media handling with a modal picker for media insertion, size selection, and improved user experience.
- Updated documentation to reflect changes in media handling and inline media specifications.
- Adjusted various API specifications to support new inline media features.
2026-05-08 13:27:15 +02:00

18 KiB
Raw Permalink 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-08 (Ist-Changelog Medien bis 0.8.64; §11 Inline umgesetzt und erweitert)
ersetzt/ergänzt: operatives Upload-Format/Größen siehe weiterhin MEDIA_UPLOAD_SPEC.md
normative Governance: Sichtbarkeit & Mandanten wie ACCESS_LAYER_AND_GOVERNANCE_PLAN.md; bei Abweichung zwischen Dokumenten hat der Zugriffsplan Vorrang, außer dieses Dokument präzisiert explizit nur den Medien-Domain.


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-08 0.8.64: Bearbeiten-UX erweitert: Drag&Drop mit Auto-Scroll in Richtung Rich-Text, Medienliste in Kachel-Grid, Warnhinweis vor Wechsel „Ansehen“ bei ungespeicherten Änderungen + Rückweg „Zurück zur Bearbeitung“, Katalogvorschau ohne separaten Anhangs-Medienstreifen.
2026-05-08 0.8.63: Inline-Medien-Picker als Modal (Mediathek/Upload + separates Embed-Modal), Größenwahl `small
2026-05-08 0.8.60 §11: Inline in Übungstexten ({{exerciseMedia:id}} / data-shinkan-exercise-media); Server-Normalisierung + Validierung; Client-Sanitize und zentraler Block-Renderer (ExerciseRichTextBlock); CREATE ohne bestehende exercise_media lehnt Platzhalter ab.
2026-05-07 0.8.59: Dokumentation — Aktiver Verein (Profil/Header/effective_club_id) für Plattform-Admin und UI-Dropdown synchron; kein fachliches Archiv-Schema-Change.
2026-05-07 0.8.58: Medien official: Lifecycle schwerpunktmäßig Superadmin (nicht Plattform-Admin); Bearbeitungsdialog Bibliothek für andere Rollen Lesemodus; Superadmin-Upload: Vereinskontext folgt aktiv gesetztem Verein / effective_club_id.
2026-05-07 0.8.470.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.420.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 (Umsetzung & Leitplanken)

Status: Frontend-Renderer und API-Validierung/Normalisierung umgesetzt (App ≥ 0.8.60); verbindliche Leitplanken unten.

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 (festgelegt)

  • Kanonisches Markup nach Speichern: <span data-shinkan-exercise-media="<numerische exercise_media.id>" class="shinkan-inline-media"></span>.
  • Erweiterung (UI/Lesbarkeit): optionales Attribut data-shinkan-exercise-media-size="small|medium|full" (Default medium) und optional data-shinkan-exercise-media-caption="..." für sprechende Platzhalter-Chips im Editor.
  • Kurzsyntax (Eingabe/Import): {{exerciseMedia:123}} — Server normalisiert beim Speichern (PUT Übung / Varianten) in das kanonische span; CREATE ohne bestehende exercise_media-Zeilen verwirft Platzhalter mit 400.

11.3 Rendering & Sicherheit

  • 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).
  • Stand 0.8.64: Katalog-/Vorschaupfade priorisieren Inline-Darstellung; ein zusätzlicher Anhangsblock wird dort nicht mehr parallel erzwungen.
  • Import/Wiki: vor großen Content-Migrationen Syntax festlegen, damit nicht irreversibel „falsches“ HTML importiert wird.

11.5 Wann umsetzen (Reihenfolge)

  1. Erledigt (Basis): Medien-Archiv, media_assets, Upload/Dedupe, Speicherpfad, Papierkorb, Bibliothek /media, Verknüpfung from-asset, Governance official/Copyright.
  2. Umgesetzt (Stand 0.8.64): Platzhalter/Kurzsyntax + zentraler Frontend-Render-Pfad + Server-Validierung gemäß §11.111.4; Editor-Einstieg „Medium einfügen“ inkl. Modal-Picker (Mediathek/Upload), separates Embed-Modal, Größenwahl und Drag&Drop aus der Medienliste.
  3. Editor: kein Zwang zum vollen Block-Editor vorab; Platzhalter im bestehenden RTE ist der vorgesehene schlanke Einstieg.

11.6 Refactor-Vermeidung (jetzt schon)

  • 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