129 lines
4.5 KiB
Markdown
129 lines
4.5 KiB
Markdown
# 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 zwei neue Felder:
|
|
|
|
```go
|
|
CreatedByUserID string // leer = kein Besitzer (legacy)
|
|
OwnerIsRestricted bool // true wenn Uploader Rolle "restricted" hat
|
|
```
|
|
|
|
`OwnerIsRestricted` wird 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
|
|
|
|
### 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
|
|
|
|
- Anzeige des Besitzernamens bei Medien
|
|
- Ü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 |
|