2020-07-29 21:11:28 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"io/ioutil"
|
2020-11-02 23:20:06 +00:00
|
|
|
"log"
|
2021-01-11 19:17:43 +00:00
|
|
|
"path/filepath"
|
2020-09-22 23:01:07 +00:00
|
|
|
"strconv"
|
2020-11-29 18:01:10 +00:00
|
|
|
"strings"
|
2020-07-29 21:11:28 +00:00
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
type Storage struct {
|
2020-09-20 10:21:04 +00:00
|
|
|
timePattern string
|
|
|
|
invite_path, emails_path, policy_path, configuration_path, displayprefs_path, ombi_path, profiles_path string
|
|
|
|
invites Invites
|
|
|
|
profiles map[string]Profile
|
2020-09-23 17:48:00 +00:00
|
|
|
defaultProfile string
|
2020-09-20 10:21:04 +00:00
|
|
|
emails, policy, configuration, displayprefs, ombi_template map[string]interface{}
|
2020-10-30 22:51:47 +00:00
|
|
|
lang Lang
|
|
|
|
}
|
|
|
|
|
2021-01-14 17:51:12 +00:00
|
|
|
type EmailLang map[string]map[string]map[string]interface{} // Map of lang codes to email name to fields
|
|
|
|
|
|
|
|
func (el *EmailLang) format(lang, email, field string, vals ...string) string {
|
|
|
|
text := (*el)[lang][email][field].(string)
|
|
|
|
for _, val := range vals {
|
|
|
|
text = strings.Replace(text, "{n}", val, 1)
|
|
|
|
}
|
|
|
|
return text
|
|
|
|
}
|
|
|
|
func (el *EmailLang) get(lang, email, field string) string { return (*el)[lang][email][field].(string) }
|
|
|
|
|
2020-10-30 22:51:47 +00:00
|
|
|
type Lang struct {
|
2021-01-12 00:11:40 +00:00
|
|
|
chosenFormLang string
|
|
|
|
chosenAdminLang string
|
2021-01-14 17:51:12 +00:00
|
|
|
chosenEmailLang string
|
2021-01-12 00:11:40 +00:00
|
|
|
AdminPath string
|
|
|
|
Admin map[string]map[string]interface{}
|
2021-01-12 23:15:12 +00:00
|
|
|
AdminJSON map[string]string
|
2021-01-12 00:11:40 +00:00
|
|
|
FormPath string
|
|
|
|
Form map[string]map[string]interface{}
|
2021-01-14 17:51:12 +00:00
|
|
|
EmailPath string
|
|
|
|
Email EmailLang
|
2020-07-29 21:11:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// timePattern: %Y-%m-%dT%H:%M:%S.%f
|
|
|
|
|
2020-09-20 10:21:04 +00:00
|
|
|
type Profile struct {
|
2020-09-22 23:01:07 +00:00
|
|
|
Admin bool `json:"admin,omitempty"`
|
|
|
|
LibraryAccess string `json:"libraries,omitempty"`
|
|
|
|
FromUser string `json:"fromUser,omitempty"`
|
|
|
|
Policy map[string]interface{} `json:"policy,omitempty"`
|
|
|
|
Configuration map[string]interface{} `json:"configuration,omitempty"`
|
|
|
|
Displayprefs map[string]interface{} `json:"displayprefs,omitempty"`
|
2020-09-23 17:48:00 +00:00
|
|
|
Default bool `json:"default,omitempty"`
|
2020-09-20 10:21:04 +00:00
|
|
|
}
|
|
|
|
|
2020-07-29 21:11:28 +00:00
|
|
|
type Invite struct {
|
2020-07-31 11:48:37 +00:00
|
|
|
Created time.Time `json:"created"`
|
2020-07-29 21:11:28 +00:00
|
|
|
NoLimit bool `json:"no-limit"`
|
|
|
|
RemainingUses int `json:"remaining-uses"`
|
|
|
|
ValidTill time.Time `json:"valid_till"`
|
|
|
|
Email string `json:"email"`
|
|
|
|
UsedBy [][]string `json:"used-by"`
|
|
|
|
Notify map[string]map[string]bool `json:"notify"`
|
2020-09-20 10:21:04 +00:00
|
|
|
Profile string `json:"profile"`
|
2020-07-29 21:11:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type Invites map[string]Invite
|
|
|
|
|
|
|
|
func (st *Storage) loadInvites() error {
|
|
|
|
return loadJSON(st.invite_path, &st.invites)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (st *Storage) storeInvites() error {
|
|
|
|
return storeJSON(st.invite_path, st.invites)
|
|
|
|
}
|
|
|
|
|
2020-10-30 22:51:47 +00:00
|
|
|
func (st *Storage) loadLang() error {
|
2021-01-12 23:15:12 +00:00
|
|
|
loadData := func(path string, stringJson bool) (map[string]string, map[string]map[string]interface{}, error) {
|
2021-01-12 00:11:40 +00:00
|
|
|
files, err := ioutil.ReadDir(path)
|
2021-01-12 23:15:12 +00:00
|
|
|
outString := map[string]string{}
|
2021-01-12 00:11:40 +00:00
|
|
|
out := map[string]map[string]interface{}{}
|
|
|
|
if err != nil {
|
2021-01-12 23:15:12 +00:00
|
|
|
return nil, nil, err
|
2021-01-12 00:11:40 +00:00
|
|
|
}
|
|
|
|
for _, f := range files {
|
|
|
|
index := strings.TrimSuffix(f.Name(), filepath.Ext(f.Name()))
|
|
|
|
var data map[string]interface{}
|
2021-01-12 23:15:12 +00:00
|
|
|
var file []byte
|
|
|
|
var err error
|
|
|
|
file, err = ioutil.ReadFile(filepath.Join(path, f.Name()))
|
|
|
|
if err != nil {
|
|
|
|
file = []byte("{}")
|
|
|
|
}
|
2021-01-14 17:51:12 +00:00
|
|
|
// Replace Jellyfin with something if necessary
|
2021-01-12 00:11:40 +00:00
|
|
|
if substituteStrings != "" {
|
2021-01-12 23:15:12 +00:00
|
|
|
fileString := strings.ReplaceAll(string(file), "Jellyfin", substituteStrings)
|
|
|
|
file = []byte(fileString)
|
|
|
|
}
|
|
|
|
err = json.Unmarshal(file, &data)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("ERROR: Failed to read \"%s\": %s", path, err)
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
if stringJson {
|
|
|
|
stringJSON, err := json.Marshal(data)
|
2021-01-12 00:11:40 +00:00
|
|
|
if err != nil {
|
2021-01-12 23:15:12 +00:00
|
|
|
return nil, nil, err
|
2021-01-12 00:11:40 +00:00
|
|
|
}
|
2021-01-12 23:15:12 +00:00
|
|
|
outString[index] = string(stringJSON)
|
2021-01-12 00:11:40 +00:00
|
|
|
}
|
|
|
|
out[index] = data
|
2021-01-12 23:15:12 +00:00
|
|
|
|
2021-01-12 00:11:40 +00:00
|
|
|
}
|
2021-01-12 23:15:12 +00:00
|
|
|
return outString, out, nil
|
2021-01-12 00:11:40 +00:00
|
|
|
}
|
2021-01-12 23:15:12 +00:00
|
|
|
_, form, err := loadData(st.lang.FormPath, false)
|
2020-10-30 22:51:47 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-01-12 00:11:40 +00:00
|
|
|
for index, lang := range form {
|
|
|
|
strings := lang["strings"].(map[string]interface{})
|
2021-01-11 19:17:43 +00:00
|
|
|
validationStrings := strings["validationStrings"].(map[string]interface{})
|
|
|
|
vS, err := json.Marshal(validationStrings)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
strings["validationStrings"] = string(vS)
|
2021-01-12 00:11:40 +00:00
|
|
|
lang["strings"] = strings
|
|
|
|
form[index] = lang
|
2020-10-30 22:51:47 +00:00
|
|
|
}
|
2021-01-12 00:11:40 +00:00
|
|
|
st.lang.Form = form
|
2021-01-12 23:15:12 +00:00
|
|
|
adminJSON, admin, err := loadData(st.lang.AdminPath, true)
|
2021-01-12 00:11:40 +00:00
|
|
|
st.lang.Admin = admin
|
2021-01-12 23:15:12 +00:00
|
|
|
st.lang.AdminJSON = adminJSON
|
2021-01-14 17:51:12 +00:00
|
|
|
|
|
|
|
_, emails, err := loadData(st.lang.EmailPath, false)
|
|
|
|
fixedEmails := map[string]map[string]map[string]interface{}{}
|
|
|
|
for lang, e := range emails {
|
|
|
|
f := map[string]map[string]interface{}{}
|
|
|
|
for field, vals := range e {
|
|
|
|
f[field] = vals.(map[string]interface{})
|
|
|
|
}
|
|
|
|
fixedEmails[lang] = f
|
|
|
|
}
|
|
|
|
st.lang.Email = fixedEmails
|
2021-01-12 00:11:40 +00:00
|
|
|
return err
|
2020-10-30 22:51:47 +00:00
|
|
|
}
|
|
|
|
|
2020-07-29 21:11:28 +00:00
|
|
|
func (st *Storage) loadEmails() error {
|
|
|
|
return loadJSON(st.emails_path, &st.emails)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (st *Storage) storeEmails() error {
|
|
|
|
return storeJSON(st.emails_path, st.emails)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (st *Storage) loadPolicy() error {
|
|
|
|
return loadJSON(st.policy_path, &st.policy)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (st *Storage) storePolicy() error {
|
|
|
|
return storeJSON(st.policy_path, st.policy)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (st *Storage) loadConfiguration() error {
|
|
|
|
return loadJSON(st.configuration_path, &st.configuration)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (st *Storage) storeConfiguration() error {
|
|
|
|
return storeJSON(st.configuration_path, st.configuration)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (st *Storage) loadDisplayprefs() error {
|
|
|
|
return loadJSON(st.displayprefs_path, &st.displayprefs)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (st *Storage) storeDisplayprefs() error {
|
|
|
|
return storeJSON(st.displayprefs_path, st.displayprefs)
|
|
|
|
}
|
|
|
|
|
2020-09-05 16:32:49 +00:00
|
|
|
func (st *Storage) loadOmbiTemplate() error {
|
|
|
|
return loadJSON(st.ombi_path, &st.ombi_template)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (st *Storage) storeOmbiTemplate() error {
|
|
|
|
return storeJSON(st.ombi_path, st.ombi_template)
|
|
|
|
}
|
|
|
|
|
2020-09-20 10:21:04 +00:00
|
|
|
func (st *Storage) loadProfiles() error {
|
2020-09-22 23:01:07 +00:00
|
|
|
err := loadJSON(st.profiles_path, &st.profiles)
|
|
|
|
for name, profile := range st.profiles {
|
2020-09-23 17:48:00 +00:00
|
|
|
if profile.Default {
|
|
|
|
st.defaultProfile = name
|
|
|
|
}
|
2020-09-22 23:01:07 +00:00
|
|
|
change := false
|
|
|
|
if profile.Policy["IsAdministrator"] != nil {
|
|
|
|
profile.Admin = profile.Policy["IsAdministrator"].(bool)
|
|
|
|
change = true
|
|
|
|
}
|
|
|
|
if profile.Policy["EnabledFolders"] != nil {
|
|
|
|
length := len(profile.Policy["EnabledFolders"].([]interface{}))
|
|
|
|
if length == 0 {
|
|
|
|
profile.LibraryAccess = "All"
|
|
|
|
} else {
|
|
|
|
profile.LibraryAccess = strconv.Itoa(length)
|
|
|
|
}
|
|
|
|
change = true
|
|
|
|
}
|
|
|
|
if profile.FromUser == "" {
|
|
|
|
profile.FromUser = "Unknown"
|
|
|
|
change = true
|
|
|
|
}
|
|
|
|
if change {
|
|
|
|
st.profiles[name] = profile
|
|
|
|
}
|
|
|
|
}
|
2020-09-23 17:48:00 +00:00
|
|
|
if st.defaultProfile == "" {
|
|
|
|
for n := range st.profiles {
|
|
|
|
st.defaultProfile = n
|
|
|
|
}
|
2020-09-22 23:01:07 +00:00
|
|
|
}
|
|
|
|
return err
|
2020-09-20 10:21:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (st *Storage) storeProfiles() error {
|
|
|
|
return storeJSON(st.profiles_path, st.profiles)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (st *Storage) migrateToProfile() error {
|
|
|
|
st.loadPolicy()
|
|
|
|
st.loadConfiguration()
|
|
|
|
st.loadDisplayprefs()
|
|
|
|
st.loadProfiles()
|
|
|
|
st.profiles["Default"] = Profile{
|
|
|
|
Policy: st.policy,
|
|
|
|
Configuration: st.configuration,
|
|
|
|
Displayprefs: st.displayprefs,
|
|
|
|
}
|
|
|
|
return st.storeProfiles()
|
|
|
|
}
|
|
|
|
|
2020-07-29 21:11:28 +00:00
|
|
|
func loadJSON(path string, obj interface{}) error {
|
2020-08-01 20:20:02 +00:00
|
|
|
var file []byte
|
|
|
|
var err error
|
|
|
|
file, err = ioutil.ReadFile(path)
|
2020-07-29 21:11:28 +00:00
|
|
|
if err != nil {
|
2020-08-01 20:20:02 +00:00
|
|
|
file = []byte("{}")
|
2020-07-29 21:11:28 +00:00
|
|
|
}
|
|
|
|
err = json.Unmarshal(file, &obj)
|
2020-11-02 23:20:06 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Printf("ERROR: Failed to read \"%s\": %s", path, err)
|
|
|
|
}
|
2020-07-29 21:11:28 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func storeJSON(path string, obj interface{}) error {
|
|
|
|
data, err := json.Marshal(obj)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
err = ioutil.WriteFile(path, data, 0644)
|
2020-11-02 23:20:06 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Printf("ERROR: Failed to write to \"%s\": %s", path, err)
|
|
|
|
}
|
2020-07-29 21:11:28 +00:00
|
|
|
return err
|
|
|
|
}
|
2020-11-29 18:01:10 +00:00
|
|
|
|
2020-12-18 15:44:19 +00:00
|
|
|
// One build of JF 10.7.0 hyphenated user IDs while another one later didn't. These functions will hyphenate/de-hyphenate email storage.
|
|
|
|
|
|
|
|
func hyphenate(userID string) string {
|
|
|
|
if userID[8] == '-' {
|
|
|
|
return userID
|
|
|
|
}
|
|
|
|
return userID[:8] + "-" + userID[8:12] + "-" + userID[12:16] + "-" + userID[16:20] + "-" + userID[20:]
|
|
|
|
}
|
|
|
|
|
|
|
|
func (app *appContext) deHyphenateEmailStorage(old map[string]interface{}) (map[string]interface{}, int, error) {
|
|
|
|
jfUsers, status, err := app.jf.GetUsers(false)
|
|
|
|
if status != 200 || err != nil {
|
|
|
|
return nil, status, err
|
|
|
|
}
|
|
|
|
newEmails := map[string]interface{}{}
|
|
|
|
for _, user := range jfUsers {
|
|
|
|
unHyphenated := user["Id"].(string)
|
|
|
|
hyphenated := hyphenate(unHyphenated)
|
|
|
|
email, ok := old[hyphenated]
|
|
|
|
if ok {
|
|
|
|
newEmails[unHyphenated] = email
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return newEmails, status, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (app *appContext) hyphenateEmailStorage(old map[string]interface{}) (map[string]interface{}, int, error) {
|
2020-11-29 18:01:10 +00:00
|
|
|
jfUsers, status, err := app.jf.GetUsers(false)
|
|
|
|
if status != 200 || err != nil {
|
|
|
|
return nil, status, err
|
|
|
|
}
|
|
|
|
newEmails := map[string]interface{}{}
|
|
|
|
for _, user := range jfUsers {
|
|
|
|
unstripped := user["Id"].(string)
|
|
|
|
stripped := strings.ReplaceAll(unstripped, "-", "")
|
|
|
|
email, ok := old[stripped]
|
|
|
|
if ok {
|
|
|
|
newEmails[unstripped] = email
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return newEmails, status, err
|
|
|
|
}
|