Add optional MDBList movie ratings

This commit is contained in:
ponzischeme89
2026-08-03 08:55:52 +12:00
parent b6b2a9c25a
commit 666da9c5d3
18 changed files with 840 additions and 4 deletions
+88
View File
@@ -5,6 +5,7 @@ import (
"encoding/json"
"errors"
"fmt"
"strings"
"time"
"github.com/jackc/pgx/v5"
@@ -21,6 +22,93 @@ const RequestPolicyKey = "request_policy"
// shipping a new TV build.
const PlaybackPolicyKey = "playback_policy"
// MDBListSettingsKey stores the optional movie-ratings integration. The API key stays
// in this server-owned document and is never included in client or admin status payloads.
const MDBListSettingsKey = "mdblist_settings"
var defaultMDBListSources = []string{
"imdb", "tomatoes", "audience", "metacritic", "letterboxd", "rogerebert",
"tmdb", "trakt", "mal", "score", "score_average",
}
type MDBListSettings struct {
Enabled bool `json:"enabled"`
APIKey string `json:"apiKey"`
Sources []string `json:"sources"`
UpdatedAt time.Time `json:"updatedAt"`
}
func DefaultMDBListSettings() MDBListSettings {
return MDBListSettings{Sources: append([]string(nil), defaultMDBListSources...)}
}
func MDBListSources() []string {
return append([]string(nil), defaultMDBListSources...)
}
func ValidMDBListSource(source string) bool {
source = strings.ToLower(strings.TrimSpace(source))
for _, supported := range defaultMDBListSources {
if source == supported {
return true
}
}
return false
}
func normalizeMDBListSettings(settings MDBListSettings) MDBListSettings {
settings.APIKey = strings.TrimSpace(settings.APIKey)
seen := map[string]bool{}
sources := make([]string, 0, len(settings.Sources))
for _, source := range settings.Sources {
source = strings.ToLower(strings.TrimSpace(source))
if !ValidMDBListSource(source) || seen[source] {
continue
}
seen[source] = true
sources = append(sources, source)
}
if len(sources) == 0 {
sources = append(sources, defaultMDBListSources...)
}
settings.Sources = sources
return settings
}
func (s *Store) MDBListSettings(ctx context.Context) (MDBListSettings, error) {
var raw []byte
err := s.pool.QueryRow(ctx, `SELECT value FROM app_settings WHERE key = $1`, MDBListSettingsKey).Scan(&raw)
if errors.Is(err, pgx.ErrNoRows) {
return DefaultMDBListSettings(), nil
}
if err != nil {
return DefaultMDBListSettings(), fmt.Errorf("store: read MDBList settings: %w", err)
}
var settings MDBListSettings
if err := json.Unmarshal(raw, &settings); err != nil {
return DefaultMDBListSettings(), fmt.Errorf("store: decode MDBList settings: %w", err)
}
return normalizeMDBListSettings(settings), nil
}
func (s *Store) SetMDBListSettings(ctx context.Context, settings MDBListSettings) error {
settings = normalizeMDBListSettings(settings)
settings.UpdatedAt = time.Now().UTC()
raw, err := json.Marshal(settings)
if err != nil {
return 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()`,
MDBListSettingsKey, string(raw))
if err != nil {
return fmt.Errorf("store: write MDBList settings: %w", err)
}
return nil
}
// FeaturePolicyKey is the durable operator control plane for optional behaviour.
// The catalogue of valid flags lives in the API; the store only persists overrides so
// removing or renaming a feature does not strand an unreadable database row.