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:
parent
a2561a704a
commit
d395804612
4 changed files with 60 additions and 48 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
FROM golang:1.24-alpine AS build
|
FROM golang:1.25-alpine AS build
|
||||||
|
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------
|
// ------------------------------------------------------------------
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue