diff --git a/server/backend/Dockerfile b/server/backend/Dockerfile index 15d14b6..e9e46bd 100644 --- a/server/backend/Dockerfile +++ b/server/backend/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.24-alpine AS build +FROM golang:1.25-alpine AS build WORKDIR /src COPY . . diff --git a/server/backend/internal/httpapi/manage/media.go b/server/backend/internal/httpapi/manage/media.go index 92de86b..4f890b7 100644 --- a/server/backend/internal/httpapi/manage/media.go +++ b/server/backend/internal/httpapi/manage/media.go @@ -16,23 +16,35 @@ import ( const maxUploadSize = 512 << 20 // 512 MB // 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) { - tenantID := r.PathValue("tenantId") - assets, err := media.List(r.Context(), tenantID) + tenant, err := tenants.Get(r.Context(), r.PathValue("tenantSlug")) + if err != nil { + http.Error(w, "tenant not found", http.StatusNotFound) + return + } + assets, err := media.List(r.Context(), tenant.ID) if err != nil { http.Error(w, "db error", http.StatusInternalServerError) return } + if assets == nil { + assets = []*store.MediaAsset{} + } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(assets) //nolint:errcheck } } // 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) { - 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 { http.Error(w, "request too large or not multipart", http.StatusBadRequest) diff --git a/server/backend/internal/httpapi/router.go b/server/backend/internal/httpapi/router.go index 7564f0f..b9fa4f8 100644 --- a/server/backend/internal/httpapi/router.go +++ b/server/backend/internal/httpapi/router.go @@ -105,9 +105,9 @@ func registerManageRoutes(mux *http.ServeMux, d RouterDeps) { // ── JSON API — 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", - manage.HandleUploadMedia(d.MediaStore, uploadDir)) + manage.HandleUploadMedia(d.TenantStore, d.MediaStore, uploadDir)) mux.HandleFunc("DELETE /api/v1/media/{id}", manage.HandleDeleteMedia(d.MediaStore, uploadDir)) diff --git a/server/backend/internal/store/store.go b/server/backend/internal/store/store.go index aafba7a..aff1837 100644 --- a/server/backend/internal/store/store.go +++ b/server/backend/internal/store/store.go @@ -14,58 +14,58 @@ import ( // ------------------------------------------------------------------ type Tenant struct { - ID string - Slug string - Name string - CreatedAt time.Time + ID string `json:"id"` + Slug string `json:"slug"` + Name string `json:"name"` + CreatedAt time.Time `json:"created_at"` } type Screen struct { - ID string - TenantID string - Slug string - Name string - Orientation string - CreatedAt time.Time + ID string `json:"id"` + TenantID string `json:"tenant_id"` + Slug string `json:"slug"` + Name string `json:"name"` + Orientation string `json:"orientation"` + CreatedAt time.Time `json:"created_at"` } type MediaAsset struct { - ID string - TenantID string - Title string - Type string // image | video | pdf | web - StoragePath string - OriginalURL string - MimeType string - SizeBytes int64 - Enabled bool - CreatedAt time.Time + ID string `json:"id"` + TenantID string `json:"tenant_id"` + Title string `json:"title"` + Type string `json:"type"` // image | video | pdf | web + StoragePath string `json:"storage_path,omitempty"` + OriginalURL string `json:"original_url,omitempty"` + MimeType string `json:"mime_type,omitempty"` + SizeBytes int64 `json:"size_bytes,omitempty"` + Enabled bool `json:"enabled"` + CreatedAt time.Time `json:"created_at"` } type Playlist struct { - ID string - TenantID string - ScreenID string - Name string - IsActive bool - DefaultDurationSeconds int - CreatedAt time.Time - UpdatedAt time.Time + ID string `json:"id"` + TenantID string `json:"tenant_id"` + ScreenID string `json:"screen_id"` + Name string `json:"name"` + IsActive bool `json:"is_active"` + DefaultDurationSeconds int `json:"default_duration_seconds"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` } type PlaylistItem struct { - ID string - PlaylistID string - MediaAssetID string // may be empty for web items without asset - OrderIndex int - Type string // image | video | pdf | web - Src string - Title string - DurationSeconds int - ValidFrom *time.Time - ValidUntil *time.Time - Enabled bool - CreatedAt time.Time + ID string `json:"id"` + PlaylistID string `json:"playlist_id"` + MediaAssetID string `json:"media_asset_id,omitempty"` + OrderIndex int `json:"order_index"` + Type string `json:"type"` // image | video | pdf | web + Src string `json:"src"` + Title string `json:"title,omitempty"` + DurationSeconds int `json:"duration_seconds"` + ValidFrom *time.Time `json:"valid_from,omitempty"` + ValidUntil *time.Time `json:"valid_until,omitempty"` + Enabled bool `json:"enabled"` + CreatedAt time.Time `json:"created_at"` } // ------------------------------------------------------------------