Bugfixes: JSON-Tags, Tenant-Lookup, Dockerfile Go-Version

- store: JSON-Tags auf allen Domain-Typen (snake_case statt PascalCase)
- media.go: PathValue("tenantId") → "tenantSlug" + Tenant-Lookup via TenantStore
- media.go: leere Asset-Liste gibt [] statt null zurück
- router.go: TenantStore an HandleListMedia/HandleUploadMedia weitergeben
- Dockerfile: golang:1.24 → golang:1.25 (go.mod fordert >= 1.25)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jesko Anschütz 2026-03-22 23:26:56 +01:00
parent a2561a704a
commit d395804612
4 changed files with 60 additions and 48 deletions

View file

@ -1,4 +1,4 @@
FROM golang:1.24-alpine AS build FROM golang:1.25-alpine AS build
WORKDIR /src WORKDIR /src
COPY . . COPY . .

View file

@ -16,23 +16,35 @@ import (
const maxUploadSize = 512 << 20 // 512 MB const maxUploadSize = 512 << 20 // 512 MB
// HandleListMedia returns all media assets for a tenant as JSON. // HandleListMedia returns all media assets for a tenant as JSON.
func HandleListMedia(media *store.MediaStore) http.HandlerFunc { func HandleListMedia(tenants *store.TenantStore, media *store.MediaStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
tenantID := r.PathValue("tenantId") tenant, err := tenants.Get(r.Context(), r.PathValue("tenantSlug"))
assets, err := media.List(r.Context(), tenantID) if err != nil {
http.Error(w, "tenant not found", http.StatusNotFound)
return
}
assets, err := media.List(r.Context(), tenant.ID)
if err != nil { if err != nil {
http.Error(w, "db error", http.StatusInternalServerError) http.Error(w, "db error", http.StatusInternalServerError)
return return
} }
if assets == nil {
assets = []*store.MediaAsset{}
}
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(assets) //nolint:errcheck json.NewEncoder(w).Encode(assets) //nolint:errcheck
} }
} }
// HandleUploadMedia handles multipart file upload and web-URL registration. // HandleUploadMedia handles multipart file upload and web-URL registration.
func HandleUploadMedia(media *store.MediaStore, uploadDir string) http.HandlerFunc { func HandleUploadMedia(tenants *store.TenantStore, media *store.MediaStore, uploadDir string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
tenantID := r.PathValue("tenantId") tenant, err := tenants.Get(r.Context(), r.PathValue("tenantSlug"))
if err != nil {
http.Error(w, "tenant not found", http.StatusNotFound)
return
}
tenantID := tenant.ID
if err := r.ParseMultipartForm(maxUploadSize); err != nil { if err := r.ParseMultipartForm(maxUploadSize); err != nil {
http.Error(w, "request too large or not multipart", http.StatusBadRequest) http.Error(w, "request too large or not multipart", http.StatusBadRequest)

View file

@ -105,9 +105,9 @@ func registerManageRoutes(mux *http.ServeMux, d RouterDeps) {
// ── JSON API — media ────────────────────────────────────────────────── // ── JSON API — media ──────────────────────────────────────────────────
mux.HandleFunc("GET /api/v1/tenants/{tenantSlug}/media", mux.HandleFunc("GET /api/v1/tenants/{tenantSlug}/media",
manage.HandleListMedia(d.MediaStore)) manage.HandleListMedia(d.TenantStore, d.MediaStore))
mux.HandleFunc("POST /api/v1/tenants/{tenantSlug}/media", mux.HandleFunc("POST /api/v1/tenants/{tenantSlug}/media",
manage.HandleUploadMedia(d.MediaStore, uploadDir)) manage.HandleUploadMedia(d.TenantStore, d.MediaStore, uploadDir))
mux.HandleFunc("DELETE /api/v1/media/{id}", mux.HandleFunc("DELETE /api/v1/media/{id}",
manage.HandleDeleteMedia(d.MediaStore, uploadDir)) manage.HandleDeleteMedia(d.MediaStore, uploadDir))

View file

@ -14,58 +14,58 @@ import (
// ------------------------------------------------------------------ // ------------------------------------------------------------------
type Tenant struct { type Tenant struct {
ID string ID string `json:"id"`
Slug string Slug string `json:"slug"`
Name string Name string `json:"name"`
CreatedAt time.Time CreatedAt time.Time `json:"created_at"`
} }
type Screen struct { type Screen struct {
ID string ID string `json:"id"`
TenantID string TenantID string `json:"tenant_id"`
Slug string Slug string `json:"slug"`
Name string Name string `json:"name"`
Orientation string Orientation string `json:"orientation"`
CreatedAt time.Time CreatedAt time.Time `json:"created_at"`
} }
type MediaAsset struct { type MediaAsset struct {
ID string ID string `json:"id"`
TenantID string TenantID string `json:"tenant_id"`
Title string Title string `json:"title"`
Type string // image | video | pdf | web Type string `json:"type"` // image | video | pdf | web
StoragePath string StoragePath string `json:"storage_path,omitempty"`
OriginalURL string OriginalURL string `json:"original_url,omitempty"`
MimeType string MimeType string `json:"mime_type,omitempty"`
SizeBytes int64 SizeBytes int64 `json:"size_bytes,omitempty"`
Enabled bool Enabled bool `json:"enabled"`
CreatedAt time.Time CreatedAt time.Time `json:"created_at"`
} }
type Playlist struct { type Playlist struct {
ID string ID string `json:"id"`
TenantID string TenantID string `json:"tenant_id"`
ScreenID string ScreenID string `json:"screen_id"`
Name string Name string `json:"name"`
IsActive bool IsActive bool `json:"is_active"`
DefaultDurationSeconds int DefaultDurationSeconds int `json:"default_duration_seconds"`
CreatedAt time.Time CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time UpdatedAt time.Time `json:"updated_at"`
} }
type PlaylistItem struct { type PlaylistItem struct {
ID string ID string `json:"id"`
PlaylistID string PlaylistID string `json:"playlist_id"`
MediaAssetID string // may be empty for web items without asset MediaAssetID string `json:"media_asset_id,omitempty"`
OrderIndex int OrderIndex int `json:"order_index"`
Type string // image | video | pdf | web Type string `json:"type"` // image | video | pdf | web
Src string Src string `json:"src"`
Title string Title string `json:"title,omitempty"`
DurationSeconds int DurationSeconds int `json:"duration_seconds"`
ValidFrom *time.Time ValidFrom *time.Time `json:"valid_from,omitempty"`
ValidUntil *time.Time ValidUntil *time.Time `json:"valid_until,omitempty"`
Enabled bool Enabled bool `json:"enabled"`
CreatedAt time.Time CreatedAt time.Time `json:"created_at"`
} }
// ------------------------------------------------------------------ // ------------------------------------------------------------------