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) if !slices.Equal(previous.ContentOrder, stored.ContentOrder) { s.publishAdmin(r.Context(), adminevents.Event{ Type: adminevents.TypeSettingsChanged, Severity: adminevents.SeverityInfo, Title: "Metadata hero changed", Summary: "The household-wide metadata hero content order was changed", Actor: operator, Link: "/admin/metadata-hero", }) } writeJSON(w, http.StatusOK, s.metadataHeroSettingsResponse()) }