Release Memby 0.2.64

This commit is contained in:
ponzischeme89
2026-08-14 13:32:14 +12:00
parent ad0c74f880
commit 05d95f51cd
33 changed files with 1142 additions and 112 deletions
+98 -25
View File
@@ -17,9 +17,16 @@ type heroAdminItem struct {
}
type heroAdminPolicy struct {
PinnedItems []heroAdminItem `json:"pinnedItems"`
PrimeSubtitle string `json:"primeSubtitle"`
Schedules []store.HeroSchedule `json:"schedules"`
PinnedItems []heroAdminItem `json:"pinnedItems"`
Items []heroAdminItem `json:"items"`
PrimeSubtitle string `json:"primeSubtitle"`
Placements map[string]heroAdminPlacement `json:"placements"`
Schedules []store.HeroSchedule `json:"schedules"`
}
type heroAdminPlacement struct {
PinnedItems []heroAdminItem `json:"pinnedItems"`
PrimeSubtitle string `json:"primeSubtitle"`
}
func adminHeroItem(raw json.RawMessage) (heroAdminItem, bool) {
@@ -35,12 +42,20 @@ func (s *Server) heroAdminPolicy(ctx context.Context) heroAdminPolicy {
policy, err := s.store.HeroPolicy(ctx)
if err != nil {
s.loggerFor(ctx).Warn("hero policy unavailable to admin", "error", err)
return heroAdminPolicy{PinnedItems: []heroAdminItem{}}
return heroAdminPolicy{PinnedItems: []heroAdminItem{}, Placements: map[string]heroAdminPlacement{}}
}
items, err := s.store.LibraryItemsByID(ctx, policy.PinnedItemIDs)
allIDs := []string{}
for _, placement := range policy.Placements {
allIDs = append(allIDs, placement.PinnedItemIDs...)
}
for _, schedule := range policy.Schedules {
allIDs = append(allIDs, schedule.ItemID)
}
allIDs = uniqueHeroIDs(allIDs)
items, err := s.store.LibraryItemsByID(ctx, allIDs)
if err != nil {
s.loggerFor(ctx).Warn("pinned hero titles unavailable to admin", "error", err)
return heroAdminPolicy{PinnedItems: []heroAdminItem{}}
return heroAdminPolicy{PinnedItems: []heroAdminItem{}, Placements: map[string]heroAdminPlacement{}}
}
byID := make(map[string]heroAdminItem, len(items))
for _, raw := range items {
@@ -49,15 +64,26 @@ func (s *Server) heroAdminPolicy(ctx context.Context) heroAdminPolicy {
}
}
out := heroAdminPolicy{
PinnedItems: make([]heroAdminItem, 0, len(policy.PinnedItemIDs)),
PrimeSubtitle: policy.PrimeSubtitle,
Schedules: policy.Schedules,
Placements: make(map[string]heroAdminPlacement, len(policy.Placements)),
Items: []heroAdminItem{},
Schedules: policy.Schedules,
}
for _, id := range policy.PinnedItemIDs {
for _, id := range allIDs {
if item, ok := byID[id]; ok {
out.PinnedItems = append(out.PinnedItems, item)
out.Items = append(out.Items, item)
}
}
for name, placement := range policy.Placements {
adminPlacement := heroAdminPlacement{PinnedItems: []heroAdminItem{}, PrimeSubtitle: placement.PrimeSubtitle}
for _, id := range placement.PinnedItemIDs {
if item, ok := byID[id]; ok {
adminPlacement.PinnedItems = append(adminPlacement.PinnedItems, item)
}
}
out.Placements[name] = adminPlacement
}
home := out.Placements[store.HeroPlacementHome]
out.PinnedItems, out.PrimeSubtitle = home.PinnedItems, home.PrimeSubtitle
return out
}
@@ -79,38 +105,85 @@ func (s *Server) handleAdminHeroSearch(w http.ResponseWriter, r *http.Request) {
func (s *Server) handleAdminHeroPolicy(w http.ResponseWriter, r *http.Request) {
var request struct {
PinnedItemIDs []string `json:"pinnedItemIds"`
PrimeSubtitle string `json:"primeSubtitle"`
Schedules []store.HeroSchedule `json:"schedules"`
PinnedItemIDs []string `json:"pinnedItemIds"`
PrimeSubtitle string `json:"primeSubtitle"`
Placements map[string]struct {
PinnedItemIDs []string `json:"pinnedItemIds"`
PrimeSubtitle string `json:"primeSubtitle"`
} `json:"placements"`
Schedules []store.HeroSchedule `json:"schedules"`
}
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
writeError(w, http.StatusBadRequest, "invalid hero policy")
return
}
ids := uniqueHeroIDs(request.PinnedItemIDs)
if len(ids) > 4 {
writeError(w, http.StatusBadRequest, "the hero can pin at most four titles")
return
if request.Placements == nil {
request.Placements = map[string]struct {
PinnedItemIDs []string `json:"pinnedItemIds"`
PrimeSubtitle string `json:"primeSubtitle"`
}{
store.HeroPlacementHome: {PinnedItemIDs: request.PinnedItemIDs, PrimeSubtitle: request.PrimeSubtitle},
}
}
items, err := s.store.LibraryItemsByID(r.Context(), ids)
allIDs := []string{}
placementPolicies := map[string]store.HeroPlacementPolicy{}
for name, placement := range request.Placements {
if !store.ValidHeroPlacement(name) {
writeError(w, http.StatusBadRequest, "unknown hero placement")
return
}
ids := uniqueHeroIDs(placement.PinnedItemIDs)
if len(ids) > 4 {
writeError(w, http.StatusBadRequest, "each hero can pin at most four titles")
return
}
placementPolicies[name] = store.HeroPlacementPolicy{PinnedItemIDs: ids, PrimeSubtitle: placement.PrimeSubtitle}
allIDs = append(allIDs, ids...)
}
for _, schedule := range request.Schedules {
allIDs = append(allIDs, schedule.ItemID)
}
items, err := s.store.LibraryItemsByID(r.Context(), uniqueHeroIDs(allIDs))
if err != nil {
s.loggerFor(r.Context()).Error("hero title validation failed", "error", err)
writeError(w, http.StatusInternalServerError, "could not validate hero titles")
return
}
valid := map[string]bool{}
valid := map[string]heroAdminItem{}
for _, raw := range items {
if item, ok := adminHeroItem(raw); ok {
valid[item.ID] = true
valid[item.ID] = item
}
}
for _, id := range ids {
if !valid[id] {
writeError(w, http.StatusBadRequest, "every pinned item must be a playable library film or series")
for name, placement := range placementPolicies {
for _, id := range placement.PinnedItemIDs {
item, ok := valid[id]
if !ok || (name == store.HeroPlacementMovies && !strings.EqualFold(item.Type, "Movie")) || (name == store.HeroPlacementTVShows && !strings.EqualFold(item.Type, "Series")) {
writeError(w, http.StatusBadRequest, "every pinned item must match its hero placement")
return
}
}
}
for _, schedule := range request.Schedules {
item, ok := valid[strings.TrimSpace(schedule.ItemID)]
if !ok {
writeError(w, http.StatusBadRequest, "every scheduled hero must be a playable library film or series")
return
}
placements := schedule.Placements
if len(placements) == 0 {
placements = []string{store.HeroPlacementHome}
}
for _, name := range placements {
if !store.ValidHeroPlacement(name) ||
(name == store.HeroPlacementMovies && !strings.EqualFold(item.Type, "Movie")) ||
(name == store.HeroPlacementTVShows && !strings.EqualFold(item.Type, "Series")) {
writeError(w, http.StatusBadRequest, "every scheduled item must match its hero placement")
return
}
}
}
policy := store.HeroPolicy{PinnedItemIDs: ids, PrimeSubtitle: request.PrimeSubtitle, Schedules: request.Schedules}
policy := store.HeroPolicy{Placements: placementPolicies, Schedules: request.Schedules}
if err := s.store.SetHeroPolicy(r.Context(), policy); err != nil {
s.loggerFor(r.Context()).Error("hero policy write failed", "error", err)
writeError(w, http.StatusInternalServerError, "could not save hero policy")