// backend/internal/usermap.go package internal import ( "fmt" "os" "path/filepath" "strings" "gopkg.in/yaml.v3" ) // StringOrList allows both a single string ("Stein_90s") // and a list of strings (["Stein_90s","Home Videos"]) in YAML. type StringOrList []string func (s *StringOrList) UnmarshalYAML(value *yaml.Node) error { switch value.Kind { case yaml.ScalarNode: var str string if err := value.Decode(&str); err != nil { return err } *s = StringOrList{str} return nil case yaml.SequenceNode: var list []string if err := value.Decode(&list); err != nil { return err } *s = StringOrList(list) return nil default: return fmt.Errorf("expected string or list, got kind=%v", value.Kind) } } type UserMap struct { // Maps Jellyfin username -> one or more relative media subdirs Map map[string]StringOrList `yaml:"map"` } func LoadUserMap(path string) (*UserMap, error) { b, err := os.ReadFile(path) if err != nil { return nil, err } var m UserMap if err := yaml.Unmarshal(b, &m); err != nil { return nil, err } return &m, nil } // Resolve returns the first configured library for the user (back-compat). func (m *UserMap) Resolve(username string) (string, error) { roots, err := m.ResolveAll(username) if err != nil { return "", err } return roots[0], nil } // ResolveAll returns all configured libraries for the user. func (m *UserMap) ResolveAll(username string) ([]string, error) { v, ok := m.Map[username] if !ok || len(v) == 0 { return nil, fmt.Errorf("no mapping for user %q", username) } out := make([]string, 0, len(v)) for _, dir := range v { dir = strings.TrimSpace(dir) if dir == "" { continue } clean := filepath.Clean(dir) if clean == "." || clean == "/" { continue } out = append(out, clean) } if len(out) == 0 { return nil, fmt.Errorf("no valid mapping for user %q", username) } return out, nil }