1
0
mirror of https://github.com/hrfee/jfa-go.git synced 2024-09-19 19:00:11 +00:00

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.
This commit is contained in:
Harvey Tindall 2024-07-28 16:53:27 +01:00
parent cd98e51ea9
commit 48a2058e81
Signed by: hrfee
GPG Key ID: BBC65952848FB1A2
7 changed files with 53 additions and 27 deletions

View File

@ -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) values = app.email.deletedValues(app.storage.lang.Email[lang].Strings.get("reason"), app, false)
case "UserExpiryAdjusted": case "UserExpiryAdjusted":
if noContent { 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": case "InviteEmail":
if noContent { if noContent {
msg, err = app.email.constructInvite("", Invite{}, app, true) msg, err = app.email.constructInvite("", Invite{}, app, true)

View File

@ -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) { if emailEnabled && app.config.Section("notifications").Key("enabled").MustBool(false) {
for address, settings := range invite.Notify { for address, settings := range invite.Notify {
if settings["notify-creation"] { if settings["notify-creation"] {
go func() { go func(addr string) {
msg, err := app.email.constructCreated(req.Code, req.Username, req.Email, invite, app, false) msg, err := app.email.constructCreated(req.Code, req.Username, req.Email, invite, app, false)
if err != nil { if err != nil {
app.err.Printf("%s: Failed to construct user creation notification: %v", req.Code, err) app.err.Printf("%s: Failed to construct user creation notification: %v", req.Code, err)
} else { } else {
// Check whether notify "address" is an email address of Jellyfin ID // Check whether notify "addr" is an email address of Jellyfin ID
if strings.Contains(address, "@") { if strings.Contains(addr, "@") {
err = app.email.send(msg, address) err = app.email.send(msg, addr)
} else { } else {
err = app.sendByID(msg, address) err = app.sendByID(msg, addr)
} }
if err != nil { if err != nil {
app.err.Printf("%s: Failed to send user creation notification: %v", req.Code, err) app.err.Printf("%s: Failed to send user creation notification: %v", req.Code, err)
} else { } 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) expiry.Expiry = base.AddDate(0, req.Months, req.Days).Add(time.Duration(((60 * req.Hours) + req.Minutes)) * time.Minute)
} }
app.storage.SetUserExpiryKey(id, expiry) 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) respondBool(204, true, gc)
} }

View File

@ -741,7 +741,7 @@ func (emailer *Emailer) constructEnabled(reason string, app *appContext, noSub b
return email, nil 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{}{ template := map[string]interface{}{
"yourExpiryWasAdjusted": emailer.lang.UserExpiryAdjusted.get("yourExpiryWasAdjusted"), "yourExpiryWasAdjusted": emailer.lang.UserExpiryAdjusted.get("yourExpiryWasAdjusted"),
"ifPreviouslyDisabled": emailer.lang.UserExpiryAdjusted.get("ifPreviouslyDisabled"), "ifPreviouslyDisabled": emailer.lang.UserExpiryAdjusted.get("ifPreviouslyDisabled"),
@ -750,6 +750,7 @@ func (emailer *Emailer) expiryAdjustedValues(expiry time.Time, reason string, ap
"message": "", "message": "",
} }
if noSub { if noSub {
template["helloUser"] = emailer.lang.Strings.get("helloUser")
empty := []string{"reason", "newExpiry"} empty := []string{"reason", "newExpiry"}
for _, v := range empty { for _, v := range empty {
template[v] = "{" + v + "}" template[v] = "{" + v + "}"
@ -757,6 +758,7 @@ func (emailer *Emailer) expiryAdjustedValues(expiry time.Time, reason string, ap
} else { } else {
template["reason"] = reason template["reason"] = reason
template["message"] = app.config.Section("messages").Key("message").String() template["message"] = app.config.Section("messages").Key("message").String()
template["helloUser"] = emailer.lang.Strings.template("helloUser", tmpl{"username": username})
exp := app.formatDatetime(expiry) exp := app.formatDatetime(expiry)
if !expiry.IsZero() { if !expiry.IsZero() {
if custom { if custom {
@ -771,7 +773,7 @@ func (emailer *Emailer) expiryAdjustedValues(expiry time.Time, reason string, ap
return template 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{ email := &Message{
Subject: app.config.Section("user_expiry").Key("adjustment_subject").MustString(emailer.lang.UserExpiryAdjusted.get("title")), 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{} var template map[string]interface{}
message := app.storage.MustGetCustomContentKey("UserExpiryAdjusted") message := app.storage.MustGetCustomContentKey("UserExpiryAdjusted")
if message.Enabled { if message.Enabled {
template = emailer.expiryAdjustedValues(expiry, reason, app, noSub, true) template = emailer.expiryAdjustedValues(username, expiry, reason, app, noSub, true)
} else { } else {
template = emailer.expiryAdjustedValues(expiry, reason, app, noSub, false) template = emailer.expiryAdjustedValues(username, expiry, reason, app, noSub, false)
} }
if noSub { if noSub {
template["newExpiry"] = emailer.lang.UserExpiryAdjusted.template("newExpiry", tmpl{ template["newExpiry"] = emailer.lang.UserExpiryAdjusted.template("newExpiry", tmpl{

View File

@ -60,6 +60,8 @@
<mj-section mj-class="bg"> <mj-section mj-class="bg">
<mj-column> <mj-column>
<mj-text mj-class="text" font-size="16px" font-family="Noto Sans, Helvetica, Arial, sans-serif"> <mj-text mj-class="text" font-size="16px" font-family="Noto Sans, Helvetica, Arial, sans-serif">
<p>{{ .helloUser }}</p>
<h3>{{ .yourExpiryWasAdjusted }}</h3> <h3>{{ .yourExpiryWasAdjusted }}</h3>
<p>{{ .ifPreviouslyDisabled }}</p> <p>{{ .ifPreviouslyDisabled }}</p>

View File

@ -1,3 +1,5 @@
{{ .helloUser }}
{{ .yourExpiryWasAdjusted }} {{ .yourExpiryWasAdjusted }}
{{ .ifPreviouslyDisabled }} {{ .ifPreviouslyDisabled }}
@ -7,4 +9,3 @@
{{ .reasonString }}: {{ .reason }} {{ .reasonString }}: {{ .reason }}
{{ .message }} {{ .message }}

View File

@ -261,12 +261,14 @@ type customEmailDTO struct {
} }
type extendExpiryDTO struct { type extendExpiryDTO struct {
Users []string `json:"users"` // List of user IDs to apply to. Users []string `json:"users"` // List of user IDs to apply to.
Months int `json:"months" example:"1"` // Number of months to add. Months int `json:"months" example:"1"` // Number of months to add.
Days int `json:"days" example:"1"` // Number of days to add. Days int `json:"days" example:"1"` // Number of days to add.
Hours int `json:"hours" example:"2"` // Number of hours to add. Hours int `json:"hours" example:"2"` // Number of hours to add.
Minutes int `json:"minutes" example:"3"` // Number of minutes 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. 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 { type checkUpdateDTO struct {

View File

@ -1684,22 +1684,25 @@ export class accountsList {
applyList.push(id); applyList.push(id);
} }
this._enableExpiryReason.classList.add("unfocused"); this._enableExpiryReason.classList.add("unfocused");
this._enableExpiryNotify.parentElement.classList.remove("unfocused");
this._enableExpiryNotify.checked = false;
this._enableExpiryReason.value = "";
let header: string; let header: string;
if (enableUser) { if (enableUser) {
header = window.lang.quantity("reEnableUsers", list.length); header = window.lang.quantity("reEnableUsers", list.length);
this._enableExpiryNotify.parentElement.classList.remove("unfocused");
this._enableExpiryNotify.checked = false;
this._enableExpiryReason.value = "";
} else if (this._settingExpiry) { } else if (this._settingExpiry) {
header = window.lang.quantity("setExpiry", list.length); header = window.lang.quantity("setExpiry", list.length);
this._enableExpiryNotify.parentElement.classList.add("unfocused"); // this._enableExpiryNotify.parentElement.classList.add("unfocused");
} else { } else {
header = window.lang.quantity("extendExpiry", applyList.length); 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; document.getElementById("header-extend-expiry").textContent = header;
const extend = () => { 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) { if (this._usingExtendExpiryTextInput) {
let date = (Date as any).fromString(this._extendExpiryTextInput.value) as Date; let date = (Date as any).fromString(this._extendExpiryTextInput.value) as Date;
send["timestamp"] = Math.floor(date.getTime() / 1000); send["timestamp"] = Math.floor(date.getTime() / 1000);
@ -1728,7 +1731,7 @@ export class accountsList {
this._extendExpiryForm.onsubmit = (event: Event) => { this._extendExpiryForm.onsubmit = (event: Event) => {
event.preventDefault(); event.preventDefault();
if (enableUser) { 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.readyState == 4) {
if (req.status != 200 && req.status != 204) { if (req.status != 200 && req.status != 204) {
window.modals.extendExpiry.close(); window.modals.extendExpiry.close();