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

logmessages: finish up to api-users (alphabetically), refactor

.go files done in alphabetical order. Some refactoring done to
checkInvite(s) so they share most code. Also removed some useless debug
lines.
This commit is contained in:
Harvey Tindall 2024-07-31 18:49:52 +01:00
parent e9b8d970d1
commit f348262f88
Signed by: hrfee
GPG Key ID: BBC65952848FB1A2
11 changed files with 1949 additions and 350 deletions

1503
:w Normal file

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,7 @@ package main
import ( import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
lm "github.com/hrfee/jfa-go/logmessages"
"github.com/timshannon/badgerhold/v4" "github.com/timshannon/badgerhold/v4"
) )
@ -120,7 +121,7 @@ func (app *appContext) GetActivities(gc *gin.Context) {
err := app.storage.db.Find(&results, query) err := app.storage.db.Find(&results, query)
if err != nil { if err != nil {
app.err.Printf("Failed to read activities from DB: %v\n", err) app.err.Printf(lm.FailedDBReadActivities, err)
} }
resp := GetActivitiesRespDTO{ resp := GetActivitiesRespDTO{

View File

@ -8,6 +8,7 @@ import (
"time" "time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
lm "github.com/hrfee/jfa-go/logmessages"
) )
// @Summary Creates a backup of the database. // @Summary Creates a backup of the database.
@ -35,7 +36,7 @@ func (app *appContext) GetBackup(gc *gin.Context) {
ok := (strings.HasPrefix(fname, BACKUP_PREFIX) || strings.HasPrefix(fname, BACKUP_UPLOAD_PREFIX+BACKUP_PREFIX)) && strings.HasSuffix(fname, BACKUP_SUFFIX) ok := (strings.HasPrefix(fname, BACKUP_PREFIX) || strings.HasPrefix(fname, BACKUP_UPLOAD_PREFIX+BACKUP_PREFIX)) && strings.HasSuffix(fname, BACKUP_SUFFIX)
t, err := time.Parse(BACKUP_DATEFMT, strings.TrimSuffix(strings.TrimPrefix(strings.TrimPrefix(fname, BACKUP_UPLOAD_PREFIX), BACKUP_PREFIX), BACKUP_SUFFIX)) t, err := time.Parse(BACKUP_DATEFMT, strings.TrimSuffix(strings.TrimPrefix(strings.TrimPrefix(fname, BACKUP_UPLOAD_PREFIX), BACKUP_PREFIX), BACKUP_SUFFIX))
if !ok || err != nil || t.IsZero() { if !ok || err != nil || t.IsZero() {
app.debug.Printf("Ignoring backup DL request due to fname: %v\n", err) app.debug.Printf(lm.IgnoreInvalidFilename, fname, err)
respondBool(400, false, gc) respondBool(400, false, gc)
return return
} }
@ -83,7 +84,7 @@ func (app *appContext) RestoreLocalBackup(gc *gin.Context) {
ok := strings.HasPrefix(fname, BACKUP_PREFIX) && strings.HasSuffix(fname, BACKUP_SUFFIX) ok := strings.HasPrefix(fname, BACKUP_PREFIX) && strings.HasSuffix(fname, BACKUP_SUFFIX)
t, err := time.Parse(BACKUP_DATEFMT, strings.TrimSuffix(strings.TrimPrefix(fname, BACKUP_PREFIX), BACKUP_SUFFIX)) t, err := time.Parse(BACKUP_DATEFMT, strings.TrimSuffix(strings.TrimPrefix(fname, BACKUP_PREFIX), BACKUP_SUFFIX))
if !ok || err != nil || t.IsZero() { if !ok || err != nil || t.IsZero() {
app.debug.Printf("Ignoring backup DL request due to fname: %v\n", err) app.debug.Printf(lm.IgnoreInvalidFilename, fname, err)
respondBool(400, false, gc) respondBool(400, false, gc)
return return
} }
@ -103,15 +104,15 @@ func (app *appContext) RestoreLocalBackup(gc *gin.Context) {
func (app *appContext) RestoreBackup(gc *gin.Context) { func (app *appContext) RestoreBackup(gc *gin.Context) {
file, err := gc.FormFile("backups-file") file, err := gc.FormFile("backups-file")
if err != nil { if err != nil {
app.err.Printf("Failed to get file from form data: %v\n", err) app.err.Printf(lm.FailedGetUpload, err)
respondBool(400, false, gc) respondBool(400, false, gc)
return return
} }
app.debug.Printf("Got uploaded file \"%s\"\n", file.Filename) app.debug.Printf(lm.GetUpload, file.Filename)
path := app.config.Section("backups").Key("path").String() path := app.config.Section("backups").Key("path").String()
fullpath := filepath.Join(path, BACKUP_UPLOAD_PREFIX+BACKUP_PREFIX+time.Now().Local().Format(BACKUP_DATEFMT)+BACKUP_SUFFIX) fullpath := filepath.Join(path, BACKUP_UPLOAD_PREFIX+BACKUP_PREFIX+time.Now().Local().Format(BACKUP_DATEFMT)+BACKUP_SUFFIX)
gc.SaveUploadedFile(file, fullpath) gc.SaveUploadedFile(file, fullpath)
app.debug.Printf("Saved to \"%s\"\n", fullpath) app.debug.Printf(lm.Write, fullpath)
LOADBAK = fullpath LOADBAK = fullpath
app.restart(gc) app.restart(gc)
} }

View File

@ -8,6 +8,7 @@ import (
"time" "time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
lm "github.com/hrfee/jfa-go/logmessages"
"github.com/itchyny/timefmt-go" "github.com/itchyny/timefmt-go"
"github.com/lithammer/shortuuid/v3" "github.com/lithammer/shortuuid/v3"
"github.com/timshannon/badgerhold/v4" "github.com/timshannon/badgerhold/v4"
@ -29,6 +30,7 @@ func GenerateInviteCode() string {
return inviteCode return inviteCode
} }
// checkInvites performs general housekeeping on invites, i.e. deleting expired ones and cleaning captcha data.
func (app *appContext) checkInvites() { func (app *appContext) checkInvites() {
currentTime := time.Now() currentTime := time.Now()
for _, data := range app.storage.GetInvites() { for _, data := range app.storage.GetInvites() {
@ -52,60 +54,11 @@ func (app *appContext) checkInvites() {
if !currentTime.After(expiry) { if !currentTime.After(expiry) {
continue continue
} }
app.deleteExpiredInvite(data)
app.debug.Printf("Housekeeping: Deleting old invite %s", data.Code)
// Disable referrals for the user if UseReferralExpiry is enabled, so no new ones are made.
if data.IsReferral && data.UseReferralExpiry && data.ReferrerJellyfinID != "" {
user, ok := app.storage.GetEmailsKey(data.ReferrerJellyfinID)
if ok {
user.ReferralTemplateKey = ""
app.storage.SetEmailsKey(data.ReferrerJellyfinID, user)
}
}
notify := data.Notify
if emailEnabled && app.config.Section("notifications").Key("enabled").MustBool(false) && len(notify) != 0 {
app.debug.Printf("%s: Expiry notification", data.Code)
var wait sync.WaitGroup
for address, settings := range notify {
if !settings["notify-expiry"] {
continue
}
wait.Add(1)
go func(addr string) {
defer wait.Done()
msg, err := app.email.constructExpiry(data.Code, data, app, false)
if err != nil {
app.err.Printf("%s: Failed to construct expiry notification: %v", data.Code, err)
} else {
// Check whether notify "address" is an email address of Jellyfin ID
if strings.Contains(addr, "@") {
err = app.email.send(msg, addr)
} else {
err = app.sendByID(msg, addr)
}
if err != nil {
app.err.Printf("%s: Failed to send expiry notification: %v", data.Code, err)
} else {
app.info.Printf("Sent expiry notification to %s", addr)
}
}
}(address)
}
wait.Wait()
}
app.storage.DeleteInvitesKey(data.Code)
app.storage.SetActivityKey(shortuuid.New(), Activity{
Type: ActivityDeleteInvite,
SourceType: ActivityDaemon,
InviteCode: data.Code,
Value: data.Label,
Time: time.Now(),
}, nil, false)
} }
} }
// checkInvite checks the validity of a specific invite, optionally removing it if invalid(ated).
func (app *appContext) checkInvite(code string, used bool, username string) bool { func (app *appContext) checkInvite(code string, used bool, username string) bool {
currentTime := time.Now() currentTime := time.Now()
inv, match := app.storage.GetInvitesKey(code) inv, match := app.storage.GetInvitesKey(code)
@ -114,54 +67,8 @@ func (app *appContext) checkInvite(code string, used bool, username string) bool
} }
expiry := inv.ValidTill expiry := inv.ValidTill
if currentTime.After(expiry) { if currentTime.After(expiry) {
app.debug.Printf("Housekeeping: Deleting old invite %s", code) app.deleteExpiredInvite(inv)
notify := inv.Notify
if emailEnabled && app.config.Section("notifications").Key("enabled").MustBool(false) && len(notify) != 0 {
app.debug.Printf("%s: Expiry notification", code)
var wait sync.WaitGroup
for address, settings := range notify {
if !settings["notify-expiry"] {
continue
}
wait.Add(1)
go func(addr string) {
defer wait.Done()
msg, err := app.email.constructExpiry(code, inv, app, false)
if err != nil {
app.err.Printf("%s: Failed to construct expiry notification: %v", code, err)
} else {
// Check whether notify "address" is an email address of Jellyfin ID
if strings.Contains(addr, "@") {
err = app.email.send(msg, addr)
} else {
err = app.sendByID(msg, addr)
}
if err != nil {
app.err.Printf("%s: Failed to send expiry notification: %v", code, err)
} else {
app.info.Printf("Sent expiry notification to %s", addr)
}
}
}(address)
}
wait.Wait()
}
if inv.IsReferral && inv.ReferrerJellyfinID != "" && inv.UseReferralExpiry {
user, ok := app.storage.GetEmailsKey(inv.ReferrerJellyfinID)
if ok {
user.ReferralTemplateKey = ""
app.storage.SetEmailsKey(inv.ReferrerJellyfinID, user)
}
}
match = false match = false
app.storage.DeleteInvitesKey(code)
app.storage.SetActivityKey(shortuuid.New(), Activity{
Type: ActivityDeleteInvite,
SourceType: ActivityDaemon,
InviteCode: code,
Value: inv.Label,
Time: time.Now(),
}, nil, false)
} else if used { } else if used {
del := false del := false
newInv := inv newInv := inv
@ -187,6 +94,67 @@ func (app *appContext) checkInvite(code string, used bool, username string) bool
return match return match
} }
func (app *appContext) deleteExpiredInvite(data Invite) {
app.debug.Printf(lm.DeleteOldInvite, data.Code)
// Disable referrals for the user if UseReferralExpiry is enabled, so no new ones are made.
if data.IsReferral && data.UseReferralExpiry && data.ReferrerJellyfinID != "" {
user, ok := app.storage.GetEmailsKey(data.ReferrerJellyfinID)
if ok {
user.ReferralTemplateKey = ""
app.storage.SetEmailsKey(data.ReferrerJellyfinID, user)
}
}
wait := app.sendAdminExpiryNotification(data)
app.storage.DeleteInvitesKey(data.Code)
app.storage.SetActivityKey(shortuuid.New(), Activity{
Type: ActivityDeleteInvite,
SourceType: ActivityDaemon,
InviteCode: data.Code,
Value: data.Label,
Time: time.Now(),
}, nil, false)
if wait != nil {
wait.Wait()
}
}
func (app *appContext) sendAdminExpiryNotification(data Invite) *sync.WaitGroup {
notify := data.Notify
if !emailEnabled || !app.config.Section("notifications").Key("enabled").MustBool(false) || len(notify) != 0 {
return nil
}
var wait sync.WaitGroup
for address, settings := range notify {
if !settings["notify-expiry"] {
continue
}
wait.Add(1)
go func(addr string) {
defer wait.Done()
msg, err := app.email.constructExpiry(data.Code, data, app, false)
if err != nil {
app.err.Printf(lm.FailedConstructExpiryAdmin, data.Code, err)
} else {
// Check whether notify "address" is an email address or Jellyfin ID
if strings.Contains(addr, "@") {
err = app.email.send(msg, addr)
} else {
err = app.sendByID(msg, addr)
}
if err != nil {
app.err.Printf(lm.FailedSendExpiryAdmin, data.Code, addr, err)
} else {
app.info.Printf(lm.SentExpiryAdmin, data.Code, addr)
}
}
}(address)
}
return &wait
}
// @Summary Create a new invite. // @Summary Create a new invite.
// @Produce json // @Produce json
// @Param generateInviteDTO body generateInviteDTO true "New invite request object" // @Param generateInviteDTO body generateInviteDTO true "New invite request object"
@ -196,7 +164,7 @@ func (app *appContext) checkInvite(code string, used bool, username string) bool
// @tags Invites // @tags Invites
func (app *appContext) GenerateInvite(gc *gin.Context) { func (app *appContext) GenerateInvite(gc *gin.Context) {
var req generateInviteDTO var req generateInviteDTO
app.debug.Println("Generating new invite") app.debug.Println(lm.GenerateInvite)
gc.BindJSON(&req) gc.BindJSON(&req)
currentTime := time.Now() currentTime := time.Now()
validTill := currentTime.AddDate(0, req.Months, req.Days) validTill := currentTime.AddDate(0, req.Months, req.Days)
@ -230,13 +198,12 @@ func (app *appContext) GenerateInvite(gc *gin.Context) {
if req.SendTo != "" && app.config.Section("invite_emails").Key("enabled").MustBool(false) { if req.SendTo != "" && app.config.Section("invite_emails").Key("enabled").MustBool(false) {
addressValid := false addressValid := false
discord := "" discord := ""
app.debug.Printf("%s: Sending invite message", invite.Code)
if discordEnabled && (!strings.Contains(req.SendTo, "@") || strings.HasPrefix(req.SendTo, "@")) { if discordEnabled && (!strings.Contains(req.SendTo, "@") || strings.HasPrefix(req.SendTo, "@")) {
users := app.discord.GetUsers(req.SendTo) users := app.discord.GetUsers(req.SendTo)
if len(users) == 0 { if len(users) == 0 {
invite.SendTo = fmt.Sprintf("Failed: User not found: \"%s\"", req.SendTo) invite.SendTo = fmt.Sprintf(lm.FailedSendToTooltipNoUser, req.SendTo)
} else if len(users) > 1 { } else if len(users) > 1 {
invite.SendTo = fmt.Sprintf("Failed: Multiple users found: \"%s\"", req.SendTo) invite.SendTo = fmt.Sprintf(lm.FailedSendToTooltipMultiUser, req.SendTo)
} else { } else {
invite.SendTo = req.SendTo invite.SendTo = req.SendTo
addressValid = true addressValid = true
@ -249,8 +216,10 @@ func (app *appContext) GenerateInvite(gc *gin.Context) {
if addressValid { if addressValid {
msg, err := app.email.constructInvite(invite.Code, invite, app, false) msg, err := app.email.constructInvite(invite.Code, invite, app, false)
if err != nil { if err != nil {
invite.SendTo = fmt.Sprintf("Failed to send to %s", req.SendTo) // Slight misuse of the template
app.err.Printf("%s: Failed to construct invite message: %v", invite.Code, err) invite.SendTo = fmt.Sprintf(lm.FailedConstructInviteMessage, req.SendTo, err)
app.err.Printf(lm.FailedConstructInviteMessage, invite.Code, err)
} else { } else {
var err error var err error
if discord != "" { if discord != "" {
@ -259,10 +228,10 @@ func (app *appContext) GenerateInvite(gc *gin.Context) {
err = app.email.send(msg, req.SendTo) err = app.email.send(msg, req.SendTo)
} }
if err != nil { if err != nil {
invite.SendTo = fmt.Sprintf("Failed to send to %s", req.SendTo) invite.SendTo = fmt.Sprintf(lm.FailedSendInviteMessage, invite.Code, req.SendTo, err)
app.err.Printf("%s: %s: %v", invite.Code, invite.SendTo, err) app.err.Println(invite.SendTo)
} else { } else {
app.info.Printf("%s: Sent invite email to \"%s\"", invite.Code, req.SendTo) app.info.Printf(lm.SentInviteMessage, invite.Code, req.SendTo)
} }
} }
} }
@ -297,7 +266,6 @@ func (app *appContext) GenerateInvite(gc *gin.Context) {
// @Security Bearer // @Security Bearer
// @tags Invites // @tags Invites
func (app *appContext) GetInvites(gc *gin.Context) { func (app *appContext) GetInvites(gc *gin.Context) {
app.debug.Println("Invites requested")
currentTime := time.Now() currentTime := time.Now()
app.checkInvites() app.checkInvites()
var invites []inviteDTO var invites []inviteDTO
@ -332,7 +300,7 @@ func (app *appContext) GetInvites(gc *gin.Context) {
if err != nil { if err != nil {
date, err := timefmt.Parse(pair[1], app.datePattern+" "+app.timePattern) date, err := timefmt.Parse(pair[1], app.datePattern+" "+app.timePattern)
if err != nil { if err != nil {
app.err.Printf("Failed to parse usedBy time: %v", err) app.err.Printf(lm.FailedParseTime, err)
} }
unix = date.Unix() unix = date.Unix()
} }
@ -347,7 +315,6 @@ func (app *appContext) GetInvites(gc *gin.Context) {
invite.SendTo = inv.SendTo invite.SendTo = inv.SendTo
} }
if len(inv.Notify) != 0 { if len(inv.Notify) != 0 {
// app.err.Printf("%s has notify section: %+v, you are %s\n", inv.Code, inv.Notify, gc.GetString("jfId"))
var addressOrID string var addressOrID string
if app.config.Section("ui").Key("jellyfin_login").MustBool(false) { if app.config.Section("ui").Key("jellyfin_login").MustBool(false) {
addressOrID = gc.GetString("jfId") addressOrID = gc.GetString("jfId")
@ -397,10 +364,9 @@ func (app *appContext) GetInvites(gc *gin.Context) {
func (app *appContext) SetProfile(gc *gin.Context) { func (app *appContext) SetProfile(gc *gin.Context) {
var req inviteProfileDTO var req inviteProfileDTO
gc.BindJSON(&req) gc.BindJSON(&req)
app.debug.Printf("%s: Setting profile to \"%s\"", req.Invite, req.Profile)
// "" means "Don't apply profile" // "" means "Don't apply profile"
if _, ok := app.storage.GetProfileKey(req.Profile); !ok && req.Profile != "" { if _, ok := app.storage.GetProfileKey(req.Profile); !ok && req.Profile != "" {
app.err.Printf("%s: Profile \"%s\" not found", req.Invite, req.Profile) app.err.Printf(lm.FailedGetProfile, req.Profile)
respond(500, "Profile not found", gc) respond(500, "Profile not found", gc)
return return
} }
@ -424,11 +390,11 @@ func (app *appContext) SetNotify(gc *gin.Context) {
gc.BindJSON(&req) gc.BindJSON(&req)
changed := false changed := false
for code, settings := range req { for code, settings := range req {
app.debug.Printf("%s: Notification settings change requested", code)
invite, ok := app.storage.GetInvitesKey(code) invite, ok := app.storage.GetInvitesKey(code)
if !ok { if !ok {
app.err.Printf("%s Notification setting change failed: Invalid code", code) msg := fmt.Sprintf(lm.InvalidInviteCode, code)
respond(400, "Invalid invite code", gc) app.err.Println(msg)
respond(400, msg, gc)
return return
} }
var address string var address string
@ -436,9 +402,8 @@ func (app *appContext) SetNotify(gc *gin.Context) {
if jellyfinLogin { if jellyfinLogin {
var addressAvailable bool = app.getAddressOrName(gc.GetString("jfId")) != "" var addressAvailable bool = app.getAddressOrName(gc.GetString("jfId")) != ""
if !addressAvailable { if !addressAvailable {
app.err.Printf("%s: Couldn't find contact method for admin. Make sure one is set.", code) app.err.Printf(lm.FailedGetContactMethod, gc.GetString("jfId"))
app.debug.Printf("%s: User ID \"%s\"", code, gc.GetString("jfId")) respond(500, fmt.Sprintf(lm.FailedGetContactMethod, "admin"), gc)
respond(500, "Missing user contact method", gc)
return return
} }
address = gc.GetString("jfId") address = gc.GetString("jfId")
@ -453,15 +418,12 @@ func (app *appContext) SetNotify(gc *gin.Context) {
} /*else { } /*else {
if _, ok := invite.Notify[address]["notify-expiry"]; !ok { if _, ok := invite.Notify[address]["notify-expiry"]; !ok {
*/ */
if _, ok := settings["notify-expiry"]; ok && invite.Notify[address]["notify-expiry"] != settings["notify-expiry"] { for _, notifyType := range []string{"notify-expiry", "notify-creation"} {
invite.Notify[address]["notify-expiry"] = settings["notify-expiry"] if _, ok := settings[notifyType]; ok && invite.Notify[address][notifyType] != settings[notifyType] {
app.debug.Printf("%s: Set \"notify-expiry\" to %t for %s", code, settings["notify-expiry"], address) invite.Notify[address][notifyType] = settings[notifyType]
changed = true app.debug.Printf(lm.SetAdminNotify, notifyType, settings[notifyType], address)
} changed = true
if _, ok := settings["notify-creation"]; ok && invite.Notify[address]["notify-creation"] != settings["notify-creation"] { }
invite.Notify[address]["notify-creation"] = settings["notify-creation"]
app.debug.Printf("%s: Set \"notify-creation\" to %t for %s", code, settings["notify-creation"], address)
changed = true
} }
if changed { if changed {
app.storage.SetInvitesKey(code, invite) app.storage.SetInvitesKey(code, invite)
@ -480,7 +442,6 @@ func (app *appContext) SetNotify(gc *gin.Context) {
func (app *appContext) DeleteInvite(gc *gin.Context) { func (app *appContext) DeleteInvite(gc *gin.Context) {
var req deleteInviteDTO var req deleteInviteDTO
gc.BindJSON(&req) gc.BindJSON(&req)
app.debug.Printf("%s: Deletion requested", req.Code)
inv, ok := app.storage.GetInvitesKey(req.Code) inv, ok := app.storage.GetInvitesKey(req.Code)
if ok { if ok {
app.storage.DeleteInvitesKey(req.Code) app.storage.DeleteInvitesKey(req.Code)
@ -495,10 +456,10 @@ func (app *appContext) DeleteInvite(gc *gin.Context) {
Time: time.Now(), Time: time.Now(),
}, gc, false) }, gc, false)
app.info.Printf("%s: Invite deleted", req.Code) app.info.Printf(lm.DeleteInvite, req.Code)
respondBool(200, true, gc) respondBool(200, true, gc)
return return
} }
app.err.Printf("%s: Deletion failed: Invalid code", req.Code) app.err.Printf(lm.FailedDeleteInvite, req.Code, "invalid code")
respond(400, "Code doesn't exist", gc) respond(400, "Code doesn't exist", gc)
} }

View File

@ -5,6 +5,7 @@ import (
"strconv" "strconv"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
lm "github.com/hrfee/jfa-go/logmessages"
) )
// @Summary Get a list of Jellyseerr users. // @Summary Get a list of Jellyseerr users.
@ -15,14 +16,12 @@ import (
// @Security Bearer // @Security Bearer
// @tags Jellyseerr // @tags Jellyseerr
func (app *appContext) JellyseerrUsers(gc *gin.Context) { func (app *appContext) JellyseerrUsers(gc *gin.Context) {
app.debug.Println("Jellyseerr users requested")
users, err := app.js.GetUsers() users, err := app.js.GetUsers()
if err != nil { if err != nil {
app.err.Printf("Failed to get users from Jellyseerr: %v", err) app.err.Printf(lm.FailedGetUsers, lm.Jellyseerr, err)
respond(500, "Couldn't get users", gc) respond(500, "Couldn't get users", gc)
return return
} }
app.debug.Printf("Jellyseerr users retrieved: %d", len(users))
userlist := make([]ombiUser, len(users)) userlist := make([]ombiUser, len(users))
i := 0 i := 0
for _, u := range users { for _, u := range users {
@ -60,14 +59,14 @@ func (app *appContext) SetJellyseerrProfile(gc *gin.Context) {
} }
u, err := app.js.UserByID(jellyseerrID) u, err := app.js.UserByID(jellyseerrID)
if err != nil { if err != nil {
app.err.Printf("Couldn't get user from Jellyseerr: %v", err) app.err.Printf(lm.FailedGetUsers, lm.Jellyseerr, err)
respond(500, "Couldn't get user", gc) respond(500, "Couldn't get user", gc)
return return
} }
profile.Jellyseerr.User = u.UserTemplate profile.Jellyseerr.User = u.UserTemplate
n, err := app.js.GetNotificationPreferencesByID(jellyseerrID) n, err := app.js.GetNotificationPreferencesByID(jellyseerrID)
if err != nil { if err != nil {
app.err.Printf("Couldn't get user's notification prefs from Jellyseerr: %v", err) app.err.Printf(lm.FailedGetJellyseerrNotificationPrefs, err)
respond(500, "Couldn't get user notification prefs", gc) respond(500, "Couldn't get user notification prefs", gc)
return return
} }

View File

@ -6,6 +6,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/hrfee/jfa-go/jellyseerr" "github.com/hrfee/jfa-go/jellyseerr"
lm "github.com/hrfee/jfa-go/logmessages"
"github.com/lithammer/shortuuid/v3" "github.com/lithammer/shortuuid/v3"
"gopkg.in/ini.v1" "gopkg.in/ini.v1"
) )
@ -134,7 +135,7 @@ func (app *appContext) GetCustomMessageTemplate(gc *gin.Context) {
emailAddress := app.storage.lang.Email[lang].Strings.get("emailAddress") emailAddress := app.storage.lang.Email[lang].Strings.get("emailAddress")
customMessage, ok := app.storage.GetCustomContentKey(id) customMessage, ok := app.storage.GetCustomContentKey(id)
if !ok && id != "Announcement" { if !ok && id != "Announcement" {
app.err.Printf("Failed to get custom message with ID \"%s\"", id) app.err.Printf(lm.FailedGetCustomMessage, id)
respondBool(400, false, gc) respondBool(400, false, gc)
return return
} }
@ -328,7 +329,7 @@ func (app *appContext) TelegramAddUser(gc *gin.Context) {
jellyseerr.FieldTelegram: tgUser.ChatID, jellyseerr.FieldTelegram: tgUser.ChatID,
jellyseerr.FieldTelegramEnabled: tgUser.Contact, jellyseerr.FieldTelegramEnabled: tgUser.Contact,
}); err != nil { }); err != nil {
app.err.Printf("Failed to sync contact methods with Jellyseerr: %v", err) app.err.Printf(lm.FailedSyncContactMethods, lm.Jellyseerr, err)
} }
linkExistingOmbiDiscordTelegram(app) linkExistingOmbiDiscordTelegram(app)
@ -361,11 +362,7 @@ func (app *appContext) setContactMethods(req SetContactMethodsDTO, gc *gin.Conte
tgUser.Contact = req.Telegram tgUser.Contact = req.Telegram
app.storage.SetTelegramKey(req.ID, tgUser) app.storage.SetTelegramKey(req.ID, tgUser)
if change { if change {
msg := "" app.debug.Printf(lm.SetContactPrefForService, lm.Telegram, tgUser.Username, req.Telegram)
if !req.Telegram {
msg = " not"
}
app.debug.Printf("Telegram: User \"%s\" will%s be notified through Telegram.", tgUser.Username, msg)
jsPrefs[jellyseerr.FieldTelegramEnabled] = req.Telegram jsPrefs[jellyseerr.FieldTelegramEnabled] = req.Telegram
} }
} }
@ -374,11 +371,7 @@ func (app *appContext) setContactMethods(req SetContactMethodsDTO, gc *gin.Conte
dcUser.Contact = req.Discord dcUser.Contact = req.Discord
app.storage.SetDiscordKey(req.ID, dcUser) app.storage.SetDiscordKey(req.ID, dcUser)
if change { if change {
msg := "" app.debug.Printf(lm.SetContactPrefForService, lm.Discord, dcUser.Username, req.Discord)
if !req.Discord {
msg = " not"
}
app.debug.Printf("Discord: User \"%s\" will%s be notified through Discord.", dcUser.Username, msg)
jsPrefs[jellyseerr.FieldDiscordEnabled] = req.Discord jsPrefs[jellyseerr.FieldDiscordEnabled] = req.Discord
} }
} }
@ -387,11 +380,7 @@ func (app *appContext) setContactMethods(req SetContactMethodsDTO, gc *gin.Conte
mxUser.Contact = req.Matrix mxUser.Contact = req.Matrix
app.storage.SetMatrixKey(req.ID, mxUser) app.storage.SetMatrixKey(req.ID, mxUser)
if change { if change {
msg := "" app.debug.Printf(lm.SetContactPrefForService, lm.Matrix, mxUser.UserID, req.Matrix)
if !req.Matrix {
msg = " not"
}
app.debug.Printf("Matrix: User \"%s\" will%s be notified through Matrix.", mxUser.UserID, msg)
} }
} }
if email, ok := app.storage.GetEmailsKey(req.ID); ok { if email, ok := app.storage.GetEmailsKey(req.ID); ok {
@ -399,18 +388,14 @@ func (app *appContext) setContactMethods(req SetContactMethodsDTO, gc *gin.Conte
email.Contact = req.Email email.Contact = req.Email
app.storage.SetEmailsKey(req.ID, email) app.storage.SetEmailsKey(req.ID, email)
if change { if change {
msg := "" app.debug.Printf(lm.SetContactPrefForService, lm.Email, email.Addr, req.Email)
if !req.Email {
msg = " not"
}
app.debug.Printf("\"%s\" will%s be notified via Email.", email.Addr, msg)
jsPrefs[jellyseerr.FieldEmailEnabled] = req.Email jsPrefs[jellyseerr.FieldEmailEnabled] = req.Email
} }
} }
if app.config.Section("jellyseerr").Key("enabled").MustBool(false) { if app.config.Section("jellyseerr").Key("enabled").MustBool(false) {
err := app.js.ModifyNotifications(req.ID, jsPrefs) err := app.js.ModifyNotifications(req.ID, jsPrefs)
if err != nil { if err != nil {
app.err.Printf("Failed to sync contact prefs with Jellyseerr: %v", err) app.err.Printf(lm.FailedSyncContactMethods, lm.Jellyseerr, err)
} }
} }
respondBool(200, true, gc) respondBool(200, true, gc)
@ -555,7 +540,7 @@ func (app *appContext) MatrixSendPIN(gc *gin.Context) {
func (app *appContext) MatrixCheckPIN(gc *gin.Context) { func (app *appContext) MatrixCheckPIN(gc *gin.Context) {
code := gc.Param("invCode") code := gc.Param("invCode")
if _, ok := app.storage.GetInvitesKey(code); !ok { if _, ok := app.storage.GetInvitesKey(code); !ok {
app.debug.Println("Matrix: Invite code was invalid") app.debug.Printf(lm.InvalidInviteCode, code)
respondBool(401, false, gc) respondBool(401, false, gc)
return return
} }
@ -563,12 +548,12 @@ func (app *appContext) MatrixCheckPIN(gc *gin.Context) {
pin := gc.Param("pin") pin := gc.Param("pin")
user, ok := app.matrix.tokens[pin] user, ok := app.matrix.tokens[pin]
if !ok { if !ok {
app.debug.Println("Matrix: PIN not found") app.debug.Printf(lm.InvalidPIN, pin)
respondBool(200, false, gc) respondBool(200, false, gc)
return return
} }
if user.User.UserID != userID { if user.User.UserID != userID {
app.debug.Println("Matrix: User ID of PIN didn't match") app.debug.Printf(lm.UnauthorizedPIN, pin)
respondBool(200, false, gc) respondBool(200, false, gc)
return return
} }
@ -596,7 +581,7 @@ func (app *appContext) MatrixLogin(gc *gin.Context) {
} }
token, err := app.matrix.generateAccessToken(req.Homeserver, req.Username, req.Password) token, err := app.matrix.generateAccessToken(req.Homeserver, req.Username, req.Password)
if err != nil { if err != nil {
app.err.Printf("Matrix: Failed to generate token: %v", err) app.err.Printf(lm.FailedGenerateToken, err)
respond(401, "Unauthorized", gc) respond(401, "Unauthorized", gc)
return return
} }
@ -607,7 +592,7 @@ func (app *appContext) MatrixLogin(gc *gin.Context) {
matrix.Key("token").SetValue(token) matrix.Key("token").SetValue(token)
matrix.Key("user_id").SetValue(req.Username) matrix.Key("user_id").SetValue(req.Username)
if err := tempConfig.SaveTo(app.configPath); err != nil { if err := tempConfig.SaveTo(app.configPath); err != nil {
app.err.Printf("Failed to save config to \"%s\": %v", app.configPath, err) app.err.Printf(lm.FailedWriting, app.configPath, err)
respondBool(500, false, gc) respondBool(500, false, gc)
return return
} }
@ -631,7 +616,7 @@ func (app *appContext) MatrixConnect(gc *gin.Context) {
} }
roomID, encrypted, err := app.matrix.CreateRoom(req.UserID) roomID, encrypted, err := app.matrix.CreateRoom(req.UserID)
if err != nil { if err != nil {
app.err.Printf("Matrix: Failed to create room: %v", err) app.err.Printf(lm.FailedCreateRoom, err)
respondBool(500, false, gc) respondBool(500, false, gc)
return return
} }
@ -701,7 +686,7 @@ func (app *appContext) DiscordConnect(gc *gin.Context) {
jellyseerr.FieldDiscord: req.DiscordID, jellyseerr.FieldDiscord: req.DiscordID,
jellyseerr.FieldDiscordEnabled: true, jellyseerr.FieldDiscordEnabled: true,
}); err != nil { }); err != nil {
app.err.Printf("Failed to sync contact methods with Jellyseerr: %v", err) app.err.Printf(lm.FailedSyncContactMethods, lm.Jellyseerr, err)
} }
app.storage.SetActivityKey(shortuuid.New(), Activity{ app.storage.SetActivityKey(shortuuid.New(), Activity{
@ -739,7 +724,7 @@ func (app *appContext) UnlinkDiscord(gc *gin.Context) {
jellyseerr.FieldDiscord: jellyseerr.BogusIdentifier, jellyseerr.FieldDiscord: jellyseerr.BogusIdentifier,
jellyseerr.FieldDiscordEnabled: false, jellyseerr.FieldDiscordEnabled: false,
}); err != nil { }); err != nil {
app.err.Printf("Failed to sync contact methods with Jellyseerr: %v", err) app.err.Printf(lm.FailedSyncContactMethods, lm.Jellyseerr, err)
} }
app.storage.SetActivityKey(shortuuid.New(), Activity{ app.storage.SetActivityKey(shortuuid.New(), Activity{
@ -775,7 +760,7 @@ func (app *appContext) UnlinkTelegram(gc *gin.Context) {
jellyseerr.FieldTelegram: jellyseerr.BogusIdentifier, jellyseerr.FieldTelegram: jellyseerr.BogusIdentifier,
jellyseerr.FieldTelegramEnabled: false, jellyseerr.FieldTelegramEnabled: false,
}); err != nil { }); err != nil {
app.err.Printf("Failed to sync contact methods with Jellyseerr: %v", err) app.err.Printf(lm.FailedSyncContactMethods, lm.Jellyseerr, err)
} }
app.storage.SetActivityKey(shortuuid.New(), Activity{ app.storage.SetActivityKey(shortuuid.New(), Activity{

View File

@ -5,6 +5,7 @@ import (
"net/url" "net/url"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
lm "github.com/hrfee/jfa-go/logmessages"
"github.com/hrfee/mediabrowser" "github.com/hrfee/mediabrowser"
) )
@ -66,10 +67,9 @@ func (app *appContext) getOmbiImportedUser(name string) (map[string]interface{},
// @Security Bearer // @Security Bearer
// @tags Ombi // @tags Ombi
func (app *appContext) OmbiUsers(gc *gin.Context) { func (app *appContext) OmbiUsers(gc *gin.Context) {
app.debug.Println("Ombi users requested")
users, status, err := app.ombi.GetUsers() users, status, err := app.ombi.GetUsers()
if err != nil || status != 200 { if err != nil || status != 200 {
app.err.Printf("Failed to get users from Ombi (%d): %v", status, err) app.err.Printf(lm.FailedGetUsers, lm.Ombi, err)
respond(500, "Couldn't get users", gc) respond(500, "Couldn't get users", gc)
return return
} }
@ -105,7 +105,7 @@ func (app *appContext) SetOmbiProfile(gc *gin.Context) {
} }
template, code, err := app.ombi.TemplateByID(req.ID) template, code, err := app.ombi.TemplateByID(req.ID)
if err != nil || code != 200 || len(template) == 0 { if err != nil || code != 200 || len(template) == 0 {
app.err.Printf("Couldn't get user from Ombi (%d): %v", code, err) app.err.Printf(lm.FailedGetUsers, lm.Ombi, err)
respond(500, "Couldn't get user", gc) respond(500, "Couldn't get user", gc)
return return
} }

View File

@ -1,9 +1,11 @@
package main package main
import ( import (
"fmt"
"time" "time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
lm "github.com/hrfee/jfa-go/logmessages"
"github.com/timshannon/badgerhold/v4" "github.com/timshannon/badgerhold/v4"
) )
@ -14,7 +16,6 @@ import (
// @Security Bearer // @Security Bearer
// @tags Profiles & Settings // @tags Profiles & Settings
func (app *appContext) GetProfiles(gc *gin.Context) { func (app *appContext) GetProfiles(gc *gin.Context) {
app.debug.Println("Profiles requested")
out := getProfilesDTO{ out := getProfilesDTO{
DefaultProfile: app.storage.GetDefaultProfile().Name, DefaultProfile: app.storage.GetDefaultProfile().Name,
Profiles: map[string]profileDTO{}, Profiles: map[string]profileDTO{},
@ -52,10 +53,11 @@ func (app *appContext) GetProfiles(gc *gin.Context) {
func (app *appContext) SetDefaultProfile(gc *gin.Context) { func (app *appContext) SetDefaultProfile(gc *gin.Context) {
req := profileChangeDTO{} req := profileChangeDTO{}
gc.BindJSON(&req) gc.BindJSON(&req)
app.info.Printf("Setting default profile to \"%s\"", req.Name) app.info.Printf(lm.SetDefaultProfile, req.Name)
if _, ok := app.storage.GetProfileKey(req.Name); !ok { if _, ok := app.storage.GetProfileKey(req.Name); !ok {
app.err.Printf("Profile not found: \"%s\"", req.Name) msg := fmt.Sprintf(lm.FailedGetProfile, req.Name)
respond(500, "Profile not found", gc) app.err.Println(msg)
respond(500, msg, gc)
return return
} }
app.storage.db.ForEach(&badgerhold.Query{}, func(profile *Profile) error { app.storage.db.ForEach(&badgerhold.Query{}, func(profile *Profile) error {
@ -79,13 +81,12 @@ func (app *appContext) SetDefaultProfile(gc *gin.Context) {
// @Security Bearer // @Security Bearer
// @tags Profiles & Settings // @tags Profiles & Settings
func (app *appContext) CreateProfile(gc *gin.Context) { func (app *appContext) CreateProfile(gc *gin.Context) {
app.info.Println("Profile creation requested")
var req newProfileDTO var req newProfileDTO
gc.BindJSON(&req) gc.BindJSON(&req)
app.jf.CacheExpiry = time.Now() app.jf.CacheExpiry = time.Now()
user, status, err := app.jf.UserByID(req.ID, false) user, status, err := app.jf.UserByID(req.ID, false)
if !(status == 200 || status == 204) || err != nil { if !(status == 200 || status == 204) || err != nil {
app.err.Printf("Failed to get user from Jellyfin (%d): %v", status, err) app.err.Printf(lm.FailedGetUsers, lm.Jellyfin, err)
respond(500, "Couldn't get user", gc) respond(500, "Couldn't get user", gc)
return return
} }
@ -94,12 +95,12 @@ func (app *appContext) CreateProfile(gc *gin.Context) {
Policy: user.Policy, Policy: user.Policy,
Homescreen: req.Homescreen, Homescreen: req.Homescreen,
} }
app.debug.Printf("Creating profile from user \"%s\"", user.Name) app.debug.Printf(lm.CreateProfileFromUser, user.Name)
if req.Homescreen { if req.Homescreen {
profile.Configuration = user.Configuration profile.Configuration = user.Configuration
profile.Displayprefs, status, err = app.jf.GetDisplayPreferences(req.ID) profile.Displayprefs, status, err = app.jf.GetDisplayPreferences(req.ID)
if !(status == 200 || status == 204) || err != nil { if !(status == 200 || status == 204) || err != nil {
app.err.Printf("Failed to get DisplayPrefs (%d): %v", status, err) app.err.Printf(lm.FailedGetJellyfinDisplayPrefs, req.ID, err)
respond(500, "Couldn't get displayprefs", gc) respond(500, "Couldn't get displayprefs", gc)
return return
} }
@ -145,13 +146,13 @@ func (app *appContext) EnableReferralForProfile(gc *gin.Context) {
inv, ok := app.storage.GetInvitesKey(invCode) inv, ok := app.storage.GetInvitesKey(invCode)
if !ok { if !ok {
respond(400, "Invalid invite code", gc) respond(400, "Invalid invite code", gc)
app.err.Printf("\"%s\": Failed to enable referrals: invite not found", profileName) app.err.Printf(lm.InvalidInviteCode, invCode)
return return
} }
profile, ok := app.storage.GetProfileKey(profileName) profile, ok := app.storage.GetProfileKey(profileName)
if !ok { if !ok {
respond(400, "Invalid profile", gc) respond(400, "Invalid profile", gc)
app.err.Printf("\"%s\": Failed to enable referrals: profile not found", profileName) app.err.Printf(lm.FailedGetProfile, profileName)
return return
} }

View File

@ -1,6 +1,7 @@
package main package main
import ( import (
"fmt"
"net/http" "net/http"
"os" "os"
"strings" "strings"
@ -9,6 +10,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt" "github.com/golang-jwt/jwt"
"github.com/hrfee/jfa-go/jellyseerr" "github.com/hrfee/jfa-go/jellyseerr"
lm "github.com/hrfee/jfa-go/logmessages"
"github.com/lithammer/shortuuid/v3" "github.com/lithammer/shortuuid/v3"
"github.com/timshannon/badgerhold/v4" "github.com/timshannon/badgerhold/v4"
) )
@ -29,7 +31,7 @@ func (app *appContext) MyDetails(gc *gin.Context) {
user, status, err := app.jf.UserByID(resp.Id, false) user, status, err := app.jf.UserByID(resp.Id, false)
if status != 200 || err != nil { if status != 200 || err != nil {
app.err.Printf("Failed to get Jellyfin user (%d): %+v\n", status, err) app.err.Printf(lm.FailedGetUsers, lm.Jellyfin, err)
respond(500, "Failed to get user", gc) respond(500, "Failed to get user", gc)
return return
} }
@ -133,8 +135,9 @@ func (app *appContext) SetMyContactMethods(gc *gin.Context) {
func (app *appContext) LogoutUser(gc *gin.Context) { func (app *appContext) LogoutUser(gc *gin.Context) {
cookie, err := gc.Cookie("user-refresh") cookie, err := gc.Cookie("user-refresh")
if err != nil { if err != nil {
app.debug.Printf("Couldn't get cookies: %s", err) msg := fmt.Sprintf(lm.FailedGetCookies, "user-refresh", err)
respond(500, "Couldn't fetch cookies", gc) app.debug.Println(msg)
respond(500, msg, gc)
return return
} }
app.invalidTokens = append(app.invalidTokens, cookie) app.invalidTokens = append(app.invalidTokens, cookie)
@ -174,21 +177,21 @@ func (app *appContext) confirmMyAction(gc *gin.Context, key string) {
} }
token, err := jwt.Parse(key, checkToken) token, err := jwt.Parse(key, checkToken)
if err != nil { if err != nil {
app.err.Printf("Failed to parse key: %s", err) app.err.Printf(lm.FailedParseJWT, err)
fail() fail()
// respond(500, "unknownError", gc) // respond(500, "unknownError", gc)
return return
} }
claims, ok := token.Claims.(jwt.MapClaims) claims, ok := token.Claims.(jwt.MapClaims)
if !ok { if !ok {
app.err.Printf("Failed to parse key: %s", err) app.err.Println(lm.FailedCastJWT)
fail() fail()
// respond(500, "unknownError", gc) // respond(500, "unknownError", gc)
return return
} }
expiry := time.Unix(int64(claims["exp"].(float64)), 0) expiry := time.Unix(int64(claims["exp"].(float64)), 0)
if !(ok && token.Valid && claims["type"].(string) == "confirmation" && expiry.After(time.Now())) { if !(ok && token.Valid && claims["type"].(string) == "confirmation" && expiry.After(time.Now())) {
app.err.Printf("Invalid key") app.err.Println(lm.InvalidJWT)
fail() fail()
// respond(400, "invalidKey", gc) // respond(400, "invalidKey", gc)
return return
@ -212,7 +215,7 @@ func (app *appContext) confirmMyAction(gc *gin.Context, key string) {
Time: time.Now(), Time: time.Now(),
}, gc, true) }, gc, true)
app.info.Println("Email list modified") app.info.Printf(lm.UserEmailAdjusted, gc.GetString("jfId"))
gc.Redirect(http.StatusSeeOther, "/my/account") gc.Redirect(http.StatusSeeOther, "/my/account")
return return
} }
@ -231,7 +234,6 @@ func (app *appContext) confirmMyAction(gc *gin.Context, key string) {
func (app *appContext) ModifyMyEmail(gc *gin.Context) { func (app *appContext) ModifyMyEmail(gc *gin.Context) {
var req ModifyMyEmailDTO var req ModifyMyEmailDTO
gc.BindJSON(&req) gc.BindJSON(&req)
app.debug.Println("Email modification requested")
if !strings.ContainsRune(req.Email, '@') { if !strings.ContainsRune(req.Email, '@') {
respond(400, "Invalid Email Address", gc) respond(400, "Invalid Email Address", gc)
return return
@ -251,7 +253,7 @@ func (app *appContext) ModifyMyEmail(gc *gin.Context) {
key, err := tk.SignedString([]byte(os.Getenv("JFA_SECRET"))) key, err := tk.SignedString([]byte(os.Getenv("JFA_SECRET")))
if err != nil { if err != nil {
app.err.Printf("Failed to generate confirmation token: %v", err) app.err.Printf(lm.FailedSignJWT, err)
respond(500, "errorUnknown", gc) respond(500, "errorUnknown", gc)
return return
} }
@ -262,15 +264,15 @@ func (app *appContext) ModifyMyEmail(gc *gin.Context) {
if status == 200 && err == nil { if status == 200 && err == nil {
name = user.Name name = user.Name
} }
app.debug.Printf("%s: Email confirmation required", id) app.debug.Printf(lm.EmailConfirmationRequired, id)
respond(401, "confirmEmail", gc) respond(401, "confirmEmail", gc)
msg, err := app.email.constructConfirmation("", name, key, app, false) msg, err := app.email.constructConfirmation("", name, key, app, false)
if err != nil { if err != nil {
app.err.Printf("%s: Failed to construct confirmation email: %v", name, err) app.err.Printf(lm.FailedConstructConfirmationEmail, id, err)
} else if err := app.email.send(msg, req.Email); err != nil { } else if err := app.email.send(msg, req.Email); err != nil {
app.err.Printf("%s: Failed to send user confirmation email: %v", name, err) app.err.Printf(lm.FailedSendConfirmationEmail, id, req.Email, err)
} else { } else {
app.info.Printf("%s: Sent user confirmation email to \"%s\"", name, req.Email) app.err.Printf(lm.SentConfirmationEmail, id, req.Email)
} }
return return
} }
@ -358,7 +360,7 @@ func (app *appContext) MyDiscordVerifiedInvite(gc *gin.Context) {
jellyseerr.FieldDiscord: dcUser.ID, jellyseerr.FieldDiscord: dcUser.ID,
jellyseerr.FieldDiscordEnabled: dcUser.Contact, jellyseerr.FieldDiscordEnabled: dcUser.Contact,
}); err != nil { }); err != nil {
app.err.Printf("Failed to sync contact methods with Jellyseerr: %v", err) app.err.Printf(lm.FailedSyncContactMethods, lm.Jellyseerr, err)
} }
app.storage.SetActivityKey(shortuuid.New(), Activity{ app.storage.SetActivityKey(shortuuid.New(), Activity{
@ -413,7 +415,7 @@ func (app *appContext) MyTelegramVerifiedInvite(gc *gin.Context) {
jellyseerr.FieldTelegram: tgUser.ChatID, jellyseerr.FieldTelegram: tgUser.ChatID,
jellyseerr.FieldTelegramEnabled: tgUser.Contact, jellyseerr.FieldTelegramEnabled: tgUser.Contact,
}); err != nil { }); err != nil {
app.err.Printf("Failed to sync contact methods with Jellyseerr: %v", err) app.err.Printf(lm.FailedSyncContactMethods, lm.Jellyseerr, err)
} }
app.storage.SetActivityKey(shortuuid.New(), Activity{ app.storage.SetActivityKey(shortuuid.New(), Activity{
@ -477,12 +479,12 @@ func (app *appContext) MatrixCheckMyPIN(gc *gin.Context) {
pin := gc.Param("pin") pin := gc.Param("pin")
user, ok := app.matrix.tokens[pin] user, ok := app.matrix.tokens[pin]
if !ok { if !ok {
app.debug.Println("Matrix: PIN not found") app.debug.Printf(lm.InvalidPIN, pin)
respondBool(200, false, gc) respondBool(200, false, gc)
return return
} }
if user.User.UserID != userID { if user.User.UserID != userID {
app.debug.Println("Matrix: User ID of PIN didn't match") app.debug.Printf(lm.UnauthorizedPIN, pin)
respondBool(200, false, gc) respondBool(200, false, gc)
return return
} }
@ -523,7 +525,7 @@ func (app *appContext) UnlinkMyDiscord(gc *gin.Context) {
jellyseerr.FieldDiscord: jellyseerr.BogusIdentifier, jellyseerr.FieldDiscord: jellyseerr.BogusIdentifier,
jellyseerr.FieldDiscordEnabled: false, jellyseerr.FieldDiscordEnabled: false,
}); err != nil { }); err != nil {
app.err.Printf("Failed to sync contact methods with Jellyseerr: %v", err) app.err.Printf(lm.FailedSyncContactMethods, lm.Jellyseerr, err)
} }
app.storage.SetActivityKey(shortuuid.New(), Activity{ app.storage.SetActivityKey(shortuuid.New(), Activity{
@ -551,7 +553,7 @@ func (app *appContext) UnlinkMyTelegram(gc *gin.Context) {
jellyseerr.FieldTelegram: jellyseerr.BogusIdentifier, jellyseerr.FieldTelegram: jellyseerr.BogusIdentifier,
jellyseerr.FieldTelegramEnabled: false, jellyseerr.FieldTelegramEnabled: false,
}); err != nil { }); err != nil {
app.err.Printf("Failed to sync contact methods with Jellyseerr: %v", err) app.err.Printf(lm.FailedSyncContactMethods, lm.Jellyseerr, err)
} }
app.storage.SetActivityKey(shortuuid.New(), Activity{ app.storage.SetActivityKey(shortuuid.New(), Activity{
@ -606,7 +608,6 @@ func (app *appContext) ResetMyPassword(gc *gin.Context) {
contactMethodAllowed := app.config.Section("user_page").Key("allow_pwr_contact_method").MustBool(true) contactMethodAllowed := app.config.Section("user_page").Key("allow_pwr_contact_method").MustBool(true)
address := gc.Param("address") address := gc.Param("address")
if address == "" { if address == "" {
app.debug.Println("Ignoring empty request for PWR")
cancel.Stop() cancel.Stop()
respondBool(400, false, gc) respondBool(400, false, gc)
return return
@ -616,7 +617,7 @@ func (app *appContext) ResetMyPassword(gc *gin.Context) {
jfUser, ok := app.ReverseUserSearch(address, usernameAllowed, emailAllowed, contactMethodAllowed) jfUser, ok := app.ReverseUserSearch(address, usernameAllowed, emailAllowed, contactMethodAllowed)
if !ok { if !ok {
app.debug.Printf("Ignoring PWR request: User not found") app.debug.Printf(lm.FailedGetUsers, lm.Jellyfin, "no results")
for range timerWait { for range timerWait {
respondBool(204, true, gc) respondBool(204, true, gc)
@ -626,7 +627,7 @@ func (app *appContext) ResetMyPassword(gc *gin.Context) {
} }
pwr, err = app.GenInternalReset(jfUser.ID) pwr, err = app.GenInternalReset(jfUser.ID)
if err != nil { if err != nil {
app.err.Printf("Failed to get user from Jellyfin: %v", err) app.err.Printf(lm.FailedGetUsers, lm.Jellyfin, err)
for range timerWait { for range timerWait {
respondBool(204, true, gc) respondBool(204, true, gc)
return return
@ -647,16 +648,16 @@ func (app *appContext) ResetMyPassword(gc *gin.Context) {
}, app, false, }, app, false,
) )
if err != nil { if err != nil {
app.err.Printf("Failed to construct password reset message for \"%s\": %v", pwr.Username, err) app.err.Printf(lm.FailedConstructPWRMessage, pwr.Username, err)
for range timerWait { for range timerWait {
respondBool(204, true, gc) respondBool(204, true, gc)
return return
} }
return return
} else if err := app.sendByID(msg, jfUser.ID); err != nil { } else if err := app.sendByID(msg, jfUser.ID); err != nil {
app.err.Printf("Failed to send password reset message to \"%s\": %v", address, err) app.err.Printf(lm.FailedSendPWRMessage, pwr.Username, "?", err)
} else { } else {
app.info.Printf("Sent password reset message to \"%s\"", address) app.info.Printf(lm.SentPWRMessage, pwr.Username, "?")
} }
for range timerWait { for range timerWait {
respondBool(204, true, gc) respondBool(204, true, gc)
@ -683,14 +684,13 @@ func (app *appContext) ChangeMyPassword(gc *gin.Context) {
validation := app.validator.validate(req.New) validation := app.validator.validate(req.New)
for _, val := range validation { for _, val := range validation {
if !val { if !val {
app.debug.Printf("%s: Change password failed: Invalid password", gc.GetString("jfId"))
gc.JSON(400, validation) gc.JSON(400, validation)
return return
} }
} }
user, status, err := app.jf.UserByID(gc.GetString("jfId"), false) user, status, err := app.jf.UserByID(gc.GetString("jfId"), false)
if status != 200 || err != nil { if status != 200 || err != nil {
app.err.Printf("Failed to change password: couldn't find user (%d): %+v", status, err) app.err.Printf(lm.FailedGetUser, gc.GetString("jfId"), lm.Jellyfin, err)
respondBool(500, false, gc) respondBool(500, false, gc)
return return
} }
@ -718,16 +718,16 @@ func (app *appContext) ChangeMyPassword(gc *gin.Context) {
func() { func() {
ombiUser, status, err := app.getOmbiUser(gc.GetString("jfId")) ombiUser, status, err := app.getOmbiUser(gc.GetString("jfId"))
if status != 200 || err != nil { if status != 200 || err != nil {
app.err.Printf("Failed to get user \"%s\" from ombi (%d): %v", user.Name, status, err) app.err.Printf(lm.FailedGetUser, user.Name, lm.Ombi, err)
return return
} }
ombiUser["password"] = req.New ombiUser["password"] = req.New
status, err = app.ombi.ModifyUser(ombiUser) status, err = app.ombi.ModifyUser(ombiUser)
if status != 200 || err != nil { if status != 200 || err != nil {
app.err.Printf("Failed to set password for ombi user \"%s\" (%d): %v", ombiUser["userName"], status, err) app.err.Printf(lm.FailedChangePassword, lm.Ombi, ombiUser["userName"], err)
return return
} }
app.debug.Printf("Reset password for ombi user \"%s\"", ombiUser["userName"]) app.debug.Printf(lm.ChangePassword, lm.Ombi, ombiUser["userName"])
}() }()
} }
cookie, err := gc.Cookie("user-refresh") cookie, err := gc.Cookie("user-refresh")
@ -735,7 +735,7 @@ func (app *appContext) ChangeMyPassword(gc *gin.Context) {
app.invalidTokens = append(app.invalidTokens, cookie) app.invalidTokens = append(app.invalidTokens, cookie)
gc.SetCookie("refresh", "invalid", -1, "/my", gc.Request.URL.Hostname(), true, true) gc.SetCookie("refresh", "invalid", -1, "/my", gc.Request.URL.Hostname(), true, true)
} else { } else {
app.debug.Printf("Couldn't get cookies: %s", err) app.debug.Printf(lm.FailedGetCookies, "user-refresh", err)
} }
respondBool(204, true, gc) respondBool(204, true, gc)
} }
@ -761,7 +761,7 @@ func (app *appContext) GetMyReferral(gc *gin.Context) {
user, ok := app.storage.GetEmailsKey(gc.GetString("jfId")) user, ok := app.storage.GetEmailsKey(gc.GetString("jfId"))
err = app.storage.db.Get(user.ReferralTemplateKey, &inv) err = app.storage.db.Get(user.ReferralTemplateKey, &inv)
if !ok || err != nil || user.ReferralTemplateKey == "" { if !ok || err != nil || user.ReferralTemplateKey == "" {
app.debug.Printf("Ignoring referral request, couldn't find template.") app.debug.Printf(lm.FailedGetReferralTemplate, user.ReferralTemplateKey, err)
respondBool(400, false, gc) respondBool(400, false, gc)
return return
} }
@ -782,6 +782,7 @@ func (app *appContext) GetMyReferral(gc *gin.Context) {
// If UseReferralExpiry is enabled, we delete it and return nothing. // If UseReferralExpiry is enabled, we delete it and return nothing.
app.storage.DeleteInvitesKey(inv.Code) app.storage.DeleteInvitesKey(inv.Code)
if inv.UseReferralExpiry { if inv.UseReferralExpiry {
app.debug.Printf(lm.DeleteOldReferral, inv.Code)
user, ok := app.storage.GetEmailsKey(gc.GetString("jfId")) user, ok := app.storage.GetEmailsKey(gc.GetString("jfId"))
if ok { if ok {
user.ReferralTemplateKey = "" user.ReferralTemplateKey = ""
@ -791,6 +792,7 @@ func (app *appContext) GetMyReferral(gc *gin.Context) {
respondBool(400, false, gc) respondBool(400, false, gc)
return return
} }
app.debug.Printf(lm.RenewOldReferral, inv.Code)
inv.Code = GenerateInviteCode() inv.Code = GenerateInviteCode()
inv.Created = time.Now() inv.Created = time.Now()
inv.ValidTill = inv.Created.Add(REFERRAL_EXPIRY_DAYS * 24 * time.Hour) inv.ValidTill = inv.Created.Add(REFERRAL_EXPIRY_DAYS * 24 * time.Hour)

View File

@ -10,6 +10,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt" "github.com/golang-jwt/jwt"
"github.com/hrfee/jfa-go/jellyseerr" "github.com/hrfee/jfa-go/jellyseerr"
lm "github.com/hrfee/jfa-go/logmessages"
"github.com/hrfee/mediabrowser" "github.com/hrfee/mediabrowser"
"github.com/lithammer/shortuuid/v3" "github.com/lithammer/shortuuid/v3"
"github.com/timshannon/badgerhold/v4" "github.com/timshannon/badgerhold/v4"
@ -36,14 +37,14 @@ func (app *appContext) NewUserAdmin(gc *gin.Context) {
gc.BindJSON(&req) gc.BindJSON(&req)
existingUser, _, _ := app.jf.UserByName(req.Username, false) existingUser, _, _ := app.jf.UserByName(req.Username, false)
if existingUser.Name != "" { if existingUser.Name != "" {
msg := fmt.Sprintf("User already exists named %s", req.Username) msg := lm.UserExists
app.info.Printf("%s New user failed: %s", req.Username, msg) app.info.Printf(lm.FailedCreateUser, lm.Jellyfin, req.Username, msg)
respondUser(401, false, false, msg, gc) respondUser(401, false, false, msg, gc)
return return
} }
user, status, err := app.jf.NewUser(req.Username, req.Password) user, status, err := app.jf.NewUser(req.Username, req.Password)
if !(status == 200 || status == 204) || err != nil { if !(status == 200 || status == 204) || err != nil {
app.err.Printf("%s New user failed (%d): %v", req.Username, status, err) app.err.Printf(lm.FailedCreateUser, lm.Jellyfin, req.Username, err)
respondUser(401, false, false, err.Error(), gc) respondUser(401, false, false, err.Error(), gc)
return return
} }
@ -64,19 +65,19 @@ func (app *appContext) NewUserAdmin(gc *gin.Context) {
if p, ok := app.storage.GetProfileKey(req.Profile); ok { if p, ok := app.storage.GetProfileKey(req.Profile); ok {
profile = p profile = p
} else { } else {
app.debug.Printf("Couldn't find profile \"%s\", using default", req.Profile) app.debug.Printf(lm.FailedGetProfile+lm.FallbackToDefault, req.Profile)
} }
status, err = app.jf.SetPolicy(id, profile.Policy) status, err = app.jf.SetPolicy(id, profile.Policy)
if !(status == 200 || status == 204 || err == nil) { if !(status == 200 || status == 204 || err == nil) {
app.err.Printf("%s: Failed to set user policy (%d): %v", req.Username, status, err) app.err.Printf(lm.FailedApplyTemplate, "policy", lm.Jellyfin, req.Username, err)
} }
status, err = app.jf.SetConfiguration(id, profile.Configuration) status, err = app.jf.SetConfiguration(id, profile.Configuration)
if (status == 200 || status == 204) && err == nil { if (status == 200 || status == 204) && err == nil {
status, err = app.jf.SetDisplayPreferences(id, profile.Displayprefs) status, err = app.jf.SetDisplayPreferences(id, profile.Displayprefs)
} }
if !((status == 200 || status == 204) && err == nil) { if !((status == 200 || status == 204) && err == nil) {
app.err.Printf("%s: Failed to set configuration template (%d): %v", req.Username, status, err) app.err.Printf(lm.FailedApplyTemplate, "configuration", lm.Jellyfin, req.Username, err)
} }
} }
app.jf.CacheExpiry = time.Now() app.jf.CacheExpiry = time.Now()
@ -89,48 +90,47 @@ func (app *appContext) NewUserAdmin(gc *gin.Context) {
} }
errors, code, err := app.ombi.NewUser(req.Username, req.Password, req.Email, profile.Ombi) errors, code, err := app.ombi.NewUser(req.Username, req.Password, req.Email, profile.Ombi)
if err != nil || code != 200 { if err != nil || code != 200 {
app.err.Printf("Failed to create Ombi user (%d): %v", code, err) app.err.Printf(lm.FailedCreateUser, lm.Ombi, req.Username, err)
app.debug.Printf("Errors reported by Ombi: %s", strings.Join(errors, ", ")) app.debug.Printf(lm.AdditionalOmbiErrors, strings.Join(errors, ", "))
} else { } else {
app.info.Println("Created Ombi user") app.info.Printf(lm.CreateUser, lm.Ombi, req.Username)
} }
} }
if app.config.Section("jellyseerr").Key("enabled").MustBool(false) { if app.config.Section("jellyseerr").Key("enabled").MustBool(false) {
// Gets existing user (not possible) or imports the given user. // Gets existing user (not possible) or imports the given user.
_, err := app.js.MustGetUser(id) _, err := app.js.MustGetUser(id)
if err != nil { if err != nil {
app.err.Printf("Failed to create Jellyseerr user: %v", err) app.err.Printf(lm.FailedCreateUser, lm.Jellyseerr, req.Username, err)
} else { } else {
app.info.Println("Created Jellyseerr user") app.info.Printf(lm.CreateUser, lm.Jellyseerr, req.Username)
} }
err = app.js.ApplyTemplateToUser(id, profile.Jellyseerr.User) err = app.js.ApplyTemplateToUser(id, profile.Jellyseerr.User)
if err != nil { if err != nil {
app.err.Printf("Failed to apply Jellyseerr user template: %v\n", err) app.err.Printf(lm.FailedApplyTemplate, "user", lm.Jellyseerr, req.Username, err)
} }
err = app.js.ApplyNotificationsTemplateToUser(id, profile.Jellyseerr.Notifications) err = app.js.ApplyNotificationsTemplateToUser(id, profile.Jellyseerr.Notifications)
if err != nil { if err != nil {
app.err.Printf("Failed to apply Jellyseerr notifications template: %v\n", err) app.err.Printf(lm.FailedApplyTemplate, "notifications", lm.Jellyseerr, req.Username, err)
} }
if emailEnabled { if emailEnabled {
err = app.js.ModifyUser(id, map[jellyseerr.UserField]any{jellyseerr.FieldEmail: req.Email}) err = app.js.ModifyUser(id, map[jellyseerr.UserField]any{jellyseerr.FieldEmail: req.Email})
if err != nil { if err != nil {
app.err.Printf("Failed to set Jellyseerr email address: %v\n", err) app.err.Printf(lm.FailedSetEmailAddress, lm.Jellyseerr, id, err)
} }
} }
} }
if emailEnabled && app.config.Section("welcome_email").Key("enabled").MustBool(false) && req.Email != "" { if emailEnabled && app.config.Section("welcome_email").Key("enabled").MustBool(false) && req.Email != "" {
app.debug.Printf("%s: Sending welcome email to %s", req.Username, req.Email)
msg, err := app.email.constructWelcome(req.Username, time.Time{}, app, false) msg, err := app.email.constructWelcome(req.Username, time.Time{}, app, false)
if err != nil { if err != nil {
app.err.Printf("%s: Failed to construct welcome email: %v", req.Username, err) app.err.Printf(lm.FailedConstructWelcomeMessage, id, err)
respondUser(500, true, false, err.Error(), gc) respondUser(500, true, false, err.Error(), gc)
return return
} else if err := app.email.send(msg, req.Email); err != nil { } else if err := app.email.send(msg, req.Email); err != nil {
app.err.Printf("%s: Failed to send welcome email: %v", req.Username, err) app.err.Printf(lm.FailedSendWelcomeMessage, req.Username, req.Email, err)
respondUser(500, true, false, err.Error(), gc) respondUser(500, true, false, err.Error(), gc)
return return
} else { } else {
app.info.Printf("%s: Sent welcome email to %s", req.Username, req.Email) app.info.Printf(lm.SentWelcomeMessage, req.Username, req.Email)
} }
} }
respondUser(200, true, true, "", gc) respondUser(200, true, true, "", gc)
@ -143,8 +143,8 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool, gc *gin.Context)
existingUser, _, _ := app.jf.UserByName(req.Username, false) existingUser, _, _ := app.jf.UserByName(req.Username, false)
if existingUser.Name != "" { if existingUser.Name != "" {
f = func(gc *gin.Context) { f = func(gc *gin.Context) {
msg := fmt.Sprintf("User %s already exists", req.Username) msg := lm.UserExists
app.info.Printf("%s: New user failed: %s", req.Code, msg) app.info.Printf(lm.FailedCreateUser, lm.Jellyfin, req.Username, msg)
respond(401, "errorUserExists", gc) respond(401, "errorUserExists", gc)
} }
success = false success = false
@ -156,7 +156,7 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool, gc *gin.Context)
if req.DiscordPIN == "" { if req.DiscordPIN == "" {
if app.config.Section("discord").Key("required").MustBool(false) { if app.config.Section("discord").Key("required").MustBool(false) {
f = func(gc *gin.Context) { f = func(gc *gin.Context) {
app.debug.Printf("%s: New user failed: Discord verification not completed", req.Code) app.info.Printf(lm.FailedLinkUser, lm.Discord, "?", req.Code, lm.AccountUnverified)
respond(401, "errorDiscordVerification", gc) respond(401, "errorDiscordVerification", gc)
} }
success = false success = false
@ -166,7 +166,7 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool, gc *gin.Context)
discordUser, discordVerified = app.discord.UserVerified(req.DiscordPIN) discordUser, discordVerified = app.discord.UserVerified(req.DiscordPIN)
if !discordVerified { if !discordVerified {
f = func(gc *gin.Context) { f = func(gc *gin.Context) {
app.debug.Printf("%s: New user failed: Discord PIN was invalid", req.Code) app.info.Printf(lm.FailedLinkUser, lm.Discord, "?", req.Code, fmt.Sprintf(lm.InvalidPIN, req.DiscordPIN))
respond(401, "errorInvalidPIN", gc) respond(401, "errorInvalidPIN", gc)
} }
success = false success = false
@ -174,7 +174,7 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool, gc *gin.Context)
} }
if app.config.Section("discord").Key("require_unique").MustBool(false) && app.discord.UserExists(discordUser.ID) { if app.config.Section("discord").Key("require_unique").MustBool(false) && app.discord.UserExists(discordUser.ID) {
f = func(gc *gin.Context) { f = func(gc *gin.Context) {
app.debug.Printf("%s: New user failed: Discord user already linked", req.Code) app.debug.Printf(lm.FailedLinkUser, lm.Discord, discordUser.ID, req.Code, lm.AccountLinked)
respond(400, "errorAccountLinked", gc) respond(400, "errorAccountLinked", gc)
} }
success = false success = false
@ -183,7 +183,7 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool, gc *gin.Context)
err := app.discord.ApplyRole(discordUser.ID) err := app.discord.ApplyRole(discordUser.ID)
if err != nil { if err != nil {
f = func(gc *gin.Context) { f = func(gc *gin.Context) {
app.err.Printf("%s: New user failed: Failed to set member role: %v", req.Code, err) app.err.Printf(lm.FailedLinkUser, lm.Discord, discordUser.ID, req.Code, fmt.Sprintf(lm.FailedSetDiscordMemberRole, err))
respond(401, "error", gc) respond(401, "error", gc)
} }
success = false success = false
@ -197,7 +197,7 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool, gc *gin.Context)
if req.MatrixPIN == "" { if req.MatrixPIN == "" {
if app.config.Section("matrix").Key("required").MustBool(false) { if app.config.Section("matrix").Key("required").MustBool(false) {
f = func(gc *gin.Context) { f = func(gc *gin.Context) {
app.debug.Printf("%s: New user failed: Matrix verification not completed", req.Code) app.info.Printf(lm.FailedLinkUser, lm.Matrix, "?", req.Code, lm.AccountUnverified)
respond(401, "errorMatrixVerification", gc) respond(401, "errorMatrixVerification", gc)
} }
success = false success = false
@ -208,7 +208,11 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool, gc *gin.Context)
if !ok || !user.Verified { if !ok || !user.Verified {
matrixVerified = false matrixVerified = false
f = func(gc *gin.Context) { f = func(gc *gin.Context) {
app.debug.Printf("%s: New user failed: Matrix PIN was invalid", req.Code) uid := ""
if ok {
uid = user.User.UserID
}
app.info.Printf(lm.FailedLinkUser, lm.Matrix, uid, req.Code, fmt.Sprintf(lm.InvalidPIN, req.MatrixPIN))
respond(401, "errorInvalidPIN", gc) respond(401, "errorInvalidPIN", gc)
} }
success = false success = false
@ -216,7 +220,7 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool, gc *gin.Context)
} }
if app.config.Section("matrix").Key("require_unique").MustBool(false) && app.matrix.UserExists(user.User.UserID) { if app.config.Section("matrix").Key("require_unique").MustBool(false) && app.matrix.UserExists(user.User.UserID) {
f = func(gc *gin.Context) { f = func(gc *gin.Context) {
app.debug.Printf("%s: New user failed: Matrix user already linked", req.Code) app.debug.Printf(lm.FailedLinkUser, lm.Matrix, user.User.UserID, req.Code, lm.AccountLinked)
respond(400, "errorAccountLinked", gc) respond(400, "errorAccountLinked", gc)
} }
success = false success = false
@ -233,7 +237,7 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool, gc *gin.Context)
if req.TelegramPIN == "" { if req.TelegramPIN == "" {
if app.config.Section("telegram").Key("required").MustBool(false) { if app.config.Section("telegram").Key("required").MustBool(false) {
f = func(gc *gin.Context) { f = func(gc *gin.Context) {
app.debug.Printf("%s: New user failed: Telegram verification not completed", req.Code) app.info.Printf(lm.FailedLinkUser, lm.Telegram, "?", req.Code, lm.AccountUnverified)
respond(401, "errorTelegramVerification", gc) respond(401, "errorTelegramVerification", gc)
} }
success = false success = false
@ -243,7 +247,7 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool, gc *gin.Context)
tgToken, telegramVerified = app.telegram.TokenVerified(req.TelegramPIN) tgToken, telegramVerified = app.telegram.TokenVerified(req.TelegramPIN)
if !telegramVerified { if !telegramVerified {
f = func(gc *gin.Context) { f = func(gc *gin.Context) {
app.debug.Printf("%s: New user failed: Telegram PIN was invalid", req.Code) app.info.Printf(lm.FailedLinkUser, lm.Telegram, tgToken.Username, req.Code, fmt.Sprintf(lm.InvalidPIN, req.TelegramPIN))
respond(401, "errorInvalidPIN", gc) respond(401, "errorInvalidPIN", gc)
} }
success = false success = false
@ -251,7 +255,7 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool, gc *gin.Context)
} }
if app.config.Section("telegram").Key("require_unique").MustBool(false) && app.telegram.UserExists(tgToken.Username) { if app.config.Section("telegram").Key("require_unique").MustBool(false) && app.telegram.UserExists(tgToken.Username) {
f = func(gc *gin.Context) { f = func(gc *gin.Context) {
app.debug.Printf("%s: New user failed: Telegram user already linked", req.Code) app.debug.Printf(lm.FailedLinkUser, lm.Telegram, tgToken.Username, req.Code, lm.AccountLinked)
respond(400, "errorAccountLinked", gc) respond(400, "errorAccountLinked", gc)
} }
success = false success = false
@ -270,7 +274,7 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool, gc *gin.Context)
key, err := tk.SignedString([]byte(os.Getenv("JFA_SECRET"))) key, err := tk.SignedString([]byte(os.Getenv("JFA_SECRET")))
if err != nil { if err != nil {
f = func(gc *gin.Context) { f = func(gc *gin.Context) {
app.info.Printf("Failed to generate confirmation token: %v", err) app.info.Printf(lm.FailedSignJWT, err)
respond(500, "errorUnknown", gc) respond(500, "errorUnknown", gc)
} }
success = false success = false
@ -288,15 +292,15 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool, gc *gin.Context)
app.ConfirmationKeys[req.Code] = cKeys app.ConfirmationKeys[req.Code] = cKeys
app.confirmationKeysLock.Unlock() app.confirmationKeysLock.Unlock()
f = func(gc *gin.Context) { f = func(gc *gin.Context) {
app.debug.Printf("%s: Email confirmation required", req.Code) app.debug.Printf(lm.EmailConfirmationRequired, req.Username)
respond(401, "confirmEmail", gc) respond(401, "confirmEmail", gc)
msg, err := app.email.constructConfirmation(req.Code, req.Username, key, app, false) msg, err := app.email.constructConfirmation(req.Code, req.Username, key, app, false)
if err != nil { if err != nil {
app.err.Printf("%s: Failed to construct confirmation email: %v", req.Code, err) app.err.Printf(lm.FailedConstructConfirmationEmail, req.Code, err)
} else if err := app.email.send(msg, req.Email); err != nil { } else if err := app.email.send(msg, req.Email); err != nil {
app.err.Printf("%s: Failed to send user confirmation email: %v", req.Code, err) app.err.Printf(lm.FailedSendConfirmationEmail, req.Code, req.Email, err)
} else { } else {
app.info.Printf("%s: Sent user confirmation email to \"%s\"", req.Code, req.Email) app.err.Printf(lm.SentConfirmationEmail, req.Code, req.Email)
} }
} }
success = false success = false
@ -306,7 +310,7 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool, gc *gin.Context)
user, status, err := app.jf.NewUser(req.Username, req.Password) user, status, err := app.jf.NewUser(req.Username, req.Password)
if !(status == 200 || status == 204) || err != nil { if !(status == 200 || status == 204) || err != nil {
f = func(gc *gin.Context) { f = func(gc *gin.Context) {
app.err.Printf("%s New user failed (%d): %v", req.Code, status, err) app.err.Printf(lm.FailedCreateUser, lm.Jellyfin, req.Username, err)
respond(401, app.storage.lang.Admin[app.storage.lang.chosenAdminLang].Notifications.get("errorUnknown"), gc) respond(401, app.storage.lang.Admin[app.storage.lang.chosenAdminLang].Notifications.get("errorUnknown"), gc)
} }
success = false success = false
@ -320,7 +324,7 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool, gc *gin.Context)
go func(addr string) { 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(lm.FailedConstructCreationAdmin, req.Code, err)
} else { } else {
// Check whether notify "addr" is an email address of Jellyfin ID // Check whether notify "addr" is an email address of Jellyfin ID
if strings.Contains(addr, "@") { if strings.Contains(addr, "@") {
@ -329,9 +333,9 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool, gc *gin.Context)
err = app.sendByID(msg, addr) 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(lm.FailedSendCreationAdmin, req.Code, addr, err)
} else { } else {
app.info.Printf("Sent user creation notification to %s", addr) app.info.Printf(lm.SentCreationAdmin, req.Code, addr)
} }
} }
}(address) }(address)
@ -373,24 +377,22 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool, gc *gin.Context)
var profile Profile var profile Profile
if invite.Profile != "" { if invite.Profile != "" {
app.debug.Printf("Applying settings from profile \"%s\"", invite.Profile) app.debug.Printf(lm.ApplyProfile, invite.Profile)
var ok bool var ok bool
profile, ok = app.storage.GetProfileKey(invite.Profile) profile, ok = app.storage.GetProfileKey(invite.Profile)
if !ok { if !ok {
profile = app.storage.GetDefaultProfile() profile = app.storage.GetDefaultProfile()
} }
app.debug.Printf("Applying policy from profile \"%s\"", invite.Profile)
status, err = app.jf.SetPolicy(id, profile.Policy) status, err = app.jf.SetPolicy(id, profile.Policy)
if !((status == 200 || status == 204) && err == nil) { if !((status == 200 || status == 204) && err == nil) {
app.err.Printf("%s: Failed to set user policy (%d): %v", req.Code, status, err) app.err.Printf(lm.FailedApplyTemplate, "policy", lm.Jellyfin, id, err)
} }
app.debug.Printf("Applying homescreen from profile \"%s\"", invite.Profile)
status, err = app.jf.SetConfiguration(id, profile.Configuration) status, err = app.jf.SetConfiguration(id, profile.Configuration)
if (status == 200 || status == 204) && err == nil { if (status == 200 || status == 204) && err == nil {
status, err = app.jf.SetDisplayPreferences(id, profile.Displayprefs) status, err = app.jf.SetDisplayPreferences(id, profile.Displayprefs)
} }
if !((status == 200 || status == 204) && err == nil) { if !((status == 200 || status == 204) && err == nil) {
app.err.Printf("%s: Failed to set configuration template (%d): %v", req.Code, status, err) app.err.Printf(lm.FailedApplyTemplate, "configuration", lm.Jellyfin, id, err)
} }
if app.config.Section("user_page").Key("enabled").MustBool(false) && app.config.Section("user_page").Key("referrals").MustBool(false) && profile.ReferralTemplateKey != "" { if app.config.Section("user_page").Key("enabled").MustBool(false) && app.config.Section("user_page").Key("referrals").MustBool(false) && profile.ReferralTemplateKey != "" {
emailStore.ReferralTemplateKey = profile.ReferralTemplateKey emailStore.ReferralTemplateKey = profile.ReferralTemplateKey
@ -454,23 +456,23 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool, gc *gin.Context)
// Check if on the off chance, Ombi's user importer has already added the account. // Check if on the off chance, Ombi's user importer has already added the account.
ombiUser, status, err = app.getOmbiImportedUser(req.Username) ombiUser, status, err = app.getOmbiImportedUser(req.Username)
if status == 200 && err == nil { if status == 200 && err == nil {
app.info.Println("Found existing Ombi user, applying changes") app.info.Println(lm.Ombi + " " + lm.UserExists)
accountExists = true accountExists = true
template["password"] = req.Password template["password"] = req.Password
status, err = app.applyOmbiProfile(ombiUser, template) status, err = app.applyOmbiProfile(ombiUser, template)
if status != 200 || err != nil { if status != 200 || err != nil {
app.err.Printf("Failed to modify existing Ombi user (%d): %v\n", status, err) app.err.Printf(lm.FailedApplyProfile, lm.Ombi, req.Username, err)
} }
} else { } else {
app.info.Printf("Failed to create Ombi user (%d): %s", code, err) app.info.Printf(lm.FailedCreateUser, lm.Ombi, req.Username, err)
app.debug.Printf("Errors reported by Ombi: %s", strings.Join(errors, ", ")) app.debug.Printf(lm.AdditionalOmbiErrors, strings.Join(errors, ", "))
} }
} else { } else {
ombiUser, status, err = app.getOmbiUser(id) ombiUser, status, err = app.getOmbiUser(id)
if status != 200 || err != nil { if status != 200 || err != nil {
app.err.Printf("Failed to get Ombi user (%d): %v", status, err) app.err.Printf(lm.FailedGetUser, id, lm.Ombi, err)
} else { } else {
app.info.Println("Created Ombi user") app.info.Println(lm.CreateUser, lm.Ombi, id)
accountExists = true accountExists = true
} }
} }
@ -487,13 +489,11 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool, gc *gin.Context)
} }
resp, status, err := app.ombi.SetNotificationPrefs(ombiUser, dID, tUser) resp, status, err := app.ombi.SetNotificationPrefs(ombiUser, dID, tUser)
if !(status == 200 || status == 204) || err != nil { if !(status == 200 || status == 204) || err != nil {
app.err.Printf("Failed to link Telegram/Discord to Ombi (%d): %v", status, err) app.err.Printf(lm.FailedSyncContactMethods, lm.Ombi, err)
app.debug.Printf("Response: %v", resp) app.debug.Printf(lm.AdditionalOmbiErrors, resp)
} }
} }
} }
} else {
app.debug.Printf("Skipping Ombi: Profile \"%s\" was empty", invite.Profile)
} }
} }
if invite.Profile != "" && app.config.Section("jellyseerr").Key("enabled").MustBool(false) { if invite.Profile != "" && app.config.Section("jellyseerr").Key("enabled").MustBool(false) {
@ -501,23 +501,23 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool, gc *gin.Context)
// Gets existing user (not possible) or imports the given user. // Gets existing user (not possible) or imports the given user.
_, err := app.js.MustGetUser(id) _, err := app.js.MustGetUser(id)
if err != nil { if err != nil {
app.err.Printf("Failed to create Jellyseerr user: %v", err) app.err.Printf(lm.FailedCreateUser, lm.Jellyseerr, id, err)
} else { } else {
app.info.Println("Created Jellyseerr user") app.info.Printf(lm.CreateUser, lm.Jellyseerr, id)
} }
err = app.js.ApplyTemplateToUser(id, profile.Jellyseerr.User) err = app.js.ApplyTemplateToUser(id, profile.Jellyseerr.User)
if err != nil { if err != nil {
app.err.Printf("Failed to apply Jellyseerr user template: %v\n", err) app.err.Printf(lm.FailedApplyTemplate, "user", lm.Jellyseerr, id, err)
} }
err = app.js.ApplyNotificationsTemplateToUser(id, profile.Jellyseerr.Notifications) err = app.js.ApplyNotificationsTemplateToUser(id, profile.Jellyseerr.Notifications)
if err != nil { if err != nil {
app.err.Printf("Failed to apply Jellyseerr notifications template: %v\n", err) app.err.Printf(lm.FailedApplyTemplate, "notifications", lm.Jellyseerr, id, err)
} }
contactMethods := map[jellyseerr.NotificationsField]any{} contactMethods := map[jellyseerr.NotificationsField]any{}
if emailEnabled { if emailEnabled {
err = app.js.ModifyMainUserSettings(id, jellyseerr.MainUserSettings{Email: req.Email}) err = app.js.ModifyMainUserSettings(id, jellyseerr.MainUserSettings{Email: req.Email})
if err != nil { if err != nil {
app.err.Printf("Failed to set Jellyseerr email address: %v\n", err) app.err.Printf(lm.FailedSetEmailAddress, lm.Jellyseerr, id, err)
} else { } else {
contactMethods[jellyseerr.FieldEmailEnabled] = req.EmailContact contactMethods[jellyseerr.FieldEmailEnabled] = req.EmailContact
} }
@ -534,11 +534,9 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool, gc *gin.Context)
if emailEnabled || discordVerified || telegramVerified { if emailEnabled || discordVerified || telegramVerified {
err := app.js.ModifyNotifications(id, contactMethods) err := app.js.ModifyNotifications(id, contactMethods)
if err != nil { if err != nil {
app.err.Printf("Failed to sync contact methods with Jellyseerr: %v", err) app.err.Printf(lm.FailedSyncContactMethods, lm.Jellyseerr, err)
} }
} }
} else {
app.debug.Printf("Skipping Jellyseerr: Profile \"%s\" was empty", invite.Profile)
} }
} }
if matrixVerified { if matrixVerified {
@ -551,14 +549,13 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool, gc *gin.Context)
} }
if (emailEnabled && app.config.Section("welcome_email").Key("enabled").MustBool(false) && req.Email != "") || telegramVerified || discordVerified || matrixVerified { if (emailEnabled && app.config.Section("welcome_email").Key("enabled").MustBool(false) && req.Email != "") || telegramVerified || discordVerified || matrixVerified {
name := app.getAddressOrName(user.ID) name := app.getAddressOrName(user.ID)
app.debug.Printf("%s: Sending welcome message to %s", req.Username, name)
msg, err := app.email.constructWelcome(req.Username, expiry, app, false) msg, err := app.email.constructWelcome(req.Username, expiry, app, false)
if err != nil { if err != nil {
app.err.Printf("%s: Failed to construct welcome message: %v", req.Username, err) app.err.Printf(lm.FailedConstructWelcomeMessage, id, err)
} else if err := app.sendByID(msg, user.ID); err != nil { } else if err := app.sendByID(msg, user.ID); err != nil {
app.err.Printf("%s: Failed to send welcome message: %v", req.Username, err) app.err.Printf(lm.FailedSendWelcomeMessage, id, req.Email, err)
} else { } else {
app.info.Printf("%s: Sent welcome message to \"%s\"", req.Username, name) app.info.Printf(lm.SentWelcomeMessage, id, req.Email)
} }
} }
app.jf.CacheExpiry = time.Now() app.jf.CacheExpiry = time.Now()
@ -576,14 +573,13 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool, gc *gin.Context)
func (app *appContext) NewUser(gc *gin.Context) { func (app *appContext) NewUser(gc *gin.Context) {
var req newUserDTO var req newUserDTO
gc.BindJSON(&req) gc.BindJSON(&req)
app.debug.Printf("%s: New user attempt", req.Code)
if app.config.Section("captcha").Key("enabled").MustBool(false) && !app.verifyCaptcha(req.Code, req.CaptchaID, req.CaptchaText, false) { if app.config.Section("captcha").Key("enabled").MustBool(false) && !app.verifyCaptcha(req.Code, req.CaptchaID, req.CaptchaText, false) {
app.info.Printf("%s: New user failed: Captcha Incorrect", req.Code) app.info.Printf(lm.FailedCreateUser, lm.Jellyfin, req.Username, lm.IncorrectCaptcha)
respond(400, "errorCaptcha", gc) respond(400, "errorCaptcha", gc)
return return
} }
if !app.checkInvite(req.Code, false, "") { if !app.checkInvite(req.Code, false, "") {
app.info.Printf("%s New user failed: invalid code", req.Code) app.info.Printf(lm.FailedCreateUser, lm.Jellyfin, req.Username, fmt.Sprintf(lm.InvalidInviteCode, req.Code))
respond(401, "errorInvalidCode", gc) respond(401, "errorInvalidCode", gc)
return return
} }
@ -597,18 +593,15 @@ func (app *appContext) NewUser(gc *gin.Context) {
} }
if !valid { if !valid {
// 200 bcs idk what i did in js // 200 bcs idk what i did in js
app.info.Printf("%s: New user failed: Invalid password", req.Code)
gc.JSON(200, validation) gc.JSON(200, validation)
return return
} }
if emailEnabled { if emailEnabled {
if app.config.Section("email").Key("required").MustBool(false) && !strings.Contains(req.Email, "@") { if app.config.Section("email").Key("required").MustBool(false) && !strings.Contains(req.Email, "@") {
app.info.Printf("%s: New user failed: Email Required", req.Code)
respond(400, "errorNoEmail", gc) respond(400, "errorNoEmail", gc)
return return
} }
if app.config.Section("email").Key("require_unique").MustBool(false) && req.Email != "" && app.EmailAddressExists(req.Email) { if app.config.Section("email").Key("require_unique").MustBool(false) && req.Email != "" && app.EmailAddressExists(req.Email) {
app.info.Printf("%s: New user failed: Email already in use", req.Code)
respond(400, "errorEmailLinked", gc) respond(400, "errorEmailLinked", gc)
return return
} }
@ -653,7 +646,7 @@ func (app *appContext) EnableDisableUsers(gc *gin.Context) {
msg, err = app.email.constructDisabled(req.Reason, app, false) msg, err = app.email.constructDisabled(req.Reason, app, false)
} }
if err != nil { if err != nil {
app.err.Printf("Failed to construct account enabled/disabled emails: %v", err) app.err.Printf(lm.FailedConstructEnableDisableMessage, "?", err)
sendMail = false sendMail = false
} }
} }
@ -665,14 +658,14 @@ func (app *appContext) EnableDisableUsers(gc *gin.Context) {
user, status, err := app.jf.UserByID(userID, false) user, status, err := app.jf.UserByID(userID, false)
if status != 200 || err != nil { if status != 200 || err != nil {
errors["GetUser"][userID] = fmt.Sprintf("%d %v", status, err) errors["GetUser"][userID] = fmt.Sprintf("%d %v", status, err)
app.err.Printf("Failed to get user \"%s\" (%d): %v", userID, status, err) app.err.Printf(lm.FailedGetUser, userID, lm.Jellyfin, err)
continue continue
} }
user.Policy.IsDisabled = !req.Enabled user.Policy.IsDisabled = !req.Enabled
status, err = app.jf.SetPolicy(userID, user.Policy) status, err = app.jf.SetPolicy(userID, user.Policy)
if !(status == 200 || status == 204) || err != nil { if !(status == 200 || status == 204) || err != nil {
errors["SetPolicy"][userID] = fmt.Sprintf("%d %v", status, err) errors["SetPolicy"][userID] = fmt.Sprintf("%d %v", status, err)
app.err.Printf("Failed to set policy for user \"%s\" (%d): %v", userID, status, err) app.err.Printf(lm.FailedApplyTemplate, "policy", lm.Jellyfin, userID, err)
continue continue
} }
@ -687,7 +680,7 @@ func (app *appContext) EnableDisableUsers(gc *gin.Context) {
if sendMail && req.Notify { if sendMail && req.Notify {
if err := app.sendByID(msg, userID); err != nil { if err := app.sendByID(msg, userID); err != nil {
app.err.Printf("Failed to send account enabled/disabled email: %v", err) app.err.Printf(lm.FailedSendEnableDisableMessage, userID, "?", err)
continue continue
} }
} }
@ -720,7 +713,7 @@ func (app *appContext) DeleteUsers(gc *gin.Context) {
if sendMail { if sendMail {
msg, err = app.email.constructDeleted(req.Reason, app, false) msg, err = app.email.constructDeleted(req.Reason, app, false)
if err != nil { if err != nil {
app.err.Printf("Failed to construct account deletion emails: %v", err) app.err.Printf(lm.FailedConstructDeletionMessage, "?", err)
sendMail = false sendMail = false
} }
} }
@ -731,7 +724,7 @@ func (app *appContext) DeleteUsers(gc *gin.Context) {
if id, ok := ombiUser["id"]; ok { if id, ok := ombiUser["id"]; ok {
status, err := app.ombi.DeleteUser(id.(string)) status, err := app.ombi.DeleteUser(id.(string))
if err != nil || status != 200 { if err != nil || status != 200 {
app.err.Printf("Failed to delete ombi user (%d): %v", status, err) app.err.Printf(lm.FailedDeleteUser, lm.Ombi, userID, err)
errors[userID] = fmt.Sprintf("Ombi: %d %v, ", status, err) errors[userID] = fmt.Sprintf("Ombi: %d %v, ", status, err)
} }
} }
@ -765,14 +758,14 @@ func (app *appContext) DeleteUsers(gc *gin.Context) {
if sendMail && req.Notify { if sendMail && req.Notify {
if err := app.sendByID(msg, userID); err != nil { if err := app.sendByID(msg, userID); err != nil {
app.err.Printf("Failed to send account deletion email: %v", err) app.err.Printf(lm.FailedSendDeletionMessage, userID, "?", err)
} }
} }
} }
app.jf.CacheExpiry = time.Now() app.jf.CacheExpiry = time.Now()
if len(errors) == len(req.Users) { if len(errors) == len(req.Users) {
respondBool(500, false, gc) respondBool(500, false, gc)
app.err.Printf("Account deletion failed: %s", errors[req.Users[0]]) app.err.Printf(lm.FailedDeleteUsers, lm.Jellyfin, errors[req.Users[0]])
return return
} else if len(errors) != 0 { } else if len(errors) != 0 {
gc.JSON(500, errors) gc.JSON(500, errors)
@ -801,10 +794,8 @@ func (app *appContext) ExtendExpiry(gc *gin.Context) {
base := time.Now() base := time.Now()
if expiry, ok := app.storage.GetUserExpiryKey(id); ok { if expiry, ok := app.storage.GetUserExpiryKey(id); ok {
base = expiry.Expiry base = expiry.Expiry
app.debug.Printf("Expiry extended for \"%s\"", id)
} else {
app.debug.Printf("Created expiry for \"%s\"", id)
} }
app.debug.Printf(lm.ExtendCreateExpiry, id)
expiry := UserExpiry{} expiry := UserExpiry{}
if req.Timestamp != 0 { if req.Timestamp != 0 {
expiry.Expiry = time.Unix(req.Timestamp, 0) expiry.Expiry = time.Unix(req.Timestamp, 0)
@ -820,11 +811,11 @@ func (app *appContext) ExtendExpiry(gc *gin.Context) {
} }
msg, err := app.email.constructExpiryAdjusted(user.Name, exp, req.Reason, app, false) msg, err := app.email.constructExpiryAdjusted(user.Name, exp, req.Reason, app, false)
if err != nil { if err != nil {
app.err.Printf("%s: Failed to construct expiry adjustment notification: %v", uid, err) app.err.Printf(lm.FailedConstructExpiryAdjustmentMessage, uid, err)
return return
} }
if err := app.sendByID(msg, uid); err != nil { if err := app.sendByID(msg, uid); err != nil {
app.err.Printf("%s: Failed to send expiry adjustment notification: %v", uid, err) app.err.Printf(lm.FailedSendExpiryAdjustmentMessage, uid, "?", err)
} }
}(id, expiry.Expiry) }(id, expiry.Expiry)
} }
@ -867,17 +858,17 @@ func (app *appContext) EnableReferralForUsers(gc *gin.Context) {
profile, ok := app.storage.GetProfileKey(source) profile, ok := app.storage.GetProfileKey(source)
err := app.storage.db.Get(profile.ReferralTemplateKey, &baseInv) err := app.storage.db.Get(profile.ReferralTemplateKey, &baseInv)
if !ok || profile.ReferralTemplateKey == "" || err != nil { if !ok || profile.ReferralTemplateKey == "" || err != nil {
app.debug.Printf("Couldn't find template to source from") app.debug.Printf(lm.FailedGetReferralTemplate, profile.ReferralTemplateKey, err)
respondBool(400, false, gc) respondBool(400, false, gc)
return return
} }
app.debug.Printf("Found referral template in profile: %+v\n", profile.ReferralTemplateKey) app.debug.Printf(lm.GetReferralTemplate, profile.ReferralTemplateKey)
} else if mode == "invite" { } else if mode == "invite" {
// Get the invite, and modify it to turn it into a referral // Get the invite, and modify it to turn it into a referral
err := app.storage.db.Get(source, &baseInv) err := app.storage.db.Get(source, &baseInv)
if err != nil { if err != nil {
app.debug.Printf("Couldn't find invite to source from") app.debug.Printf(lm.InvalidInviteCode, source)
respondBool(400, false, gc) respondBool(400, false, gc)
return return
} }
@ -949,33 +940,34 @@ func (app *appContext) Announce(gc *gin.Context) {
for _, userID := range req.Users { for _, userID := range req.Users {
user, status, err := app.jf.UserByID(userID, false) user, status, err := app.jf.UserByID(userID, false)
if status != 200 || err != nil { if status != 200 || err != nil {
app.err.Printf("Failed to get user with ID \"%s\" (%d): %v", userID, status, err) app.err.Printf(lm.FailedGetUser, userID, lm.Jellyfin, err)
continue continue
} }
msg, err := app.email.constructTemplate(req.Subject, req.Message, app, user.Name) msg, err := app.email.constructTemplate(req.Subject, req.Message, app, user.Name)
if err != nil { if err != nil {
app.err.Printf("Failed to construct announcement message: %v", err) app.err.Printf(lm.FailedConstructAnnouncementMessage, userID, err)
respondBool(500, false, gc) respondBool(500, false, gc)
return return
} else if err := app.sendByID(msg, userID); err != nil { } else if err := app.sendByID(msg, userID); err != nil {
app.err.Printf("Failed to send announcement message: %v", err) app.err.Printf(lm.FailedSendAnnouncementMessage, userID, "?", err)
respondBool(500, false, gc) respondBool(500, false, gc)
return return
} }
} }
app.info.Printf(lm.SentAnnouncementMessage, userID, "?")
} else { } else {
msg, err := app.email.constructTemplate(req.Subject, req.Message, app) msg, err := app.email.constructTemplate(req.Subject, req.Message, app)
if err != nil { if err != nil {
app.err.Printf("Failed to construct announcement messages: %v", err) app.err.Printf(lm.FailedConstructAnnouncementMessage, "*", err)
respondBool(500, false, gc) respondBool(500, false, gc)
return return
} else if err := app.sendByID(msg, req.Users...); err != nil { } else if err := app.sendByID(msg, req.Users...); err != nil {
app.err.Printf("Failed to send announcement messages: %v", err) app.err.Printf(lm.FailedSendAnnouncementMessage, "*", "?", err)
respondBool(500, false, gc) respondBool(500, false, gc)
return return
} }
app.info.Printf(lm.SentAnnouncementMessage, "*", "?")
} }
app.info.Println("Sent announcement messages")
respondBool(200, true, gc) respondBool(200, true, gc)
} }
@ -1063,7 +1055,6 @@ func (app *appContext) AdminPasswordReset(gc *gin.Context) {
var req AdminPasswordResetDTO var req AdminPasswordResetDTO
gc.BindJSON(&req) gc.BindJSON(&req)
if req.Users == nil || len(req.Users) == 0 { if req.Users == nil || len(req.Users) == 0 {
app.debug.Println("Ignoring empty request for PWR")
respondBool(400, false, gc) respondBool(400, false, gc)
return return
} }
@ -1074,7 +1065,7 @@ func (app *appContext) AdminPasswordReset(gc *gin.Context) {
for _, id := range req.Users { for _, id := range req.Users {
pwr, err = app.GenInternalReset(id) pwr, err = app.GenInternalReset(id)
if err != nil { if err != nil {
app.err.Printf("Failed to get user from Jellyfin: %v", err) app.err.Printf(lm.FailedGetUser, id, lm.Jellyfin, err)
respondBool(500, false, gc) respondBool(500, false, gc)
return return
} }
@ -1100,13 +1091,13 @@ func (app *appContext) AdminPasswordReset(gc *gin.Context) {
}, app, false, }, app, false,
) )
if err != nil { if err != nil {
app.err.Printf("Failed to construct password reset message for \"%s\": %v", pwr.Username, err) app.err.Printf(lm.FailedConstructPWRMessage, id, err)
respondBool(500, false, gc) respondBool(500, false, gc)
return return
} else if err := app.sendByID(msg, id); err != nil { } else if err := app.sendByID(msg, id); err != nil {
app.err.Printf("Failed to send password reset message to \"%s\": %v", sendAddress, err) app.err.Printf(lm.FailedSendPWRMessage, id, sendAddress, err)
} else { } else {
app.info.Printf("Sent password reset message to \"%s\"", sendAddress) app.info.Printf(lm.SentPWRMessage, id, sendAddress)
} }
} }
} }
@ -1125,12 +1116,11 @@ func (app *appContext) AdminPasswordReset(gc *gin.Context) {
// @Security Bearer // @Security Bearer
// @tags Users // @tags Users
func (app *appContext) GetUsers(gc *gin.Context) { func (app *appContext) GetUsers(gc *gin.Context) {
app.debug.Println("Users requested")
var resp getUsersDTO var resp getUsersDTO
users, status, err := app.jf.GetUsers(false) users, status, err := app.jf.GetUsers(false)
resp.UserList = make([]respUser, len(users)) resp.UserList = make([]respUser, len(users))
if !(status == 200 || status == 204) || err != nil { if !(status == 200 || status == 204) || err != nil {
app.err.Printf("Failed to get users from Jellyfin (%d): %v", status, err) app.err.Printf(lm.FailedGetUsers, lm.Jellyfin, err)
respond(500, "Couldn't get users", gc) respond(500, "Couldn't get users", gc)
return return
} }
@ -1202,10 +1192,9 @@ func (app *appContext) GetUsers(gc *gin.Context) {
func (app *appContext) SetAccountsAdmin(gc *gin.Context) { func (app *appContext) SetAccountsAdmin(gc *gin.Context) {
var req setAccountsAdminDTO var req setAccountsAdminDTO
gc.BindJSON(&req) gc.BindJSON(&req)
app.debug.Println("Admin modification requested")
users, status, err := app.jf.GetUsers(false) users, status, err := app.jf.GetUsers(false)
if !(status == 200 || status == 204) || err != nil { if !(status == 200 || status == 204) || err != nil {
app.err.Printf("Failed to get users from Jellyfin (%d): %v", status, err) app.err.Printf(lm.FailedGetUsers, lm.Jellyfin, err)
respond(500, "Couldn't get users", gc) respond(500, "Couldn't get users", gc)
return return
} }
@ -1218,9 +1207,9 @@ func (app *appContext) SetAccountsAdmin(gc *gin.Context) {
} }
emailStore.Admin = admin emailStore.Admin = admin
app.storage.SetEmailsKey(id, emailStore) app.storage.SetEmailsKey(id, emailStore)
app.info.Printf(lm.UserAdminAdjusted, id, admin)
} }
} }
app.info.Println("Email list modified")
respondBool(204, true, gc) respondBool(204, true, gc)
} }
@ -1235,10 +1224,9 @@ func (app *appContext) SetAccountsAdmin(gc *gin.Context) {
func (app *appContext) ModifyLabels(gc *gin.Context) { func (app *appContext) ModifyLabels(gc *gin.Context) {
var req modifyEmailsDTO var req modifyEmailsDTO
gc.BindJSON(&req) gc.BindJSON(&req)
app.debug.Println("Label modification requested")
users, status, err := app.jf.GetUsers(false) users, status, err := app.jf.GetUsers(false)
if !(status == 200 || status == 204) || err != nil { if !(status == 200 || status == 204) || err != nil {
app.err.Printf("Failed to get users from Jellyfin (%d): %v", status, err) app.err.Printf(lm.FailedGetUsers, lm.Jellyfin, err)
respond(500, "Couldn't get users", gc) respond(500, "Couldn't get users", gc)
return return
} }
@ -1250,10 +1238,10 @@ func (app *appContext) ModifyLabels(gc *gin.Context) {
emailStore = oldEmail emailStore = oldEmail
} }
emailStore.Label = label emailStore.Label = label
app.debug.Println(lm.UserLabelAdjusted, id, label)
app.storage.SetEmailsKey(id, emailStore) app.storage.SetEmailsKey(id, emailStore)
} }
} }
app.info.Println("Email list modified")
respondBool(204, true, gc) respondBool(204, true, gc)
} }
@ -1275,21 +1263,21 @@ func (app *appContext) modifyEmail(jfID string, addr string) {
ombiUser["emailAddress"] = addr ombiUser["emailAddress"] = addr
code, err = app.ombi.ModifyUser(ombiUser) code, err = app.ombi.ModifyUser(ombiUser)
if code != 200 || err != nil { if code != 200 || err != nil {
app.err.Printf("%s: Failed to change ombi email address (%d): %v", ombiUser["userName"].(string), code, err) app.err.Printf(lm.FailedSetEmailAddress, lm.Ombi, jfID, err)
} }
} }
} }
if app.config.Section("jellyseerr").Key("enabled").MustBool(false) { if app.config.Section("jellyseerr").Key("enabled").MustBool(false) {
err := app.js.ModifyMainUserSettings(jfID, jellyseerr.MainUserSettings{Email: addr}) err := app.js.ModifyMainUserSettings(jfID, jellyseerr.MainUserSettings{Email: addr})
if err != nil { if err != nil {
app.err.Printf("Failed to set Jellyseerr email address: %v\n", err) app.err.Printf(lm.FailedSetEmailAddress, lm.Jellyseerr, jfID, err)
} else if contactPrefChanged { } else if contactPrefChanged {
contactMethods := map[jellyseerr.NotificationsField]any{ contactMethods := map[jellyseerr.NotificationsField]any{
jellyseerr.FieldEmailEnabled: true, jellyseerr.FieldEmailEnabled: true,
} }
err := app.js.ModifyNotifications(jfID, contactMethods) err := app.js.ModifyNotifications(jfID, contactMethods)
if err != nil { if err != nil {
app.err.Printf("Failed to sync contact methods with Jellyseerr: %v", err) app.err.Printf(lm.FailedSyncContactMethods, lm.Jellyseerr, err)
} }
} }
} }
@ -1306,10 +1294,9 @@ func (app *appContext) modifyEmail(jfID string, addr string) {
func (app *appContext) ModifyEmails(gc *gin.Context) { func (app *appContext) ModifyEmails(gc *gin.Context) {
var req modifyEmailsDTO var req modifyEmailsDTO
gc.BindJSON(&req) gc.BindJSON(&req)
app.debug.Println("Email modification requested")
users, status, err := app.jf.GetUsers(false) users, status, err := app.jf.GetUsers(false)
if !(status == 200 || status == 204) || err != nil { if !(status == 200 || status == 204) || err != nil {
app.err.Printf("Failed to get users from Jellyfin (%d): %v", status, err) app.err.Printf(lm.FailedGetUsers, lm.Jellyfin, err)
respond(500, "Couldn't get users", gc) respond(500, "Couldn't get users", gc)
return return
} }
@ -1318,6 +1305,8 @@ func (app *appContext) ModifyEmails(gc *gin.Context) {
if address, ok := req[id]; ok { if address, ok := req[id]; ok {
app.modifyEmail(id, address) app.modifyEmail(id, address)
app.info.Printf(lm.UserEmailAdjusted, gc.GetString("jfId"))
activityType := ActivityContactLinked activityType := ActivityContactLinked
if address == "" { if address == "" {
activityType = ActivityContactUnlinked activityType = ActivityContactUnlinked
@ -1332,7 +1321,6 @@ func (app *appContext) ModifyEmails(gc *gin.Context) {
}, gc, false) }, gc, false)
} }
} }
app.info.Println("Email list modified")
respondBool(200, true, gc) respondBool(200, true, gc)
} }
@ -1348,7 +1336,8 @@ func (app *appContext) ApplySettings(gc *gin.Context) {
app.info.Println("User settings change requested") app.info.Println("User settings change requested")
var req userSettingsDTO var req userSettingsDTO
gc.BindJSON(&req) gc.BindJSON(&req)
applyingFrom := "profile" applyingFromType := lm.Profile
applyingFromSource := "?"
var policy mediabrowser.Policy var policy mediabrowser.Policy
var configuration mediabrowser.Configuration var configuration mediabrowser.Configuration
var displayprefs map[string]interface{} var displayprefs map[string]interface{}
@ -1359,18 +1348,21 @@ func (app *appContext) ApplySettings(gc *gin.Context) {
// Check profile exists & isn't empty // Check profile exists & isn't empty
profile, ok := app.storage.GetProfileKey(req.Profile) profile, ok := app.storage.GetProfileKey(req.Profile)
if !ok { if !ok {
app.err.Printf("Couldn't find profile \"%s\" or profile was empty", req.Profile) app.err.Printf(lm.FailedGetProfile, req.Profile)
respond(500, "Couldn't find profile", gc) respond(500, "Couldn't find profile", gc)
return return
} }
applyingFromSource = req.Profile
if req.Homescreen { if req.Homescreen {
if !profile.Homescreen { if profile.Homescreen {
app.err.Printf("No homescreen saved in profile \"%s\"", req.Profile) configuration = profile.Configuration
displayprefs = profile.Displayprefs
} else {
req.Homescreen = false
app.err.Printf(lm.ProfileNoHomescreen, req.Profile)
respond(500, "No homescreen template available", gc) respond(500, "No homescreen template available", gc)
return return
} }
configuration = profile.Configuration
displayprefs = profile.Displayprefs
} }
if req.Policy { if req.Policy {
policy = profile.Policy policy = profile.Policy
@ -1387,29 +1379,29 @@ func (app *appContext) ApplySettings(gc *gin.Context) {
} }
} else if req.From == "user" { } else if req.From == "user" {
applyingFrom = "user" applyingFromType = lm.User
app.jf.CacheExpiry = time.Now() app.jf.CacheExpiry = time.Now()
user, status, err := app.jf.UserByID(req.ID, false) user, status, err := app.jf.UserByID(req.ID, false)
if !(status == 200 || status == 204) || err != nil { if !(status == 200 || status == 204) || err != nil {
app.err.Printf("Failed to get user from Jellyfin (%d): %v", status, err) app.err.Printf(lm.FailedGetUser, req.ID, lm.Jellyfin, err)
respond(500, "Couldn't get user", gc) respond(500, "Couldn't get user", gc)
return return
} }
applyingFrom = "\"" + user.Name + "\"" applyingFromSource = user.Name
if req.Policy { if req.Policy {
policy = user.Policy policy = user.Policy
} }
if req.Homescreen { if req.Homescreen {
displayprefs, status, err = app.jf.GetDisplayPreferences(req.ID) displayprefs, status, err = app.jf.GetDisplayPreferences(req.ID)
if !(status == 200 || status == 204) || err != nil { if !(status == 200 || status == 204) || err != nil {
app.err.Printf("Failed to get DisplayPrefs (%d): %v", status, err) app.err.Printf(lm.FailedGetJellyfinDisplayPrefs, req.ID, err)
respond(500, "Couldn't get displayprefs", gc) respond(500, "Couldn't get displayprefs", gc)
return return
} }
configuration = user.Configuration configuration = user.Configuration
} }
} }
app.info.Printf("Applying settings to %d user(s) from %s", len(req.ApplyTo), applyingFrom) app.info.Printf(lm.ApplyingTemplatesFrom, applyingFromType, applyingFromSource, len(req.ApplyTo))
errors := errorListDTO{ errors := errorListDTO{
"policy": map[string]string{}, "policy": map[string]string{},
"homescreen": map[string]string{}, "homescreen": map[string]string{},
@ -1420,9 +1412,10 @@ func (app *appContext) ApplySettings(gc *gin.Context) {
and can crash and mess up its database. Issue #160 says this occurs when more and can crash and mess up its database. Issue #160 says this occurs when more
than 100 users are modified. A delay totalling 500ms between requests is used than 100 users are modified. A delay totalling 500ms between requests is used
if so. */ if so. */
var shouldDelay bool = len(req.ApplyTo) >= 100 const requestDelayThreshold = 100
var shouldDelay bool = len(req.ApplyTo) >= requestDelayThreshold
if shouldDelay { if shouldDelay {
app.debug.Println("Adding delay between requests for large batch") app.debug.Printf(lm.DelayingRequests, requestDelayThreshold)
} }
for _, id := range req.ApplyTo { for _, id := range req.ApplyTo {
var status int var status int

View File

@ -1,9 +1,19 @@
package logmessages package logmessages
const ( const (
Jellyseerr = "Jellyseerr"
Jellyfin = "Jellyfin"
Ombi = "Ombi"
Discord = "Discord"
Telegram = "Telegram"
Matrix = "Matrix"
Email = "Email"
// main.go
FailedLogging = "Failed to start log wrapper: %v\n" FailedLogging = "Failed to start log wrapper: %v\n"
NoConfig = "Couldn't find default config file" NoConfig = "Couldn't find default config file"
Write = "Wrote to \"%s\""
FailedWriting = "Failed to write to \"%s\": %v" FailedWriting = "Failed to write to \"%s\": %v"
FailedReading = "Failed to read from \"%s\": %v" FailedReading = "Failed to read from \"%s\": %v"
FailedOpen = "Failed to open \"%s\": %v" FailedOpen = "Failed to open \"%s\": %v"
@ -24,15 +34,15 @@ const (
UsingTLS = "Using TLS/HTTP2" UsingTLS = "Using TLS/HTTP2"
UsingOmbi = "Starting Ombi client" UsingOmbi = "Starting " + " + Ombi + " + " client"
UsingJellyseerr = "Starting Jellyseerr client" UsingJellyseerr = "Starting " + Jellyseerr + " client"
UsingEmby = "Using Emby server type (EXPERIMENTAL: PWRs are not available, and support is limited.)" UsingEmby = "Using Emby server type (EXPERIMENTAL: PWRs are not available, and support is limited.)"
UsingJellyfin = "Using Jellyfin server type" UsingJellyfin = "Using " + Jellyfin + " server type"
UsingJellyfinAuth = "Using Jellyfin for authentication" UsingJellyfinAuth = "Using " + Jellyfin + " for authentication"
UsingLocalAuth = "Using local username/pw authentication (NOT RECOMMENDED)" UsingLocalAuth = "Using local username/pw authentication (NOT RECOMMENDED)"
AuthJellyfin = "Authenticated with Jellyfin @ \"%s\"" AuthJellyfin = "Authenticated with " + Jellyfin + " @ \"%s\""
FailedAuthJellyfin = "Failed to authenticate with Jellyfin @ \"%s\" (code %d): %v" FailedAuthJellyfin = "Failed to authenticate with " + Jellyfin + " @ \"%s\" (code %d): %v"
InitDiscord = "Initialized Discord daemon" InitDiscord = "Initialized Discord daemon"
FailedInitDiscord = "Failed to initialize Discord daemon: %v" FailedInitDiscord = "Failed to initialize Discord daemon: %v"
@ -59,4 +69,147 @@ const (
Quitting = "Shutting down..." Quitting = "Shutting down..."
Quit = "Server shut down." Quit = "Server shut down."
FailedQuit = "Server shutdown failed: %v" FailedQuit = "Server shutdown failed: %v"
// api-activities.go
FailedDBReadActivities = "Failed to read activities from DB: %v"
// api-backups.go
IgnoreInvalidFilename = "Invalid filename \"%s\", ignoring: %v"
GetUpload = "Retrieved uploaded file \"%s\""
FailedGetUpload = "Failed to retrieve file from form data: %v"
// api-invites.go
DeleteOldInvite = "Deleting old invite \"%s\""
DeleteInvite = "Deleting invite \"%s\""
FailedDeleteInvite = "Failed to delete invite \"%s\": %v"
GenerateInvite = "Generating new invite"
InvalidInviteCode = "Invalid invite code \"%s\""
FailedSendToTooltipNoUser = "Failed: \"%s\" not found"
FailedSendToTooltipMultiUser = "Failed: \"%s\" linked to multiple users"
FailedParseTime = "Failed to parse time value: %v"
FailedGetContactMethod = "Failed to get contact method for \"%s\", make sure one is set."
SetAdminNotify = "Set \"%s\" to %t for admin address \"%s\""
// api-jellyseerr.go
FailedGetUsers = "Failed to get user(s) from %s: %v"
// FIXME: Once done, look back at uses of FailedGetUsers for places where this would make more sense.
FailedGetUser = "Failed to get user \"%s\" from %s: %v"
FailedGetJellyseerrNotificationPrefs = "Failed to get user's notification prefs from " + Jellyseerr + ": %v"
FailedSyncContactMethods = "Failed to sync contact methods with %s: %v"
// api-messages.go
FailedGetCustomMessage = "Failed to get custom message \"%s\""
SetContactPrefForService = "Set contact preference for %s (\"%s\"): %t"
// Matrix
InvalidPIN = "Invalid PIN \"%s\""
UnauthorizedPIN = "Unauthorized PIN \"%s\""
FailedCreateRoom = "Failed to create room: %v"
FailedGenerateToken = "Failed to generate token: %v"
// api-profiles.go
SetDefaultProfile = "Setting default profile to \"%s\""
FailedApplyProfile = "Failed to apply profile for %s user \"%s\": %v"
ApplyProfile = "Applying settings from profile \"%s\""
FailedGetProfile = "Failed to find profile \"%s\""
FailedApplyTemplate = "Failed to apply %s template for %s user \"%s\": %v"
FallbackToDefault = ", using default"
CreateProfileFromUser = "Creating profile from user \"%s\""
FailedGetJellyfinDisplayPrefs = "Failed to get DisplayPreferences for user \"%s\" from " + Jellyfin + ": %v"
ProfileNoHomescreen = "No homescreen template in profile \"%s\""
Profile = "profile"
User = "user"
ApplyingTemplatesFrom = "Applying templates from %s: \"%s\" to %d users"
DelayingRequests = "Delay will be added between requests (count = %d)"
// api-userpage.go
EmailConfirmationRequired = "User \"%s\" requires email confirmation"
ChangePassword = "Changed password for %s user \"%s\""
FailedChangePassword = "Failed to change password for %s user \"%s\": %v"
GetReferralTemplate = "Found referral template \"%s\""
FailedGetReferralTemplate = "Failed to find referral template \"%s\": %v"
DeleteOldReferral = "Deleting old referral \"%s\""
RenewOldReferral = "Renewing old referral \"%s\""
// api-users.go
CreateUser = "Created %s user \"%s\""
FailedCreateUser = "Failed to create new %s user \"%s\": %v"
LinkUser = "Linked %s user \"%s\""
FailedLinkUser = "Failed to link %s user \"%s\" with \"%s\": %v"
DeleteUser = "Deleted %s user \"%s\""
FailedDeleteUser = "Failed to delete %s user \"%s\": %v"
FailedDeleteUsers = "Failed to delete %s user(s): %v"
UserExists = "user already exists"
AccountLinked = "account already linked and require_unique enabled"
AccountUnverified = "unverified"
FailedSetDiscordMemberRole = "Failed to set " + Discord + " member role: %v"
FailedSetEmailAddress = "Failed to set email address for %s user \"%s\": %v"
AdditionalOmbiErrors = "Additional errors from " + Ombi + ": %v"
IncorrectCaptcha = "captcha incorrect"
ExtendCreateExpiry = "Extended or created expiry for user \"%s\""
UserEmailAdjusted = "Email for user \"%s\" adjusted"
UserAdminAdjusted = "Admin state for user \"%s\" set to %t"
UserLabelAdjusted = "Label for user \"%s\" set to \"%s\""
)
const (
FailedGetCookies = "Failed to get cookie(s) \"%s\": %v"
FailedParseJWT = "Failed to parse JWT: %v"
FailedCastJWT = "JWT claims unreadable"
InvalidJWT = "JWT was invalidated, of incorrect type or has expired"
FailedSignJWT = "Failed to sign JWT: %v"
)
const (
FailedConstructExpiryAdmin = "Failed to construct expiry notification for \"%s\": %v"
FailedSendExpiryAdmin = "Failed to send expiry notification for \"%s\" to \"%s\": %v"
SentExpiryAdmin = "Sent expiry notification for \"%s\" to \"%s\""
FailedConstructCreationAdmin = "Failed to construct creation notification for \"%s\": %v"
FailedSendCreationAdmin = "Failed to send creation notification for \"%s\" to \"%s\": %v"
SentCreationAdmin = "Sent creation notification for \"%s\" to \"%s\""
FailedConstructInviteMessage = "Failed to construct invite message for \"%s\": %v"
FailedSendInviteMessage = "Failed to send invite message for \"%s\" to \"%s\": %v"
SentInviteMessage = "Sent invite message for \"%s\" to \"%s\""
FailedConstructConfirmationEmail = "Failed to construct confirmation email for \"%s\": %v"
FailedSendConfirmationEmail = "Failed to send confirmation email for \"%s\" to \"%s\": %v"
SentConfirmationEmail = "Sent confirmation email for \"%s\" to \"%s\""
FailedConstructPWRMessage = "Failed to construct PWR message for \"%s\": %v"
FailedSendPWRMessage = "Failed to send PWR message for \"%s\" to \"%s\": %v"
SentPWRMessage = "Sent PWR message for \"%s\" to \"%s\""
FailedConstructWelcomeMessage = "Failed to construct welcome message for \"%s\": %v"
FailedSendWelcomeMessage = "Failed to send welcome message for \"%s\" to \"%s\": %v"
SentWelcomeMessage = "Sent welcome message for \"%s\" to \"%s\""
FailedConstructEnableDisableMessage = "Failed to construct enable/disable message for \"%s\": %v"
FailedSendEnableDisableMessage = "Failed to send enable/disable message for \"%s\" to \"%s\": %v"
SentEnableDisableMessage = "Sent enable/disable message for \"%s\" to \"%s\""
FailedConstructDeletionMessage = "Failed to construct account deletion message for \"%s\": %v"
FailedSendDeletionMessage = "Failed to send account deletion message for \"%s\" to \"%s\": %v"
SentDeletionMessage = "Sent account deletion message for \"%s\" to \"%s\""
FailedConstructExpiryAdjustmentMessage = "Failed to construct expiry adjustment message for \"%s\": %v"
FailedSendExpiryAdjustmentMessage = "Failed to send expiry adjustment message for \"%s\" to \"%s\": %v"
SentExpiryAdjustmentMessage = "Sent expiry adjustment message for \"%s\" to \"%s\""
FailedConstructAnnouncementMessage = "Failed to construct announcement message for \"%s\": %v"
FailedSendAnnouncementMessage = "Failed to send announcement message for \"%s\" to \"%s\": %v"
SentAnnouncementMessage = "Sent announcement message for \"%s\" to \"%s\""
) )