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