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

Compare commits

...

6 Commits

Author SHA1 Message Date
e44d11c58c
html: add noindex tag to header.html
should stop instances being indexed by search engines.
2024-07-28 17:10:06 +01:00
48a2058e81
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.
2024-07-28 16:53:27 +01:00
cd98e51ea9
adjust wording in expiry adjusted email
previously stated your account would be enabled, however the admin can
adjust expiry without re-enabling the user, so wording has been changed
to "your account may have been re-enabled.".
2024-07-28 16:52:35 +01:00
fbbb03a47d
email: add new "expiry adjusted email"
just the email at this point. Also wrote up a little guide on adding new
emails on wiki.jfa-go.com.
2024-07-28 16:02:47 +01:00
6f5fc0948a
css: fix inv creation card width on mobile
for #339. Cards were fixed at half-width, even when wrapping. Instead of
    fixing with breakpoints, remove the width specification and set each to
    "flex-grow: 1".
2024-07-28 13:49:25 +01:00
05d473dc97
mediabrowser: bump version for JF 10.9.8
Adds missing field(s). For #349.
2024-07-28 13:41:48 +01:00
18 changed files with 344 additions and 106 deletions

View File

@ -26,18 +26,19 @@ func (app *appContext) GetCustomContent(gc *gin.Context) {
adminLang = app.storage.lang.chosenAdminLang adminLang = app.storage.lang.chosenAdminLang
} }
list := emailListDTO{ list := emailListDTO{
"UserCreated": {Name: app.storage.lang.Email[lang].UserCreated["name"], Enabled: app.storage.MustGetCustomContentKey("UserCreated").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}, "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}, "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}, "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}, "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}, "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}, "UserExpiryAdjusted": {Name: app.storage.lang.Email[lang].UserExpiryAdjusted["name"], Enabled: app.storage.MustGetCustomContentKey("UserExpiryAdjusted").Enabled},
"WelcomeEmail": {Name: app.storage.lang.Email[lang].WelcomeEmail["name"], Enabled: app.storage.MustGetCustomContentKey("WelcomeEmail").Enabled}, "InviteEmail": {Name: app.storage.lang.Email[lang].InviteEmail["name"], Enabled: app.storage.MustGetCustomContentKey("InviteEmail").Enabled},
"EmailConfirmation": {Name: app.storage.lang.Email[lang].EmailConfirmation["name"], Enabled: app.storage.MustGetCustomContentKey("EmailConfirmation").Enabled}, "WelcomeEmail": {Name: app.storage.lang.Email[lang].WelcomeEmail["name"], Enabled: app.storage.MustGetCustomContentKey("WelcomeEmail").Enabled},
"UserExpired": {Name: app.storage.lang.Email[lang].UserExpired["name"], Enabled: app.storage.MustGetCustomContentKey("UserExpired").Enabled}, "EmailConfirmation": {Name: app.storage.lang.Email[lang].EmailConfirmation["name"], Enabled: app.storage.MustGetCustomContentKey("EmailConfirmation").Enabled},
"UserLogin": {Name: app.storage.lang.Admin[adminLang].Strings["userPageLogin"], Enabled: app.storage.MustGetCustomContentKey("UserLogin").Enabled}, "UserExpired": {Name: app.storage.lang.Email[lang].UserExpired["name"], Enabled: app.storage.MustGetCustomContentKey("UserExpired").Enabled},
"UserPage": {Name: app.storage.lang.Admin[adminLang].Strings["userPagePage"], Enabled: app.storage.MustGetCustomContentKey("UserPage").Enabled}, "UserLogin": {Name: app.storage.lang.Admin[adminLang].Strings["userPageLogin"], Enabled: app.storage.MustGetCustomContentKey("UserLogin").Enabled},
"UserPage": {Name: app.storage.lang.Admin[adminLang].Strings["userPagePage"], Enabled: app.storage.MustGetCustomContentKey("UserPage").Enabled},
} }
filter := gc.Query("filter") filter := gc.Query("filter")
@ -51,39 +52,6 @@ func (app *appContext) GetCustomContent(gc *gin.Context) {
gc.JSON(200, list) gc.JSON(200, list)
} }
// 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{}
case "UserCreated":
return &app.storage.customEmails.UserCreated
case "InviteExpiry":
return &app.storage.customEmails.InviteExpiry
case "PasswordReset":
return &app.storage.customEmails.PasswordReset
case "UserDeleted":
return &app.storage.customEmails.UserDeleted
case "UserDisabled":
return &app.storage.customEmails.UserDisabled
case "UserEnabled":
return &app.storage.customEmails.UserEnabled
case "InviteEmail":
return &app.storage.customEmails.InviteEmail
case "WelcomeEmail":
return &app.storage.customEmails.WelcomeEmail
case "EmailConfirmation":
return &app.storage.customEmails.EmailConfirmation
case "UserExpired":
return &app.storage.customEmails.UserExpired
case "UserLogin":
return &app.storage.userPage.Login
case "UserPage":
return &app.storage.userPage.Page
}
return nil
} */
// @Summary Sets the corresponding custom content. // @Summary Sets the corresponding custom content.
// @Produce json // @Produce json
// @Param CustomContent body CustomContent true "Content = email (in markdown)." // @Param CustomContent body CustomContent true "Content = email (in markdown)."
@ -217,6 +185,11 @@ func (app *appContext) GetCustomMessageTemplate(gc *gin.Context) {
msg, err = app.email.constructEnabled("", app, true) msg, err = app.email.constructEnabled("", app, true)
} }
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":
if noContent {
msg, err = app.email.constructExpiryAdjusted("", time.Time{}, "", app, 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

@ -105,6 +105,9 @@ func (app *appContext) loadConfig() error {
app.MustSetValue("user_expiry", "email_html", "jfa-go:"+"user-expired.html") app.MustSetValue("user_expiry", "email_html", "jfa-go:"+"user-expired.html")
app.MustSetValue("user_expiry", "email_text", "jfa-go:"+"user-expired.txt") app.MustSetValue("user_expiry", "email_text", "jfa-go:"+"user-expired.txt")
app.MustSetValue("user_expiry", "adjustment_email_html", "jfa-go:"+"expiry-adjusted.html")
app.MustSetValue("user_expiry", "adjustment_email_text", "jfa-go:"+"expiry-adjusted.txt")
app.MustSetValue("matrix", "topic", "Jellyfin notifications") app.MustSetValue("matrix", "topic", "Jellyfin notifications")
app.MustSetValue("matrix", "show_on_reg", "true") app.MustSetValue("matrix", "show_on_reg", "true")

View File

@ -1713,7 +1713,7 @@
"order": [], "order": [],
"meta": { "meta": {
"name": "User Expiry", "name": "User Expiry",
"description": "When set on an invite, users will be deleted or disabled a specified amount of time after they create their account." "description": "When set on an invite, users will be deleted or disabled a specified amount of time after they create their account. Expiries can also be set and extended for invididual users, optionally with a message why."
}, },
"settings": { "settings": {
"behaviour": { "behaviour": {
@ -1765,6 +1765,35 @@
"type": "text", "type": "text",
"value": "", "value": "",
"description": "Path to custom email in plain text" "description": "Path to custom email in plain text"
},
"adjustment_subject": {
"name": "Adjustment: email subject",
"required": false,
"requires_restart": false,
"depends_true": "messages|enabled",
"type": "text",
"value": "",
"description": "Subject of adjustment emails, sent optionally when setting/extending an expiry."
},
"adjustment_email_html": {
"name": "Adjustment: Custom email (HTML)",
"required": false,
"requires_restart": false,
"advanced": true,
"depends_true": "messages|enabled",
"type": "text",
"value": "",
"description": "Path to custom email html"
},
"adjustment_email_text": {
"name": "Adjustment: Custom email (plaintext)",
"required": false,
"requires_restart": false,
"advanced": true,
"depends_true": "messages|enabled",
"type": "text",
"value": "",
"description": "Path to custom email in plain text"
} }
} }
}, },

View File

@ -741,6 +741,72 @@ func (emailer *Emailer) constructEnabled(reason string, app *appContext, noSub b
return email, nil return email, nil
} }
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"),
"reasonString": emailer.lang.Strings.get("reason"),
"newExpiry": "",
"message": "",
}
if noSub {
template["helloUser"] = emailer.lang.Strings.get("helloUser")
empty := []string{"reason", "newExpiry"}
for _, v := range empty {
template[v] = "{" + v + "}"
}
} 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 {
template["newExpiry"] = exp
} else if !expiry.IsZero() {
template["newExpiry"] = emailer.lang.UserExpiryAdjusted.template("newExpiry", tmpl{
"date": exp,
})
}
}
}
return template
}
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")),
}
var err error
var template map[string]interface{}
message := app.storage.MustGetCustomContentKey("UserExpiryAdjusted")
if message.Enabled {
template = emailer.expiryAdjustedValues(username, expiry, reason, app, noSub, true)
} else {
template = emailer.expiryAdjustedValues(username, expiry, reason, app, noSub, false)
}
if noSub {
template["newExpiry"] = emailer.lang.UserExpiryAdjusted.template("newExpiry", tmpl{
"date": "{newExpiry}",
})
}
if message.Enabled {
content := templateEmail(
message.Content,
message.Variables,
nil,
template,
)
email, err = emailer.constructTemplate(email.Subject, content, app)
} else {
email.HTML, email.Text, email.Markdown, err = emailer.construct(app, "user_expiry", "adjustment_email_", template)
}
if err != nil {
return nil, err
}
return email, nil
}
func (emailer *Emailer) welcomeValues(username string, expiry time.Time, app *appContext, noSub bool, custom bool) map[string]interface{} { func (emailer *Emailer) welcomeValues(username string, expiry time.Time, app *appContext, noSub bool, custom bool) map[string]interface{} {
template := map[string]interface{}{ template := map[string]interface{}{
"welcome": emailer.lang.WelcomeEmail.get("welcome"), "welcome": emailer.lang.WelcomeEmail.get("welcome"),

6
go.mod
View File

@ -18,6 +18,7 @@ replace github.com/hrfee/jfa-go/easyproxy => ./easyproxy
require ( require (
github.com/bwmarrin/discordgo v0.27.1 github.com/bwmarrin/discordgo v0.27.1
github.com/dgraph-io/badger/v3 v3.2103.5
github.com/emersion/go-autostart v0.0.0-20210130080809-00ed301c8e9a github.com/emersion/go-autostart v0.0.0-20210130080809-00ed301c8e9a
github.com/fatih/color v1.15.0 github.com/fatih/color v1.15.0
github.com/fsnotify/fsnotify v1.6.0 github.com/fsnotify/fsnotify v1.6.0
@ -30,10 +31,11 @@ require (
github.com/gomarkdown/markdown v0.0.0-20230322041520-c84983bdbf2a github.com/gomarkdown/markdown v0.0.0-20230322041520-c84983bdbf2a
github.com/hrfee/jfa-go/common v0.0.0-20230626224816-f72960635dc3 github.com/hrfee/jfa-go/common v0.0.0-20230626224816-f72960635dc3
github.com/hrfee/jfa-go/docs v0.0.0-20230626224816-f72960635dc3 github.com/hrfee/jfa-go/docs v0.0.0-20230626224816-f72960635dc3
github.com/hrfee/jfa-go/easyproxy v0.0.0-00010101000000-000000000000
github.com/hrfee/jfa-go/linecache v0.0.0-20230626224816-f72960635dc3 github.com/hrfee/jfa-go/linecache v0.0.0-20230626224816-f72960635dc3
github.com/hrfee/jfa-go/logger v0.0.0-20230626224816-f72960635dc3 github.com/hrfee/jfa-go/logger v0.0.0-20230626224816-f72960635dc3
github.com/hrfee/jfa-go/ombi v0.0.0-20230626224816-f72960635dc3 github.com/hrfee/jfa-go/ombi v0.0.0-20230626224816-f72960635dc3
github.com/hrfee/mediabrowser v0.3.12 github.com/hrfee/mediabrowser v0.3.13
github.com/itchyny/timefmt-go v0.1.5 github.com/itchyny/timefmt-go v0.1.5
github.com/lithammer/shortuuid/v3 v3.0.7 github.com/lithammer/shortuuid/v3 v3.0.7
github.com/mailgun/mailgun-go/v4 v4.9.1 github.com/mailgun/mailgun-go/v4 v4.9.1
@ -54,7 +56,6 @@ require (
github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/dgraph-io/badger/v3 v3.2103.5 // indirect
github.com/dgraph-io/ristretto v0.1.1 // indirect github.com/dgraph-io/ristretto v0.1.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect
@ -87,7 +88,6 @@ require (
github.com/google/uuid v1.3.0 // indirect github.com/google/uuid v1.3.0 // indirect
github.com/gorilla/mux v1.8.0 // indirect github.com/gorilla/mux v1.8.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect
github.com/hrfee/jfa-go/easyproxy v0.0.0-00010101000000-000000000000 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.16.6 // indirect github.com/klauspost/compress v1.16.6 // indirect

13
go.sum
View File

@ -224,8 +224,8 @@ github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= 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/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/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hrfee/mediabrowser v0.3.12 h1:fqDxt1be3e+ZNjAtlKc8MTqg7peo6fuGCrk2wOXo20k= github.com/hrfee/mediabrowser v0.3.13 h1:NgQNbq+JWwsP68BdWXL/rwbpfE/oO5LJ5KVkE+aNbX8=
github.com/hrfee/mediabrowser v0.3.12/go.mod h1:PnHZbdxmbv1wCVdAQyM7nwPwpVj9fdKx2EcET7sAk+U= github.com/hrfee/mediabrowser v0.3.13/go.mod h1:PnHZbdxmbv1wCVdAQyM7nwPwpVj9fdKx2EcET7sAk+U=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 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 h1:G0INE2la8S6ru/ZI5JecgyzbbJNs5lG1RcBqa7Jm6GE=
github.com/itchyny/timefmt-go v0.1.5/go.mod h1:nEP7L+2YmAbT2kZ2HfSs1d8Xtw9LY8D2stDBckWakZ8= github.com/itchyny/timefmt-go v0.1.5/go.mod h1:nEP7L+2YmAbT2kZ2HfSs1d8Xtw9LY8D2stDBckWakZ8=
@ -394,8 +394,6 @@ github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijb
github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
github.com/writeas/go-strip-markdown v2.0.1+incompatible h1:IIqxTM5Jr7RzhigcL6FkrCNfXkvbR+Nbu1ls48pXYcw= github.com/writeas/go-strip-markdown v2.0.1+incompatible h1:IIqxTM5Jr7RzhigcL6FkrCNfXkvbR+Nbu1ls48pXYcw=
github.com/writeas/go-strip-markdown v2.0.1+incompatible/go.mod h1:Rsyu10ZhbEK9pXdk8V6MVnZmTzRG0alMNLMwa0J01fE= 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/xhit/go-simple-mail/v2 v2.16.0 h1:ouGy/Ww4kuaqu2E2UrDw7SvLaziWTB60ICLkIkNVccA= github.com/xhit/go-simple-mail/v2 v2.16.0 h1:ouGy/Ww4kuaqu2E2UrDw7SvLaziWTB60ICLkIkNVccA=
github.com/xhit/go-simple-mail/v2 v2.16.0/go.mod h1:b7P5ygho6SYE+VIqpxA6QkYfv4teeyG4MKqB3utRu98= github.com/xhit/go-simple-mail/v2 v2.16.0/go.mod h1:b7P5ygho6SYE+VIqpxA6QkYfv4teeyG4MKqB3utRu98=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
@ -437,8 +435,6 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= 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-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.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@ -478,8 +474,6 @@ golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 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.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.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@ -523,8 +517,6 @@ golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@ -536,7 +528,6 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=

View File

@ -554,7 +554,7 @@
<div class="card @low dark:~d_neutral"> <div class="card @low dark:~d_neutral">
<span class="heading">{{ .strings.create }}</span> <span class="heading">{{ .strings.create }}</span>
<div class="flex flex-col md:flex-row gap-3 mt-2" id="create-inv"> <div class="flex flex-col md:flex-row gap-3 mt-2" id="create-inv">
<div class="card ~neutral @low flex flex-col gap-2 w-1/2"> <div class="card ~neutral @low flex flex-col gap-2 grow">
<div class="flex flex-row gap-2"> <div class="flex flex-row gap-2">
<label class="w-1/2"> <label class="w-1/2">
<input type="radio" name="duration" class="unfocused" id="radio-inv-duration" checked> <input type="radio" name="duration" class="unfocused" id="radio-inv-duration" checked>
@ -662,7 +662,7 @@
<input type="text" id="create-user-label" class="input ~neutral @low"> <input type="text" id="create-user-label" class="input ~neutral @low">
</div> </div>
</div> </div>
<div class="card ~neutral @low flex flex-col justify-between gap-2 w-1/2"> <div class="card ~neutral @low flex flex-col justify-between gap-2 grow">
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<label class="label supra" for="create-uses">{{ .strings.inviteNumberOfUses }}</label> <label class="label supra" for="create-uses">{{ .strings.inviteNumberOfUses }}</label>

View File

@ -2,6 +2,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="Description" content="jfa-go, a better way to manage Jellyfin users."> <meta name="Description" content="jfa-go, a better way to manage Jellyfin users.">
<meta name="color-scheme" content="dark light"> <meta name="color-scheme" content="dark light">
<meta name="robots" content="noindex">
<link rel="apple-touch-icon" sizes="180x180" href="{{ .urlBase }}/apple-touch-icon.png"> <link rel="apple-touch-icon" sizes="180x180" href="{{ .urlBase }}/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="{{ .urlBase }}/favicon-32x32.png"> <link rel="icon" type="image/png" sizes="32x32" href="{{ .urlBase }}/favicon-32x32.png">

25
lang.go
View File

@ -93,18 +93,19 @@ func (ls *emailLangs) getOptions() [][2]string {
} }
type emailLang struct { type emailLang struct {
Meta langMeta `json:"meta"` Meta langMeta `json:"meta"`
Strings langSection `json:"strings"` Strings langSection `json:"strings"`
UserCreated langSection `json:"userCreated"` UserCreated langSection `json:"userCreated"`
InviteExpiry langSection `json:"inviteExpiry"` InviteExpiry langSection `json:"inviteExpiry"`
PasswordReset langSection `json:"passwordReset"` PasswordReset langSection `json:"passwordReset"`
UserDeleted langSection `json:"userDeleted"` UserDeleted langSection `json:"userDeleted"`
UserDisabled langSection `json:"userDisabled"` UserDisabled langSection `json:"userDisabled"`
UserEnabled langSection `json:"userEnabled"` UserEnabled langSection `json:"userEnabled"`
InviteEmail langSection `json:"inviteEmail"` UserExpiryAdjusted langSection `json:"userExpiryAdjusted"`
WelcomeEmail langSection `json:"welcomeEmail"` InviteEmail langSection `json:"inviteEmail"`
EmailConfirmation langSection `json:"emailConfirmation"` WelcomeEmail langSection `json:"welcomeEmail"`
UserExpired langSection `json:"userExpired"` EmailConfirmation langSection `json:"emailConfirmation"`
UserExpired langSection `json:"userExpired"`
} }
type setupLangs map[string]setupLang type setupLangs map[string]setupLang

View File

@ -45,6 +45,13 @@
"title": "Your account has been re-enabled - Jellyfin", "title": "Your account has been re-enabled - Jellyfin",
"yourAccountWasEnabled": "Your account was re-enabled." "yourAccountWasEnabled": "Your account was re-enabled."
}, },
"userExpiryAdjusted": {
"name": "Expiry adjusted",
"title": "Account expiry adjusted - Jellyfin",
"yourExpiryWasAdjusted": "Your account's expiry date has been adjusted.",
"ifPreviouslyDisabled": "If your account was previously disabled, it may have been re-enabled.",
"newExpiry": "Your account will now expire on {date}."
},
"inviteEmail": { "inviteEmail": {
"name": "Invite email", "name": "Invite email",
"title": "Invite - Jellyfin", "title": "Invite - Jellyfin",
@ -74,4 +81,4 @@
"yourAccountHasExpired": "Your account has expired.", "yourAccountHasExpired": "Your account has expired.",
"contactTheAdmin": "Contact the administrator for more info." "contactTheAdmin": "Contact the administrator for more info."
} }
} }

83
mail/expiry-adjusted.mjml Normal file
View File

@ -0,0 +1,83 @@
<mjml>
<mj-head>
<mj-raw>
<meta name="color-scheme" content="light dark">
<meta name="supported-color-schemes" content="light dark">
</mj-raw>
<mj-style>
:root {
Color-scheme: light dark;
supported-color-schemes: light dark;
}
@media (prefers-color-scheme: light) {
Color-scheme: dark;
.body {
background: #242424 !important;
background-color: #242424 !important;
}
[data-ogsc] .body {
background: #242424 !important;
background-color: #242424 !important;
}
[data-ogsb] .body {
background: #242424 !important;
background-color: #242424 !important;
}
}
@media (prefers-color-scheme: dark) {
Color-scheme: dark;
.body {
background: #242424 !important;
background-color: #242424 !important;
}
[data-ogsc] .body {
background: #242424 !important;
background-color: #242424 !important;
}
[data-ogsb] .body {
background: #242424 !important;
background-color: #242424 !important;
}
}
</mj-style>
<mj-attributes>
<mj-class name="bg" background-color="#101010" />
<mj-class name="bg2" background-color="#242424" />
<mj-class name="text" color="#cacaca" />
<mj-class name="bold" color="rgba(255,255,255,0.87)" />
<mj-class name="secondary" color="rgb(153,153,153)" />
<mj-class name="blue" background-color="rgb(0,164,220)" />
</mj-attributes>
<mj-font name="Quicksand" href="https://fonts.googleapis.com/css2?family=Quicksand" />
<mj-font name="Noto Sans" href="https://fonts.googleapis.com/css2?family=Noto+Sans" />
</mj-head>
<mj-body>
<mj-section mj-class="bg2">
<mj-column>
<mj-text mj-class="bold" font-size="25px" font-family="Quicksand, Noto Sans, Helvetica, Arial, sans-serif"> {{ .jellyfin }} </mj-text>
</mj-column>
</mj-section>
<mj-section mj-class="bg">
<mj-column>
<mj-text mj-class="text" font-size="16px" font-family="Noto Sans, Helvetica, Arial, sans-serif">
<p>{{ .helloUser }}</p>
<h3>{{ .yourExpiryWasAdjusted }}</h3>
<p>{{ .ifPreviouslyDisabled }}</p>
<h4>{{ .newExpiry }}</h4>
<p>{{ .reasonString }}: <i>{{ .reason }}</i></p>
</mj-text>
</mj-column>
</mj-section>
<mj-section mj-class="bg2">
<mj-column>
<mj-text mj-class="secondary" font-style="italic" font-size="14px">
{{ .message }}
</mj-text>
</mj-column>
</mj-section>
</body>
</mjml>

11
mail/expiry-adjusted.txt Normal file
View File

@ -0,0 +1,11 @@
{{ .helloUser }}
{{ .yourExpiryWasAdjusted }}
{{ .ifPreviouslyDisabled }}
{{ .newExpiry }}
{{ .reasonString }}: {{ .reason }}
{{ .message }}

View File

@ -17,6 +17,7 @@ func runMigrations(app *appContext) {
linkExistingOmbiDiscordTelegram(app) linkExistingOmbiDiscordTelegram(app)
// migrateHyphens(app) // migrateHyphens(app)
migrateToBadger(app) migrateToBadger(app)
intialiseCustomContent(app)
} }
// Migrate pre-0.2.0 user templates to profiles // Migrate pre-0.2.0 user templates to profiles
@ -329,6 +330,8 @@ func migrateToBadger(app *appContext) {
app.storage.SetCustomContentKey("UserPage", app.storage.deprecatedUserPageContent.Page) app.storage.SetCustomContentKey("UserPage", app.storage.deprecatedUserPageContent.Page)
} }
// Custom content not present here was added post-badger.
err := app.storage.db.Upsert("migrated_to_db", MigrationStatus{true}) err := app.storage.db.Upsert("migrated_to_db", MigrationStatus{true})
if err != nil { if err != nil {
app.err.Fatalf("Failed to migrate to DB: %v\n", err) app.err.Fatalf("Failed to migrate to DB: %v\n", err)
@ -336,6 +339,53 @@ func migrateToBadger(app *appContext) {
app.info.Println("All data migrated to database. JSON files in the config folder can be deleted if you are sure all data is correct in the app. Create an issue if you have problems.") app.info.Println("All data migrated to database. JSON files in the config folder can be deleted if you are sure all data is correct in the app. Create an issue if you have problems.")
} }
// Simply creates an emply CC template if not imn the DB already.
// Add new CC types here!
func intialiseCustomContent(app *appContext) {
emptyCC := CustomContent{
Enabled: false,
}
if _, ok := app.storage.GetCustomContentKey("UserCreated"); !ok {
app.storage.SetCustomContentKey("UserCreated", emptyCC)
}
if _, ok := app.storage.GetCustomContentKey("InviteExpiry"); !ok {
app.storage.SetCustomContentKey("InviteExpiry", emptyCC)
}
if _, ok := app.storage.GetCustomContentKey("PasswordReset"); !ok {
app.storage.SetCustomContentKey("PasswordReset", emptyCC)
}
if _, ok := app.storage.GetCustomContentKey("UserDeleted"); !ok {
app.storage.SetCustomContentKey("UserDeleted", emptyCC)
}
if _, ok := app.storage.GetCustomContentKey("UserDisabled"); !ok {
app.storage.SetCustomContentKey("UserDisabled", emptyCC)
}
if _, ok := app.storage.GetCustomContentKey("UserEnabled"); !ok {
app.storage.SetCustomContentKey("UserEnabled", emptyCC)
}
if _, ok := app.storage.GetCustomContentKey("InviteEmail"); !ok {
app.storage.SetCustomContentKey("InviteEmail", emptyCC)
}
if _, ok := app.storage.GetCustomContentKey("WelcomeEmail"); !ok {
app.storage.SetCustomContentKey("WelcomeEmail", emptyCC)
}
if _, ok := app.storage.GetCustomContentKey("EmailConfirmation"); !ok {
app.storage.SetCustomContentKey("EmailConfirmation", emptyCC)
}
if _, ok := app.storage.GetCustomContentKey("UserExpired"); !ok {
app.storage.SetCustomContentKey("UserExpired", emptyCC)
}
if _, ok := app.storage.GetCustomContentKey("UserLogin"); !ok {
app.storage.SetCustomContentKey("UserLogin", emptyCC)
}
if _, ok := app.storage.GetCustomContentKey("UserPage"); !ok {
app.storage.SetCustomContentKey("UserPage", emptyCC)
}
if _, ok := app.storage.GetCustomContentKey("UserExpiryAdjusted"); !ok {
app.storage.SetCustomContentKey("UserExpiryAdjusted", emptyCC)
}
}
// Migrate between hyphenated & non-hyphenated user IDs. Doesn't seem to happen anymore, so disabled. // Migrate between hyphenated & non-hyphenated user IDs. Doesn't seem to happen anymore, so disabled.
// func migrateHyphens(app *appContext) { // func migrateHyphens(app *appContext) {
// checkVersion := func(version string) int { // checkVersion := func(version string) int {

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 {

1
package-lock.json generated
View File

@ -15,7 +15,6 @@
"any-date-parser": "^1.5.4", "any-date-parser": "^1.5.4",
"browserslist": "^4.21.7", "browserslist": "^4.21.7",
"cheerio": "^1.0.0-rc.12", "cheerio": "^1.0.0-rc.12",
"esbuild": "^0.18.20",
"fs-cheerio": "^3.0.0", "fs-cheerio": "^3.0.0",
"inline-source": "^8.0.2", "inline-source": "^8.0.2",
"jsdom": "^22.1.0", "jsdom": "^22.1.0",

View File

@ -610,16 +610,17 @@ type EmailAddress struct {
} }
type customEmails struct { type customEmails struct {
UserCreated CustomContent `json:"userCreated"` UserCreated CustomContent `json:"userCreated"`
InviteExpiry CustomContent `json:"inviteExpiry"` InviteExpiry CustomContent `json:"inviteExpiry"`
PasswordReset CustomContent `json:"passwordReset"` PasswordReset CustomContent `json:"passwordReset"`
UserDeleted CustomContent `json:"userDeleted"` UserDeleted CustomContent `json:"userDeleted"`
UserDisabled CustomContent `json:"userDisabled"` UserDisabled CustomContent `json:"userDisabled"`
UserEnabled CustomContent `json:"userEnabled"` UserEnabled CustomContent `json:"userEnabled"`
InviteEmail CustomContent `json:"inviteEmail"` UserExpiryAdjusted CustomContent `json:"userExpiryAdjusted"`
WelcomeEmail CustomContent `json:"welcomeEmail"` InviteEmail CustomContent `json:"inviteEmail"`
EmailConfirmation CustomContent `json:"emailConfirmation"` WelcomeEmail CustomContent `json:"welcomeEmail"`
UserExpired CustomContent `json:"userExpired"` EmailConfirmation CustomContent `json:"emailConfirmation"`
UserExpired CustomContent `json:"userExpired"`
} }
// CustomContent stores customized versions of jfa-go content, including emails and user messages. // CustomContent stores customized versions of jfa-go content, including emails and user messages.
@ -1225,6 +1226,7 @@ func (st *Storage) loadLangEmail(filesystems ...fs.FS) error {
patchLang(&lang.UserDeleted, &fallback.UserDeleted, &english.UserDeleted) patchLang(&lang.UserDeleted, &fallback.UserDeleted, &english.UserDeleted)
patchLang(&lang.UserDisabled, &fallback.UserDisabled, &english.UserDisabled) patchLang(&lang.UserDisabled, &fallback.UserDisabled, &english.UserDisabled)
patchLang(&lang.UserEnabled, &fallback.UserEnabled, &english.UserEnabled) patchLang(&lang.UserEnabled, &fallback.UserEnabled, &english.UserEnabled)
patchLang(&lang.UserExpiryAdjusted, &fallback.UserExpiryAdjusted, &english.UserExpiryAdjusted)
patchLang(&lang.InviteEmail, &fallback.InviteEmail, &english.InviteEmail) patchLang(&lang.InviteEmail, &fallback.InviteEmail, &english.InviteEmail)
patchLang(&lang.WelcomeEmail, &fallback.WelcomeEmail, &english.WelcomeEmail) patchLang(&lang.WelcomeEmail, &fallback.WelcomeEmail, &english.WelcomeEmail)
patchLang(&lang.EmailConfirmation, &fallback.EmailConfirmation, &english.EmailConfirmation) patchLang(&lang.EmailConfirmation, &fallback.EmailConfirmation, &english.EmailConfirmation)
@ -1239,6 +1241,7 @@ func (st *Storage) loadLangEmail(filesystems ...fs.FS) error {
patchLang(&lang.UserDeleted, &english.UserDeleted) patchLang(&lang.UserDeleted, &english.UserDeleted)
patchLang(&lang.UserDisabled, &english.UserDisabled) patchLang(&lang.UserDisabled, &english.UserDisabled)
patchLang(&lang.UserEnabled, &english.UserEnabled) patchLang(&lang.UserEnabled, &english.UserEnabled)
patchLang(&lang.UserExpiryAdjusted, &english.UserExpiryAdjusted)
patchLang(&lang.InviteEmail, &english.InviteEmail) patchLang(&lang.InviteEmail, &english.InviteEmail)
patchLang(&lang.WelcomeEmail, &english.WelcomeEmail) patchLang(&lang.WelcomeEmail, &english.WelcomeEmail)
patchLang(&lang.EmailConfirmation, &english.EmailConfirmation) patchLang(&lang.EmailConfirmation, &english.EmailConfirmation)

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();