From 48a2058e811c40cecba042dd4bd98709d56525ca Mon Sep 17 00:00:00 2001 From: Harvey Tindall Date: Sun, 28 Jul 2024 16:53:27 +0100 Subject: [PATCH] accounts: notify users of expiry adjustment "Send notification message" in the extend expiry dialog will send a message to the user with their new expiry. For #345. --- api-messages.go | 4 ++-- api-users.go | 30 +++++++++++++++++++++++------- email.go | 10 ++++++---- mail/expiry-adjusted.mjml | 2 ++ mail/expiry-adjusted.txt | 3 ++- models.go | 14 ++++++++------ ts/modules/accounts.ts | 17 ++++++++++------- 7 files changed, 53 insertions(+), 27 deletions(-) diff --git a/api-messages.go b/api-messages.go index 499c1ff..e4b60e7 100644 --- a/api-messages.go +++ b/api-messages.go @@ -187,9 +187,9 @@ func (app *appContext) GetCustomMessageTemplate(gc *gin.Context) { values = app.email.deletedValues(app.storage.lang.Email[lang].Strings.get("reason"), app, false) case "UserExpiryAdjusted": if noContent { - msg, err = app.email.constructExpiryAdjusted(time.Time{}, "", app, true) + msg, err = app.email.constructExpiryAdjusted("", time.Time{}, "", app, true) } - values = app.email.expiryAdjustedValues(time.Now(), app.storage.lang.Email[lang].Strings.get("reason"), app, false, true) + values = app.email.expiryAdjustedValues(username, time.Now(), app.storage.lang.Email[lang].Strings.get("reason"), app, false, true) case "InviteEmail": if noContent { msg, err = app.email.constructInvite("", Invite{}, app, true) diff --git a/api-users.go b/api-users.go index 8af1efc..64d1997 100644 --- a/api-users.go +++ b/api-users.go @@ -293,24 +293,24 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool, gc *gin.Context) if emailEnabled && app.config.Section("notifications").Key("enabled").MustBool(false) { for address, settings := range invite.Notify { if settings["notify-creation"] { - go func() { + go func(addr string) { msg, err := app.email.constructCreated(req.Code, req.Username, req.Email, invite, app, false) if err != nil { app.err.Printf("%s: Failed to construct user creation notification: %v", req.Code, err) } else { - // Check whether notify "address" is an email address of Jellyfin ID - if strings.Contains(address, "@") { - err = app.email.send(msg, address) + // Check whether notify "addr" is an email address of Jellyfin ID + if strings.Contains(addr, "@") { + err = app.email.send(msg, addr) } else { - err = app.sendByID(msg, address) + err = app.sendByID(msg, addr) } if err != nil { app.err.Printf("%s: Failed to send user creation notification: %v", req.Code, err) } else { - app.info.Printf("Sent user creation notification to %s", address) + app.info.Printf("Sent user creation notification to %s", addr) } } - }() + }(address) } } } @@ -739,6 +739,22 @@ func (app *appContext) ExtendExpiry(gc *gin.Context) { expiry.Expiry = base.AddDate(0, req.Months, req.Days).Add(time.Duration(((60 * req.Hours) + req.Minutes)) * time.Minute) } app.storage.SetUserExpiryKey(id, expiry) + if messagesEnabled && req.Notify { + go func(uid string, exp time.Time) { + user, status, err := app.jf.UserByID(uid, false) + if status != 200 || err != nil { + return + } + msg, err := app.email.constructExpiryAdjusted(user.Name, exp, req.Reason, app, false) + if err != nil { + app.err.Printf("%s: Failed to construct expiry adjustment notification: %v", uid, err) + return + } + if err := app.sendByID(msg, uid); err != nil { + app.err.Printf("%s: Failed to send expiry adjustment notification: %v", uid, err) + } + }(id, expiry.Expiry) + } } respondBool(204, true, gc) } diff --git a/email.go b/email.go index 505b71d..7a21a26 100644 --- a/email.go +++ b/email.go @@ -741,7 +741,7 @@ func (emailer *Emailer) constructEnabled(reason string, app *appContext, noSub b return email, nil } -func (emailer *Emailer) expiryAdjustedValues(expiry time.Time, reason string, app *appContext, noSub bool, custom bool) map[string]interface{} { +func (emailer *Emailer) expiryAdjustedValues(username string, expiry time.Time, reason string, app *appContext, noSub bool, custom bool) map[string]interface{} { template := map[string]interface{}{ "yourExpiryWasAdjusted": emailer.lang.UserExpiryAdjusted.get("yourExpiryWasAdjusted"), "ifPreviouslyDisabled": emailer.lang.UserExpiryAdjusted.get("ifPreviouslyDisabled"), @@ -750,6 +750,7 @@ func (emailer *Emailer) expiryAdjustedValues(expiry time.Time, reason string, ap "message": "", } if noSub { + template["helloUser"] = emailer.lang.Strings.get("helloUser") empty := []string{"reason", "newExpiry"} for _, v := range empty { template[v] = "{" + v + "}" @@ -757,6 +758,7 @@ func (emailer *Emailer) expiryAdjustedValues(expiry time.Time, reason string, ap } else { template["reason"] = reason template["message"] = app.config.Section("messages").Key("message").String() + template["helloUser"] = emailer.lang.Strings.template("helloUser", tmpl{"username": username}) exp := app.formatDatetime(expiry) if !expiry.IsZero() { if custom { @@ -771,7 +773,7 @@ func (emailer *Emailer) expiryAdjustedValues(expiry time.Time, reason string, ap return template } -func (emailer *Emailer) constructExpiryAdjusted(expiry time.Time, reason string, app *appContext, noSub bool) (*Message, error) { +func (emailer *Emailer) constructExpiryAdjusted(username string, expiry time.Time, reason string, app *appContext, noSub bool) (*Message, error) { email := &Message{ Subject: app.config.Section("user_expiry").Key("adjustment_subject").MustString(emailer.lang.UserExpiryAdjusted.get("title")), } @@ -779,9 +781,9 @@ func (emailer *Emailer) constructExpiryAdjusted(expiry time.Time, reason string, var template map[string]interface{} message := app.storage.MustGetCustomContentKey("UserExpiryAdjusted") if message.Enabled { - template = emailer.expiryAdjustedValues(expiry, reason, app, noSub, true) + template = emailer.expiryAdjustedValues(username, expiry, reason, app, noSub, true) } else { - template = emailer.expiryAdjustedValues(expiry, reason, app, noSub, false) + template = emailer.expiryAdjustedValues(username, expiry, reason, app, noSub, false) } if noSub { template["newExpiry"] = emailer.lang.UserExpiryAdjusted.template("newExpiry", tmpl{ diff --git a/mail/expiry-adjusted.mjml b/mail/expiry-adjusted.mjml index ef30478..55ddf7f 100644 --- a/mail/expiry-adjusted.mjml +++ b/mail/expiry-adjusted.mjml @@ -60,6 +60,8 @@ +

