104 lines
3.0 KiB
Go
104 lines
3.0 KiB
Go
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
|
|
}
|