Files
silo-server/internal/usercollections/types.go
T

104 lines
3.2 KiB
Go

// Package usercollections owns the import + sync logic for personal,
// profile-scoped collections (the user-facing analogue of the admin
// library_collections subsystem).
//
// User collections live in user_personal_collections and use external sources
// (TMDB / Trakt / MDBList) the same way admin collections do, but resolved
// against the entire catalog. Per-user library access is enforced at read
// time by the catalog resolver, so the sync service does not have to scope
// item resolution to a particular set of libraries.
package usercollections
import (
"encoding/json"
"errors"
"fmt"
"strings"
)
// NormalizeMDBListURL accepts either a list page URL
// (https://mdblist.com/lists/user/slug) or its JSON variant and returns the
// canonical JSON URL. Trailing slashes are tolerated. Empty input is
// returned unchanged so callers can keep their own validation.
func NormalizeMDBListURL(url string) string {
url = strings.TrimSpace(url)
if url == "" {
return ""
}
url = strings.TrimRight(url, "/")
if !strings.HasSuffix(url, "/json") {
url += "/json"
}
return url
}
type SourceMode string
const (
SourceModeMDBList SourceMode = "mdblist_json"
SourceModeTMDBPreset SourceMode = "tmdb_preset"
SourceModeTraktPreset SourceMode = "trakt_preset"
)
// MinSyncIntervalHours is the smallest interval (in hours between fires) that
// a user is allowed to schedule. Stricter than admin to keep TMDB/Trakt API
// quota bounded across many users.
const MinSyncIntervalHours = 24
var ErrSyncUnsupported = errors.New("collection cannot be synced")
type SourceConfig struct {
Mode SourceMode `json:"mode"`
URL string `json:"url,omitempty"`
Preset string `json:"preset,omitempty"`
Provider string `json:"provider,omitempty"`
MediaType string `json:"media_type,omitempty"`
TimeWindow string `json:"time_window,omitempty"`
ProfileID string `json:"profile_id,omitempty"`
Limit *int `json:"limit,omitempty"`
// LibraryIDs narrows sync resolution to these libraries. Empty/nil means
// resolve against every library the requesting user can access.
LibraryIDs []int `json:"library_ids,omitempty"`
}
func MarshalSourceConfig(cfg SourceConfig) (string, error) {
raw, err := json.Marshal(cfg)
if err != nil {
return "", err
}
return string(raw), nil
}
func ParseSourceConfig(raw string) (SourceConfig, error) {
var cfg SourceConfig
if raw == "" {
return cfg, nil
}
if err := json.Unmarshal([]byte(raw), &cfg); err != nil {
return cfg, err
}
return cfg, nil
}
// DisplayURL produces a stable, human-friendly identifier for the source —
// stored on the row so list views can render it without reparsing
// SourceConfig. Mirrors the admin tmdb://… / trakt://… scheme.
func (c SourceConfig) DisplayURL() string {
switch c.Mode {
case SourceModeMDBList:
return c.URL
case SourceModeTMDBPreset:
if c.Preset == "trending" {
return fmt.Sprintf("tmdb://%s/%s/%s", c.Preset, c.MediaType, c.TimeWindow)
}
return fmt.Sprintf("tmdb://%s/%s", c.Preset, c.MediaType)
case SourceModeTraktPreset:
if c.Preset == "recommended" {
return fmt.Sprintf("trakt://%s/%s/%s", c.Preset, c.MediaType, c.ProfileID)
}
return fmt.Sprintf("trakt://%s/%s", c.Preset, c.MediaType)
default:
return ""
}
}