2026-08-22 08:26:10 +12:00
|
|
|
package api
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"net/http"
|
|
|
|
|
"slices"
|
|
|
|
|
"sync"
|
|
|
|
|
|
|
|
|
|
"github.com/ponzischeme89/memby/server/internal/adminevents"
|
|
|
|
|
"github.com/ponzischeme89/memby/server/internal/store"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type metadataHeroSettingsState struct {
|
|
|
|
|
mu sync.RWMutex
|
|
|
|
|
value store.MetadataHeroSettings
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (state *metadataHeroSettingsState) get() store.MetadataHeroSettings {
|
|
|
|
|
state.mu.RLock()
|
|
|
|
|
defer state.mu.RUnlock()
|
|
|
|
|
return store.NormalizeMetadataHeroSettings(state.value)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (state *metadataHeroSettingsState) set(value store.MetadataHeroSettings) {
|
|
|
|
|
state.mu.Lock()
|
|
|
|
|
state.value = store.NormalizeMetadataHeroSettings(value)
|
|
|
|
|
state.mu.Unlock()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// LoadMetadataHeroSettings primes the household-wide presentation setting before the
|
|
|
|
|
// status endpoint starts telling televisions what to draw.
|
|
|
|
|
func (s *Server) LoadMetadataHeroSettings(ctx context.Context) error {
|
|
|
|
|
settings, err := s.store.MetadataHeroSettings(ctx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
s.metadataHeroSettings.set(settings)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type metadataHeroOption struct {
|
|
|
|
|
Value string `json:"value"`
|
|
|
|
|
Label string `json:"label"`
|
|
|
|
|
Description string `json:"description"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var metadataHeroOptions = []metadataHeroOption{
|
|
|
|
|
{store.MetadataHeroTitle, "Movie logo or title", "The focused title's logo artwork, with a text title as the fallback."},
|
|
|
|
|
{store.MetadataHeroRatings, "Ratings", "The available third-party and community ratings."},
|
|
|
|
|
{store.MetadataHeroFacts, "Metadata information", "Year, runtime, classification, genres and outlined media badges."},
|
|
|
|
|
{store.MetadataHeroSummary, "Plot summary", "A short overview of the focused title."},
|
|
|
|
|
{store.MetadataHeroReason, "Why this was recommended", "The gateway's short explanation, such as Because you watched a related title."},
|
|
|
|
|
{store.MetadataHeroTime, "Time remaining", "A resume progress bar followed by the number of minutes remaining."},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type metadataHeroSettingsResponse struct {
|
|
|
|
|
Settings store.MetadataHeroSettings `json:"settings"`
|
|
|
|
|
Options []metadataHeroOption `json:"options"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *Server) metadataHeroSettingsResponse() metadataHeroSettingsResponse {
|
|
|
|
|
return metadataHeroSettingsResponse{
|
|
|
|
|
Settings: s.metadataHeroSettings.get(),
|
|
|
|
|
Options: metadataHeroOptions,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *Server) handleAdminMetadataHeroSettings(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
if r.Method == http.MethodGet {
|
|
|
|
|
writeJSON(w, http.StatusOK, s.metadataHeroSettingsResponse())
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
var req store.MetadataHeroSettings
|
|
|
|
|
if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 8<<10)).Decode(&req); err != nil {
|
|
|
|
|
writeError(w, http.StatusBadRequest, "malformed request body")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
previous := s.metadataHeroSettings.get()
|
|
|
|
|
_, operator, _ := s.browserSession(r, adminSessionPurpose)
|
|
|
|
|
req.UpdatedBy = operator
|
|
|
|
|
stored, err := s.store.SetMetadataHeroSettings(r.Context(), req)
|
|
|
|
|
if err != nil {
|
|
|
|
|
s.loggerFor(r.Context()).Error("metadata hero settings write failed", "error", err)
|
|
|
|
|
writeError(w, http.StatusInternalServerError, "could not save metadata hero settings")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
s.metadataHeroSettings.set(stored)
|
2026-08-22 11:32:50 +12:00
|
|
|
if !slices.Equal(previous.ContentOrder, stored.ContentOrder) ||
|
|
|
|
|
previous.TimeRemainingColour != stored.TimeRemainingColour {
|
2026-08-22 08:26:10 +12:00
|
|
|
s.publishAdmin(r.Context(), adminevents.Event{
|
|
|
|
|
Type: adminevents.TypeSettingsChanged,
|
|
|
|
|
Severity: adminevents.SeverityInfo,
|
|
|
|
|
Title: "Metadata hero changed",
|
2026-08-22 11:32:50 +12:00
|
|
|
Summary: "The household-wide metadata hero presentation was changed",
|
2026-08-22 08:26:10 +12:00
|
|
|
Actor: operator,
|
|
|
|
|
Link: "/admin/metadata-hero",
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
writeJSON(w, http.StatusOK, s.metadataHeroSettingsResponse())
|
|
|
|
|
}
|