All checks were successful
Deploy Development / deploy (push) Successful in 39s
Test Suite / pytest-backend (push) Successful in 34s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 10s
Test Suite / playwright-tests (push) Successful in 55s
985 lines
58 KiB
Markdown
985 lines
58 KiB
Markdown
# P-06 – Fachlich-technische Spezifikation: Upload-Einwilligungsdialog
|
||
|
||
**Paket-ID:** P-06 (kanonisch, unveränderlich)
|
||
**Kanonischer Titel:** Upload-Einwilligungsdialog (Recht am eigenen Bild, Personen, Minderjährige)
|
||
**Dokument-Typ:** Spezifikation + Umsetzungsnachweis
|
||
**Erstellt:** 2026-05-10
|
||
**App-Version bei Erstellung:** 0.8.74
|
||
**Letzte Aktualisierung:** 2026-05-11 (App-Version 0.8.86, P-11 vollständig inkl. Auslieferungssperre + Frontend-Placeholder)
|
||
**Status P-06:** ⚠️ technisch umgesetzt (P-06a–P-06d, P-06+) | KRIT-04 offen bis juristische Validierung
|
||
**Rechtlicher Hinweis:** Alle als „juristisch zu prüfen" markierten Einschätzungen sind keine Rechtsberatung. Keine konkrete Textfassung von Erklärungen in diesem Dokument — Formulierungen obliegen dem Rechtsanwalt.
|
||
|
||
---
|
||
|
||
## 1. Kontext und Ziel
|
||
|
||
### 1.1 Ausgangslage (Initial-Audit-Befund)
|
||
|
||
Das Initial-Audit (`docs/compliance-audit.md`, §8.2–8.3) identifiziert folgende Lücken:
|
||
|
||
- Keine verbindliche Rechteerklärung beim Medienupload
|
||
- Keine Abfrage, ob erkennbare Personen enthalten sind
|
||
- Keine Abfrage, ob Minderjährige abgebildet sind
|
||
- Keine Abfrage, ob erforderliche Einwilligungen abgebildeter Personen vorliegen
|
||
- Keine Dokumentation, wer wann welche Erklärung abgegeben hat
|
||
|
||
### 1.2 Regulatorische Prüfanker (technische Modellierung)
|
||
|
||
| Norm | Relevanz für Datenmodell | Juristisch zu prüfen |
|
||
|------|--------------------------|----------------------|
|
||
| § 22 KUG | Bildnisse erkennbarer Personen erfordern Einwilligung vor Verbreitung/Zurschaustellung | Gilt auch für vereinsinterne Sichtbarkeit? |
|
||
| Art. 8 DSGVO | Bei Minderjährigen ggf. Einwilligung Erziehungsberechtigter | Ab welchem Alter? Welche Mechanismen? |
|
||
| § 19a UrhG | Öffentliche Zugänglichmachung = eigener Nutzungstatbestand | Ab wann gilt `official` als „öffentlich zugänglich"? |
|
||
| DSGVO Art. 5(1)(e) | Datenminimierung — welche Nachweisdaten dürfen gespeichert werden? | Was ist verhältnismäßig? |
|
||
|
||
### 1.3 Ziel dieser Spezifikation
|
||
|
||
P-06 soll sicherstellen, dass:
|
||
|
||
1. Jeder Medien-Upload mit einer strukturierten, gespeicherten Rechteerklärung verknüpft ist
|
||
2. Die Erklärung nach Sichtbarkeitsstufe gestuft wird (private minimal, official vollständig)
|
||
3. Bestehende Prüfungen aus P-04 (copyright_notice) komplementär erhalten bleiben
|
||
4. Juristische Textanpassungen ohne Codeänderungen möglich sind (Versionierung)
|
||
5. Altmedien ohne rückwirkenden Zwang behandelt werden können
|
||
6. Backend-Erzwingung (keine reine Frontend-Kosmetik) besteht
|
||
|
||
---
|
||
|
||
## 2. IST-Analyse
|
||
|
||
### 2.1 Upload-Pfade (vollständige Bestandsaufnahme)
|
||
|
||
| # | Endpoint | Datei | Upload-Typ | Sichtbarkeit wählbar | Copyright bei Upload |
|
||
|---|----------|-------|-----------|---------------------|----------------------|
|
||
| U1 | `POST /api/exercises/{id}/media` | `routers/exercises.py:2518` | Einzeldatei oder Embed-URL | Folgt Übungssichtbarkeit (implizit) | Nicht abgefragt |
|
||
| U2 | `POST /api/media-assets/bulk-upload` | `routers/media_assets.py:787` | 1–n Dateien | `visibility` + `club_id` als Form-Parameter | Optional (`copyright_notice` fehlt im POST-Body, nur via PATCH nachträglich) |
|
||
|
||
**Kein weiterer Upload-Endpoint vorhanden** (geprüft über Codebase-Suche nach `UploadFile`).
|
||
|
||
**Weitere indirekte Upload-Pfade:**
|
||
- `POST /api/exercises/{id}/media/from-asset` (`exercises.py`) — verknüpft ein bestehendes Archiv-Asset mit einer Übung; kein Upload, kein neues Medium
|
||
- Embed-URL (`embed_url`, `embed_platform`) — keine Datei, keine Rechteerklärung für Dateiinhalte; externe Plattform verantwortlich
|
||
|
||
### 2.2 Promotionspfade (Sichtbarkeitsänderung)
|
||
|
||
| # | Endpoint | Datei | Mögliche Promotion |
|
||
|---|----------|-------|--------------------|
|
||
| PR1 | `PATCH /api/media-assets/{id}` | `routers/media_assets.py:1223` | private→club, private→official, club→official, auch Rückstufung |
|
||
| PR2 | `POST /api/media-assets/bulk-patch` | `routers/media_assets.py:1094` | Massenpromotion, gleiche Richtungen |
|
||
| PR3 | Implizit bei `PUT /api/exercises/{id}` | `routers/exercises.py` | Übungssichtbarkeit→club/official löst `apply_official_exercise_media_rules()` oder `apply_club_exercise_media_copyright_rules()` aus |
|
||
|
||
**Rückstufungen** (official→club, club→private) werden nicht gesperrt — juristisch zu prüfen, ob Rückstufung Rechte-Konsequenzen hat.
|
||
|
||
### 2.3 Datenmodell `media_assets` (Migration 045 + 046)
|
||
|
||
```
|
||
media_assets
|
||
├── id SERIAL PK
|
||
├── mime_type VARCHAR(100)
|
||
├── byte_size INT
|
||
├── sha256 CHAR(64) NOT NULL
|
||
├── original_filename VARCHAR(300)
|
||
├── visibility VARCHAR(32) NOT NULL DEFAULT 'private' ← Sichtbarkeitsstufe
|
||
├── club_id INT REFERENCES clubs(id) ← Vereinszuordnung
|
||
├── uploaded_by_profile_id INT REFERENCES profiles(id) ← Uploader
|
||
├── copyright_notice TEXT ← Urheberrechtshinweis (P-04)
|
||
├── storage_backend VARCHAR(32)
|
||
├── storage_key TEXT NOT NULL UNIQUE
|
||
├── lifecycle_state VARCHAR(32) DEFAULT 'active'
|
||
├── trash_soft_at, trash_hidden_at, purge_after_at TIMESTAMP
|
||
├── created_at, updated_at TIMESTAMP
|
||
└── tags TEXT[] DEFAULT '{}'
|
||
```
|
||
|
||
**Fehlende P-06-Felder:** Keine Einwilligungsfelder vorhanden. Keine Personenabbildungsmarkierung. Keine Deklarations-Versionierung.
|
||
|
||
### 2.4 Rollen und Berechtigungen (Zusammenfassung `_item_permissions()`)
|
||
|
||
| Aktion | private | club | official |
|
||
|--------|---------|------|----------|
|
||
| Upload (neu) | Jeder authentifizierte Nutzer | Vereinsmitglied + club_id | Nur Superadmin |
|
||
| `edit_metadata` | Superadmin, Plattform-Admin, Uploader | Superadmin, Plattform-Admin, Club-Admin | Nur Superadmin |
|
||
| Promotion zu club | Eigener Upload: Uploader; Club-Asset: Club-Admin | — | — |
|
||
| Promotion zu official | Nur Superadmin | Nur Superadmin | — |
|
||
| Lifecycle (trash) | Uploader, Plattform-Admin, Superadmin | Club-Admin, Plattform-Admin, Superadmin | Nur Superadmin |
|
||
|
||
### 2.5 Bestehende Frontend-Dialoge
|
||
|
||
| Stelle | Datei | Aktuelles Verhalten |
|
||
|--------|-------|---------------------|
|
||
| Übungsformular Medienupload | `ExerciseFormPage.jsx` | Datei-Picker, kein Einwilligungsdialog |
|
||
| Archiv Bulk-Upload | `MediaLibraryPage.jsx` | Datei-Picker + optional `copyright_notice` im Editmodal, kein Pflichtdialog |
|
||
| Übung → official (API-Error) | `ExerciseFormPage.jsx:624` | `window.prompt()` für copyright_notice nach 422-Fehler — reaktiv, kein proaktiver Dialog |
|
||
| Archiv PATCH Sichtbarkeit | `MediaLibraryPage.jsx` | Backend wirft 400 wenn copyright fehlt; Frontend-Feedback unklar |
|
||
|
||
**Ergebnis:** Kein proaktiver strukturierter Einwilligungsdialog vorhanden. Alle Copyright-Abfragen sind reaktiv (nach API-Fehler) oder optional.
|
||
|
||
### 2.6 Bestehende Copyright-Prüfungen P-04 (Referenz)
|
||
|
||
P-04 erzwingt `copyright_notice` bei Promotion zu `club` oder `official`:
|
||
- `patch_media_asset()`: HTTP 400 wenn `copyright_notice` leer bei Ziel club/official
|
||
- `bulk_media_patch()`: Asset in `failed`-Liste wenn copyright fehlt
|
||
- `apply_official_exercise_media_rules()`: HTTP 422 `OFFICIAL_MEDIA_CONFIRM_REQUIRED` wenn Assets kein copyright
|
||
- Mindestlänge: 3 Zeichen (`_MIN_OFFICIAL_MEDIA_COPYRIGHT_LEN = 3`)
|
||
|
||
**Verhältnis zu P-06:** P-04 (copyright_notice) = Attribution für Dritte (wer besitzt das Recht). P-06 = Selbsterklärung des Uploaders (habe ich das Recht, dieses Medium hochzuladen). Ergänzend, nicht konkurrierend.
|
||
|
||
### 2.7 IST-Lücken (P-06-spezifisch)
|
||
|
||
| Lücke | Schwere |
|
||
|-------|---------|
|
||
| Kein Pflichtfeld „Ich bin Rechteinhaber" beim Upload | Kritisch (Grundlage aller Uploads) |
|
||
| Keine Abfrage „erkennbare Personen enthalten?" | Hoch (§ 22 KUG-Risiko bei club/official) |
|
||
| Keine Abfrage „Minderjährige enthalten?" | Hoch (Art. 8 DSGVO, besonderer Schutz) |
|
||
| Keine Speicherung von Einwilligungszeitpunkt und -version | Hoch (Nachweisbarkeit) |
|
||
| Keine Erzwingung im Backend (nur Frontend-Checkbox wäre zu wenig) | Kritisch |
|
||
| Kein Einwilligungslog (wer hat wann welche Version bestätigt?) | Hoch |
|
||
| copyright_notice beim Erstupload nicht abgefragt | Mittel (P-04 fragt erst bei Promotion) |
|
||
|
||
### 2.8 Altbestandsrisiken
|
||
|
||
- Alle bestehenden `media_assets`-Zeilen haben keine Einwilligungsmetadaten
|
||
- Unbekannt, ob bei bestehenden Medien erkennbare Personen/Minderjährige abgebildet sind
|
||
- Medien mit `visibility=club` oder `visibility=official` existieren ohne dokumentierte Einwilligung
|
||
- Superadmin kann bestehende official-Medien weiter sehen/nutzen ohne Nachdeklaration
|
||
- Bei Neuzuweisung zu einer anderen Übung werden bestehende Medien ohne Prüfung verknüpft
|
||
|
||
---
|
||
|
||
## 3. Entscheidungsmatrix
|
||
|
||
Anforderungen nach **Sichtbarkeitsstufe** (Zeilen) × **Aktion** (Spalten). „Pflicht" = Backend-Sperre wenn fehlend. „Optional" = abfragen aber nicht erzwingen.
|
||
|
||
| Aktion → | Erstupload | Promotion club | Promotion official | Metadaten-Edit | Reuse in neuer Übung |
|
||
|-----------|-----------|---------------|-------------------|---------------|---------------------|
|
||
| **private** | `rights_holder`: Pflicht; Personen-Fragen: optional | — | — | Keine P-06-Pflicht | Keine P-06-Pflicht |
|
||
| **club** | `rights_holder`: Pflicht; `contains_persons`: Pflicht; `person_consent`: Pflicht wenn Personen | `contains_persons` + `person_consent`: Pflicht; `contains_minors`: Pflicht | — | Keine P-06-Pflicht | Keine P-06-Pflicht |
|
||
| **official** | Alles Pflicht | Alles Pflicht | Alle Felder Pflicht + copyright_notice (P-04) | Keine P-06-Pflicht | Keine P-06-Pflicht |
|
||
|
||
**B: Medieninhalt — Konsequenzen:**
|
||
|
||
| Inhalt | private | club | official |
|
||
|--------|---------|------|----------|
|
||
| Keine erkennbaren Personen | `person_consent` entfällt | `person_consent` entfällt | `person_consent` entfällt |
|
||
| Erkennbare Erwachsene | Optional zu deklarieren | Einwilligung Pflicht (juristisch zu prüfen) | Einwilligung Pflicht |
|
||
| Erkennbare Minderjährige | Optional zu deklarieren | Einwilligung Erziehungsberechtigter Pflicht (juristisch zu prüfen) | Einwilligung Pflicht |
|
||
| Unklar / nicht beantwortet | Zulässig bei private | Blockiert bei club/official | Blockiert |
|
||
|
||
**C: Rechteinhalt — Konsequenzen:**
|
||
|
||
| Inhalt | Konsequenz |
|
||
|--------|-----------|
|
||
| Nutzer ist selbst Urheber | `rights_holder_confirmed = true` genügt |
|
||
| Nutzer hat Nutzungsrechte | `rights_holder_confirmed = true` genügt; copyright_notice sollte Rechteinhaber nennen |
|
||
| Drittmaterial enthalten | `rights_holder_confirmed = true` nur wenn Lizenz vorhanden; copyright_notice Pflicht bei club/official |
|
||
| Musik enthalten | `contains_music = true`; `music_rights_confirmed` Pflicht (juristisch zu prüfen) |
|
||
| Logos/Marken/sonstige Fremdinhalte | Fällt unter `rights_holder_confirmed`; juristisch zu prüfen |
|
||
|
||
**D: Aktion — Blockierungslogik:**
|
||
|
||
| Aktion | Fehlendes Pflichtfeld | Verhalten Backend |
|
||
|--------|----------------------|-------------------|
|
||
| Upload | rights_holder_confirmed fehlt | HTTP 400 `RIGHTS_DECLARATION_REQUIRED` |
|
||
| Promotion club | contains_persons / person_consent fehlt | HTTP 400 `CONSENT_REQUIRED_FOR_CLUB` |
|
||
| Promotion official | Beliebiges Pflichtfeld fehlt | HTTP 400 `CONSENT_REQUIRED_FOR_OFFICIAL` |
|
||
| Promotion mit Altmedium (legacy) | rights_status = legacy_unreviewed | HTTP 400 `LEGACY_REDECLARATION_REQUIRED` |
|
||
|
||
---
|
||
|
||
## 4. Empfohlenes Zielmodell
|
||
|
||
### 4.1 Neue Tabelle: `media_asset_rights_declarations`
|
||
|
||
Append-only Audit-Log. Jede Erklärung (Upload, Promotion, Nachdeklaration) erzeugt einen neuen Eintrag. Bestehende Einträge werden nie geändert.
|
||
|
||
```sql
|
||
CREATE TABLE media_asset_rights_declarations (
|
||
id SERIAL PRIMARY KEY,
|
||
media_asset_id INT NOT NULL REFERENCES media_assets(id) ON DELETE CASCADE,
|
||
declared_by_profile_id INT REFERENCES profiles(id) ON DELETE SET NULL,
|
||
declared_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||
|
||
-- Kontext
|
||
action_type VARCHAR(50) NOT NULL
|
||
CHECK (action_type IN (
|
||
'upload', -- Erstupload
|
||
'promote_club', -- Promotion zu club
|
||
'promote_official', -- Promotion zu official
|
||
're_declaration', -- Nachdeklaration (auf Aufforderung)
|
||
'legacy_re_declaration' -- Altmedium: erste Deklaration nachgereicht
|
||
)),
|
||
target_visibility VARCHAR(32) NOT NULL, -- Sichtbarkeit für die erklärt wird
|
||
declaration_version VARCHAR(20) NOT NULL DEFAULT 'p06-v1.0', -- Textversion der Erklärung
|
||
|
||
-- Pflichtfeld (alle Sichtbarkeiten)
|
||
rights_holder_confirmed BOOLEAN NOT NULL,
|
||
|
||
-- Personenrechte (Pflicht ab club)
|
||
contains_identifiable_persons BOOLEAN,
|
||
person_consent_confirmed BOOLEAN, -- Pflicht wenn contains_identifiable_persons = true
|
||
contains_minors BOOLEAN,
|
||
parental_consent_confirmed BOOLEAN, -- Pflicht wenn contains_minors = true (juristisch zu prüfen)
|
||
|
||
-- Drittmaterial (optional MVP, Pflicht später)
|
||
contains_music BOOLEAN,
|
||
music_rights_confirmed BOOLEAN,
|
||
third_party_content_confirmed BOOLEAN,
|
||
|
||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||
);
|
||
|
||
CREATE INDEX idx_mar_declarations_asset ON media_asset_rights_declarations (media_asset_id);
|
||
CREATE INDEX idx_mar_declarations_profile ON media_asset_rights_declarations (declared_by_profile_id);
|
||
```
|
||
|
||
### 4.2 Neue Felder in `media_assets`
|
||
|
||
Drei neue Schnellzugriffs-Felder (kein Ersatz für den Log, nur für performante Abfragen):
|
||
|
||
```sql
|
||
ALTER TABLE media_assets
|
||
ADD COLUMN rights_status VARCHAR(32)
|
||
NOT NULL DEFAULT 'legacy_unreviewed'
|
||
CHECK (rights_status IN ('pending', 'declared', 'legacy_unreviewed', 'blocked')),
|
||
ADD COLUMN rights_declared_for_visibility VARCHAR(32), -- Höchste Stufe, für die erklärt wurde
|
||
ADD COLUMN rights_declared_at TIMESTAMP WITH TIME ZONE; -- Zeitpunkt der aktuellsten Erklärung
|
||
```
|
||
|
||
**Status-Semantik:**
|
||
|
||
| rights_status | Bedeutung |
|
||
|---------------|-----------|
|
||
| `legacy_unreviewed` | Bestehendes Medium ohne P-06-Daten — Altbestand |
|
||
| `pending` | Neues Medium, Upload noch ohne Deklaration (Zwischenzustand, darf nie persistent sein) |
|
||
| `declared` | Gültige Deklaration vorhanden für `rights_declared_for_visibility` |
|
||
| `blocked` | Deklaration widerrufen oder durch Admin gesperrt (→ P-11/P-13 Schnittstelle) |
|
||
|
||
**Visibility-Hierarchie für Prüfung:**
|
||
```
|
||
private (1) < club (2) < official (3)
|
||
```
|
||
|
||
Promotion ist erlaubt wenn: `rights_declared_for_visibility` ≥ Ziel-Visibility. Sonst ist neue Deklaration erforderlich.
|
||
|
||
### 4.3 Feld-Bewertung: MVP vs. später
|
||
|
||
| Feld | MVP-Pflicht | Sinnvoll später | Unnötig / zu früh | Juristisch abhängig |
|
||
|------|-------------|-----------------|-------------------|---------------------|
|
||
| `rights_holder_confirmed` | ✅ | — | — | Nein |
|
||
| `contains_identifiable_persons` | ✅ ab club | — | — | Nein |
|
||
| `person_consent_confirmed` | ✅ wenn Personen + ≥ club | — | — | Teilweise |
|
||
| `contains_minors` | ✅ ab official | ✅ auch club | — | Ja (Schwelle) |
|
||
| `parental_consent_confirmed` | — | ✅ | — | Ja (juristisch zu klären) |
|
||
| `contains_music` | — | ✅ | — | Ja |
|
||
| `music_rights_confirmed` | — | ✅ | — | Ja |
|
||
| `third_party_content_confirmed` | — | ✅ | — | Teilweise |
|
||
| `declaration_version` | ✅ | — | — | Nein |
|
||
| `action_type` | ✅ | — | — | Nein |
|
||
| `rights_status` in media_assets | ✅ | — | — | Nein |
|
||
| `rights_declared_for_visibility` | ✅ | — | — | Nein |
|
||
| Separates Upload von Einwilligungsdokumenten | — | — | Zu früh | Ja |
|
||
| `may_be_embedded_in_exercises` | — | — | Unnötig (exercise_media-Tabelle reicht) | — |
|
||
| `declaration_scope_*` als einzelne Booleans | — | — | Unnötig (visibility-String reicht) | — |
|
||
|
||
### 4.4 Designentscheidungen (mit Begründung)
|
||
|
||
**Entscheidung 1: Gestufte Erklärungen je Sichtbarkeitsstufe**
|
||
|
||
Nicht eine universale Erklärung für alle Stufen, sondern Promotion auf höhere Stufe erfordert neue Erklärung.
|
||
|
||
*Begründung:* Rechtliche Anforderungen steigen mit Sichtbarkeit. Private Medien: nur Eigentümer sieht sie. Official-Medien: plattformweit sichtbar (ggf. öffentlich). Stufenmodell ist rechtsanschlussfähig — spätere juristische Textanpassungen pro Stufe ohne Systemwechsel möglich.
|
||
|
||
**Entscheidung 2: Minimalset beim Erstupload**
|
||
|
||
Pflicht beim privaten Upload: nur `rights_holder_confirmed`. Personen-Fragen: optional beim Upload, Pflicht erst bei Promotion zu club/official.
|
||
|
||
*Begründung:* Die meisten Uploads bleiben privat und werden nie promoted. Volle Fragebogenbelastung beim Upload würde Nutzung hemmen ohne rechtlichen Mehrwert. Personen-Fragen werden erzwungen sobald der Upload für andere sichtbar wird.
|
||
|
||
**Entscheidung 3: Keine globale Nutzer-Einwilligung (pro Upload)**
|
||
|
||
Erklärung wird pro Upload abgegeben, nicht einmalig pro Nutzer.
|
||
|
||
*Begründung:* Jedes Medium ist inhaltlich unterschiedlich. Eine pauschale Einwilligung beim Registrieren oder einmalig je Nutzer deckt nicht ab, ob bei diesem konkreten Video Minderjährige abgebildet sind.
|
||
|
||
**Entscheidung 4: Append-only Audit-Log**
|
||
|
||
Einmal abgegebene Erklärungen werden nie geändert oder gelöscht. Neue Erklärung = neuer Eintrag.
|
||
|
||
*Begründung:* Nachweisbarkeit. Bei späterem Streit muss belegbar sein, was wann erklärt wurde.
|
||
|
||
**Entscheidung 5: Versionierung via `declaration_version`-String**
|
||
|
||
Wenn Rechtsanwalt Erklärungstexte ändert, wird die neue Version als "p06-v1.1" deklariert. Keine Datenbank-FK auf eine Textversion-Tabelle (zu komplex für MVP).
|
||
|
||
*Begründung:* Einfach nachvollziehbar. Ob bei Versionswechsel alle Altdeklarationen erneuert werden müssen, ist juristisch zu entscheiden — technisch ermöglicht das System beides.
|
||
|
||
**Entscheidung 6: copyright_notice bleibt separat (P-04 unberührt)**
|
||
|
||
P-06 führt keine copyright_notice-Redundanz ein. copyright_notice (P-04) = Attribution. rights_holder_confirmed (P-06) = Berechtigung. Beide sind Pflicht bei official.
|
||
|
||
---
|
||
|
||
## 5. Ziel-Flows
|
||
|
||
### Flow 1: Erstupload eines privaten Mediums (Übung oder Archiv)
|
||
|
||
**Frontend:**
|
||
1. Nutzer öffnet Upload-Dialog (Exercise oder Archiv)
|
||
2. Dateiauswahl
|
||
3. **P-06-Dialog erscheint vor Upload-Button-Aktivierung:**
|
||
- ☑ „Ich bin der Rechteinhaber oder besitze die erforderlichen Nutzungsrechte an diesem Medium." [Pflicht]
|
||
- ☐ „Das Medium enthält erkennbare Personen." [optional]
|
||
- → wenn ja: ☑ „Alle abgebildeten Personen haben der Nutzung zugestimmt." [optional bei private, Pflicht bei Promotion]
|
||
- ☐ „Das Medium enthält Minderjährige." [optional]
|
||
4. Upload-Button aktiv erst wenn Pflicht-Checkbox gesetzt
|
||
|
||
**Backend:**
|
||
- Empfängt neue Felder: `rights_holder_confirmed`, optional `contains_identifiable_persons`, `person_consent_confirmed`, `contains_minors`
|
||
- Prüft: `rights_holder_confirmed == true` — sonst HTTP 400 `RIGHTS_DECLARATION_REQUIRED`
|
||
- INSERT in `media_assets` mit `rights_status = 'declared'`, `rights_declared_for_visibility = 'private'`, `rights_declared_at = NOW()`
|
||
- INSERT in `media_asset_rights_declarations` mit `action_type = 'upload'`, `declaration_version = 'p06-v1.0'`, alle Felder
|
||
|
||
**Fehlermeldungen:**
|
||
- `RIGHTS_DECLARATION_REQUIRED`: "Bitte bestätige, dass du die erforderlichen Rechte an diesem Medium besitzt."
|
||
|
||
**Tests:**
|
||
- Upload ohne `rights_holder_confirmed` → 400
|
||
- Upload mit `rights_holder_confirmed=true` → 201, `rights_status='declared'` in DB
|
||
- Declaration-Log-Eintrag vorhanden mit korrektem `action_type='upload'`
|
||
|
||
**Offene juristische Fragen:** Reicht Selbsterklärung oder muss der Betreiber den Upload ansonsten ablehnen? (→ §7.1)
|
||
|
||
---
|
||
|
||
### Flow 2: Erstupload direkt als club-Medium (Archiv-Bulk-Upload)
|
||
|
||
**Frontend:**
|
||
- Nutzer wählt `visibility = club` im Bulk-Upload-Dialog
|
||
- P-06-Dialog erweitert (zusätzlich zur privaten Pflicht-Checkbox):
|
||
- ☑ „Das Medium enthält erkennbare Personen." [Antwort Pflicht bei club]
|
||
- → wenn ja: ☑ „Alle abgebildeten Personen haben der vereinsinternen Nutzung zugestimmt." [Pflicht]
|
||
- ☑ „Das Medium enthält Minderjährige." [Antwort Pflicht]
|
||
- → wenn ja: ☑ „Einwilligung der Erziehungsberechtigten liegt vor." [Pflicht, juristisch zu prüfen]
|
||
|
||
**Backend:**
|
||
- Prüft alle Pflichtfelder für club: `rights_holder_confirmed`, `contains_identifiable_persons` (must be answered), `person_consent_confirmed` (wenn contains_persons=true)
|
||
- `rights_declared_for_visibility = 'club'`
|
||
- Eintrag in `media_asset_rights_declarations` mit `action_type = 'upload'`, `target_visibility = 'club'`
|
||
|
||
---
|
||
|
||
### Flow 3: Bulk-Upload durch Admin (Plattform-Admin / Superadmin)
|
||
|
||
**Besonderheit:** Admin lädt oft viele Dateien auf einmal. Dialog-Design muss batch-fähig sein.
|
||
|
||
**Empfehlung:** Eine Einwilligungserklärung für den gesamten Batch (nicht je Datei). Alle Dateien des Batches erhalten den gleichen Declarations-Record, jedoch referenziert je `media_asset_id`.
|
||
|
||
**Frontend:**
|
||
- Vor Upload: Batch-Deklaration (gilt für alle Dateien des Batches)
|
||
- Alternativ: Je Datei separate Deklaration (aufwändiger aber präziser) — juristisch zu prüfen welche Variante ausreichend ist
|
||
|
||
**Backend:**
|
||
- `action_type = 'upload'` für alle Dateien des Batches
|
||
- Wenn Batch-Deklaration: eine Deklarations-Transaktion, n Einträge in Log (je asset_id)
|
||
|
||
**Offene juristische Fragen:** Genügt eine Sammelbestätigung für Batches? (→ §7.6)
|
||
|
||
---
|
||
|
||
### Flow 4: Promotion private → club
|
||
|
||
**Frontend:**
|
||
1. Nutzer ändert Sichtbarkeit eines Mediums zu `club` (im Archiv oder via Übungssichtbarkeit)
|
||
2. Frontend erkennt Sichtbarkeitserhöhung
|
||
3. **P-06-Promotions-Dialog:**
|
||
- ☑ „Das Medium enthält erkennbare Personen." [Pflicht — Antwort erforderlich]
|
||
- → wenn ja: ☑ „Alle abgebildeten Personen haben der vereinsinternen Nutzung zugestimmt." [Pflicht]
|
||
- ☑ „Das Medium enthält Minderjährige." [Pflicht — Antwort erforderlich]
|
||
- → wenn ja: ☑ „Einwilligung der Erziehungsberechtigten liegt vor." [Pflicht, juristisch zu prüfen]
|
||
4. Bestehende copyright_notice-Abfrage (P-04) bleibt erhalten — läuft parallel oder im gleichen Dialog
|
||
|
||
**Backend:**
|
||
- PATCH `/api/media-assets/{id}` empfängt: `visibility='club'`, P-06-Felder, `copyright_notice`
|
||
- Prüft: rights_status='declared' AND rights_declared_for_visibility ≥ 'club' — wenn ja, keine neue Deklaration nötig (z. B. bereits als club hochgeladen)
|
||
- Wenn nicht: neue Pflichtfelder prüfen, INSERT in Declarations-Log mit `action_type='promote_club'`
|
||
- Bestehende P-04-Prüfung: copyright_notice weiterhin Pflicht
|
||
- `rights_declared_for_visibility` in media_assets auf 'club' setzen
|
||
|
||
**Backend-Fehler:**
|
||
- `CONSENT_REQUIRED_FOR_CLUB`: Personenfragen nicht beantwortet
|
||
- `LEGACY_REDECLARATION_REQUIRED`: Altmedium, muss Nachdeklaration abgeben
|
||
- `RIGHTS_DECLARATION_REQUIRED` (P-04): copyright_notice fehlt (bestehend)
|
||
|
||
---
|
||
|
||
### Flow 5: Promotion private/club → official
|
||
|
||
**Frontend:**
|
||
1. Nur Superadmin kann zu official promoten
|
||
2. Dialog zeigt vollständige Erklärung:
|
||
- Alle Felder aus Flow 4 (Pflicht)
|
||
- ☑ zusätzlich: „Das Medium enthält Musik oder sonstige Fremdinhalte." [Pflicht — Antwort erforderlich]
|
||
- → wenn ja: ☑ „Ich besitze die erforderlichen Lizenzen für die öffentliche Nutzung." [Pflicht]
|
||
3. copyright_notice (P-04) weiterhin Pflicht (bestehende Logik)
|
||
|
||
**Backend:**
|
||
- Prüft alle Felder + copyright_notice
|
||
- `action_type = 'promote_official'`, `target_visibility = 'official'`
|
||
- `rights_declared_for_visibility = 'official'`
|
||
|
||
**Offene juristische Fragen:** Reicht Selbstdeklaration oder muss Superadmin bei official höhere Sorgfalt nachweisen? (→ §7.3)
|
||
|
||
---
|
||
|
||
### Flow 6: Nachbearbeitung von Metadaten (ohne Sichtbarkeitsänderung)
|
||
|
||
**Szenario:** Nutzer ändert nur `copyright_notice`, `original_filename`, `tags` — keine Sichtbarkeitsänderung.
|
||
|
||
**P-06-Anforderung:** Keine neue Deklaration erforderlich (Metadaten-Edit ist kein Promotions-Schritt).
|
||
|
||
**Ausnahme:** Wenn `rights_status = 'blocked'` (durch P-11/P-13 gesetzt) → Metadaten-Edit erlaubt, aber Sichtbarkeitsänderungen bleiben gesperrt.
|
||
|
||
---
|
||
|
||
### Flow 7: Verwendung bestehender Medien in neuer Übung (`from-asset`)
|
||
|
||
**Szenario:** Trainer verknüpft ein bereits im Archiv liegendes Medium mit einer neuen Übung über `POST /api/exercises/{id}/media/from-asset`.
|
||
|
||
**P-06-Anforderung:** Keine neue Deklaration erforderlich. Die Deklaration gilt für das Medium selbst, nicht für die Verknüpfung. Voraussetzung: `rights_status` des Assets ist 'declared' für die Sichtbarkeit der Übung.
|
||
|
||
**Backend-Prüfung (neu):**
|
||
- Asset-Sichtbarkeit ≥ Übungs-Sichtbarkeit? Bestehende Prüfung.
|
||
- `rights_declared_for_visibility` ≥ Übungs-Sichtbarkeit? → Neue Prüfung durch P-06
|
||
- Wenn nicht: HTTP 400 `RIGHTS_DECLARATION_REQUIRED_FOR_TARGET_VISIBILITY`
|
||
|
||
---
|
||
|
||
### Flow 8: Behandlung von Altmedien ohne P-06-Metadaten
|
||
|
||
**Strategie: Soft-Block bei Promotion, kein Rückwirkungs-Zwang für private Altmedien**
|
||
|
||
| Situation | Verhalten |
|
||
|-----------|-----------|
|
||
| Altmedium private → weiterhin privat | Unverändert zugänglich; keine Zwangs-Nachdeklaration |
|
||
| Altmedium privat → Anzeige in Übung (private) | Unverändert; keine neue Deklaration |
|
||
| Altmedium privat → Promotion zu club | Blocked: `LEGACY_REDECLARATION_REQUIRED`; Nutzer muss Nachdeklaration abgeben |
|
||
| Altmedium club → Promotion zu official | Blocked; Superadmin muss Nachdeklaration abgeben |
|
||
| Altmedium mit `rights_status = 'legacy_unreviewed'` | Einsehbar, nicht promotable |
|
||
|
||
**Nachdeklarations-Flow:**
|
||
1. Nutzer versucht Promotion eines Altmediums
|
||
2. Backend gibt HTTP 400 `LEGACY_REDECLARATION_REQUIRED` zurück
|
||
3. Frontend zeigt Hinweis und Deklarations-Dialog (analog zu neuem Upload-Dialog)
|
||
4. Nutzer füllt Deklaration aus
|
||
5. PATCH mit `legacy_re_declaration=true` + alle Pflichtfelder + `visibility`
|
||
6. Backend: INSERT in Declarations-Log mit `action_type='legacy_re_declaration'`, aktualisiert `rights_status`, `rights_declared_for_visibility`
|
||
|
||
**Migrationsstrategie für bestehende Daten:**
|
||
- Alle bestehenden `media_assets`-Zeilen bleiben mit `rights_status = 'legacy_unreviewed'` (Default in Migration)
|
||
- Keine rückwirkende Sperrung
|
||
- Bestehende club/official-Medien: `rights_status = 'legacy_unreviewed'` — die historische Sichtbarkeit bleibt erhalten, spätere Änderungen erfordern aber Nachdeklaration
|
||
|
||
**Juristisch zu prüfen:** Muss der Betreiber aktiv alle Altmedien auf club/official sperren bis zur Nachdeklaration? (→ §7.8)
|
||
|
||
---
|
||
|
||
### Flow 9: Widerruf / nachträglich fehlende Rechte erkannt
|
||
|
||
**Szenario:** Abgebildete Person widerruft Einwilligung oder Uploader erkennt, dass er keine Rechte hatte.
|
||
|
||
**P-06 implementiert keinen Widerrufsmechanismus direkt.** Anschluss an spätere Pakete:
|
||
|
||
- **P-11 (Legal-Hold):** Superadmin kann `rights_status = 'blocked'` setzen → Medium sofort aus allen öffentlichen Abfragen entfernt
|
||
- **P-13 (Content-Melde-Backend):** Dritte können Rechtsverletzung melden → Moderationsqueue → Superadmin entscheidet über Sperrung
|
||
|
||
**P-06-Schnittstelle:** Das Feld `rights_status = 'blocked'` in `media_assets` ist die technische Brücke zu P-11 und P-13.
|
||
|
||
**Empfehlung:** Declaration-Log-Tabelle hat `ON DELETE CASCADE` auf `media_assets.id` — wenn ein Medium purged wird, werden auch alle Deklarationseinträge entfernt. Juristisch zu prüfen, ob Deklarationslog nach Löschung aufbewahrt werden muss (Rechenschaftspflicht Art. 5 Abs. 2 DSGVO).
|
||
|
||
---
|
||
|
||
### Flow 10: Zusammenspiel mit P-11 (vollständig implementiert) und P-13
|
||
|
||
**P-11 Status: ✅ Vollständig implementiert (v0.8.84–0.8.86)**
|
||
|
||
| P-06-Element | P-11-Umsetzung | P-13-Anschluss |
|
||
|-------------|---------------|---------------|
|
||
| `rights_status = 'blocked'` | P-11 setzt diesen Status als Spiegel bei `legal_hold_active=TRUE`; bei Freigabe auf `declared`/`legacy_unreviewed` zurückgesetzt | P-13-Moderator triggert `set_legal_hold()` |
|
||
| Declaration-Log | Zeigt, welche Version bei Sperrung galt; wird bei `release_legal_hold` zur Wiederherstellung von `rights_status` genutzt | Kontext für Moderationsentscheidung |
|
||
| `contains_identifiable_persons` | Relevant für Löschbegründung (KUG), `reason_code=consent_withdrawn` | Relevanzmerkmal für Meldekategorie |
|
||
| `contains_minors` | Höchste Priorität: `reason_code=youth_protection` | CSAM-Eskalationspfad in P-13 |
|
||
|
||
**Implementiertes Schnittstellenmodell P-06 ↔ P-11 (komplett):**
|
||
|
||
_Datenhaltung und Audit (v0.8.84):_
|
||
- `media_legal_hold.py`: `set_legal_hold()` schreibt `rights_status='blocked'` als Spiegel
|
||
- `media_legal_hold.py`: `release_legal_hold()` stellt `rights_status` anhand vorhandener Deklarationen wieder her (`declared` wenn `COUNT(rights_holder_confirmed=TRUE)>0`, sonst `legacy_unreviewed`)
|
||
- Audit-Log `media_asset_audit_log` protokolliert `legal_hold_set`/`legal_hold_released` als eigene Ereignistypen (orthogonal zu `copyright_change`, `visibility_change` etc.)
|
||
|
||
_Sichtbarkeit und Auslieferungssperre (v0.8.85–0.8.86):_
|
||
- `list_media_assets`: `include_legal_hold=is_sup` — nur Superadmin sieht gesperrte Assets in der Medienliste; Plattform-Admins ohne Superadmin-Rolle sehen sie nicht mehr
|
||
- `download_exercise_media_file` (v0.8.86): gibt HTTP **451 Unavailable For Legal Reasons** zurück wenn `asset_legal_hold_active=TRUE` — keine Dateiauslieferung, auch wenn das Asset bereits mit einer Übung verknüpft ist
|
||
- `enrich_exercise_detail` (v0.8.86): liefert `asset_legal_hold_active` im Media-Array mit — Frontend-Komponenten erhalten den Hold-Status ohne zusätzlichen API-Call
|
||
- `ExerciseMediaEmbed.jsx` + `ExerciseMediaThumbTile.jsx` + `ExerciseFormPage.jsx` (v0.8.86): zeigen „Medium nicht verfügbar / Gesperrt"-Placeholder statt Datei, wenn `asset_legal_hold_active=true`
|
||
|
||
**P-13-Vorbereitung:** `set_legal_hold()` in `media_legal_hold.py` ist eine generische Funktion — P-13 ruft sie mit `reason_code` aus der Moderationsqueue auf, ohne eigene Sperr-Logik implementieren zu müssen. Die Auslieferungssperre (HTTP 451) greift dann automatisch für alle bereits verknüpften Medien.
|
||
|
||
---
|
||
|
||
## 6. Legacy- und Migrationskonzept
|
||
|
||
### 6.1 Fragen und Antworten
|
||
|
||
| Frage | Empfehlung | Juristisch zu prüfen |
|
||
|-------|-----------|----------------------|
|
||
| Dürfen Altmedien sichtbar bleiben? | Ja — keine rückwirkende Sperrung | Ja — insb. club/official ohne Einwilligung |
|
||
| Dürfen Altmedien in neuen Übungen verwendet werden? | Ja, wenn Sichtbarkeit kompatibel | Ja — falls keine Rechte vorhanden |
|
||
| Dürfen Altmedien zu club promoted werden? | Nein — Nachdeklaration erforderlich | Ja — ob Selbstdeklaration genügt |
|
||
| Dürfen Altmedien zu official promoted werden? | Nein — vollständige Nachdeklaration | Ja — höhere Anforderungen |
|
||
| Muss bei erster Bearbeitung Nachdeklaration erzwungen werden? | Nein (nur bei Promotion) | Ja — Betreiber-Ermessen |
|
||
|
||
### 6.2 Migration-Schritte
|
||
|
||
1. **Migration (P-06a):** Neue Spalten in `media_assets` mit `DEFAULT 'legacy_unreviewed'` → alle bestehenden Zeilen erhalten automatisch Status `legacy_unreviewed`
|
||
2. **Neue Uploads nach P-06:** Code setzt `rights_status = 'declared'` nach erfolgreicher Deklaration
|
||
3. **Altmedien-Promotion:** Backend blockiert mit `LEGACY_REDECLARATION_REQUIRED`; Nutzer muss Nachdeklaration abgeben
|
||
4. **Keine automatische Batch-Re-Deklaration** durch Betreiber (wäre fachlich falsch — Betreiber kann nicht für Uploader erklären)
|
||
|
||
### 6.3 Empfohlene Übergangsstrategie (Minimales Risiko)
|
||
|
||
```
|
||
private Altmedien: weiter sichtbar und nutzbar | Promotion blockiert
|
||
club Altmedien: weiter sichtbar | Sichtbarkeitsänderung blockiert
|
||
official Altmedien: weiter sichtbar | Sichtbarkeitsänderung blockiert
|
||
```
|
||
|
||
*Begründung:* Vollständige Sperrung aller Altmedien würde Betrieb lahmlegen. Die Blockierung bei Promotion schützt vor Eskalation ohne existierende Nutzung zu zerstören. Juristisch bleibt das Risiko bei bereits exponierten Medien — das ist ein organisatorisches Problem, kein technisches.
|
||
|
||
---
|
||
|
||
## 7. Juristische Klärungsliste
|
||
|
||
Alle folgenden Punkte müssen durch einen Rechtsanwalt oder Datenschutzbeauftragten bewertet werden. Keine der folgenden Einschätzungen ist rechtlich bindend.
|
||
|
||
| # | Frage | Technische Relevanz | Dringlichkeit |
|
||
|---|-------|---------------------|---------------|
|
||
| 7.1 | Welche Erklärung ist für **private** Medien (nur Uploader sieht sie) erforderlich? Reicht `rights_holder_confirmed` oder ist auch ohne Sichtbarkeit Dritter eine Personeneinwilligung nötig? | Ob Personen-Fragen bei private Pflicht werden | Vor MVP |
|
||
| 7.2 | Welche Erklärung ist für **club**-interne Sichtbarkeit (Vereinsmitglieder) erforderlich? Gilt § 22 KUG für vereinsintern? Reicht Selbsterklärung des Uploaders? | Pflichtfelder bei Promotion zu club | Vor MVP |
|
||
| 7.3 | Welche Erklärung ist für **official** (plattformweit, ggf. öffentlich) erforderlich? Reicht Selbsterklärung oder muss Einwilligung nachgewiesen werden? | Pflichtfelder bei Promotion zu official | Vor MVP |
|
||
| 7.4 | Wie ist mit erkennbaren **Minderjährigen** zu verfahren? Ab welchem Alter benötigen Minderjährige elterliche Einwilligung? Welche Einwilligungsform ist rechtssicher? | `contains_minors` + `parental_consent_confirmed` — Pflicht ab welcher Stufe? | Vor MVP |
|
||
| 7.5 | Reicht eine **Selbsterklärung** des Uploaders oder müssen Einwilligungs-Dokumente (Scan, Upload) archiviert werden? | Dateiupload für Einwilligungs-Scan nötig? (MVP oder später?) | Vor MVP |
|
||
| 7.6 | Ist eine **Batch-Deklaration** (eine Erklärung für mehrere Dateien des Uploads) rechtlich ausreichend? | Flow 3: Bulk-Upload-Design | Vor MVP |
|
||
| 7.7 | Muss die **Textfassung** der Erklärungen (Checkbox-Labels) durch Rechtsanwalt freigegeben werden? Welche konkreten Formulierungen sind erforderlich? | `declaration_version` Textbasis | Vor MVP |
|
||
| 7.8 | Wie sind **Altmedien** zu behandeln, die bereits mit club/official-Sichtbarkeit in der DB stehen? Muss der Betreiber diese sperren bis zur Nachdeklaration? | Migrationsstrategie §6.3 | Zeitnah |
|
||
| 7.9 | Wie ist bei **Widerruf** einer Einwilligung vorzugehen? Welche Fristen gelten? Muss das Medium sofort gelöscht oder nur gesperrt werden? | rights_status='blocked', Schnittstelle P-11 | Vor P-11 |
|
||
| 7.10 | Welche **Nachweisdaten** dürfen/sollen gespeichert werden (Datensparsamkeit vs. Rechenschaftspflicht)? Wie lange muss der Declaration-Log aufbewahrt werden? | Retention-Policy für `media_asset_rights_declarations` | Vor MVP |
|
||
| 7.11 | Welche Anforderungen gelten für **Trainingsvideos mit mehreren Personen**? Muss jede Person einzeln einwilligen oder reicht eine kollektive Erklärung? | `person_consent_confirmed` als Boolean vs. Mehrfach-Einwilligung | Vor MVP |
|
||
| 7.12 | Welche Anforderungen gelten für **Musik, Logos, Marken, Grafiken** von Dritten im Video/Bild? Ab wann ist eine separate Lizenz nötig? Reicht `music_rights_confirmed` als Selbsterklärung? | `contains_music`, `music_rights_confirmed` — MVP oder Pflicht? | Mittel |
|
||
|
||
---
|
||
|
||
## 8. Umsetzungsplan (für spätere Codephase — keine Umsetzungsfreigabe)
|
||
|
||
### P-06a – Datenmodell / Migration
|
||
|
||
| Feld | Wert |
|
||
|------|------|
|
||
| **Ziel** | Neue Tabelle `media_asset_rights_declarations` anlegen; drei neue Spalten in `media_assets` |
|
||
| **Betroffene Dateien** | `backend/migrations/048_media_rights_declarations.sql` (neu) |
|
||
| **Migrationsbedarf** | Neue Migration; `DEFAULT 'legacy_unreviewed'` automatisch für bestehende Zeilen |
|
||
| **Abhängigkeiten** | Keine (neue Tabelle) |
|
||
| **Testbedarf** | Migration idempotent; bestehende `media_assets`-Zeilen haben `rights_status='legacy_unreviewed'` nach Migration; neue Zeilen haben `rights_status='declared'` nach P-06b |
|
||
| **Akzeptanzkriterien** | Tabelle angelegt, alle Spalten korrekt typisiert, Indizes vorhanden, ALTER-Migration ohne Downtime anwendbar |
|
||
| **Risiko** | Niedrig (neue Tabelle + ALTER mit DEFAULT ohne Table-Lock in PostgreSQL 16) |
|
||
| **Rollback** | DROP TABLE media_asset_rights_declarations; ALTER TABLE media_assets DROP COLUMN rights_status, rights_declared_for_visibility, rights_declared_at |
|
||
|
||
---
|
||
|
||
### P-06b – Backend-Erzwingung
|
||
|
||
| Feld | Wert |
|
||
|------|------|
|
||
| **Ziel** | Backend blockiert Upload und Promotion wenn Pflichtfelder fehlen; schreibt Declarations-Log |
|
||
| **Betroffene Dateien** | `backend/routers/media_assets.py` (bulk-upload + patch + bulk-patch), `backend/routers/exercises.py` (upload_exercise_media, from-asset), neues Modul `backend/media_rights.py` (Hilfslogik) |
|
||
| **Neue Pydantic-Models** | `RightsDeclaration` (Pflichtfelder + optionale Felder); Integration in `MediaAssetPatch` + Upload-Endpoints |
|
||
| **Neue Fehlercodes** | `RIGHTS_DECLARATION_REQUIRED`, `CONSENT_REQUIRED_FOR_CLUB`, `CONSENT_REQUIRED_FOR_OFFICIAL`, `LEGACY_REDECLARATION_REQUIRED`, `RIGHTS_DECLARATION_REQUIRED_FOR_TARGET_VISIBILITY` |
|
||
| **Abhängigkeiten** | P-06a (Migration) muss abgeschlossen sein |
|
||
| **Testbedarf** | Unit-Tests für jeden neuen Fehlercode; Integrationstests Upload → Deklarationslog; Promotion ohne/mit Deklaration; Legacy-Block |
|
||
| **Akzeptanzkriterien** | Upload ohne `rights_holder_confirmed` → 400; promotion ohne Personen-Deklaration → 400; gültige Deklaration → Eintrag in DB vorhanden; bestehender P-04-Test weiterhin grün |
|
||
| **Risiko** | Mittel (Breaking Change für alle Upload-Clients); Feature-Flag zum schrittweisen Rollout empfohlen |
|
||
| **Rollback** | Feature-Flag deaktivieren; kein DB-Rollback nötig |
|
||
|
||
---
|
||
|
||
### P-06c – Frontend-Dialoge
|
||
|
||
| Feld | Wert |
|
||
|------|------|
|
||
| **Ziel** | Einwilligungsdialog in allen Upload-Pfaden; Promotions-Dialog bei Sichtbarkeitsänderung |
|
||
| **Betroffene Dateien** | `frontend/src/components/RightsDeclarationDialog.jsx` (neu, wiederverwendbar), `frontend/src/pages/ExerciseFormPage.jsx`, `frontend/src/pages/MediaLibraryPage.jsx`, `frontend/src/utils/api.js` |
|
||
| **Dialog-Typen** | Upload-Dialog (privat/club/official je nach Kontext), Promotions-Dialog (erkennt Sichtbarkeitserhöhung), Nachdeklarations-Dialog (für Legacy) |
|
||
| **Abhängigkeiten** | P-06b (Backend-Codes müssen definiert sein); juristische Textfreigabe für Checkbox-Labels |
|
||
| **Testbedarf** | Playwright E2E: Dialog erscheint; Upload ohne Checkbox → deaktivierter Button; Dialog mit allem ausgefüllt → erfolgreicher Upload; Backend-Fehler → korrekte Fehlermeldung |
|
||
| **Akzeptanzkriterien** | Dialog erscheint bei allen Upload-Pfaden; Checkbox-State korrekt; Labels entsprechen juristisch geprüften Formulierungen; Promotions-Dialog erkennt Sichtbarkeitserhöhung |
|
||
| **Risiko** | Mittel (UX-Änderung an häufig genutzten Pfaden) |
|
||
| **Rollback** | Feature-Flag; Dialoge können ohne Backend-Änderung deaktiviert werden |
|
||
|
||
---
|
||
|
||
### P-06d – Tests / Dokumentation
|
||
|
||
| Feld | Wert |
|
||
|------|------|
|
||
| **Ziel** | Vollständige Testabdeckung für P-06 + Doku-Update |
|
||
| **Betroffene Dateien** | `backend/tests/test_media_rights_declaration.py` (neu), `tests/dev-smoke-test.spec.js` (neue P-06-Tests), `docs/compliance-implementation.md`, `docs/compliance-package-register.md`, Access-Layer-Audit aktualisieren |
|
||
| **Backend-Tests** | Upload-Flows, Promotions-Flows, Legacy-Block, Declaration-Log, alle Fehlercodes |
|
||
| **Playwright-Tests** | Dialog erscheint, Checkbox-Validierung, vollständiger Upload-Flow, Promotions-Dialog |
|
||
| **Abhängigkeiten** | P-06a, P-06b, P-06c abgeschlossen |
|
||
| **Akzeptanzkriterien** | Alle neuen Tests grün; bestehende Tests unverändert grün; ACCESS_LAYER_STRICT=1 grün |
|
||
| **Risiko** | Niedrig |
|
||
|
||
---
|
||
|
||
### P-06e – Legacy-Nacharbeit (optional, nach juristischer Klärung)
|
||
|
||
| Feld | Wert |
|
||
|------|------|
|
||
| **Ziel** | Massenprüfung und ggf. Sperrung bestehender club/official-Medien ohne Deklaration, falls juristisch notwendig |
|
||
| **Betroffene Dateien** | `backend/scripts/media_rights_audit.py` (neu), evtl. Admin-UI |
|
||
| **Inhalt** | Script listet alle `visibility IN ('club', 'official')` mit `rights_status='legacy_unreviewed'`; Superadmin kann Massenmarkierung oder individuelle Überprüfung anstoßen |
|
||
| **Abhängigkeiten** | P-06a–d; juristische Klärung §7.8 |
|
||
| **Akzeptanzkriterien** | Script läuft ohne Fehler; erzeugt CSV-Report mit betroffenen Assets |
|
||
| **Risiko** | Mittel (wenn Sperrung aktiv: Betriebsunterbrechung für Vereinsmedien) |
|
||
| **Hinweis** | Erst nach juristischer Entscheidung über Altbestand-Behandlung umsetzen |
|
||
|
||
---
|
||
|
||
## 9. Abschlussbewertung
|
||
|
||
### 9.1 Neue / geänderte Dokumente
|
||
|
||
| Dokument | Änderung |
|
||
|----------|---------|
|
||
| `docs/p06-upload-rights-spec.md` | Neu erstellt (dieses Dokument) |
|
||
| `docs/compliance-roadmap.md` | P-06: Spezifikation erstellt vermerkt; nächste Freigabe angepasst |
|
||
| `docs/compliance-implementation.md` | Kurzer P-06-Eintrag: Spezifikation erstellt, keine Umsetzung |
|
||
| `docs/compliance-package-register.md` | P-06 Letzter Stand: Spezifikation vorhanden |
|
||
|
||
### 9.2 Empfohlene P-06-Zielarchitektur (Kurzfassung)
|
||
|
||
- **Append-only Log-Tabelle** `media_asset_rights_declarations` (Wer, Wann, Was, Welche Version)
|
||
- **Drei Status-Felder** in `media_assets`: `rights_status` / `rights_declared_for_visibility` / `rights_declared_at`
|
||
- **Gestufte Erklärungen**: Upload = Minimalset; Promotion club = Personen-Pflicht; Promotion official = vollständig
|
||
- **Backend-Erzwingung** über neue Fehlercodes; keine reine Frontend-Checkbox
|
||
- **Versionierung** über `declaration_version`-String; kein FK auf Texttabelle
|
||
- **Legacy**: `rights_status = 'legacy_unreviewed'`; Promotion blockiert; Nachdeklarations-Flow
|
||
|
||
### 9.3 Kritische Designentscheidungen
|
||
|
||
| Entscheidung | Begründung |
|
||
|-------------|-----------|
|
||
| Gestufte Erklärungen je Sichtbarkeitsstufe | Rechtsrisiko steigt mit Sichtbarkeit; Rechtsanschlussfähigkeit |
|
||
| Minimalset beim Erstupload | Nutzbarkeit vs. Compliance; Pflicht-Erweiterung bei Promotion |
|
||
| Append-only Log (nie löschen/ändern) | Nachweisbarkeit; Audit-Integrität |
|
||
| rights_status = 'legacy_unreviewed' per Default | Keine rückwirkende Sperrung; kontrollierbarer Übergang |
|
||
| Kein Einwilligungs-Datei-Upload (MVP) | Aufwand vs. Mehrwert; spätere Nachrüstung möglich |
|
||
| `rights_status = 'blocked'` als P-11-Schnittstelle | Zukunftssicherheit; P-11/P-13 können sofort anschließen |
|
||
|
||
### 9.4 Offene juristische Fragen (Priorisiert)
|
||
|
||
| Priorität | Frage | Paket-Impact |
|
||
|-----------|-------|-------------|
|
||
| Vor MVP | §7.7 Textfassung der Erklärungen | P-06c (Dialog-Labels) |
|
||
| Vor MVP | §7.1 Private: Reicht rights_holder allein? | Pflichtfeld-Definition |
|
||
| Vor MVP | §7.2 Club: § 22 KUG vereinsintern? | contains_persons bei club Pflicht? |
|
||
| Vor MVP | §7.4 Minderjährige: Schwelle und Form | parental_consent MVP oder später |
|
||
| Vor MVP | §7.5 Selbsterklärung oder Dokumenten-Upload? | Scope P-06c |
|
||
| Zeitnah | §7.8 Altmedien club/official: Sperrpflicht? | P-06e Scope |
|
||
| ~~Vor P-11~~ ✅ | §7.9 Widerruf: Fristen und Mechanismus | P-11 implementiert: `set_legal_hold()` als Sperrmechanismus; juristische Frist-Frage bleibt offen |
|
||
| Mittel | §7.10 Aufbewahrung Declaration-Log | Retention-Policy |
|
||
|
||
### 9.5 Empfohlene spätere Umsetzungsschritte
|
||
|
||
1. Rechtsanwalt beauftragt für §7.7 (Texte) und §7.1–7.3 (Anforderungen je Stufe)
|
||
2. Nach Klärung §7.7: Freigabe P-06a + P-06b parallel (kein Frontend-Merge nötig)
|
||
3. Nach §7.7 Textfreigabe: Freigabe P-06c (Frontend-Dialoge)
|
||
4. P-06d: Tests + Doku beim gleichen Merge wie P-06b/P-06c
|
||
5. Nach §7.8-Klärung: ggf. P-06e (Legacy-Massenprüfung)
|
||
|
||
### 9.6 Implementierungsbereitschaft
|
||
|
||
| Kriterium | Einschätzung |
|
||
|-----------|-------------|
|
||
| P-06 implementierungsreif | Teilweise — Datenmodell und Backend-Logik spezifizierbar; Dialog-Labels nicht ohne juristische Textfreigabe |
|
||
| Juristische Vorabklärung erforderlich | Ja — §7.7 (Textfassung) blockiert P-06c; §7.1–7.3 bestimmen Pflichtfelder |
|
||
| P-06a (Migration) vorab umsetzbar | Ja — unabhängig von juristischen Texten |
|
||
| P-06b (Backend) vorab umsetzbar | Ja — mit Platzhalter-Fehlercodes, Texte nachrüstbar |
|
||
| P-06c (Frontend-Dialog) vorab umsetzbar | Nein — Dialog-Labels müssen juristisch freigegeben sein |
|
||
|
||
### 9.7 Scope-Drift-Prüfung (Stand nach vollständiger Umsetzung)
|
||
|
||
| Prüfung | Ergebnis |
|
||
|---------|---------|
|
||
| Codeänderungen vorgenommen? | Ja — P-06a–P-06d vollständig; P-06+ nachgezogen |
|
||
| Neue Migrationen erstellt? | Ja — Migration 048 (Deklarations-Log + Schnellfelder) + Migration 049 (Kontext-Felder) + Migration 050 (Audit-Log + correction_note) |
|
||
| Neue Endpoints erstellt? | Ja — 5 neue Endpoints (re-declaration, legacy-summary, legacy-assets, journal, correction) |
|
||
| Paket-ID geändert? | Nein |
|
||
| Andere Paket-IDs berührt? | Nein (P-11, P-13 nur als Referenz) |
|
||
| P-06 bleibt nach dieser Arbeit offen? | Ja — technisch umgesetzt; KRIT-04 (juristische Validierung) bleibt offen |
|
||
|
||
---
|
||
|
||
**P-06 technisch umgesetzt (2026-05-11, v0.8.83). KRIT-04 bleibt bis juristische Validierung (§22 KUG, §8 DSGVO, Textfreigabe).**
|
||
|
||
---
|
||
|
||
## 10. Vorläufige Implementierungsannahmen (2026-05-11)
|
||
|
||
> **Wichtiger Hinweis:** Die folgenden Annahmen gelten als vorläufige Erstfassung. Sie wurden bewusst konservativ gewählt. Die juristische Validierung (insb. §7.1–§7.7 dieser Spec) steht weiterhin aus. Texte, Pflichtfelder und Stufenlogik können nach juristischer Prüfung gelockert oder präzisiert werden. P-06 gilt nach dieser technischen Umsetzung als **technisch umgesetzt unter vorläufigen Annahmen** — nicht als rechtlich endgültig abgeschlossen. KRIT-04 bleibt bis zur anwaltlichen Prüfung als rechtlicher Blocker offen.
|
||
|
||
### 10.1 Abweichung gegenüber ursprünglicher Matrix (§3)
|
||
|
||
Die Entscheidungsmatrix in §3 sah vor:
|
||
- **private Upload:** Personenfragen optional
|
||
- **club/official Upload:** Personenfragen Pflicht
|
||
|
||
**Geändert für conservative Erstannahme:**
|
||
- Personenfragen sind bei **allen** Uploads (auch `private`) Pflicht zu beantworten
|
||
- Begründung: lieber zunächst strenger und nach juristischer Klärung gezielt lockern; ein bereits gesammeltes „Nein, keine erkennbaren Personen" ist besser als gar kein Datenpunkt
|
||
- Diese Verschärfung ist als `declaration_version = 'p06-v1-conservative'` dokumentiert
|
||
- Spätere Lockerung auf `'p06-v1.1'` o. ä. möglich ohne Datenverlust
|
||
|
||
### 10.2 Verbindliche Erstannahmen
|
||
|
||
| Annahme | Technisch umgesetzt als |
|
||
|---------|------------------------|
|
||
| Jeder Upload (auch `private`) erfordert Rechteerklärung | `rights_holder_confirmed = true` Pflicht bei allen Uploads |
|
||
| Personenfragen bei allen Uploads Pflicht | `contains_identifiable_persons` muss beantwortet sein |
|
||
| Personeneinwilligung Pflicht wenn Personen vorhanden | `person_consent_confirmed = true` wenn `contains_identifiable_persons = true` |
|
||
| Minderjährigenfrage bei allen Uploads Pflicht | `contains_minors` muss beantwortet sein |
|
||
| Elterneinwilligung Pflicht wenn Minderjährige vorhanden | `parental_consent_confirmed = true` wenn `contains_minors = true` |
|
||
| Musikfrage Pflicht | `contains_music` muss beantwortet sein |
|
||
| Musikrechte Pflicht wenn Musik vorhanden | `music_rights_confirmed = true` wenn `contains_music = true` |
|
||
| Fremdmaterial-Frage Pflicht | `contains_third_party_content` muss beantwortet sein |
|
||
| Fremdmaterialrechte Pflicht wenn Fremdmaterial vorhanden | `third_party_rights_confirmed = true` wenn `contains_third_party_content = true` |
|
||
| Erklärung gilt für konkrete Zielsichtbarkeit | `target_visibility` = `private` / `club` / `official` |
|
||
| Promotion zu höherer Sichtbarkeit erfordert neue Erklärung | Backend blockiert ohne passende neue Declaration |
|
||
| Altmedien bleiben sichtbar | `rights_status = 'legacy_unreviewed'` per Migration-Default |
|
||
| Altmedien-Promotion blockiert | HTTP 400 `LEGACY_REDECLARATION_REQUIRED` |
|
||
| Selbsterklärung genügt (MVP) | Kein Dokumenten-Upload erforderlich |
|
||
| Deklarationsversion | `declaration_version = 'p06-v1-conservative'` |
|
||
|
||
### 10.3 Vorläufige Dialogtexte (Arbeitsfassung — juristisch nicht geprüft)
|
||
|
||
Die folgenden Texte sind Arbeitsfassungen und explizit als vorläufig zu behandeln. Sie werden in der UI mit einem entsprechenden Hinweis angezeigt. Alle Labels sind in der Frontend-Komponente zentral definiert und ohne Code-Änderung austauschbar.
|
||
|
||
| # | Label | Text (Vorläufig) |
|
||
|---|-------|-----------------|
|
||
| T1 | Rechteinhaber-Bestätigung | „Ich bestätige, dass ich berechtigt bin, dieses Medium hochzuladen und in der gewählten Sichtbarkeitsstufe zu verwenden, und dass ich über die dafür erforderlichen Rechte verfüge." |
|
||
| T2 | Personen-Frage | „Sind auf diesem Medium Personen eindeutig erkennbar?" |
|
||
| T3 | Personen-Einwilligung | „Ich bestätige, dass mir für alle erkennbaren Personen die für diese Nutzung erforderlichen Einwilligungen vorliegen." |
|
||
| T4 | Minderjährigen-Frage | „Sind auf diesem Medium Minderjährige eindeutig erkennbar?" |
|
||
| T5 | Minderjährigen-Einwilligung | „Ich bestätige, dass mir die für diese Nutzung erforderlichen Einwilligungen der Sorgeberechtigten vorliegen." |
|
||
| T6 | Musik-Frage | „Enthält das Medium Musik?" |
|
||
| T7 | Musik-Rechte | „Ich bestätige, dass ich für die enthaltene Musik die für diese Nutzung erforderlichen Rechte habe." |
|
||
| T8 | Fremdmaterial-Frage | „Enthält das Medium Logos, Grafiken oder sonstige fremde geschützte Inhalte?" |
|
||
| T9 | Fremdmaterial-Rechte | „Ich bestätige, dass ich für alle enthaltenen fremden Inhalte die für diese Nutzung erforderlichen Rechte habe." |
|
||
| T10 | Hinweis | „Diese Erklärung wird protokolliert. Die konkrete rechtliche Ausgestaltung wird noch abschließend geprüft." |
|
||
|
||
### 10.4 Tatsächlich implementierte Endpunkte und Felder
|
||
|
||
**Migration 048:** `backend/migrations/048_media_rights_declarations.sql`
|
||
- Neue Tabelle: `media_asset_rights_declarations` (append-only, alle Felder aus §4.1)
|
||
- Neues Feld `rights_status` in `media_assets` (DEFAULT `'legacy_unreviewed'`)
|
||
- Neues Feld `rights_declared_for_visibility` in `media_assets`
|
||
- Neues Feld `rights_declared_at` in `media_assets`
|
||
|
||
**Neues Backend-Modul:** `backend/media_rights.py`
|
||
- `VISIBILITY_LEVELS`: Hierarchie private(1) < club(2) < official(3)
|
||
- `validate_rights_declaration()`: Prüft vollständige Deklaration je Sichtbarkeit
|
||
- `check_rights_coverage()`: Prüft ob vorhandene Erklärung Zielsichtbarkeit abdeckt
|
||
- `write_rights_declaration()`: Schreibt append-only Declaration-Log
|
||
- `update_rights_quick_fields()`: Aktualisiert Schnellfelder in `media_assets`
|
||
|
||
**Angepasste Endpunkte:**
|
||
| Endpunkt | Änderung |
|
||
|----------|---------|
|
||
| `POST /api/media-assets/bulk-upload` | P-06-Pflichtfelder als Form-Parameter; Declaration-Log bei Erfolg |
|
||
| `PATCH /api/media-assets/{id}` | Rechte-Prüfung bei Promotion; Declaration-Log wenn neue Erklärung |
|
||
| `POST /api/media-assets/bulk-patch` | Rechte-Prüfung bei Promotion per Asset |
|
||
| `POST /api/exercises/{id}/media` | P-06-Pflichtfelder; Declaration-Log bei Datei-Upload |
|
||
| `POST /api/exercises/{id}/media/from-asset` | `rights_status`-Prüfung des Assets gegen Übungssichtbarkeit |
|
||
|
||
**Neue Endpunkte (P-06b):**
|
||
| Endpunkt | Beschreibung |
|
||
|----------|-------------|
|
||
| `POST /api/media-assets/{id}/rights-declarations` | Re-/Nachdeklaration für vorhandene Medien |
|
||
| `GET /api/admin/media-rights/legacy-summary` | Auswertung `legacy_unreviewed` nach Sichtbarkeit |
|
||
| `GET /api/admin/media-rights/legacy-assets` | Liste `legacy_unreviewed` club/official-Medien |
|
||
|
||
**Frontend-Komponente:**
|
||
- `frontend/src/components/RightsDeclarationDialog.jsx` — wiederverwendbarer Dialog
|
||
- Integration in `MediaLibraryPage.jsx` (Bulk-Upload) und `ExerciseFormPage.jsx` (Upload)
|
||
|
||
**Migration 049 (Kontext-Felder — ergänzend zu 048):**
|
||
- `person_consent_context TEXT`, `parental_consent_context TEXT`, `music_rights_context TEXT`, `third_party_rights_context TEXT` — Freitextfelder für Einwilligungskontext (z. B. Einwilligungsreferenz, Datum, Beschreibung)
|
||
|
||
### 10.5 Juristische Restoffenheit
|
||
|
||
Folgende Punkte aus §7 sind technisch mit konservativer Annahme implementiert, aber rechtlich noch zu validieren:
|
||
|
||
| §7-Punkt | Konservative Annahme | Juristisch offen |
|
||
|----------|---------------------|-----------------|
|
||
| §7.1 private: Reicht rights_holder allein? | Nein — alle Fragen Pflicht | Ja |
|
||
| §7.2 club: KUG vereinsintern? | Ja — volle Erklärung Pflicht | Ja |
|
||
| §7.4 Minderjährige: Schwelle, Form? | Pflichtfeld ab Upload | Ja |
|
||
| §7.5 Selbsterklärung genügt? | Ja (MVP) | Ja |
|
||
| §7.6 Batch-Deklaration genügend? | Ja — eine Erklärung gilt für alle Batch-Dateien | Ja |
|
||
| §7.7 Textfassung der Erklärungen | Vorläufige Arbeitsfassung (T1–T10) | Ja — anwaltliche Freigabe nötig |
|
||
| §7.10 Aufbewahrung Declaration-Log | `ON DELETE SET NULL` für declared_by; CASCADE auf media_asset_id | Ja |
|
||
|
||
---
|
||
|
||
---
|
||
|
||
## 11. P-06+ Erweiterungen: Volljournal + Korrektur (2026-05-11, v0.8.82–0.8.83)
|
||
|
||
> Diese Erweiterungen gehen über die ursprüngliche P-06-Spezifikation (§8) hinaus und wurden in der Implementierungsphase ergänzt, da das Volljournal und die Korrekturfähigkeit als notwendige Bestandteile eines nachweisfähigen Einwilligungs-Systems erkannt wurden.
|
||
|
||
### 11.1 Motivation
|
||
|
||
Die ursprüngliche P-06-Spezifikation sah einen append-only Deklarations-Log vor (§4.1, Entscheidung 4). Nicht explizit geplant war:
|
||
- Protokollierung von Nicht-Deklarations-Änderungen (Sichtbarkeit, Copyright, Lifecycle)
|
||
- Eine einheitliche chronologische Ansicht aller Ereignisse zu einem Asset
|
||
- Die Möglichkeit, abgegebene Deklarationen mit Begründung nachträglich zu korrigieren (bei Irrtum oder Widerruf)
|
||
|
||
### 11.2 Migration 050: Vollständiger Audit-Log
|
||
|
||
**Datei:** `backend/migrations/050_media_audit_log.sql`
|
||
|
||
**Neue Tabelle `media_asset_audit_log`:**
|
||
|
||
```sql
|
||
CREATE TABLE media_asset_audit_log (
|
||
id SERIAL PRIMARY KEY,
|
||
media_asset_id INT NOT NULL REFERENCES media_assets(id) ON DELETE CASCADE,
|
||
acting_profile_id INT REFERENCES profiles(id) ON DELETE SET NULL,
|
||
occurred_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
event_type VARCHAR(50) NOT NULL
|
||
CHECK (event_type IN (
|
||
'visibility_change',
|
||
'copyright_change',
|
||
'metadata_change',
|
||
'lifecycle_change'
|
||
)),
|
||
old_values JSONB,
|
||
new_values JSONB
|
||
);
|
||
```
|
||
|
||
Append-only. Wird nie geändert oder gelöscht (außer ON DELETE CASCADE des Assets).
|
||
|
||
**Neue Spalte `correction_note`** in `media_asset_rights_declarations`:
|
||
```sql
|
||
ALTER TABLE media_asset_rights_declarations
|
||
ADD COLUMN IF NOT EXISTS correction_note TEXT;
|
||
```
|
||
|
||
**Erweiterter CHECK** in `media_asset_rights_declarations`:
|
||
- `action_type` nimmt jetzt auch `'correction'` an (zusätzlich zu upload, promote_club, promote_official, re_declaration, legacy_re_declaration)
|
||
|
||
### 11.3 Neue Backend-Funktionen (`backend/media_rights.py`)
|
||
|
||
| Funktion | Beschreibung |
|
||
|----------|-------------|
|
||
| `write_audit_log_entry(cur, asset_id, acting_profile_id, event_type, old_values, new_values)` | Schreibt einen Eintrag in `media_asset_audit_log` (JSONB-serialisiert) |
|
||
| `write_rights_correction_declaration(cur, asset_id, profile_id, target_visibility, decl, correction_note)` | Schreibt action_type='correction' in `media_asset_rights_declarations` |
|
||
|
||
### 11.4 Neue Endpoints (`backend/routers/media_assets.py`)
|
||
|
||
| Endpoint | Auth | Beschreibung |
|
||
|----------|------|-------------|
|
||
| `GET /api/admin/media-rights/assets/{id}/journal` | Superadmin / Uploader / Vereins-Admin | Vollständiges Medien-Journal: Deklarationen + Audit-Ereignisse chronologisch gemischt (`events[]` mit `kind='declaration'` oder `kind='audit'`). Gibt auch `can_correct` zurück. |
|
||
| `POST /api/admin/media-rights/assets/{id}/correction` | Superadmin / Uploader / Vereins-Admin | Korrektur-Deklaration einreichen (append-only, neueste gilt). Felder identisch mit P-06-Dialog + `correction_note`. |
|
||
|
||
**Journal-Response-Format:**
|
||
```json
|
||
{
|
||
"asset": { "id": 57, "original_filename": "...", "visibility": "club", ... },
|
||
"events": [
|
||
{
|
||
"kind": "declaration",
|
||
"id": 12,
|
||
"at": "2026-05-11T10:00:00Z",
|
||
"action_type": "upload",
|
||
"target_visibility": "private",
|
||
"rights_holder_confirmed": true,
|
||
"contains_identifiable_persons": false,
|
||
"correction_note": null,
|
||
"declared_by_name": "Lars",
|
||
"declared_by_email": "stommer@gmail.com"
|
||
},
|
||
{
|
||
"kind": "audit",
|
||
"id": 3,
|
||
"at": "2026-05-11T11:00:00Z",
|
||
"event_type": "visibility_change",
|
||
"old_values": {"visibility": "private", "club_id": null},
|
||
"new_values": {"visibility": "club", "club_id": 2},
|
||
"acting_name": "Lars"
|
||
}
|
||
],
|
||
"can_correct": true
|
||
}
|
||
```
|
||
|
||
### 11.5 Automatische Audit-Log-Einträge
|
||
|
||
| Aktion | event_type | Auslöser |
|
||
|--------|-----------|----------|
|
||
| Sichtbarkeitsänderung via PATCH | `visibility_change` | `PATCH /api/media-assets/{id}` wenn `visibility` oder `club_id` sich ändert |
|
||
| Copyright-Änderung via PATCH | `copyright_change` | `PATCH /api/media-assets/{id}` wenn `copyright_notice` sich ändert |
|
||
| Metadaten-Änderung (Dateiname etc.) | `metadata_change` | `PATCH /api/media-assets/{id}` wenn `original_filename` etc. sich ändert |
|
||
| Lifecycle-Übergang | `lifecycle_change` | `_apply_lifecycle_action`: trash_soft, trash_hidden, recover, reactivate (nicht bei Hard-Delete oder Purge) |
|
||
|
||
### 11.6 Frontend-Erweiterungen (`MediaLibraryPage.jsx`)
|
||
|
||
- **Unified Journal Modal:** Journal-Ansicht zeigt `events[]` chronologisch. Deklarationseinträge und Audit-Einträge visuell unterschieden (`--audit` CSS-Modifier).
|
||
- **Korrektur-Formular:** Inline-Formular im Journal-Modal. Erscheint wenn `can_correct = true` und Nutzer „Korrektur erfassen" klickt. Enthält alle P-06-Felder + `correction_note` Freitextfeld.
|
||
- **Neue `api.js`-Funktion:** `addMediaAssetDeclarationCorrection(assetId, body)` — POST auf Korrektur-Endpoint.
|
||
|
||
### 11.7 Zugriffsmodell
|
||
|
||
Journal und Korrektur sind **nicht** auf Superadmin beschränkt — bewusste Entscheidung:
|
||
|
||
| Rolle | Zugang Journal | Zugang Korrektur |
|
||
|-------|---------------|-----------------|
|
||
| Superadmin | ✅ | ✅ |
|
||
| Uploader (ursprünglicher Upload) | ✅ | ✅ |
|
||
| Vereins-Admin (bei club-Medien) | ✅ | ✅ |
|
||
| Andere | ❌ 403 | ❌ 403 |
|
||
|
||
Club-Admin-Prüfung erfolgt über `has_club_role(cur, profile_id, club_id, "club_admin")` (Tabelle `club_member_roles`), nicht über direktes `role`-Feld in `club_members`.
|
||
|
||
### 11.8 Bugfixes (2026-05-11)
|
||
|
||
| Bug | Ursache | Fix |
|
||
|-----|---------|-----|
|
||
| Journal und Korrektur gaben 500 zurück | Club-Admin-Prüfung nutzte `AND role = 'admin'` auf `club_members` — diese Spalte existiert dort nicht; Rollen liegen in `club_member_roles` | Ersetzt durch `has_club_role(cur, profile_id, club_id, "club_admin")` |
|
||
| Frontend-Build-Fehler nach Duplikat `lcLabel` | Funktion `lcLabel` wurde doppelt deklariert (bei Zeile 98 und 264) | Duplikat bei Zeile 264 entfernt |
|
||
|
||
### 11.9 Juristische Erweiterungsfragen
|
||
|
||
Die Korrekturfähigkeit von Deklarationen wirft zusätzliche juristische Fragen auf:
|
||
|
||
| Frage | Technisches Verhalten | Juristisch zu prüfen |
|
||
|-------|----------------------|---------------------|
|
||
| Wie lange ist eine Korrektur zulässig? | Kein zeitliches Limit implementiert | Fristenregelung? |
|
||
| Wer darf eine Korrektur einreichen? | Superadmin, Uploader, Vereins-Admin | Nur Uploader? |
|
||
| Gilt die neueste Korrektur immer als maßgeblich? | Ja — `rights_declared_for_visibility` wird aktualisiert | Ja, solange append-only |
|
||
| Muss eine Korrektur begründet werden? | `correction_note` optional, nicht Pflicht | Pflichtfeld? |
|
||
|
||
---
|
||
|
||
*Erstellt: 2026-05-10 | Implementierungsannahmen hinzugefügt: 2026-05-11 | P-06+ Volljournal + Korrektur: 2026-05-11 (v0.8.82–0.8.83) | Autor: Claude Code | Kein Rechtsanwalt; alle rechtlichen Einschätzungen sind juristisch zu prüfen.*
|