# User-spezifische Medien für Restricted Users — Design

**Datum:** 2026-03-28

## Problem

Restricted Users können aktuell alle Medien des Tenants sehen und löschen. Sie sollen nur ihre eigenen Medien sehen und verwalten dürfen.

## Anforderungen

- **Restricted Users** sehen ausschließlich Medien, die sie selbst hochgeladen haben
- **Restricted Users** dürfen nur ihre eigenen Medien löschen
- **Admins** sehen alle Medien des Tenants — inkl. solcher ohne Besitzer (Legacy-Medien)
- **screen_users** sehen alle Medien des Tenants (unverändert)
- Bestehende Medien (ohne Besitzer) bleiben erhalten und funktionieren weiter
- **Admins und screen_users** können Medien von Restricted Users ein-/ausblenden (Toggle). Default: **ausgeblendet**

## Ansatz: Filter im Store-Layer

Ownership wird als Datenbankfeld gespeichert. Die Filter-Logik liegt im Store-Layer, Handlers delegieren nach Rolle. Entspricht dem bestehenden pgx/raw-SQL-Muster (analog K4-Checks in playlist.go).

## Datenbank

### Migration

Neue Spalte in `media_assets`:

```sql
ALTER TABLE media_assets
  ADD COLUMN created_by_user_id text NULL
  REFERENCES users(id) ON DELETE SET NULL;
```

- **Nullable**: bestehende Medien behalten `NULL` als Besitzer
- **ON DELETE SET NULL**: wird ein User gelöscht, bleibt das Medium erhalten, verliert aber den Besitzer
- Kein Backfill nötig — `NULL` = kein Besitzer (Legacy oder Admin-Upload)

## Go-Datenmodell

`MediaAsset`-Struct bekommt drei neue Felder:

```go
CreatedByUserID    string // leer = kein Besitzer (legacy)
OwnerIsRestricted  bool   // true wenn Uploader Rolle "restricted" hat
OwnerUsername      string // Benutzername des Uploaders; leer wenn kein Besitzer
```

`OwnerIsRestricted` und `OwnerUsername` werden per `LEFT JOIN users` in der List-Query befüllt — kein separater Lookup nötig.

## Store-Layer

### List

```go
func (s *MediaStore) List(ctx context.Context, tenantID, ownerUserID string) ([]MediaAsset, error)
```

- `ownerUserID == ""` → alle Tenant-Medien (Admin, screen_user); `OwnerIsRestricted` per LEFT JOIN befüllt
- `ownerUserID != ""` → `AND m.created_by_user_id = $2` (Restricted User); kein JOIN nötig

### Create

Kein Signatur-Wandel. `MediaAsset.CreatedByUserID` wird vom Handler befüllt und per Struct übergeben. Bestehende Create-Logik bleibt unverändert.

## Handler-Layer

### Upload (API + Manage-Endpoint)

Beide Upload-Handler befüllen `CreatedByUserID` mit `user.ID`:
- `POST /api/v1/tenants/{tenantSlug}/media`
- `POST /manage/{screenSlug}/upload`

### List

Handler übergibt an `Store.List()`:
- `restricted` → `ownerUserID = u.ID`
- alle anderen → `ownerUserID = ""`

### Delete (K3-Check)

Aktuell: `u.TenantID == asset.TenantID || u.Role == "admin"`

Neu:

| Rolle | Bedingung |
|---|---|
| `admin` | immer erlaubt |
| `screen_user` | Tenant-Match reicht (wie bisher) |
| `restricted` | Tenant-Match **und** `asset.CreatedByUserID == u.ID` |

## UI

### Admin- und screen_user-Ansicht

**Badge für Medien ohne Besitzer** (nur für Admins, da screen_users keine Legacy-Medien hochladen können):

```html
<span class="tag is-warning is-light">Kein Besitzer</span>
```

Wird angezeigt wenn `CreatedByUserID == ""`. Kein Benutzername wird angezeigt.

**Toggle: Restricted-Medien ein-/ausblenden** (Admin + screen_user):

- Button in der Medienliste: `Restricted-Medien anzeigen` (Bulma `button is-small`)
- Jedes Medium von einem Restricted-User erhält `data-owner-restricted="true"` im HTML
- Default: diese Elemente sind per CSS ausgeblendet (`display: none`)
- Vanilla JS: Toggle-Button wechselt eine CSS-Klasse auf dem Container; Items mit `data-owner-restricted="true"` werden sichtbar/unsichtbar
- Kein Page-Reload, kein Server-Request — rein clientseitig

**Besitzer-Kennzeichnung** bei Restricted-Medien (Admin + screen_user, wenn sichtbar):

```html
<span class="tag is-info is-light">{{ .OwnerUsername }}</span>
```

Wird neben dem Medientitel angezeigt, wenn `OwnerIsRestricted == true`.

### Restricted-User-Ansicht

Keine strukturellen Änderungen. Die Liste ist serverseitig gefiltert — der User sieht einfach nur eigene Einträge. Kein Toggle-Button sichtbar.

## Nicht im Scope

- Übertragung von Medien zwischen Usern
- Persistierung der Toggle-Einstellung (wird nicht gespeichert, reset bei Seitenladen)

## Betroffene Dateien

| Datei | Änderung |
|---|---|
| `server/backend/db/migrations/00X_media_owner.sql` | neue Spalte `created_by_user_id` |
| `server/backend/internal/store/store.go` | `MediaAsset`-Struct, `List()`-Signatur, `Create()` |
| `server/backend/internal/httpapi/manage/media.go` | Upload + Delete + List Handler |
| `manage/templates.go` | Badge für Medien ohne Besitzer; Toggle-Button + JS; `data-owner-restricted` Attribut |
| `docs/SCHEMA.md` | neue Spalte dokumentieren |
| `docs/API-ENDPOINTS.md` | ggf. List-Endpoint-Verhalten dokumentieren |
