4.8 KiB
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:
ALTER TABLE media_assets
ADD COLUMN created_by_user_id text NULL
REFERENCES users(id) ON DELETE SET NULL;
- Nullable: bestehende Medien behalten
NULLals 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:
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
func (s *MediaStore) List(ctx context.Context, tenantID, ownerUserID string) ([]MediaAsset, error)
ownerUserID == ""→ alle Tenant-Medien (Admin, screen_user);OwnerIsRestrictedper LEFT JOIN befülltownerUserID != ""→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}/mediaPOST /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):
<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(Bulmabutton 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):
<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 |