diff --git a/api-messages.go b/api-messages.go index b4ea494..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) diff --git a/email.go b/email.go index d74613b..0f517ca 100644 --- a/email.go +++ b/email.go @@ -331,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, ) @@ -414,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, ) @@ -453,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, ) @@ -507,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, ) @@ -580,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, ) @@ -621,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, ) @@ -662,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, ) @@ -703,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, ) @@ -758,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) @@ -768,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) @@ -803,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, ) diff --git a/migrations.go b/migrations.go index 2c856a2..64cba42 100644 --- a/migrations.go +++ b/migrations.go @@ -241,6 +241,45 @@ func migrateToBadger(app *appContext) { for k, v := range app.storage.deprecatedProfiles { app.storage.SetProfileKey(k, v) } + + app.storage.loadCustomEmails() + app.storage.loadUserPageContent() + if _, ok := app.storage.GetCustomContentKey("UserCreated"); !ok { + app.storage.SetCustomContentKey("UserCreated", app.storage.deprecatedCustomEmails.UserCreated) + } + if _, ok := app.storage.GetCustomContentKey("InviteExpiry"); !ok { + app.storage.SetCustomContentKey("InviteExpiry", app.storage.deprecatedCustomEmails.InviteExpiry) + } + if _, ok := app.storage.GetCustomContentKey("PasswordReset"); !ok { + app.storage.SetCustomContentKey("PasswordReset", app.storage.deprecatedCustomEmails.PasswordReset) + } + if _, ok := app.storage.GetCustomContentKey("UserDeleted"); !ok { + app.storage.SetCustomContentKey("UserDeleted", app.storage.deprecatedCustomEmails.UserDeleted) + } + if _, ok := app.storage.GetCustomContentKey("UserDisabled"); !ok { + app.storage.SetCustomContentKey("UserDisabled", app.storage.deprecatedCustomEmails.UserDisabled) + } + if _, ok := app.storage.GetCustomContentKey("UserEnabled"); !ok { + app.storage.SetCustomContentKey("UserEnabled", app.storage.deprecatedCustomEmails.UserEnabled) + } + if _, ok := app.storage.GetCustomContentKey("InviteEmail"); !ok { + app.storage.SetCustomContentKey("InviteEmail", app.storage.deprecatedCustomEmails.InviteEmail) + } + if _, ok := app.storage.GetCustomContentKey("WelcomeEmail"); !ok { + app.storage.SetCustomContentKey("WelcomeEmail", app.storage.deprecatedCustomEmails.WelcomeEmail) + } + if _, ok := app.storage.GetCustomContentKey("EmailConfirmation"); !ok { + app.storage.SetCustomContentKey("EmailConfirmation", app.storage.deprecatedCustomEmails.EmailConfirmation) + } + if _, ok := app.storage.GetCustomContentKey("UserExpired"); !ok { + app.storage.SetCustomContentKey("UserExpired", app.storage.deprecatedCustomEmails.UserExpired) + } + if _, ok := app.storage.GetCustomContentKey("UserLogin"); !ok { + app.storage.SetCustomContentKey("UserLogin", app.storage.deprecatedUserPageContent.Login) + } + if _, ok := app.storage.GetCustomContentKey("UserPage"); !ok { + app.storage.SetCustomContentKey("UserPage", app.storage.deprecatedUserPageContent.Page) + } } // Migrate between hyphenated & non-hyphenated user IDs. Doesn't seem to happen anymore, so disabled. diff --git a/storage.go b/storage.go index 4729743..8a21d78 100644 --- a/storage.go +++ b/storage.go @@ -40,12 +40,12 @@ type Storage struct { deprecatedTelegram telegramStore // Map of Jellyfin User IDs to telegram users. deprecatedDiscord discordStore // Map of Jellyfin user IDs to discord users. deprecatedMatrix matrixStore // Map of Jellyfin user IDs to Matrix users. - customEmails customEmails - userPage userPageContent deprecatedPolicy mediabrowser.Policy deprecatedConfiguration mediabrowser.Configuration - lang Lang deprecatedAnnouncements map[string]announcementTemplate + deprecatedCustomEmails customEmails + deprecatedUserPageContent userPageContent + lang Lang } func (app *appContext) ConnectDB() { @@ -368,6 +368,49 @@ func (st *Storage) GetDefaultProfile() Profile { return defaultProfile } +// GetCustomContent returns a copy of the store. +func (st *Storage) GetCustomContent() []CustomContent { + result := []CustomContent{} + err := st.db.Find(&result, &badgerhold.Query{}) + if err != nil { + // fmt.Printf("Failed to find custom content: %v\n", err) + } + return result +} + +// GetCustomContentKey returns the value stored in the store's key. +func (st *Storage) GetCustomContentKey(k string) (CustomContent, bool) { + result := CustomContent{} + err := st.db.Get(k, &result) + ok := true + if err != nil { + // fmt.Printf("Failed to find custom content: %v\n", err) + ok = false + } + return result, ok +} + +// MustGetCustomContentKey returns the value stored in the store's key, or an empty value. +func (st *Storage) MustGetCustomContentKey(k string) CustomContent { + result := CustomContent{} + st.db.Get(k, &result) + return result +} + +// SetCustomContentKey stores value v in key k. +func (st *Storage) SetCustomContentKey(k string, v CustomContent) { + v.Name = k + err := st.db.Upsert(k, v) + if err != nil { + // fmt.Printf("Failed to set custom content: %v\n", err) + } +} + +// DeleteCustomContentKey deletes value at key k. +func (st *Storage) DeleteCustomContentKey(k string) { + st.db.Delete(k, CustomContent{}) +} + type TelegramUser struct { JellyfinID string `badgerhold:"key"` ChatID int64 `badgerhold:"index"` @@ -395,19 +438,21 @@ type EmailAddress struct { } type customEmails struct { - UserCreated customContent `json:"userCreated"` - InviteExpiry customContent `json:"inviteExpiry"` - PasswordReset customContent `json:"passwordReset"` - UserDeleted customContent `json:"userDeleted"` - UserDisabled customContent `json:"userDisabled"` - UserEnabled customContent `json:"userEnabled"` - InviteEmail customContent `json:"inviteEmail"` - WelcomeEmail customContent `json:"welcomeEmail"` - EmailConfirmation customContent `json:"emailConfirmation"` - UserExpired customContent `json:"userExpired"` + UserCreated CustomContent `json:"userCreated"` + InviteExpiry CustomContent `json:"inviteExpiry"` + PasswordReset CustomContent `json:"passwordReset"` + UserDeleted CustomContent `json:"userDeleted"` + UserDisabled CustomContent `json:"userDisabled"` + UserEnabled CustomContent `json:"userEnabled"` + InviteEmail CustomContent `json:"inviteEmail"` + WelcomeEmail CustomContent `json:"welcomeEmail"` + EmailConfirmation CustomContent `json:"emailConfirmation"` + UserExpired CustomContent `json:"userExpired"` } -type customContent struct { +// CustomContent stores customized versions of jfa-go content, including emails and user messages. +type CustomContent struct { + Name string `json:"name" badgerhold:"key"` Enabled bool `json:"enabled,omitempty"` Content string `json:"content"` Variables []string `json:"variables,omitempty"` @@ -415,8 +460,8 @@ type customContent struct { } type userPageContent struct { - Login customContent `json:"login"` - Page customContent `json:"page"` + Login CustomContent `json:"login"` + Page CustomContent `json:"page"` } // timePattern: %Y-%m-%dT%H:%M:%S.%f @@ -1197,19 +1242,19 @@ func (st *Storage) storeMatrixUsers() error { } func (st *Storage) loadCustomEmails() error { - return loadJSON(st.customEmails_path, &st.customEmails) + return loadJSON(st.customEmails_path, &st.deprecatedCustomEmails) } func (st *Storage) storeCustomEmails() error { - return storeJSON(st.customEmails_path, st.customEmails) + return storeJSON(st.customEmails_path, st.deprecatedCustomEmails) } func (st *Storage) loadUserPageContent() error { - return loadJSON(st.userPage_path, &st.userPage) + return loadJSON(st.userPage_path, &st.deprecatedUserPageContent) } func (st *Storage) storeUserPageContent() error { - return storeJSON(st.userPage_path, st.userPage) + return storeJSON(st.userPage_path, st.deprecatedUserPageContent) } func (st *Storage) loadPolicy() error { diff --git a/views.go b/views.go index b9df9a3..f2e2dbc 100644 --- a/views.go +++ b/views.go @@ -224,13 +224,13 @@ func (app *appContext) MyUserPage(gc *gin.Context) { data["discordInviteLink"] = app.discord.inviteChannelName != "" } - pageMessages := map[string]*customContent{ - "Login": app.getCustomMessage("UserLogin"), - "Page": app.getCustomMessage("UserPage"), - } + pageMessagesExist := map[string]bool{} + pageMessages := map[string]CustomContent{} + pageMessages["Login"], pageMessagesExist["Login"] = app.storage.GetCustomContentKey("UserLogin") + pageMessages["Page"], pageMessagesExist["Page"] = app.storage.GetCustomContentKey("UserPage") for name, msg := range pageMessages { - if msg == nil { + if !pageMessagesExist[name] { continue } data[name+"MessageEnabled"] = msg.Enabled