Files
memby/server/internal/store/metadata_hero.go
T

104 lines
3.0 KiB
Go
Raw Normal View History

2026-08-22 08:26:10 +12:00
package store
import (
"context"
"encoding/json"
"errors"
"fmt"
"time"
"github.com/jackc/pgx/v5"
)
// MetadataHeroSettingsKey is the app_settings row backing the household-wide metadata
// hero composition.
const MetadataHeroSettingsKey = "metadata_hero_settings"
const (
MetadataHeroTitle = "title"
MetadataHeroRatings = "ratings"
MetadataHeroFacts = "facts"
MetadataHeroSummary = "summary"
MetadataHeroReason = "recommendation_reason"
MetadataHeroTime = "time_remaining"
)
var DefaultMetadataHeroContentOrder = []string{
MetadataHeroTitle,
MetadataHeroRatings,
MetadataHeroFacts,
MetadataHeroSummary,
}
var MetadataHeroContentOptions = []string{
MetadataHeroTitle,
MetadataHeroRatings,
MetadataHeroFacts,
MetadataHeroSummary,
MetadataHeroReason,
MetadataHeroTime,
}
// MetadataHeroSettings is one presentation decision for the whole household. It lives
// outside user preferences because the hero is part of the product's shared composition,
// not something that should change when the viewer changes.
type MetadataHeroSettings struct {
ContentOrder []string `json:"contentOrder"`
UpdatedAt time.Time `json:"updatedAt"`
UpdatedBy string `json:"updatedBy,omitempty"`
}
func NormalizeMetadataHeroSettings(settings MetadataHeroSettings) MetadataHeroSettings {
seen := map[string]bool{}
order := make([]string, 0, len(MetadataHeroContentOptions))
for _, entry := range settings.ContentOrder {
if seen[entry] || !containsString(MetadataHeroContentOptions, entry) {
continue
}
seen[entry] = true
order = append(order, entry)
}
if len(order) == 0 {
order = append([]string(nil), DefaultMetadataHeroContentOrder...)
}
settings.ContentOrder = order
return settings
}
func (s *Store) MetadataHeroSettings(ctx context.Context) (MetadataHeroSettings, error) {
var raw []byte
err := s.pool.QueryRow(ctx,
`SELECT value FROM app_settings WHERE key = $1`, MetadataHeroSettingsKey).Scan(&raw)
if errors.Is(err, pgx.ErrNoRows) {
return NormalizeMetadataHeroSettings(MetadataHeroSettings{}), nil
}
if err != nil {
return MetadataHeroSettings{}, fmt.Errorf("store: read metadata hero settings: %w", err)
}
var settings MetadataHeroSettings
if err := json.Unmarshal(raw, &settings); err != nil {
return MetadataHeroSettings{}, fmt.Errorf("store: decode metadata hero settings: %w", err)
}
return NormalizeMetadataHeroSettings(settings), nil
}
func (s *Store) SetMetadataHeroSettings(
ctx context.Context, settings MetadataHeroSettings,
) (MetadataHeroSettings, error) {
settings = NormalizeMetadataHeroSettings(settings)
settings.UpdatedAt = time.Now().UTC()
raw, err := json.Marshal(settings)
if err != nil {
return MetadataHeroSettings{}, err
}
_, err = s.pool.Exec(ctx, `
INSERT INTO app_settings (key, value, updated_at)
VALUES ($1, $2::jsonb, now())
ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = now()`,
MetadataHeroSettingsKey, string(raw))
if err != nil {
return MetadataHeroSettings{}, fmt.Errorf("store: write metadata hero settings: %w", err)
}
return settings, nil
}