mirror of
https://github.com/hrfee/jfa-go.git
synced 2026-01-18 16:47:42 +01:00
expiry: add "remind N days before"
new setting to send an email/message N days before a user is due to expire. Multiple can be set.
This commit is contained in:
@@ -39,6 +39,7 @@ func (app *appContext) GetCustomContent(gc *gin.Context) {
|
|||||||
"WelcomeEmail": {Name: app.storage.lang.Email[lang].WelcomeEmail["name"], Enabled: app.storage.MustGetCustomContentKey("WelcomeEmail").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},
|
"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},
|
"UserExpired": {Name: app.storage.lang.Email[lang].UserExpired["name"], Enabled: app.storage.MustGetCustomContentKey("UserExpired").Enabled},
|
||||||
|
"ExpiryReminder": {Name: app.storage.lang.Email[lang].ExpiryReminder["name"], Enabled: app.storage.MustGetCustomContentKey("ExpiryReminder").Enabled},
|
||||||
"UserLogin": {Name: app.storage.lang.Admin[adminLang].Strings["userPageLogin"], Enabled: app.storage.MustGetCustomContentKey("UserLogin").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},
|
"UserPage": {Name: app.storage.lang.Admin[adminLang].Strings["userPagePage"], Enabled: app.storage.MustGetCustomContentKey("UserPage").Enabled},
|
||||||
"PostSignupCard": {Name: app.storage.lang.Admin[adminLang].Strings["postSignupCard"], Enabled: app.storage.MustGetCustomContentKey("PostSignupCard").Enabled, Description: app.storage.lang.Admin[adminLang].Strings["postSignupCardDescription"]},
|
"PostSignupCard": {Name: app.storage.lang.Admin[adminLang].Strings["postSignupCard"], Enabled: app.storage.MustGetCustomContentKey("PostSignupCard").Enabled, Description: app.storage.lang.Admin[adminLang].Strings["postSignupCardDescription"]},
|
||||||
@@ -196,7 +197,12 @@ func (app *appContext) GetCustomMessageTemplate(gc *gin.Context) {
|
|||||||
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(username, time.Now(), app.storage.lang.Email[lang].Strings.get("reason"), app, false, true)
|
values = app.email.expiryAdjustedValues(username, time.Time{}, app.storage.lang.Email[lang].Strings.get("reason"), app, false, true)
|
||||||
|
case "ExpiryReminder":
|
||||||
|
if noContent {
|
||||||
|
msg, err = app.email.constructExpiryReminder("", time.Now().AddDate(0, 0, 3), app, true)
|
||||||
|
}
|
||||||
|
values = app.email.expiryReminderValues(username, time.Now().AddDate(0, 0, 3), 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)
|
||||||
|
|||||||
@@ -552,6 +552,7 @@ func (app *appContext) ExtendExpiry(gc *gin.Context) {
|
|||||||
}(id, expiry.Expiry)
|
}(id, expiry.Expiry)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
app.InvalidateWebUserCache()
|
||||||
respondBool(204, true, gc)
|
respondBool(204, true, gc)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -563,6 +564,7 @@ func (app *appContext) ExtendExpiry(gc *gin.Context) {
|
|||||||
// @tags Users
|
// @tags Users
|
||||||
func (app *appContext) RemoveExpiry(gc *gin.Context) {
|
func (app *appContext) RemoveExpiry(gc *gin.Context) {
|
||||||
app.storage.DeleteUserExpiryKey(gc.Param("id"))
|
app.storage.DeleteUserExpiryKey(gc.Param("id"))
|
||||||
|
app.InvalidateWebUserCache()
|
||||||
respondBool(200, true, gc)
|
respondBool(200, true, gc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ var telegramEnabled = false
|
|||||||
var discordEnabled = false
|
var discordEnabled = false
|
||||||
var matrixEnabled = false
|
var matrixEnabled = false
|
||||||
|
|
||||||
// URL subpaths. Ignore the "Current" field.
|
// URL subpaths. Ignore the "Current" field, it's populated when in copies of the struct used for page templating.
|
||||||
// IMPORTANT: When linking straight to a page, rather than appending further to the URL (like accessing an API route), append a /.
|
// IMPORTANT: When linking straight to a page, rather than appending further to the URL (like accessing an API route), append a /.
|
||||||
var PAGES = PagePaths{}
|
var PAGES = PagePaths{}
|
||||||
|
|
||||||
@@ -240,6 +240,9 @@ func (app *appContext) loadConfig() error {
|
|||||||
app.MustSetValue("user_expiry", "adjustment_email_html", "jfa-go:"+"expiry-adjusted.html")
|
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("user_expiry", "adjustment_email_text", "jfa-go:"+"expiry-adjusted.txt")
|
||||||
|
|
||||||
|
app.MustSetValue("user_expiry", "reminder_email_html", "jfa-go:"+"expiry-reminder.html")
|
||||||
|
app.MustSetValue("user_expiry", "reminder_email_text", "jfa-go:"+"expiry-reminder.txt")
|
||||||
|
|
||||||
app.MustSetValue("email", "collect", "true")
|
app.MustSetValue("email", "collect", "true")
|
||||||
|
|
||||||
app.MustSetValue("matrix", "topic", "Jellyfin notifications")
|
app.MustSetValue("matrix", "topic", "Jellyfin notifications")
|
||||||
|
|||||||
@@ -1474,6 +1474,10 @@ sections:
|
|||||||
value: true
|
value: true
|
||||||
depends_true: messages|enabled
|
depends_true: messages|enabled
|
||||||
description: Send an email when a user's account expires.
|
description: Send an email when a user's account expires.
|
||||||
|
- setting: send_reminder_n_days_before
|
||||||
|
name: Send message N days before expiry
|
||||||
|
type: list
|
||||||
|
description: Send users a message N days before their account is due to expire. Multiple can be set.
|
||||||
- setting: subject
|
- setting: subject
|
||||||
name: Email subject
|
name: Email subject
|
||||||
depends_true: messages|enabled
|
depends_true: messages|enabled
|
||||||
@@ -1509,6 +1513,23 @@ sections:
|
|||||||
depends_true: messages|enabled
|
depends_true: messages|enabled
|
||||||
type: text
|
type: text
|
||||||
description: Path to custom email in plain text
|
description: Path to custom email in plain text
|
||||||
|
- setting: reminder_subject
|
||||||
|
name: 'Reminder: email subject'
|
||||||
|
depends_true: messages|enabled
|
||||||
|
type: text
|
||||||
|
description: Subject of expiry reminder emails.
|
||||||
|
- setting: reminder_email_html
|
||||||
|
name: 'Reminder: Custom email (HTML)'
|
||||||
|
advanced: true
|
||||||
|
depends_true: messages|enabled
|
||||||
|
type: text
|
||||||
|
description: Path to custom email html
|
||||||
|
- setting: reminder_email_text
|
||||||
|
name: 'Reminder: Custom email (plaintext)'
|
||||||
|
advanced: true
|
||||||
|
depends_true: messages|enabled
|
||||||
|
type: text
|
||||||
|
description: Path to custom email in plain text
|
||||||
- section: disable_enable
|
- section: disable_enable
|
||||||
meta:
|
meta:
|
||||||
name: Account Disabling/Enabling
|
name: Account Disabling/Enabling
|
||||||
|
|||||||
73
email.go
73
email.go
@@ -849,6 +849,79 @@ func (emailer *Emailer) constructExpiryAdjusted(username string, expiry time.Tim
|
|||||||
return email, nil
|
return email, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (emailer *Emailer) expiryReminderValues(username string, expiry time.Time, app *appContext, noSub bool, custom bool) map[string]interface{} {
|
||||||
|
template := map[string]interface{}{
|
||||||
|
"yourAccountIsDueToExpire": emailer.lang.ExpiryReminder.get("yourAccountIsDueToExpire"),
|
||||||
|
"expiresIn": "",
|
||||||
|
"date": "",
|
||||||
|
"time": "",
|
||||||
|
"message": "",
|
||||||
|
}
|
||||||
|
if noSub {
|
||||||
|
template["helloUser"] = emailer.lang.Strings.get("helloUser")
|
||||||
|
empty := []string{"date", "expiresIn"}
|
||||||
|
for _, v := range empty {
|
||||||
|
template[v] = "{" + v + "}"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
template["message"] = app.config.Section("messages").Key("message").String()
|
||||||
|
template["helloUser"] = emailer.lang.Strings.template("helloUser", tmpl{"username": username})
|
||||||
|
d, t, expiresIn := emailer.formatExpiry(expiry, false, app.datePattern, app.timePattern)
|
||||||
|
if !expiry.IsZero() {
|
||||||
|
if custom {
|
||||||
|
template["expiresIn"] = expiresIn
|
||||||
|
template["date"] = d
|
||||||
|
template["time"] = t
|
||||||
|
} else if !expiry.IsZero() {
|
||||||
|
template["yourAccountIsDueToExpire"] = emailer.lang.ExpiryReminder.template("yourAccountIsDueToExpire", tmpl{
|
||||||
|
"expiresIn": expiresIn,
|
||||||
|
"date": d,
|
||||||
|
"time": t,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return template
|
||||||
|
}
|
||||||
|
|
||||||
|
func (emailer *Emailer) constructExpiryReminder(username string, expiry time.Time, app *appContext, noSub bool) (*Message, error) {
|
||||||
|
email := &Message{
|
||||||
|
Subject: app.config.Section("user_expiry").Key("reminder_subject").MustString(emailer.lang.ExpiryReminder.get("title")),
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
var template map[string]interface{}
|
||||||
|
message := app.storage.MustGetCustomContentKey("ExpiryReminder")
|
||||||
|
if message.Enabled {
|
||||||
|
template = emailer.expiryReminderValues(username, expiry, app, noSub, true)
|
||||||
|
} else {
|
||||||
|
template = emailer.expiryReminderValues(username, expiry, app, noSub, false)
|
||||||
|
}
|
||||||
|
/*if noSub {
|
||||||
|
template["newExpiry"] = emailer.lang.UserExpiryAdjusted.template("newExpiry", tmpl{
|
||||||
|
"date": "{newExpiry}",
|
||||||
|
})
|
||||||
|
}*/
|
||||||
|
if message.Enabled {
|
||||||
|
var content string
|
||||||
|
content, err = templateEmail(
|
||||||
|
message.Content,
|
||||||
|
message.Variables,
|
||||||
|
nil,
|
||||||
|
template,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
app.err.Printf(lm.FailedConstructCustomContent, app.config.Section("user_expiry").Key("reminder_subject").MustString(emailer.lang.ExpiryReminder.get("title")), err)
|
||||||
|
}
|
||||||
|
email, err = emailer.constructTemplate(email.Subject, content, app)
|
||||||
|
} else {
|
||||||
|
email.HTML, email.Text, email.Markdown, err = emailer.construct(app, "user_expiry", "reminder_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"),
|
||||||
|
|||||||
1
lang.go
1
lang.go
@@ -108,6 +108,7 @@ type emailLang struct {
|
|||||||
WelcomeEmail langSection `json:"welcomeEmail"`
|
WelcomeEmail langSection `json:"welcomeEmail"`
|
||||||
EmailConfirmation langSection `json:"emailConfirmation"`
|
EmailConfirmation langSection `json:"emailConfirmation"`
|
||||||
UserExpired langSection `json:"userExpired"`
|
UserExpired langSection `json:"userExpired"`
|
||||||
|
ExpiryReminder langSection `json:"expiryReminder"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type setupLangs map[string]setupLang
|
type setupLangs map[string]setupLang
|
||||||
|
|||||||
@@ -80,5 +80,10 @@
|
|||||||
"title": "Your account has expired - Jellyfin",
|
"title": "Your account has expired - Jellyfin",
|
||||||
"yourAccountHasExpired": "Your account has expired.",
|
"yourAccountHasExpired": "Your account has expired.",
|
||||||
"contactTheAdmin": "Contact the administrator for more info."
|
"contactTheAdmin": "Contact the administrator for more info."
|
||||||
|
},
|
||||||
|
"expiryReminder": {
|
||||||
|
"name": "Expiry reminder",
|
||||||
|
"title": "Reminder: your account will expire soon - Jellyfin",
|
||||||
|
"yourAccountIsDueToExpire": "Your account is due to expire in {expiresIn}, or on {date} at {time}."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -384,6 +384,10 @@ const (
|
|||||||
FailedSendExpiryAdjustmentMessage = "Failed to send expiry adjustment message for \"%s\" to \"%s\": %v"
|
FailedSendExpiryAdjustmentMessage = "Failed to send expiry adjustment message for \"%s\" to \"%s\": %v"
|
||||||
SentExpiryAdjustmentMessage = "Sent expiry adjustment message for \"%s\" to \"%s\""
|
SentExpiryAdjustmentMessage = "Sent expiry adjustment message for \"%s\" to \"%s\""
|
||||||
|
|
||||||
|
FailedConstructExpiryReminderMessage = "Failed to construct expiry reminder message for \"%s\": %v"
|
||||||
|
FailedSendExpiryReminderMessage = "Failed to send expiry reminder message for \"%s\" to \"%s\": %v"
|
||||||
|
SentExpiryReminderMessage = "Sent expiry reminder message for \"%s\" to \"%s\""
|
||||||
|
|
||||||
FailedConstructExpiryMessage = "Failed to construct expiry message for \"%s\": %v"
|
FailedConstructExpiryMessage = "Failed to construct expiry message for \"%s\": %v"
|
||||||
FailedSendExpiryMessage = "Failed to send expiry message for \"%s\" to \"%s\": %v"
|
FailedSendExpiryMessage = "Failed to send expiry message for \"%s\" to \"%s\": %v"
|
||||||
SentExpiryMessage = "Sent expiry message for \"%s\" to \"%s\""
|
SentExpiryMessage = "Sent expiry message for \"%s\" to \"%s\""
|
||||||
|
|||||||
77
mail/expiry-reminder.mjml
Normal file
77
mail/expiry-reminder.mjml
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
<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>
|
||||||
|
|
||||||
|
<p>{{ .yourAccountIsDueToExpire }}</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>
|
||||||
5
mail/expiry-reminder.txt
Normal file
5
mail/expiry-reminder.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{{ .helloUser }}
|
||||||
|
|
||||||
|
{{ .yourAccountIsDueToExpire }}
|
||||||
|
|
||||||
|
{{ .message }}
|
||||||
@@ -387,6 +387,9 @@ func intialiseCustomContent(app *appContext) {
|
|||||||
if _, ok := app.storage.GetCustomContentKey("UserExpiryAdjusted"); !ok {
|
if _, ok := app.storage.GetCustomContentKey("UserExpiryAdjusted"); !ok {
|
||||||
app.storage.SetCustomContentKey("UserExpiryAdjusted", emptyCC)
|
app.storage.SetCustomContentKey("UserExpiryAdjusted", emptyCC)
|
||||||
}
|
}
|
||||||
|
if _, ok := app.storage.GetCustomContentKey("ExpiryReminder"); !ok {
|
||||||
|
app.storage.SetCustomContentKey("ExpiryReminder", emptyCC)
|
||||||
|
}
|
||||||
if _, ok := app.storage.GetCustomContentKey("PostSignupCard"); !ok {
|
if _, ok := app.storage.GetCustomContentKey("PostSignupCard"); !ok {
|
||||||
app.storage.SetCustomContentKey("PostSignupCard", emptyCC)
|
app.storage.SetCustomContentKey("PostSignupCard", emptyCC)
|
||||||
|
|
||||||
|
|||||||
@@ -66,7 +66,8 @@ type Activity struct {
|
|||||||
type UserExpiry struct {
|
type UserExpiry struct {
|
||||||
JellyfinID string `badgerhold:"key"`
|
JellyfinID string `badgerhold:"key"`
|
||||||
Expiry time.Time
|
Expiry time.Time
|
||||||
DeleteAfterPeriod bool // Whether or not to further disable the user later on
|
DeleteAfterPeriod bool // Whether or not to further disable the user later on
|
||||||
|
LastNotified time.Time // Last time an expiry notification/reminder was sent to the user.
|
||||||
}
|
}
|
||||||
|
|
||||||
type DebugLogAction int
|
type DebugLogAction int
|
||||||
@@ -679,6 +680,7 @@ type customEmails struct {
|
|||||||
WelcomeEmail CustomContent `json:"welcomeEmail"`
|
WelcomeEmail CustomContent `json:"welcomeEmail"`
|
||||||
EmailConfirmation CustomContent `json:"emailConfirmation"`
|
EmailConfirmation CustomContent `json:"emailConfirmation"`
|
||||||
UserExpired CustomContent `json:"userExpired"`
|
UserExpired CustomContent `json:"userExpired"`
|
||||||
|
ExpiryReminder CustomContent `json:"expiryReminder"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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.
|
||||||
@@ -1311,6 +1313,7 @@ func (st *Storage) loadLangEmail(filesystems ...fs.FS) error {
|
|||||||
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)
|
||||||
patchLang(&lang.UserExpired, &fallback.UserExpired, &english.UserExpired)
|
patchLang(&lang.UserExpired, &fallback.UserExpired, &english.UserExpired)
|
||||||
|
patchLang(&lang.ExpiryReminder, &fallback.ExpiryReminder, &english.ExpiryReminder)
|
||||||
patchLang(&lang.Strings, &fallback.Strings, &english.Strings)
|
patchLang(&lang.Strings, &fallback.Strings, &english.Strings)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1326,6 +1329,7 @@ func (st *Storage) loadLangEmail(filesystems ...fs.FS) error {
|
|||||||
patchLang(&lang.WelcomeEmail, &english.WelcomeEmail)
|
patchLang(&lang.WelcomeEmail, &english.WelcomeEmail)
|
||||||
patchLang(&lang.EmailConfirmation, &english.EmailConfirmation)
|
patchLang(&lang.EmailConfirmation, &english.EmailConfirmation)
|
||||||
patchLang(&lang.UserExpired, &english.UserExpired)
|
patchLang(&lang.UserExpired, &english.UserExpired)
|
||||||
|
patchLang(&lang.ExpiryReminder, &english.ExpiryReminder)
|
||||||
patchLang(&lang.Strings, &english.Strings)
|
patchLang(&lang.Strings, &english.Strings)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
4
timer.go
4
timer.go
@@ -26,7 +26,7 @@ type DayTimerSet struct {
|
|||||||
clock Clock
|
clock Clock
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDayTimerSet(deltaStrings []string, unit time.Duration) DayTimerSet {
|
func NewDayTimerSet(deltaStrings []string, unit time.Duration) *DayTimerSet {
|
||||||
as := DayTimerSet{
|
as := DayTimerSet{
|
||||||
deltas: make([]time.Duration, 0, len(deltaStrings)),
|
deltas: make([]time.Duration, 0, len(deltaStrings)),
|
||||||
clock: realClock{},
|
clock: realClock{},
|
||||||
@@ -39,7 +39,7 @@ func NewDayTimerSet(deltaStrings []string, unit time.Duration) DayTimerSet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return as
|
return &as
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns one or no time.Duration values, Giving the delta for the timer which went off. Pass a non-zero lastFired to stop too many going off at once, and store the returned time.Time value to pass as this later.
|
// Returns one or no time.Duration values, Giving the delta for the timer which went off. Pass a non-zero lastFired to stop too many going off at once, and store the returned time.Time value to pass as this later.
|
||||||
|
|||||||
35
user-d.go
35
user-d.go
@@ -9,9 +9,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func newUserDaemon(interval time.Duration, app *appContext) *GenericDaemon {
|
func newUserDaemon(interval time.Duration, app *appContext) *GenericDaemon {
|
||||||
|
preExpiryCutoffDays := app.config.Section("user_expiry").Key("send_reminder_n_days_before").StringsWithShadows("|")
|
||||||
|
var as *DayTimerSet
|
||||||
|
if len(preExpiryCutoffDays) > 0 {
|
||||||
|
as = NewDayTimerSet(preExpiryCutoffDays, -24*time.Hour)
|
||||||
|
}
|
||||||
d := NewGenericDaemon(interval, app,
|
d := NewGenericDaemon(interval, app,
|
||||||
func(app *appContext) {
|
func(app *appContext) {
|
||||||
app.checkUsers()
|
app.checkUsers(as)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
d.Name("User daemon")
|
d.Name("User daemon")
|
||||||
@@ -23,7 +28,7 @@ const (
|
|||||||
ExpiryModeDelete
|
ExpiryModeDelete
|
||||||
)
|
)
|
||||||
|
|
||||||
func (app *appContext) checkUsers() {
|
func (app *appContext) checkUsers(remindBeforeExpiry *DayTimerSet) {
|
||||||
if len(app.storage.GetUserExpiries()) == 0 {
|
if len(app.storage.GetUserExpiries()) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -63,7 +68,29 @@ func (app *appContext) checkUsers() {
|
|||||||
app.storage.DeleteUserExpiryKey(expiry.JellyfinID)
|
app.storage.DeleteUserExpiryKey(expiry.JellyfinID)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if !time.Now().After(expiry.Expiry) {
|
if !time.Now().After(expiry.Expiry) {
|
||||||
|
if shouldContact && remindBeforeExpiry != nil {
|
||||||
|
app.debug.Printf("Checking for expiry reminder timers")
|
||||||
|
duration := remindBeforeExpiry.Check(expiry.Expiry, expiry.LastNotified)
|
||||||
|
if duration != 0 {
|
||||||
|
expiry.LastNotified = time.Now()
|
||||||
|
app.storage.SetUserExpiryKey(user.ID, expiry)
|
||||||
|
name := app.getAddressOrName(user.ID)
|
||||||
|
// Skip blank contact info
|
||||||
|
if name == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
msg, err := app.email.constructExpiryReminder(user.Name, expiry.Expiry, app, false)
|
||||||
|
if err != nil {
|
||||||
|
app.err.Printf(lm.FailedConstructExpiryReminderMessage, user.ID, err)
|
||||||
|
} else if err := app.sendByID(msg, user.ID); err != nil {
|
||||||
|
app.err.Printf(lm.FailedSendExpiryReminderMessage, user.ID, name, err)
|
||||||
|
} else {
|
||||||
|
app.info.Printf(lm.SentExpiryReminderMessage, user.ID, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,6 +158,10 @@ func (app *appContext) checkUsers() {
|
|||||||
} else if deleteAfterPeriod > 0 && !alreadyExpired {
|
} else if deleteAfterPeriod > 0 && !alreadyExpired {
|
||||||
// Otherwise, mark the expiry as done pending a delete after N days.
|
// Otherwise, mark the expiry as done pending a delete after N days.
|
||||||
expiry.DeleteAfterPeriod = true
|
expiry.DeleteAfterPeriod = true
|
||||||
|
// Sure, we haven't contacted them yet, but we're about to
|
||||||
|
if shouldContact {
|
||||||
|
expiry.LastNotified = time.Now()
|
||||||
|
}
|
||||||
app.storage.SetUserExpiryKey(user.ID, expiry)
|
app.storage.SetUserExpiryKey(user.ID, expiry)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
2
users.go
2
users.go
@@ -143,8 +143,8 @@ func (app *appContext) NewUserPostVerification(p NewUserParams) (out NewUserData
|
|||||||
if len(webhookURIs) != 0 {
|
if len(webhookURIs) != 0 {
|
||||||
summary := app.userSummary(out.User)
|
summary := app.userSummary(out.User)
|
||||||
for _, uri := range webhookURIs {
|
for _, uri := range webhookURIs {
|
||||||
|
pendingTasks.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
pendingTasks.Add(1)
|
|
||||||
app.webhooks.Send(uri, summary)
|
app.webhooks.Send(uri, summary)
|
||||||
pendingTasks.Done()
|
pendingTasks.Done()
|
||||||
}()
|
}()
|
||||||
|
|||||||
Reference in New Issue
Block a user