diff --git a/api-invites.go b/api-invites.go index 268fdac..4d94a0d 100644 --- a/api-invites.go +++ b/api-invites.go @@ -10,21 +10,20 @@ import ( "github.com/gin-gonic/gin" "github.com/itchyny/timefmt-go" "github.com/lithammer/shortuuid/v3" + "github.com/timshannon/badgerhold/v4" ) func (app *appContext) checkInvites() { currentTime := time.Now() - app.storage.loadInvites() - changed := false - for code, data := range app.storage.GetInvites() { + for _, data := range app.storage.GetInvites() { expiry := data.ValidTill if !currentTime.After(expiry) { continue } - app.debug.Printf("Housekeeping: Deleting old invite %s", code) + app.debug.Printf("Housekeeping: Deleting old invite %s", data.Code) notify := data.Notify if emailEnabled && app.config.Section("notifications").Key("enabled").MustBool(false) && len(notify) != 0 { - app.debug.Printf("%s: Expiry notification", code) + app.debug.Printf("%s: Expiry notification", data.Code) var wait sync.WaitGroup for address, settings := range notify { if !settings["notify-expiry"] { @@ -33,9 +32,9 @@ func (app *appContext) checkInvites() { wait.Add(1) go func(addr string) { defer wait.Done() - msg, err := app.email.constructExpiry(code, data, app, false) + msg, err := app.email.constructExpiry(data.Code, data, app, false) if err != nil { - app.err.Printf("%s: Failed to construct expiry notification: %v", code, err) + app.err.Printf("%s: Failed to construct expiry notification: %v", data.Code, err) } else { // Check whether notify "address" is an email address of Jellyfin ID if strings.Contains(addr, "@") { @@ -44,7 +43,7 @@ func (app *appContext) checkInvites() { err = app.sendByID(msg, addr) } if err != nil { - app.err.Printf("%s: Failed to send expiry notification: %v", code, err) + app.err.Printf("%s: Failed to send expiry notification: %v", data.Code, err) } else { app.info.Printf("Sent expiry notification to %s", addr) } @@ -53,18 +52,12 @@ func (app *appContext) checkInvites() { } wait.Wait() } - changed = true - app.storage.DeleteInvitesKey(code) - } - if changed { - app.storage.storeInvites() + app.storage.DeleteInvitesKey(data.Code) } } func (app *appContext) checkInvite(code string, used bool, username string) bool { currentTime := time.Now() - app.storage.loadInvites() - changed := false inv, match := app.storage.GetInvitesKey(code) if !match { return false @@ -103,11 +96,9 @@ func (app *appContext) checkInvite(code string, used bool, username string) bool } wait.Wait() } - changed = true match = false app.storage.DeleteInvitesKey(code) } else if used { - changed = true del := false newInv := inv if newInv.RemainingUses == 1 { @@ -122,9 +113,6 @@ func (app *appContext) checkInvite(code string, used bool, username string) bool app.storage.SetInvitesKey(code, newInv) } } - if changed { - app.storage.storeInvites() - } return match } @@ -138,7 +126,6 @@ func (app *appContext) checkInvite(code string, used bool, username string) bool func (app *appContext) GenerateInvite(gc *gin.Context) { var req generateInviteDTO app.debug.Println("Generating new invite") - app.storage.loadInvites() gc.BindJSON(&req) currentTime := time.Now() validTill := currentTime.AddDate(0, req.Months, req.Days) @@ -213,14 +200,13 @@ func (app *appContext) GenerateInvite(gc *gin.Context) { } } if req.Profile != "" { - if _, ok := app.storage.profiles[req.Profile]; ok { + if _, ok := app.storage.GetProfileKey(req.Profile); ok { invite.Profile = req.Profile } else { invite.Profile = "Default" } } app.storage.SetInvitesKey(inviteCode, invite) - app.storage.storeInvites() respondBool(200, true, gc) } @@ -233,13 +219,12 @@ func (app *appContext) GenerateInvite(gc *gin.Context) { func (app *appContext) GetInvites(gc *gin.Context) { app.debug.Println("Invites requested") currentTime := time.Now() - app.storage.loadInvites() app.checkInvites() var invites []inviteDTO - for code, inv := range app.storage.GetInvites() { + for _, inv := range app.storage.GetInvites() { _, months, days, hours, minutes, _ := timeDiff(inv.ValidTill, currentTime) invite := inviteDTO{ - Code: code, + Code: inv.Code, Months: months, Days: days, Hours: hours, @@ -277,37 +262,36 @@ func (app *appContext) GetInvites(gc *gin.Context) { invite.SendTo = inv.SendTo } if len(inv.Notify) != 0 { - var address string + // app.err.Printf("%s has notify section: %+v, you are %s\n", inv.Code, inv.Notify, gc.GetString("jfId")) + var addressOrID string if app.config.Section("ui").Key("jellyfin_login").MustBool(false) { - app.storage.loadEmails() - if addr, ok := app.storage.GetEmailsKey(gc.GetString("jfId")); ok && addr.Addr != "" { - address = addr.Addr - } + addressOrID = gc.GetString("jfId") } else { - address = app.config.Section("ui").Key("email").String() + addressOrID = app.config.Section("ui").Key("email").String() } - if _, ok := inv.Notify[address]; ok { - if _, ok = inv.Notify[address]["notify-expiry"]; ok { - invite.NotifyExpiry = inv.Notify[address]["notify-expiry"] + if _, ok := inv.Notify[addressOrID]; ok { + if _, ok = inv.Notify[addressOrID]["notify-expiry"]; ok { + invite.NotifyExpiry = inv.Notify[addressOrID]["notify-expiry"] } - if _, ok = inv.Notify[address]["notify-creation"]; ok { - invite.NotifyCreation = inv.Notify[address]["notify-creation"] + if _, ok = inv.Notify[addressOrID]["notify-creation"]; ok { + invite.NotifyCreation = inv.Notify[addressOrID]["notify-creation"] } } } invites = append(invites, invite) } - profiles := make([]string, len(app.storage.profiles)) - if len(app.storage.profiles) != 0 { - profiles[0] = app.storage.defaultProfile + fullProfileList := app.storage.GetProfiles() + profiles := make([]string, len(fullProfileList)) + if len(profiles) != 0 { + defaultProfile := app.storage.GetDefaultProfile() + profiles[0] = defaultProfile.Name i := 1 - if len(app.storage.profiles) > 1 { - for p := range app.storage.profiles { - if p != app.storage.defaultProfile { - profiles[i] = p - i++ - } - } + if len(fullProfileList) > 1 { + app.storage.db.ForEach(badgerhold.Where("Name").Ne(profiles[0]), func(p *Profile) error { + profiles[i] = p.Name + i++ + return nil + }) } } resp := getInvitesDTO{ @@ -330,7 +314,7 @@ func (app *appContext) SetProfile(gc *gin.Context) { gc.BindJSON(&req) app.debug.Printf("%s: Setting profile to \"%s\"", req.Invite, req.Profile) // "" means "Don't apply profile" - if _, ok := app.storage.profiles[req.Profile]; !ok && req.Profile != "" { + if _, ok := app.storage.GetProfileKey(req.Profile); !ok && req.Profile != "" { app.err.Printf("%s: Profile \"%s\" not found", req.Invite, req.Profile) respond(500, "Profile not found", gc) return @@ -338,7 +322,6 @@ func (app *appContext) SetProfile(gc *gin.Context) { inv, _ := app.storage.GetInvitesKey(req.Invite) inv.Profile = req.Profile app.storage.SetInvitesKey(req.Invite, inv) - app.storage.storeInvites() respondBool(200, true, gc) } @@ -357,8 +340,6 @@ func (app *appContext) SetNotify(gc *gin.Context) { changed := false for code, settings := range req { app.debug.Printf("%s: Notification settings change requested", code) - app.storage.loadInvites() - app.storage.loadEmails() invite, ok := app.storage.GetInvitesKey(code) if !ok { app.err.Printf("%s Notification setting change failed: Invalid code", code) @@ -401,9 +382,6 @@ func (app *appContext) SetNotify(gc *gin.Context) { app.storage.SetInvitesKey(code, invite) } } - if changed { - app.storage.storeInvites() - } } // @Summary Delete an invite. @@ -422,7 +400,6 @@ func (app *appContext) DeleteInvite(gc *gin.Context) { _, ok = app.storage.GetInvitesKey(req.Code) if ok { app.storage.DeleteInvitesKey(req.Code) - app.storage.storeInvites() app.info.Printf("%s: Invite deleted", req.Code) respondBool(200, true, gc) return diff --git a/api-messages.go b/api-messages.go index c571eb7..9973907 100644 --- a/api-messages.go +++ b/api-messages.go @@ -25,18 +25,18 @@ func (app *appContext) GetCustomContent(gc *gin.Context) { adminLang = app.storage.lang.chosenAdminLang } list := emailListDTO{ - "UserCreated": {Name: app.storage.lang.Email[lang].UserCreated["name"], Enabled: app.storage.customEmails.UserCreated.Enabled}, - "InviteExpiry": {Name: app.storage.lang.Email[lang].InviteExpiry["name"], Enabled: app.storage.customEmails.InviteExpiry.Enabled}, - "PasswordReset": {Name: app.storage.lang.Email[lang].PasswordReset["name"], Enabled: app.storage.customEmails.PasswordReset.Enabled}, - "UserDeleted": {Name: app.storage.lang.Email[lang].UserDeleted["name"], Enabled: app.storage.customEmails.UserDeleted.Enabled}, - "UserDisabled": {Name: app.storage.lang.Email[lang].UserDisabled["name"], Enabled: app.storage.customEmails.UserDisabled.Enabled}, - "UserEnabled": {Name: app.storage.lang.Email[lang].UserEnabled["name"], Enabled: app.storage.customEmails.UserEnabled.Enabled}, - "InviteEmail": {Name: app.storage.lang.Email[lang].InviteEmail["name"], Enabled: app.storage.customEmails.InviteEmail.Enabled}, - "WelcomeEmail": {Name: app.storage.lang.Email[lang].WelcomeEmail["name"], Enabled: app.storage.customEmails.WelcomeEmail.Enabled}, - "EmailConfirmation": {Name: app.storage.lang.Email[lang].EmailConfirmation["name"], Enabled: app.storage.customEmails.EmailConfirmation.Enabled}, - "UserExpired": {Name: app.storage.lang.Email[lang].UserExpired["name"], Enabled: app.storage.customEmails.UserExpired.Enabled}, - "UserLogin": {Name: app.storage.lang.Admin[adminLang].Strings["userPageLogin"], Enabled: app.storage.userPage.Login.Enabled}, - "UserPage": {Name: app.storage.lang.Admin[adminLang].Strings["userPagePage"], Enabled: app.storage.userPage.Page.Enabled}, + "UserCreated": {Name: app.storage.lang.Email[lang].UserCreated["name"], Enabled: app.storage.MustGetCustomContentKey("UserCreated").Enabled}, + "InviteExpiry": {Name: app.storage.lang.Email[lang].InviteExpiry["name"], Enabled: app.storage.MustGetCustomContentKey("InviteExpiry").Enabled}, + "PasswordReset": {Name: app.storage.lang.Email[lang].PasswordReset["name"], Enabled: app.storage.MustGetCustomContentKey("PasswordReset").Enabled}, + "UserDeleted": {Name: app.storage.lang.Email[lang].UserDeleted["name"], Enabled: app.storage.MustGetCustomContentKey("UserDeleted").Enabled}, + "UserDisabled": {Name: app.storage.lang.Email[lang].UserDisabled["name"], Enabled: app.storage.MustGetCustomContentKey("UserDisabled").Enabled}, + "UserEnabled": {Name: app.storage.lang.Email[lang].UserEnabled["name"], Enabled: app.storage.MustGetCustomContentKey("UserEnabled").Enabled}, + "InviteEmail": {Name: app.storage.lang.Email[lang].InviteEmail["name"], Enabled: app.storage.MustGetCustomContentKey("InviteEmail").Enabled}, + "WelcomeEmail": {Name: app.storage.lang.Email[lang].WelcomeEmail["name"], Enabled: app.storage.MustGetCustomContentKey("WelcomeEmail").Enabled}, + "EmailConfirmation": {Name: app.storage.lang.Email[lang].EmailConfirmation["name"], Enabled: app.storage.MustGetCustomContentKey("EmailConfirmation").Enabled}, + "UserExpired": {Name: app.storage.lang.Email[lang].UserExpired["name"], Enabled: app.storage.MustGetCustomContentKey("UserExpired").Enabled}, + "UserLogin": {Name: app.storage.lang.Admin[adminLang].Strings["userPageLogin"], Enabled: app.storage.MustGetCustomContentKey("Login").Enabled}, + "UserPage": {Name: app.storage.lang.Admin[adminLang].Strings["userPagePage"], Enabled: app.storage.MustGetCustomContentKey("Page").Enabled}, } filter := gc.Query("filter") @@ -50,10 +50,11 @@ func (app *appContext) GetCustomContent(gc *gin.Context) { gc.JSON(200, list) } -func (app *appContext) getCustomMessage(id string) *customContent { +// No longer needed, these are stored by string keys in the database now. +/* func (app *appContext) getCustomMessage(id string) *CustomContent { switch id { case "Announcement": - return &customContent{} + return &CustomContent{} case "UserCreated": return &app.storage.customEmails.UserCreated case "InviteExpiry": @@ -80,45 +81,38 @@ func (app *appContext) getCustomMessage(id string) *customContent { return &app.storage.userPage.Page } return nil -} +} */ -// @Summary Sets the corresponding custom email. +// @Summary Sets the corresponding custom content. // @Produce json -// @Param customEmails body customEmails true "Content = email (in markdown)." +// @Param CustomContent body CustomContent true "Content = email (in markdown)." // @Success 200 {object} boolResponse // @Failure 400 {object} boolResponse // @Failure 500 {object} boolResponse -// @Param id path string true "ID of email" +// @Param id path string true "ID of content" // @Router /config/emails/{id} [post] // @Security Bearer // @tags Configuration func (app *appContext) SetCustomMessage(gc *gin.Context) { - var req customContent + var req CustomContent gc.BindJSON(&req) id := gc.Param("id") if req.Content == "" { respondBool(400, false, gc) return } - message := app.getCustomMessage(id) - if message == nil { + message, ok := app.storage.GetCustomContentKey(id) + if !ok { respondBool(400, false, gc) return } message.Content = req.Content message.Enabled = true - if app.storage.storeCustomEmails() != nil { - respondBool(500, false, gc) - return - } - if app.storage.storeUserPageContent() != nil { - respondBool(500, false, gc) - return - } + app.storage.SetCustomContentKey(id, message) respondBool(200, true, gc) } -// @Summary Enable/Disable custom email. +// @Summary Enable/Disable custom content. // @Produce json // @Success 200 {object} boolResponse // @Failure 400 {object} boolResponse @@ -137,24 +131,17 @@ func (app *appContext) SetCustomMessageState(gc *gin.Context) { } else if s != "disable" { respondBool(400, false, gc) } - message := app.getCustomMessage(id) - if message == nil { + message, ok := app.storage.GetCustomContentKey(id) + if !ok { respondBool(400, false, gc) return } message.Enabled = enabled - if app.storage.storeCustomEmails() != nil { - respondBool(500, false, gc) - return - } - if app.storage.storeUserPageContent() != nil { - respondBool(500, false, gc) - return - } + app.storage.SetCustomContentKey(id, message) respondBool(200, true, gc) } -// @Summary Returns the custom email/message (generating it if not set) and list of used variables in it. +// @Summary Returns the custom content/message (generating it if not set) and list of used variables in it. // @Produce json // @Success 200 {object} customEmailDTO // @Failure 400 {object} boolResponse @@ -174,8 +161,8 @@ func (app *appContext) GetCustomMessageTemplate(gc *gin.Context) { var values map[string]interface{} username := app.storage.lang.Email[lang].Strings.get("username") emailAddress := app.storage.lang.Email[lang].Strings.get("emailAddress") - customMessage := app.getCustomMessage(id) - if customMessage == nil { + customMessage, ok := app.storage.GetCustomContentKey(id) + if !ok { app.err.Printf("Failed to get custom message with ID \"%s\"", id) respondBool(400, false, gc) return @@ -280,13 +267,7 @@ func (app *appContext) GetCustomMessageTemplate(gc *gin.Context) { if variables == nil { variables = []string{} } - if app.storage.storeCustomEmails() != nil { - respondBool(500, false, gc) - return - } - if app.storage.storeUserPageContent() != nil { - respondBool(500, false, gc) - } + app.storage.SetCustomContentKey(id, customMessage) var mail *Message if id != "UserLogin" && id != "UserPage" { mail, err = app.email.constructTemplate("", "
", app) @@ -387,11 +368,6 @@ func (app *appContext) setContactMethods(req SetContactMethodsDTO, gc *gin.Conte change := dcUser.Contact != req.Discord dcUser.Contact = req.Discord app.storage.SetDiscordKey(req.ID, dcUser) - if err := app.storage.storeDiscordUsers(); err != nil { - respondBool(500, false, gc) - app.err.Printf("Discord: Failed to store users: %v", err) - return - } if change { msg := "" if !req.Discord { @@ -404,11 +380,6 @@ func (app *appContext) setContactMethods(req SetContactMethodsDTO, gc *gin.Conte change := mxUser.Contact != req.Matrix mxUser.Contact = req.Matrix app.storage.SetMatrixKey(req.ID, mxUser) - if err := app.storage.storeMatrixUsers(); err != nil { - respondBool(500, false, gc) - app.err.Printf("Matrix: Failed to store users: %v", err) - return - } if change { msg := "" if !req.Matrix { @@ -421,11 +392,6 @@ func (app *appContext) setContactMethods(req SetContactMethodsDTO, gc *gin.Conte change := email.Contact != req.Email email.Contact = req.Email app.storage.SetEmailsKey(req.ID, email) - if err := app.storage.storeEmails(); err != nil { - respondBool(500, false, gc) - app.err.Printf("Failed to store emails: %v", err) - return - } if change { msg := "" if !req.Email { @@ -646,7 +612,7 @@ func (app *appContext) MatrixConnect(gc *gin.Context) { var req MatrixConnectUserDTO gc.BindJSON(&req) if app.storage.GetMatrix() == nil { - app.storage.matrix = matrixStore{} + app.storage.deprecatedMatrix = matrixStore{} } roomID, encrypted, err := app.matrix.CreateRoom(req.UserID) if err != nil { @@ -662,11 +628,6 @@ func (app *appContext) MatrixConnect(gc *gin.Context) { Encrypted: encrypted, }) app.matrix.isEncrypted[roomID] = encrypted - if err := app.storage.storeMatrixUsers(); err != nil { - app.err.Printf("Failed to store Matrix users: %v", err) - respondBool(500, false, gc) - return - } respondBool(200, true, gc) } @@ -717,11 +678,6 @@ func (app *appContext) DiscordConnect(gc *gin.Context) { return } app.storage.SetDiscordKey(req.JellyfinID, user) - if err := app.storage.storeDiscordUsers(); err != nil { - app.err.Printf("Failed to store Discord users: %v", err) - respondBool(500, false, gc) - return - } linkExistingOmbiDiscordTelegram(app) respondBool(200, true, gc) } diff --git a/api-ombi.go b/api-ombi.go index 40a8f72..0113885 100644 --- a/api-ombi.go +++ b/api-ombi.go @@ -71,7 +71,7 @@ func (app *appContext) SetOmbiProfile(gc *gin.Context) { var req ombiUser gc.BindJSON(&req) profileName := gc.Param("profile") - profile, ok := app.storage.profiles[profileName] + profile, ok := app.storage.GetProfileKey(profileName) if !ok { respondBool(400, false, gc) return @@ -83,12 +83,7 @@ func (app *appContext) SetOmbiProfile(gc *gin.Context) { return } profile.Ombi = template - app.storage.profiles[profileName] = profile - if err := app.storage.storeProfiles(); err != nil { - respond(500, "Failed to store profile", gc) - app.err.Printf("Failed to store profiles: %v", err) - return - } + app.storage.SetProfileKey(profileName, profile) respondBool(204, true, gc) } @@ -103,17 +98,12 @@ func (app *appContext) SetOmbiProfile(gc *gin.Context) { // @tags Ombi func (app *appContext) DeleteOmbiProfile(gc *gin.Context) { profileName := gc.Param("profile") - profile, ok := app.storage.profiles[profileName] + profile, ok := app.storage.GetProfileKey(profileName) if !ok { respondBool(400, false, gc) return } profile.Ombi = nil - app.storage.profiles[profileName] = profile - if err := app.storage.storeProfiles(); err != nil { - respond(500, "Failed to store profile", gc) - app.err.Printf("Failed to store profiles: %v", err) - return - } + app.storage.SetProfileKey(profileName, profile) respondBool(204, true, gc) } diff --git a/api-profiles.go b/api-profiles.go index 475857d..579cdff 100644 --- a/api-profiles.go +++ b/api-profiles.go @@ -4,6 +4,7 @@ import ( "time" "github.com/gin-gonic/gin" + "github.com/timshannon/badgerhold/v4" ) // @Summary Get a list of profiles @@ -13,14 +14,13 @@ import ( // @Security Bearer // @tags Profiles & Settings func (app *appContext) GetProfiles(gc *gin.Context) { - app.storage.loadProfiles() app.debug.Println("Profiles requested") out := getProfilesDTO{ - DefaultProfile: app.storage.defaultProfile, + DefaultProfile: app.storage.GetDefaultProfile().Name, Profiles: map[string]profileDTO{}, } - for name, p := range app.storage.profiles { - out.Profiles[name] = profileDTO{ + for _, p := range app.storage.GetProfiles() { + out.Profiles[p.Name] = profileDTO{ Admin: p.Admin, LibraryAccess: p.LibraryAccess, FromUser: p.FromUser, @@ -42,20 +42,20 @@ func (app *appContext) SetDefaultProfile(gc *gin.Context) { req := profileChangeDTO{} gc.BindJSON(&req) app.info.Printf("Setting default profile to \"%s\"", req.Name) - if _, ok := app.storage.profiles[req.Name]; !ok { + if _, ok := app.storage.GetProfileKey(req.Name); !ok { app.err.Printf("Profile not found: \"%s\"", req.Name) respond(500, "Profile not found", gc) return } - for name, profile := range app.storage.profiles { - if name == req.Name { - profile.Admin = true - app.storage.profiles[name] = profile + app.storage.db.ForEach(&badgerhold.Query{}, func(profile *Profile) error { + if profile.Name == req.Name { + profile.Default = true } else { - profile.Admin = false + profile.Default = false } - } - app.storage.defaultProfile = req.Name + app.storage.SetProfileKey(profile.Name, *profile) + return nil + }) respondBool(200, true, gc) } @@ -92,10 +92,7 @@ func (app *appContext) CreateProfile(gc *gin.Context) { return } } - app.storage.loadProfiles() - app.storage.profiles[req.Name] = profile - app.storage.storeProfiles() - app.storage.loadProfiles() + app.storage.SetProfileKey(req.Name, profile) respondBool(200, true, gc) } @@ -110,12 +107,6 @@ func (app *appContext) DeleteProfile(gc *gin.Context) { req := profileChangeDTO{} gc.BindJSON(&req) name := req.Name - if _, ok := app.storage.profiles[name]; ok { - if app.storage.defaultProfile == name { - app.storage.defaultProfile = "" - } - delete(app.storage.profiles, name) - } - app.storage.storeProfiles() + app.storage.DeleteProfileKey(name) respondBool(200, true, gc) } diff --git a/api-userpage.go b/api-userpage.go index 7996157..a517ee8 100644 --- a/api-userpage.go +++ b/api-userpage.go @@ -38,15 +38,10 @@ func (app *appContext) MyDetails(gc *gin.Context) { } resp.Disabled = user.Policy.IsDisabled - if exp, ok := app.storage.users[user.ID]; ok { - resp.Expiry = exp.Unix() + if exp, ok := app.storage.GetUserExpiryKey(user.ID); ok { + resp.Expiry = exp.Expiry.Unix() } - app.storage.loadEmails() - app.storage.loadDiscordUsers() - app.storage.loadMatrixUsers() - app.storage.loadTelegramUsers() - if emailEnabled { resp.Email = &MyDetailsContactMethodsDTO{} if email, ok := app.storage.GetEmailsKey(user.ID); ok && email.Addr != "" { @@ -199,7 +194,6 @@ func (app *appContext) confirmMyAction(gc *gin.Context, key string) { } } - app.storage.storeEmails() app.info.Println("Email list modified") gc.Redirect(http.StatusSeeOther, "/my/account") return @@ -511,8 +505,8 @@ func (app *appContext) ResetMyPassword(gc *gin.Context) { var pwr InternalPWR var err error - jfID := app.ReverseUserSearch(address) - if jfID == "" { + jfUser, ok := app.ReverseUserSearch(address) + if !ok { app.debug.Printf("Ignoring PWR request: User not found") for range timerWait { @@ -521,7 +515,7 @@ func (app *appContext) ResetMyPassword(gc *gin.Context) { } return } - pwr, err = app.GenInternalReset(jfID) + pwr, err = app.GenInternalReset(jfUser.ID) if err != nil { app.err.Printf("Failed to get user from Jellyfin: %v", err) for range timerWait { @@ -550,7 +544,7 @@ func (app *appContext) ResetMyPassword(gc *gin.Context) { return } return - } else if err := app.sendByID(msg, jfID); err != nil { + } else if err := app.sendByID(msg, jfUser.ID); err != nil { app.err.Printf("Failed to send password reset message to \"%s\": %v", address, err) } else { app.info.Printf("Sent password reset message to \"%s\"", address) diff --git a/api-users.go b/api-users.go index 18b4a10..5a01915 100644 --- a/api-users.go +++ b/api-users.go @@ -44,16 +44,16 @@ func (app *appContext) NewUserAdmin(gc *gin.Context) { return } id := user.ID - if app.storage.policy.BlockedTags != nil { - status, err = app.jf.SetPolicy(id, app.storage.policy) + profile := app.storage.GetDefaultProfile() + // Check profile isn't empty + if profile.Policy.BlockedTags != nil { + status, err = app.jf.SetPolicy(id, profile.Policy) if !(status == 200 || status == 204 || err == nil) { app.err.Printf("%s: Failed to set user policy (%d): %v", req.Username, status, err) } - } - if app.storage.configuration.GroupedFolders != nil && len(app.storage.displayprefs) != 0 { - status, err = app.jf.SetConfiguration(id, app.storage.configuration) + status, err = app.jf.SetConfiguration(id, profile.Configuration) if (status == 200 || status == 204) && err == nil { - status, err = app.jf.SetDisplayPreferences(id, app.storage.displayprefs) + status, err = app.jf.SetDisplayPreferences(id, profile.Displayprefs) } if !((status == 200 || status == 204) && err == nil) { app.err.Printf("%s: Failed to set configuration template (%d): %v", req.Username, status, err) @@ -62,18 +62,18 @@ func (app *appContext) NewUserAdmin(gc *gin.Context) { app.jf.CacheExpiry = time.Now() if emailEnabled { app.storage.SetEmailsKey(id, EmailAddress{Addr: req.Email, Contact: true}) - app.storage.storeEmails() } if app.config.Section("ombi").Key("enabled").MustBool(false) { - app.storage.loadOmbiTemplate() - if len(app.storage.ombi_template) != 0 { - errors, code, err := app.ombi.NewUser(req.Username, req.Password, req.Email, app.storage.ombi_template) - if err != nil || code != 200 { - app.err.Printf("Failed to create Ombi user (%d): %v", code, err) - app.debug.Printf("Errors reported by Ombi: %s", strings.Join(errors, ", ")) - } else { - app.info.Println("Created Ombi user") - } + profile := app.storage.GetDefaultProfile() + if profile.Ombi == nil { + profile.Ombi = map[string]interface{}{} + } + errors, code, err := app.ombi.NewUser(req.Username, req.Password, req.Email, profile.Ombi) + if err != nil || code != 200 { + app.err.Printf("Failed to create Ombi user (%d): %v", code, err) + app.debug.Printf("Errors reported by Ombi: %s", strings.Join(errors, ", ")) + } else { + app.info.Println("Created Ombi user") } } if emailEnabled && app.config.Section("welcome_email").Key("enabled").MustBool(false) && req.Email != "" { @@ -130,17 +130,13 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc success = false return } - if app.config.Section("discord").Key("require_unique").MustBool(false) { - for _, u := range app.storage.GetDiscord() { - if discordUser.ID == u.ID { - f = func(gc *gin.Context) { - app.debug.Printf("%s: New user failed: Discord user already linked", req.Code) - respond(400, "errorAccountLinked", gc) - } - success = false - return - } + if app.config.Section("discord").Key("require_unique").MustBool(false) && app.discord.UserExists(discordUser.ID) { + f = func(gc *gin.Context) { + app.debug.Printf("%s: New user failed: Discord user already linked", req.Code) + respond(400, "errorAccountLinked", gc) } + success = false + return } err := app.discord.ApplyRole(discordUser.ID) if err != nil { @@ -176,17 +172,13 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc success = false return } - if app.config.Section("matrix").Key("require_unique").MustBool(false) { - for _, u := range app.storage.GetMatrix() { - if user.User.UserID == u.UserID { - f = func(gc *gin.Context) { - app.debug.Printf("%s: New user failed: Matrix user already linked", req.Code) - respond(400, "errorAccountLinked", gc) - } - success = false - return - } + if app.config.Section("matrix").Key("require_unique").MustBool(false) && app.matrix.UserExists(user.User.UserID) { + f = func(gc *gin.Context) { + app.debug.Printf("%s: New user failed: Matrix user already linked", req.Code) + respond(400, "errorAccountLinked", gc) } + success = false + return } matrixVerified = user.Verified matrixUser = *user.User @@ -278,7 +270,6 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc success = false return } - app.storage.loadProfiles() invite, _ := app.storage.GetInvitesKey(req.Code) app.checkInvite(req.Code, true, req.Username) if emailEnabled && app.config.Section("notifications").Key("enabled").MustBool(false) { @@ -310,9 +301,9 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc if invite.Profile != "" { app.debug.Printf("Applying settings from profile \"%s\"", invite.Profile) var ok bool - profile, ok = app.storage.profiles[invite.Profile] + profile, ok = app.storage.GetProfileKey(invite.Profile) if !ok { - profile = app.storage.profiles["Default"] + profile = app.storage.GetDefaultProfile() } if profile.Policy.BlockedTags != nil { app.debug.Printf("Applying policy from profile \"%s\"", invite.Profile) @@ -335,29 +326,19 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc // if app.config.Section("password_resets").Key("enabled").MustBool(false) { if req.Email != "" { app.storage.SetEmailsKey(id, EmailAddress{Addr: req.Email, Contact: true}) - app.storage.storeEmails() } expiry := time.Time{} if invite.UserExpiry { - app.storage.usersLock.Lock() - defer app.storage.usersLock.Unlock() expiry = time.Now().AddDate(0, invite.UserMonths, invite.UserDays).Add(time.Duration((60*invite.UserHours)+invite.UserMinutes) * time.Minute) - app.storage.users[id] = expiry - if err := app.storage.storeUsers(); err != nil { - app.err.Printf("Failed to store user duration: %v", err) - } + app.storage.SetUserExpiryKey(id, UserExpiry{Expiry: expiry}) } if discordVerified { discordUser.Contact = req.DiscordContact - if app.storage.discord == nil { - app.storage.discord = discordStore{} + if app.storage.deprecatedDiscord == nil { + app.storage.deprecatedDiscord = discordStore{} } app.storage.SetDiscordKey(user.ID, discordUser) - if err := app.storage.storeDiscordUsers(); err != nil { - app.err.Printf("Failed to store Discord users: %v", err) - } else { - delete(app.discord.verifiedTokens, req.DiscordPIN) - } + delete(app.discord.verifiedTokens, req.DiscordPIN) } if telegramVerified { tgUser := TelegramUser{ @@ -368,8 +349,8 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc if lang, ok := app.telegram.languages[tgToken.ChatID]; ok { tgUser.Lang = lang } - if app.storage.telegram == nil { - app.storage.telegram = telegramStore{} + if app.storage.deprecatedTelegram == nil { + app.storage.deprecatedTelegram = telegramStore{} } app.telegram.DeleteVerifiedToken(req.TelegramPIN) app.storage.SetTelegramKey(user.ID, tgUser) @@ -412,13 +393,10 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc if matrixVerified { matrixUser.Contact = req.MatrixContact delete(app.matrix.tokens, req.MatrixPIN) - if app.storage.matrix == nil { - app.storage.matrix = matrixStore{} + if app.storage.deprecatedMatrix == nil { + app.storage.deprecatedMatrix = matrixStore{} } app.storage.SetMatrixKey(user.ID, matrixUser) - if err := app.storage.storeMatrixUsers(); err != nil { - app.err.Printf("Failed to store Matrix users: %v", err) - } } if (emailEnabled && app.config.Section("welcome_email").Key("enabled").MustBool(false) && req.Email != "") || telegramVerified || discordVerified || matrixVerified { name := app.getAddressOrName(user.ID) @@ -478,14 +456,10 @@ func (app *appContext) NewUser(gc *gin.Context) { respond(400, "errorNoEmail", gc) return } - if app.config.Section("email").Key("require_unique").MustBool(false) && req.Email != "" { - for _, email := range app.storage.GetEmails() { - if req.Email == email.Addr { - app.info.Printf("%s: New user failed: Email already in use", req.Code) - respond(400, "errorEmailLinked", gc) - return - } - } + if app.config.Section("email").Key("require_unique").MustBool(false) && req.Email != "" && app.EmailAddressExists(req.Email) { + app.info.Printf("%s: New user failed: Email already in use", req.Code) + respond(400, "errorEmailLinked", gc) + return } } f, success := app.newUser(req, false) @@ -641,21 +615,16 @@ func (app *appContext) ExtendExpiry(gc *gin.Context) { respondBool(400, false, gc) return } - app.storage.usersLock.Lock() - defer app.storage.usersLock.Unlock() for _, id := range req.Users { - if expiry, ok := app.storage.users[id]; ok { - app.storage.users[id] = expiry.AddDate(0, req.Months, req.Days).Add(time.Duration(((60 * req.Hours) + req.Minutes)) * time.Minute) + base := time.Now() + if expiry, ok := app.storage.GetUserExpiryKey(id); ok { + base = expiry.Expiry app.debug.Printf("Expiry extended for \"%s\"", id) } else { - app.storage.users[id] = time.Now().AddDate(0, req.Months, req.Days).Add(time.Duration(((60 * req.Hours) + req.Minutes)) * time.Minute) app.debug.Printf("Created expiry for \"%s\"", id) } - } - if err := app.storage.storeUsers(); err != nil { - app.err.Printf("Failed to store user duration: %v", err) - respondBool(500, false, gc) - return + expiry := UserExpiry{Expiry: base.AddDate(0, req.Months, req.Days).Add(time.Duration(((60 * req.Hours) + req.Minutes)) * time.Minute)} + app.storage.SetUserExpiryKey(id, expiry) } respondBool(204, true, gc) } @@ -727,27 +696,21 @@ func (app *appContext) SaveAnnounceTemplate(gc *gin.Context) { respondBool(400, false, gc) return } - app.storage.announcements[req.Name] = req - if err := app.storage.storeAnnouncements(); err != nil { - respondBool(500, false, gc) - app.err.Printf("Failed to store announcement templates: %v", err) - return - } + + app.storage.SetAnnouncementsKey(req.Name, req) respondBool(200, true, gc) } // @Summary Save an announcement as a template for use or editing later. // @Produce json // @Success 200 {object} getAnnouncementsDTO -// @Router /users/announce/template [get] +// @Router /users/announce [get] // @Security Bearer // @tags Users func (app *appContext) GetAnnounceTemplates(gc *gin.Context) { - resp := &getAnnouncementsDTO{make([]string, len(app.storage.announcements))} - i := 0 - for name := range app.storage.announcements { - resp.Announcements[i] = name - i++ + resp := &getAnnouncementsDTO{make([]string, len(app.storage.GetAnnouncements()))} + for i, a := range app.storage.GetAnnouncements() { + resp.Announcements[i] = a.Name } gc.JSON(200, resp) } @@ -762,7 +725,7 @@ func (app *appContext) GetAnnounceTemplates(gc *gin.Context) { // @tags Users func (app *appContext) GetAnnounceTemplate(gc *gin.Context) { name := gc.Param("name") - if announcement, ok := app.storage.announcements[name]; ok { + if announcement, ok := app.storage.GetAnnouncementsKey(name); ok { gc.JSON(200, announcement) return } @@ -779,12 +742,7 @@ func (app *appContext) GetAnnounceTemplate(gc *gin.Context) { // @tags Users func (app *appContext) DeleteAnnounceTemplate(gc *gin.Context) { name := gc.Param("name") - delete(app.storage.announcements, name) - if err := app.storage.storeAnnouncements(); err != nil { - respondBool(500, false, gc) - app.err.Printf("Failed to store announcement templates: %v", err) - return - } + app.storage.DeleteAnnouncementsKey(name) respondBool(200, false, gc) } @@ -876,8 +834,6 @@ func (app *appContext) GetUsers(gc *gin.Context) { adminOnly := app.config.Section("ui").Key("admin_only").MustBool(true) allowAll := app.config.Section("ui").Key("allow_all").MustBool(false) i := 0 - app.storage.usersLock.Lock() - defer app.storage.usersLock.Unlock() for _, jfUser := range users { user := respUser{ ID: jfUser.ID, @@ -894,9 +850,9 @@ func (app *appContext) GetUsers(gc *gin.Context) { user.Label = email.Label user.AccountsAdmin = (app.jellyfinLogin) && (email.Admin || (adminOnly && jfUser.Policy.IsAdministrator) || allowAll) } - expiry, ok := app.storage.users[jfUser.ID] + expiry, ok := app.storage.GetUserExpiryKey(jfUser.ID) if ok { - user.Expiry = expiry.Unix() + user.Expiry = expiry.Expiry.Unix() } if tgUser, ok := app.storage.GetTelegramKey(jfUser.ID); ok { user.Telegram = tgUser.Username @@ -947,10 +903,6 @@ func (app *appContext) SetAccountsAdmin(gc *gin.Context) { app.storage.SetEmailsKey(id, emailStore) } } - if err := app.storage.storeEmails(); err != nil { - app.err.Printf("Failed to store email list: %v", err) - respondBool(500, false, gc) - } app.info.Println("Email list modified") respondBool(204, true, gc) } @@ -984,10 +936,6 @@ func (app *appContext) ModifyLabels(gc *gin.Context) { app.storage.SetEmailsKey(id, emailStore) } } - if err := app.storage.storeEmails(); err != nil { - app.err.Printf("Failed to store email list: %v", err) - respondBool(500, false, gc) - } app.info.Println("Email list modified") respondBool(204, true, gc) } @@ -1022,7 +970,6 @@ func (app *appContext) ModifyEmails(gc *gin.Context) { // Auto enable contact by email for newly added addresses if !ok || oldEmail.Addr == "" { emailStore.Contact = true - app.storage.storeEmails() } emailStore.Addr = address @@ -1039,7 +986,6 @@ func (app *appContext) ModifyEmails(gc *gin.Context) { } } } - app.storage.storeEmails() app.info.Println("Email list modified") respondBool(200, true, gc) } @@ -1062,25 +1008,24 @@ func (app *appContext) ApplySettings(gc *gin.Context) { var displayprefs map[string]interface{} var ombi map[string]interface{} if req.From == "profile" { - app.storage.loadProfiles() // Check profile exists & isn't empty - if _, ok := app.storage.profiles[req.Profile]; !ok || app.storage.profiles[req.Profile].Policy.BlockedTags == nil { + profile, ok := app.storage.GetProfileKey(req.Profile) + if !ok || profile.Policy.BlockedTags == nil { app.err.Printf("Couldn't find profile \"%s\" or profile was empty", req.Profile) respond(500, "Couldn't find profile", gc) return } if req.Homescreen { - if app.storage.profiles[req.Profile].Configuration.GroupedFolders == nil || len(app.storage.profiles[req.Profile].Displayprefs) == 0 { + if profile.Configuration.GroupedFolders == nil || len(profile.Displayprefs) == 0 { app.err.Printf("No homescreen saved in profile \"%s\"", req.Profile) respond(500, "No homescreen template available", gc) return } - configuration = app.storage.profiles[req.Profile].Configuration - displayprefs = app.storage.profiles[req.Profile].Displayprefs + configuration = profile.Configuration + displayprefs = profile.Displayprefs } - policy = app.storage.profiles[req.Profile].Policy + policy = profile.Policy if app.config.Section("ombi").Key("enabled").MustBool(false) { - profile := app.storage.profiles[req.Profile] if profile.Ombi != nil && len(profile.Ombi) != 0 { ombi = profile.Ombi } diff --git a/config.go b/config.go index 7fb388c..7053c12 100644 --- a/config.go +++ b/config.go @@ -157,15 +157,6 @@ func (app *appContext) loadConfig() error { app.MustSetValue("updates", "channel", releaseChannel) } - app.storage.customEmails_path = app.config.Section("files").Key("custom_emails").String() - app.storage.loadCustomEmails() - - app.MustSetValue("user_page", "enabled", "true") - if app.config.Section("user_page").Key("enabled").MustBool(false) { - app.storage.userPage_path = app.config.Section("files").Key("custom_user_page_content").String() - app.storage.loadUserPageContent() - } - substituteStrings = app.config.Section("jellyfin").Key("substitute_jellyfin_strings").MustString("") if substituteStrings != "" { diff --git a/daemon.go b/daemon.go index 8b3b465..320afac 100644 --- a/daemon.go +++ b/daemon.go @@ -7,85 +7,53 @@ import "time" // the user cache is fresh. func (app *appContext) clearEmails() { app.debug.Println("Housekeeping: removing unused email addresses") - users, status, err := app.jf.GetUsers(false) - if status != 200 || err != nil || len(users) == 0 { - app.err.Printf("Failed to get users from Jellyfin (%d): %v", status, err) - return - } - // Rebuild email storage to from existing users to reduce time complexity - emails := emailStore{} - app.storage.emailsLock.Lock() - for _, user := range users { - if email, ok := app.storage.GetEmailsKey(user.ID); ok { - emails[user.ID] = email + emails := app.storage.GetEmails() + for _, email := range emails { + _, status, err := app.jf.UserByID(email.JellyfinID, false) + if status == 200 && err != nil { + continue } + app.storage.DeleteEmailsKey(email.JellyfinID) } - app.storage.emails = emails - app.storage.storeEmails() - app.storage.emailsLock.Unlock() } // clearDiscord does the same as clearEmails, but for Discord Users. func (app *appContext) clearDiscord() { app.debug.Println("Housekeeping: removing unused Discord IDs") - users, status, err := app.jf.GetUsers(false) - if status != 200 || err != nil || len(users) == 0 { - app.err.Printf("Failed to get users from Jellyfin (%d): %v", status, err) - return - } - // Rebuild discord storage to from existing users to reduce time complexity - dcUsers := discordStore{} - app.storage.discordLock.Lock() - for _, user := range users { - if dcUser, ok := app.storage.GetDiscordKey(user.ID); ok { - dcUsers[user.ID] = dcUser + discordUsers := app.storage.GetDiscord() + for _, discordUser := range discordUsers { + _, status, err := app.jf.UserByID(discordUser.JellyfinID, false) + if status == 200 && err != nil { + continue } + app.storage.DeleteDiscordKey(discordUser.JellyfinID) } - app.storage.discord = dcUsers - app.storage.storeDiscordUsers() - app.storage.discordLock.Unlock() } // clearMatrix does the same as clearEmails, but for Matrix Users. func (app *appContext) clearMatrix() { app.debug.Println("Housekeeping: removing unused Matrix IDs") - users, status, err := app.jf.GetUsers(false) - if status != 200 || err != nil || len(users) == 0 { - app.err.Printf("Failed to get users from Jellyfin (%d): %v", status, err) - return - } - // Rebuild matrix storage to from existing users to reduce time complexity - mxUsers := matrixStore{} - app.storage.matrixLock.Lock() - for _, user := range users { - if mxUser, ok := app.storage.GetMatrixKey(user.ID); ok { - mxUsers[user.ID] = mxUser + matrixUsers := app.storage.GetMatrix() + for _, matrixUser := range matrixUsers { + _, status, err := app.jf.UserByID(matrixUser.JellyfinID, false) + if status == 200 && err != nil { + continue } + app.storage.DeleteMatrixKey(matrixUser.JellyfinID) } - app.storage.matrix = mxUsers - app.storage.storeMatrixUsers() - app.storage.matrixLock.Unlock() } // clearTelegram does the same as clearEmails, but for Telegram Users. func (app *appContext) clearTelegram() { app.debug.Println("Housekeeping: removing unused Telegram IDs") - users, status, err := app.jf.GetUsers(false) - if status != 200 || err != nil || len(users) == 0 { - app.err.Printf("Failed to get users from Jellyfin (%d): %v", status, err) - return - } - // Rebuild telegram storage to from existing users to reduce time complexity - tgUsers := telegramStore{} - app.storage.telegramLock.Lock() - for _, user := range users { - if tgUser, ok := app.storage.GetTelegramKey(user.ID); ok { - tgUsers[user.ID] = tgUser + telegramUsers := app.storage.GetTelegram() + for _, telegramUser := range telegramUsers { + _, status, err := app.jf.UserByID(telegramUser.JellyfinID, false) + if status == 200 && err != nil { + continue } + app.storage.DeleteTelegramKey(telegramUser.JellyfinID) } - app.storage.telegram = tgUsers - app.storage.storeTelegramUsers() - app.storage.telegramLock.Unlock() } // https://bbengfort.github.io/snippets/2016/06/26/background-work-goroutines-timer.html THANKS @@ -148,7 +116,6 @@ func (rt *housekeepingDaemon) run() { break } started := time.Now() - rt.app.storage.loadInvites() for _, job := range rt.jobs { job(rt.app) diff --git a/discord.go b/discord.go index da949ef..7164114 100644 --- a/discord.go +++ b/discord.go @@ -6,6 +6,7 @@ import ( "time" dg "github.com/bwmarrin/discordgo" + "github.com/timshannon/badgerhold/v4" ) type DiscordDaemon struct { @@ -478,11 +479,11 @@ func (d *DiscordDaemon) cmdLang(s *dg.Session, i *dg.InteractionCreate, lang str code := i.ApplicationCommandData().Options[0].StringValue() if _, ok := d.app.storage.lang.Telegram[code]; ok { var user DiscordUser - for jfID, u := range d.app.storage.GetDiscord() { + for _, u := range d.app.storage.GetDiscord() { if u.ID == i.Interaction.Member.User.ID { u.Lang = code lang = code - d.app.storage.SetDiscordKey(jfID, u) + d.app.storage.SetDiscordKey(u.JellyfinID, u) user = u break } @@ -585,10 +586,10 @@ func (d *DiscordDaemon) msgLang(s *dg.Session, m *dg.MessageCreate, sects []stri } if _, ok := d.app.storage.lang.Telegram[sects[1]]; ok { var user DiscordUser - for jfID, u := range d.app.storage.GetDiscord() { + for _, u := range d.app.storage.GetDiscord() { if u.ID == m.Author.ID { u.Lang = sects[1] - d.app.storage.SetDiscordKey(jfID, u) + d.app.storage.SetDiscordKey(u.JellyfinID, u) user = u break } @@ -708,15 +709,9 @@ func (d *DiscordDaemon) AssignedUserVerified(pin string, jfID string) (user Disc } // UserExists returns whether or not a user with the given ID exists. -func (d *DiscordDaemon) UserExists(id string) (ok bool) { - ok = false - for _, u := range d.app.storage.GetDiscord() { - if u.ID == id { - ok = true - break - } - } - return +func (d *DiscordDaemon) UserExists(id string) bool { + c, err := d.app.storage.db.Count(&DiscordUser{}, badgerhold.Where("ID").Eq(id)) + return err != nil || c > 0 } // DeleteVerifiedUser removes the token with the given PIN. diff --git a/email.go b/email.go index 4ee8f36..0f517ca 100644 --- a/email.go +++ b/email.go @@ -19,8 +19,10 @@ import ( "github.com/gomarkdown/markdown" "github.com/gomarkdown/markdown/html" + "github.com/hrfee/mediabrowser" "github.com/itchyny/timefmt-go" "github.com/mailgun/mailgun-go/v4" + "github.com/timshannon/badgerhold/v4" sMail "github.com/xhit/go-simple-mail/v2" ) @@ -329,10 +331,11 @@ func (emailer *Emailer) constructConfirmation(code, username, key string, app *a } var err error template := emailer.confirmationValues(code, username, key, app, noSub) - if app.storage.customEmails.EmailConfirmation.Enabled { + message := app.storage.MustGetCustomContentKey("EmailConfirmation") + if message.Enabled { content := templateEmail( - app.storage.customEmails.EmailConfirmation.Content, - app.storage.customEmails.EmailConfirmation.Variables, + message.Content, + message.Variables, nil, template, ) @@ -412,10 +415,11 @@ func (emailer *Emailer) constructInvite(code string, invite Invite, app *appCont } template := emailer.inviteValues(code, invite, app, noSub) var err error - if app.storage.customEmails.InviteEmail.Enabled { + message := app.storage.MustGetCustomContentKey("InviteEmail") + if message.Enabled { content := templateEmail( - app.storage.customEmails.InviteEmail.Content, - app.storage.customEmails.InviteEmail.Variables, + message.Content, + message.Variables, nil, template, ) @@ -451,10 +455,11 @@ func (emailer *Emailer) constructExpiry(code string, invite Invite, app *appCont } var err error template := emailer.expiryValues(code, invite, app, noSub) - if app.storage.customEmails.InviteExpiry.Enabled { + message := app.storage.MustGetCustomContentKey("InviteExpiry") + if message.Enabled { content := templateEmail( - app.storage.customEmails.InviteExpiry.Content, - app.storage.customEmails.InviteExpiry.Variables, + message.Content, + message.Variables, nil, template, ) @@ -505,10 +510,11 @@ func (emailer *Emailer) constructCreated(code, username, address string, invite } template := emailer.createdValues(code, username, address, invite, app, noSub) var err error - if app.storage.customEmails.UserCreated.Enabled { + message := app.storage.MustGetCustomContentKey("UserCreated") + if message.Enabled { content := templateEmail( - app.storage.customEmails.UserCreated.Content, - app.storage.customEmails.UserCreated.Variables, + message.Content, + message.Variables, nil, template, ) @@ -578,10 +584,11 @@ func (emailer *Emailer) constructReset(pwr PasswordReset, app *appContext, noSub } template := emailer.resetValues(pwr, app, noSub) var err error - if app.storage.customEmails.PasswordReset.Enabled { + message := app.storage.MustGetCustomContentKey("PasswordReset") + if message.Enabled { content := templateEmail( - app.storage.customEmails.PasswordReset.Content, - app.storage.customEmails.PasswordReset.Variables, + message.Content, + message.Variables, nil, template, ) @@ -619,10 +626,11 @@ func (emailer *Emailer) constructDeleted(reason string, app *appContext, noSub b } var err error template := emailer.deletedValues(reason, app, noSub) - if app.storage.customEmails.UserDeleted.Enabled { + message := app.storage.MustGetCustomContentKey("UserDeleted") + if message.Enabled { content := templateEmail( - app.storage.customEmails.UserDeleted.Content, - app.storage.customEmails.UserDeleted.Variables, + message.Content, + message.Variables, nil, template, ) @@ -660,10 +668,11 @@ func (emailer *Emailer) constructDisabled(reason string, app *appContext, noSub } var err error template := emailer.disabledValues(reason, app, noSub) - if app.storage.customEmails.UserDisabled.Enabled { + message := app.storage.MustGetCustomContentKey("UserDisabled") + if message.Enabled { content := templateEmail( - app.storage.customEmails.UserDisabled.Content, - app.storage.customEmails.UserDisabled.Variables, + message.Content, + message.Variables, nil, template, ) @@ -701,10 +710,11 @@ func (emailer *Emailer) constructEnabled(reason string, app *appContext, noSub b } var err error template := emailer.enabledValues(reason, app, noSub) - if app.storage.customEmails.UserEnabled.Enabled { + message := app.storage.MustGetCustomContentKey("UserEnabled") + if message.Enabled { content := templateEmail( - app.storage.customEmails.UserEnabled.Content, - app.storage.customEmails.UserEnabled.Variables, + message.Content, + message.Variables, nil, template, ) @@ -756,7 +766,8 @@ func (emailer *Emailer) constructWelcome(username string, expiry time.Time, app } var err error var template map[string]interface{} - if app.storage.customEmails.WelcomeEmail.Enabled { + message := app.storage.MustGetCustomContentKey("WelcomeEmail") + if message.Enabled { template = emailer.welcomeValues(username, expiry, app, noSub, true) } else { template = emailer.welcomeValues(username, expiry, app, noSub, false) @@ -766,11 +777,11 @@ func (emailer *Emailer) constructWelcome(username string, expiry time.Time, app "date": "{yourAccountWillExpire}", }) } - if app.storage.customEmails.WelcomeEmail.Enabled { + if message.Enabled { content := templateEmail( - app.storage.customEmails.WelcomeEmail.Content, - app.storage.customEmails.WelcomeEmail.Variables, - app.storage.customEmails.WelcomeEmail.Conditionals, + message.Content, + message.Variables, + message.Conditionals, template, ) email, err = emailer.constructTemplate(email.Subject, content, app) @@ -801,10 +812,11 @@ func (emailer *Emailer) constructUserExpired(app *appContext, noSub bool) (*Mess } var err error template := emailer.userExpiredValues(app, noSub) - if app.storage.customEmails.UserExpired.Enabled { + message := app.storage.MustGetCustomContentKey("UserExpired") + if message.Enabled { content := templateEmail( - app.storage.customEmails.UserExpired.Content, - app.storage.customEmails.UserExpired.Variables, + message.Content, + message.Variables, nil, template, ) @@ -874,31 +886,63 @@ func (app *appContext) getAddressOrName(jfID string) string { // ReverseUserSearch returns the jellyfin ID of the user with the given username, email, or contact method username. // returns "" if none found. returns only the first match, might be an issue if there are users with the same contact method usernames. -func (app *appContext) ReverseUserSearch(address string) string { +func (app *appContext) ReverseUserSearch(address string) (user mediabrowser.User, ok bool) { + ok = false user, status, err := app.jf.UserByName(address, false) if status == 200 && err == nil { - return user.ID + ok = true + return } - for id, email := range app.storage.GetEmails() { - if strings.ToLower(address) == strings.ToLower(email.Addr) { - return id + emailAddresses := []EmailAddress{} + err = app.storage.db.Find(&emailAddresses, badgerhold.Where("Addr").Eq(address)) + if err == nil && len(emailAddresses) > 0 { + for _, emailUser := range emailAddresses { + user, status, err = app.jf.UserByID(emailUser.JellyfinID, false) + if status == 200 && err == nil { + ok = true + return + } } } - for id, dcUser := range app.storage.GetDiscord() { + // Dont know how we'd use badgerhold when we need to render each username, + // Apart from storing the rendered name in the db. + for _, dcUser := range app.storage.GetDiscord() { if RenderDiscordUsername(dcUser) == strings.ToLower(address) { - return id + user, status, err = app.jf.UserByID(dcUser.JellyfinID, false) + if status == 200 && err == nil { + ok = true + return + } } } tgUsername := strings.TrimPrefix(address, "@") - for id, tgUser := range app.storage.GetTelegram() { - if tgUsername == tgUser.Username { - return id + telegramUsers := []TelegramUser{} + err = app.storage.db.Find(&telegramUsers, badgerhold.Where("Username").Eq(tgUsername)) + if err == nil && len(telegramUsers) > 0 { + for _, telegramUser := range telegramUsers { + user, status, err = app.jf.UserByID(telegramUser.JellyfinID, false) + if status == 200 && err == nil { + ok = true + return + } } } - for id, mxUser := range app.storage.GetMatrix() { - if address == mxUser.UserID { - return id + matrixUsers := []MatrixUser{} + err = app.storage.db.Find(&matrixUsers, badgerhold.Where("UserID").Eq(address)) + if err == nil && len(matrixUsers) > 0 { + for _, matrixUser := range matrixUsers { + user, status, err = app.jf.UserByID(matrixUser.JellyfinID, false) + if status == 200 && err == nil { + ok = true + return + } } } - return "" + return +} + +// EmailAddressExists returns whether or not a user with the given email address exists. +func (app *appContext) EmailAddressExists(address string) bool { + c, err := app.storage.db.Count(&EmailAddress{}, badgerhold.Where("Addr").Eq(address)) + return err != nil || c > 0 } diff --git a/go.mod b/go.mod index 9267ab4..8dccf00 100644 --- a/go.mod +++ b/go.mod @@ -48,7 +48,12 @@ require ( require ( github.com/KyleBanks/depth v1.2.1 // indirect github.com/bytedance/sonic v1.9.1 // indirect + github.com/cespare/xxhash v1.1.0 // indirect + github.com/cespare/xxhash/v2 v2.1.1 // indirect github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect + github.com/dgraph-io/badger/v3 v3.2103.1 // indirect + github.com/dgraph-io/ristretto v0.1.0 // indirect + github.com/dustin/go-humanize v1.0.0 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/getlantern/context v0.0.0-20220418194847-3d5e7a086201 // indirect github.com/getlantern/errors v1.0.3 // indirect @@ -69,12 +74,19 @@ require ( github.com/go-stack/stack v1.8.1 // indirect github.com/go-test/deep v1.1.0 // indirect github.com/goccy/go-json v0.10.2 // indirect + github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect + github.com/golang/glog v0.0.0-20210429001901-424d2337a529 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/google/flatbuffers v2.0.0+incompatible // indirect github.com/google/uuid v1.3.0 // indirect github.com/gorilla/mux v1.8.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.13.1 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/leodido/go-urn v1.2.4 // indirect github.com/mailru/easyjson v0.7.7 // indirect @@ -92,9 +104,11 @@ require ( github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/sjson v1.2.5 // indirect + github.com/timshannon/badgerhold/v4 v4.0.2 // indirect github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.11 // indirect + go.opencensus.io v0.23.0 // indirect go.opentelemetry.io/otel v1.16.0 // indirect go.opentelemetry.io/otel/metric v1.16.0 // indirect go.opentelemetry.io/otel/trace v1.16.0 // indirect diff --git a/go.sum b/go.sum index b0534a7..8a032c7 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,14 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/bwmarrin/discordgo v0.27.1 h1:ib9AIc/dom1E/fSIulrBwnez0CToJE113ZGt4HoliGY= @@ -13,17 +16,39 @@ github.com/bwmarrin/discordgo v0.27.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0 github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgraph-io/badger/v3 v3.2103.1 h1:zaX53IRg7ycxVlkd5pYdCeFp1FynD6qBGQoQql3R3Hk= +github.com/dgraph-io/badger/v3 v3.2103.1/go.mod h1:dULbq6ehJ5K0cGW/1TQ9iSfUk0gbSiToDWmWmTsJ53E= +github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI= +github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/emersion/go-autostart v0.0.0-20210130080809-00ed301c8e9a h1:M88ob4TyDnEqNuL3PgsE/p3bDujfspnulR+0dQWNYZs= github.com/emersion/go-autostart v0.0.0-20210130080809-00ed301c8e9a/go.mod h1:buzQsO8HHkZX2Q45fdfGH1xejPjuDQaXH8btcYMFzPM= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 h1:0JZ+dUmQeA8IIVUMzysrX4/AKuQwWhV2dYQuPZdvdSQ= github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64= github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A= @@ -33,6 +58,7 @@ github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+ne github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= @@ -131,20 +157,54 @@ github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGF github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v0.0.0-20210429001901-424d2337a529 h1:2voWjNECnrZRbfwXxHB1/j8wa6xdKn85B5NzgVL/pTU= +github.com/golang/glog v0.0.0-20210429001901-424d2337a529/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/gomarkdown/markdown v0.0.0-20230322041520-c84983bdbf2a h1:AWZzzFrqyjYlRloN6edwTLTUbKxf5flLXNuTBDm3Ews= github.com/gomarkdown/markdown v0.0.0-20230322041520-c84983bdbf2a/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= +github.com/google/flatbuffers v1.12.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/flatbuffers v2.0.0+incompatible h1:dicJ2oXwypfwUGnB2/TYWYEKiuk9eYQlQO/AnOHl5mI= +github.com/google/flatbuffers v2.0.0+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -153,8 +213,10 @@ github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB7 github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hrfee/mediabrowser v0.3.8 h1:y0iBCb6jE3QKcsiCJSYva2fFPHRn4UA+sGRzoPuJ/Dk= github.com/hrfee/mediabrowser v0.3.8/go.mod h1:PnHZbdxmbv1wCVdAQyM7nwPwpVj9fdKx2EcET7sAk+U= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/itchyny/timefmt-go v0.1.5 h1:G0INE2la8S6ru/ZI5JecgyzbbJNs5lG1RcBqa7Jm6GE= github.com/itchyny/timefmt-go v0.1.5/go.mod h1:nEP7L+2YmAbT2kZ2HfSs1d8Xtw9LY8D2stDBckWakZ8= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -165,6 +227,11 @@ github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/klauspost/compress v1.13.1 h1:wXr2uRxZTJXHLly6qhJabee5JqIhTRoLBhDOA74hDEQ= +github.com/klauspost/compress v1.13.1/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= @@ -185,6 +252,7 @@ github.com/lithammer/shortuuid/v3 v3.0.7 h1:trX0KTHy4Pbwo/6ia8fscyHoGA+mf1jWbPJV github.com/lithammer/shortuuid/v3 v3.0.7/go.mod h1:vMk8ke37EmiewwolSO1NLW8vP4ZaKlRuDIi8tWWmAts= github.com/lxn/walk v0.0.0-20210112085537-c389da54e794/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ= github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailgun/mailgun-go/v4 v4.9.0 h1:wRbxvVQ5QObFewLxc1uVvipA16D8gxeiO+cBOca51Iw= github.com/mailgun/mailgun-go/v4 v4.9.0/go.mod h1:FJlF9rI5cQT+mrwujtJjPMbIVy3Ebor9bKTVsJ0QU40= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -206,6 +274,8 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -216,6 +286,7 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw= github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= @@ -225,6 +296,7 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/robert-nix/ansihtml v1.0.1 h1:VTiyQ6/+AxSJoSSLsMecnkh8i0ZqOEdiRl/odOc64fc= github.com/robert-nix/ansihtml v1.0.1/go.mod h1:CJwclxYaTPc2RfcxtanEACsYuTksh4yDXcNeHHKZINE= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= @@ -233,10 +305,19 @@ github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6po github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc= github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/steambap/captcha v1.4.1 h1:OmMdxLCWCqJvsFaFYwRpvMckIuvI6s8s1LsBrBw97P0= github.com/steambap/captcha v1.4.1/go.mod h1:oC9T7IfEgnrhzjDz5Djf1H7GPffCzRMbsQfFkJmhlnk= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -276,6 +357,9 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +github.com/timshannon/badgerhold/v3 v3.0.0-20210909134927-2b6764d68c1e/go.mod h1:/Seq5xGNo8jLhSbDX3jdbeZrp4yFIpQ6/7n4TjziEWs= +github.com/timshannon/badgerhold/v4 v4.0.2 h1:83OLY/NFnEaMnHEPd84bYtkLipVkjTsMbzQRYbk47g4= +github.com/timshannon/badgerhold/v4 v4.0.2/go.mod h1:rh6RyXLQFsvrvcKondPQQFZnNovpRzu+gS0FlLxYuHY= github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208 h1:PM5hJF7HVfNWmCjMdEfbuOBNXSVF2cMFGgQTPdKCbwM= github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208/go.mod h1:BzWtXXrXzZUvMacR0oF/fbDDgUPO8L36tDMmRAf14ns= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= @@ -285,6 +369,7 @@ github.com/ugorji/go v1.1.5-pre/go.mod h1:FwP/aQVg39TXzItUBMwnWp9T9gPQnXw4Poh4/o github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= github.com/ugorji/go/codec v0.0.0-20181022190402-e5e69e061d4f/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v1.1.5-pre/go.mod h1:tULtS6Gy1AE1yCENaw4Vb//HLH5njI2tfCQDUqRd8fI= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= @@ -296,8 +381,14 @@ github.com/writeas/go-strip-markdown v2.0.1+incompatible h1:IIqxTM5Jr7RzhigcL6Fk github.com/writeas/go-strip-markdown v2.0.1+incompatible/go.mod h1:Rsyu10ZhbEK9pXdk8V6MVnZmTzRG0alMNLMwa0J01fE= github.com/xhit/go-simple-mail/v2 v2.13.0 h1:OANWU9jHZrVfBkNkvLf8Ww0fexwpQVF/v/5f96fFTLI= github.com/xhit/go-simple-mail/v2 v2.13.0/go.mod h1:b7P5ygho6SYE+VIqpxA6QkYfv4teeyG4MKqB3utRu98= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/otel v1.9.0/go.mod h1:np4EoPGzoPs3O67xUVNoPPcmSvsfOxNlNA4F4AC+0Eo= go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s= go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4= @@ -320,26 +411,37 @@ go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc= golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/image v0.7.0 h1:gzS29xtG1J5ybQlv0PuyfE3nmc6R4qB73m6LUUmvFuw= golang.org/x/image v0.7.0/go.mod h1:nd/q4ef1AKKYl/4kft7g+6UyGbdiqWqTP1ZAbRoV7Rg= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -347,29 +449,45 @@ golang.org/x/net v0.0.0-20190611141213-3f473d35a33a/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -396,11 +514,16 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606050223-4d9ae51c2468/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190611222205-d73e1c7e250b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= @@ -410,13 +533,37 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/Knetic/govaluate.v3 v3.0.0/go.mod h1:csKLBORsPbafmSCGTEh3U7Ozmsuq8ZSIlKk1bcqph0E= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -435,6 +582,8 @@ gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= maunium.net/go/maulogger/v2 v2.4.1 h1:N7zSdd0mZkB2m2JtFUsiGTQQAdP0YeFWT7YMc80yAL8= maunium.net/go/maulogger/v2 v2.4.1/go.mod h1:omPuYwYBILeVQobz8uO3XC8DIRuEb5rXYlQSuqrbCho= maunium.net/go/mautrix v0.15.2 h1:fUiVajeoOR92uJoSShHbCvh7uG6lDY4ZO4Mvt90LbjU= diff --git a/lang/admin/en-us.json b/lang/admin/en-us.json index 3ff043e..db9773f 100644 --- a/lang/admin/en-us.json +++ b/lang/admin/en-us.json @@ -79,7 +79,7 @@ "ombiProfile": "Ombi user profile", "ombiUserDefaultsDescription": "Create an Ombi user and configure it, then select it below. It's settings/permissions will be stored and applied to new Ombi users created by jfa-go when this profile is selected.", "userProfiles": "User Profiles", - "userProfilesDescription": "Profiles are applied to users when they create an account. A profile include library access rights and homescreen layout.", + "userProfilesDescription": "Profiles are applied to users when they create an account. A profile includes library access rights and homescreen layout.", "userProfilesIsDefault": "Default", "userProfilesLibraries": "Libraries", "addProfile": "Add Profile", diff --git a/main.go b/main.go index ef5b1c1..bfc8e8f 100644 --- a/main.go +++ b/main.go @@ -335,59 +335,8 @@ func start(asDaemon, firstCall bool) { app.debug.Printf("Loaded config file \"%s\"", app.configPath) - app.debug.Println("Loading storage") - - app.storage.invite_path = app.config.Section("files").Key("invites").String() - if err := app.storage.loadInvites(); err != nil { - app.err.Printf("Failed to load Invites: %v", err) - } - app.storage.emails_path = app.config.Section("files").Key("emails").String() - if err := app.storage.loadEmails(); err != nil { - app.err.Printf("Failed to load Emails: %v", err) - err := migrateEmailStorage(app) - if err != nil { - app.err.Printf("Failed to migrate Email storage: %v", err) - } - } - app.storage.policy_path = app.config.Section("files").Key("user_template").String() - if err := app.storage.loadPolicy(); err != nil { - app.err.Printf("Failed to load Policy: %v", err) - } - app.storage.configuration_path = app.config.Section("files").Key("user_configuration").String() - if err := app.storage.loadConfiguration(); err != nil { - app.err.Printf("Failed to load Configuration: %v", err) - } - app.storage.displayprefs_path = app.config.Section("files").Key("user_displayprefs").String() - if err := app.storage.loadDisplayprefs(); err != nil { - app.err.Printf("Failed to load Displayprefs: %v", err) - } - app.storage.users_path = app.config.Section("files").Key("users").String() - if err := app.storage.loadUsers(); err != nil { - app.err.Printf("Failed to load Users: %v", err) - } - app.storage.telegram_path = app.config.Section("files").Key("telegram_users").String() - if err := app.storage.loadTelegramUsers(); err != nil { - app.err.Printf("Failed to load Telegram users: %v", err) - } - app.storage.discord_path = app.config.Section("files").Key("discord_users").String() - if err := app.storage.loadDiscordUsers(); err != nil { - app.err.Printf("Failed to load Discord users: %v", err) - } - app.storage.matrix_path = app.config.Section("files").Key("matrix_users").String() - if err := app.storage.loadMatrixUsers(); err != nil { - app.err.Printf("Failed to load Matrix users: %v", err) - } - app.storage.announcements_path = app.config.Section("files").Key("announcements").String() - if err := app.storage.loadAnnouncements(); err != nil { - app.err.Printf("Failed to load announcement templates: %v", err) - } - - app.storage.profiles_path = app.config.Section("files").Key("user_profiles").String() - app.storage.loadProfiles() - if app.config.Section("ombi").Key("enabled").MustBool(false) { - app.storage.ombi_path = app.config.Section("files").Key("ombi_template").String() - app.storage.loadOmbiTemplate() + app.debug.Printf("Connecting to Ombi") ombiServer := app.config.Section("ombi").Key("server").String() app.ombi = ombi.NewOmbi( ombiServer, @@ -397,6 +346,9 @@ func start(asDaemon, firstCall bool) { } + app.storage.db_path = filepath.Join(app.dataPath, "db") + app.ConnectDB() + defer app.storage.db.Close() // Read config-base for settings on web. app.configBasePath = "config-base.json" configBase, _ := fs.ReadFile(localFS, app.configBasePath) diff --git a/matrix.go b/matrix.go index 9935a2c..d20814c 100644 --- a/matrix.go +++ b/matrix.go @@ -6,6 +6,7 @@ import ( "time" "github.com/gomarkdown/markdown" + "github.com/timshannon/badgerhold/v4" "maunium.net/go/mautrix" "maunium.net/go/mautrix/event" "maunium.net/go/mautrix/id" @@ -31,11 +32,12 @@ type UnverifiedUser struct { } type MatrixUser struct { - RoomID string - Encrypted bool - UserID string - Lang string - Contact bool + RoomID string + Encrypted bool + UserID string + Lang string + Contact bool + JellyfinID string `badgerhold:"key"` } var matrixFilter = mautrix.Filter{ @@ -268,6 +270,12 @@ func (d *MatrixDaemon) Send(message *Message, users ...MatrixUser) (err error) { return } +// UserExists returns whether or not a user with the given User ID exists. +func (d *MatrixDaemon) UserExists(userID string) bool { + c, err := d.app.storage.db.Count(&MatrixUser{}, badgerhold.Where("UserID").Eq(userID)) + return err != nil || c > 0 +} + // User enters ID on sign-up, a PIN is sent to them. They enter it on sign-up. // Message the user first, to avoid E2EE by default diff --git a/migrations.go b/migrations.go index 95f69eb..0b81aea 100644 --- a/migrations.go +++ b/migrations.go @@ -16,26 +16,28 @@ func runMigrations(app *appContext) { migrateNotificationMethods(app) linkExistingOmbiDiscordTelegram(app) // migrateHyphens(app) + migrateToBadger(app) } // Migrate pre-0.2.0 user templates to profiles func migrateProfiles(app *appContext) { - if !(app.storage.policy.BlockedTags == nil && app.storage.configuration.GroupedFolders == nil && len(app.storage.displayprefs) == 0) { - app.info.Println("Migrating user template files to new profile format") - app.storage.migrateToProfile() - for _, path := range [3]string{app.storage.policy_path, app.storage.configuration_path, app.storage.displayprefs_path} { - if _, err := os.Stat(path); !os.IsNotExist(err) { - dir, fname := filepath.Split(path) - newFname := strings.Replace(fname, ".json", ".old.json", 1) - err := os.Rename(path, filepath.Join(dir, newFname)) - if err != nil { - app.err.Fatalf("Failed to rename %s: %s", fname, err) - } + if app.storage.deprecatedPolicy.BlockedTags == nil && app.storage.deprecatedConfiguration.GroupedFolders == nil && len(app.storage.deprecatedDisplayprefs) == 0 { + return + } + app.info.Println("Migrating user template files to new profile format") + app.storage.migrateToProfile() + for _, path := range [3]string{app.storage.policy_path, app.storage.configuration_path, app.storage.displayprefs_path} { + if _, err := os.Stat(path); !os.IsNotExist(err) { + dir, fname := filepath.Split(path) + newFname := strings.Replace(fname, ".json", ".old.json", 1) + err := os.Rename(path, filepath.Join(dir, newFname)) + if err != nil { + app.err.Fatalf("Failed to rename %s: %s", fname, err) } } - app.info.Println("In case of a problem, your original files have been renamed to