{{ .helloUser }}

+

{{ .yourExpiryWasAdjusted }}

{{ .ifPreviouslyDisabled }}

diff --git a/mail/expiry-adjusted.txt b/mail/expiry-adjusted.txt index b64b2b9..28b5f69 100644 --- a/mail/expiry-adjusted.txt +++ b/mail/expiry-adjusted.txt @@ -1,3 +1,5 @@ +{{ .helloUser }} + {{ .yourExpiryWasAdjusted }} {{ .ifPreviouslyDisabled }} @@ -7,4 +9,3 @@ {{ .reasonString }}: {{ .reason }} {{ .message }} - diff --git a/models.go b/models.go index f1873f4..714be96 100644 --- a/models.go +++ b/models.go @@ -261,12 +261,14 @@ type customEmailDTO struct { } type extendExpiryDTO struct { - Users []string `json:"users"` // List of user IDs to apply to. - Months int `json:"months" example:"1"` // Number of months to add. - Days int `json:"days" example:"1"` // Number of days to add. - Hours int `json:"hours" example:"2"` // Number of hours to add. - Minutes int `json:"minutes" example:"3"` // Number of minutes to add. - Timestamp int64 `json:"timestamp"` // Optional, exact time to expire at. Overrides other fields. + Users []string `json:"users"` // List of user IDs to apply to. + Months int `json:"months" example:"1"` // Number of months to add. + Days int `json:"days" example:"1"` // Number of days to add. + Hours int `json:"hours" example:"2"` // Number of hours to add. + Minutes int `json:"minutes" example:"3"` // Number of minutes to add. + Timestamp int64 `json:"timestamp"` // Optional, exact time to expire at. Overrides other fields. + Notify bool `json:"notify"` // Whether to message the user(s) about the change. + Reason string `json:"reason" example:"i felt like it"` // Reason for adjustment. } type checkUpdateDTO struct { diff --git a/ts/modules/accounts.ts b/ts/modules/accounts.ts index 95a39e9..e90b3ba 100644 --- a/ts/modules/accounts.ts +++ b/ts/modules/accounts.ts @@ -1684,22 +1684,25 @@ export class accountsList { applyList.push(id); } this._enableExpiryReason.classList.add("unfocused"); + this._enableExpiryNotify.parentElement.classList.remove("unfocused"); + this._enableExpiryNotify.checked = false; + this._enableExpiryReason.value = ""; let header: string; if (enableUser) { header = window.lang.quantity("reEnableUsers", list.length); - this._enableExpiryNotify.parentElement.classList.remove("unfocused"); - this._enableExpiryNotify.checked = false; - this._enableExpiryReason.value = ""; } else if (this._settingExpiry) { header = window.lang.quantity("setExpiry", list.length); - this._enableExpiryNotify.parentElement.classList.add("unfocused"); + // this._enableExpiryNotify.parentElement.classList.add("unfocused"); } else { header = window.lang.quantity("extendExpiry", applyList.length); - this._enableExpiryNotify.parentElement.classList.add("unfocused"); + // this._enableExpiryNotify.parentElement.classList.add("unfocused"); } document.getElementById("header-extend-expiry").textContent = header; const extend = () => { - let send = { "users": applyList, "timestamp": 0 } + let send = { "users": applyList, "timestamp": 0, "notify": this._enableExpiryNotify.checked } + if (this._enableExpiryNotify.checked) { + send["reason"] = this._enableExpiryReason.value; + } if (this._usingExtendExpiryTextInput) { let date = (Date as any).fromString(this._extendExpiryTextInput.value) as Date; send["timestamp"] = Math.floor(date.getTime() / 1000); @@ -1728,7 +1731,7 @@ export class accountsList { this._extendExpiryForm.onsubmit = (event: Event) => { event.preventDefault(); if (enableUser) { - this._enableDisableUsers(applyList, true, this._enableExpiryNotify.checked, this._enableExpiryNotify ? this._enableExpiryReason.value : null, (req: XMLHttpRequest) => { + this._enableDisableUsers(applyList, true, this._enableExpiryNotify.checked, this._enableExpiryNotify.checked ? this._enableExpiryReason.value : null, (req: XMLHttpRequest) => { if (req.readyState == 4) { if (req.status != 200 && req.status != 204) { window.modals.extendExpiry.close();