messages: custom content described in customcontent.go, message tests

customcontent.go constains a structure with all the custom content,
methods for getting display names, subjects, etc., and a list of
variables, conditionals, and placeholder values. Tests for constructX
methods included in email_test.go, and all jfa-go tests can be run with
make INTERNAL=off test.
This commit is contained in:
Harvey Tindall
2025-08-30 14:21:26 +01:00
parent 0b43ad4ed5
commit 60dbfa2d1e
27 changed files with 1646 additions and 1141 deletions
+62 -59
View File
@@ -24,7 +24,6 @@ import (
"github.com/fatih/color"
"github.com/hrfee/jfa-go/common"
_ "github.com/hrfee/jfa-go/docs"
"github.com/hrfee/jfa-go/easyproxy"
"github.com/hrfee/jfa-go/jellyseerr"
"github.com/hrfee/jfa-go/logger"
lm "github.com/hrfee/jfa-go/logmessages"
@@ -81,6 +80,11 @@ var serverTypes = map[string]string{
var serverType = mediabrowser.JellyfinServer
var substituteStrings = ""
var externalURI, externalDomain string // The latter lower-case as should be accessed through app.ExternalDomain()
var UseProxyHost bool
var datePattern, timePattern string
// User is used for auth purposes.
type User struct {
UserID string `json:"id"`
@@ -88,10 +92,15 @@ type User struct {
Password string `json:"password"`
}
// Set of the usual log channels, for ease of passing between things.
type LoggerSet struct {
info, debug, err *logger.Logger
}
// contains (almost) everything the application needs, essentially. This was a dumb design decision imo.
type appContext struct {
// defaults *Config
config *ini.File
config *Config
configPath string
configBasePath string
configBase common.Config
@@ -103,39 +112,32 @@ type appContext struct {
adminUsers []User
invalidTokens []string
// Keeping jf name because I can't think of a better one
jf *mediabrowser.MediaBrowser
authJf *mediabrowser.MediaBrowser
ombi *OmbiWrapper
js *JellyseerrWrapper
thirdPartyServices []ThirdPartyService
datePattern string
timePattern string
storage Storage
validator Validator
email *Emailer
telegram *TelegramDaemon
discord *DiscordDaemon
matrix *MatrixDaemon
contactMethods []ContactMethodLinker
info, debug, err *logger.Logger
host string
port int
version string
externalURI, externalDomain string // The latter lower-case as should be accessed through app.ExternalDomain()
UseProxyHost bool
updater *Updater
webhooks *WebhookSender
newUpdate bool // Whether whatever's in update is new.
tag Tag
update Update
proxyEnabled bool
proxyTransport *http.Transport
proxyConfig easyproxy.ProxyConfig
internalPWRs map[string]InternalPWR
pwrCaptchas map[string]Captcha
ConfirmationKeys map[string]map[string]ConfirmationKey // Map of invite code to jwt to request
confirmationKeysLock sync.Mutex
userCache *UserCache
jf *mediabrowser.MediaBrowser
authJf *mediabrowser.MediaBrowser
ombi *OmbiWrapper
js *JellyseerrWrapper
thirdPartyServices []ThirdPartyService
storage *Storage
validator Validator
email *Emailer
telegram *TelegramDaemon
discord *DiscordDaemon
matrix *MatrixDaemon
contactMethods []ContactMethodLinker
LoggerSet
host string
port int
version string
updater *Updater
webhooks *WebhookSender
newUpdate bool // Whether whatever's in update is new.
tag Tag
update Update
internalPWRs map[string]InternalPWR
pwrCaptchas map[string]Captcha
ConfirmationKeys map[string]map[string]ConfirmationKey // Map of invite code to jwt to request
confirmationKeysLock sync.Mutex
userCache *UserCache
}
func generateSecret(length int) (string, error) {
@@ -244,7 +246,9 @@ func start(asDaemon, firstCall bool) {
var debugMode bool
var address string
if err := app.loadConfig(); err != nil {
var err error = nil
app.config, err = NewConfig(app.configPath, app.dataPath, app.LoggerSet)
if err != nil {
app.err.Fatalf(lm.FailedLoadConfig, app.configPath, err)
}
app.info.Printf(lm.LoadConfig, app.configPath)
@@ -262,12 +266,8 @@ func start(asDaemon, firstCall bool) {
}
if debugMode {
app.debug = logger.NewLogger(os.Stdout, "[DEBUG] ", log.Ltime|log.Lshortfile, color.FgYellow)
// Bind debug log
app.storage.debug = app.debug
app.storage.logActions = generateLogActions(app.config)
} else {
app.debug = logger.NewEmptyLogger()
app.storage.debug = nil
}
if *PPROF {
app.info.Print(warning("\n\nWARNING: Don't use pprof in production.\n\n"))
@@ -312,14 +312,17 @@ func start(asDaemon, firstCall bool) {
}()
}
app.storage.lang.CommonPath = "common"
app.storage.lang.UserPath = "form"
app.storage.lang.AdminPath = "admin"
app.storage.lang.EmailPath = "email"
app.storage.lang.TelegramPath = "telegram"
app.storage.lang.PasswordResetPath = "pwreset"
dbPath := filepath.Join(app.dataPath, "db")
if debugMode {
app.storage = NewStorage(dbPath, app.debug, generateLogActions(app.config))
} else {
app.storage = NewStorage(dbPath, app.debug, nil)
}
// Placed here, since storage.chosenXLang is set by this function.
app.config.ReloadDependents(app)
externalLang := app.config.Section("files").Key("lang_files").MustString("")
var err error
if externalLang == "" {
err = app.storage.loadLang(langFS)
} else {
@@ -362,7 +365,7 @@ func start(asDaemon, firstCall bool) {
}
address = fmt.Sprintf("%s:%d", app.host, app.port)
// NOTE: As of writing this, the order in app.thirdPartServices doesn't matter,
// NOTE: As of writing this, the order in app.thirdPartyServices doesn't matter,
// but in future it might (like app.contactMethods does), so append to the end!
if app.config.Section("ombi").Key("enabled").MustBool(false) {
app.ombi = &OmbiWrapper{}
@@ -391,10 +394,12 @@ func start(asDaemon, firstCall bool) {
}
app.storage.db_path = filepath.Join(app.dataPath, "db")
app.loadPendingBackup()
app.ConnectDB()
defer app.storage.db.Close()
if err := app.storage.Connect(app.config); err != nil {
app.err.Fatalf(lm.FailedConnectDB, dbPath, err)
}
app.info.Printf(lm.ConnectDB, dbPath)
defer app.storage.Close()
// copy it to app.patchedConfig, and patch in settings from app.config, and language stuff.
app.PatchConfigBase()
@@ -475,10 +480,9 @@ func start(asDaemon, firstCall bool) {
time.Minute*time.Duration(app.config.Section("jellyfin").Key("web_cache_sync_timeout").MustInt()),
)
// Since email depends on language, the email reload in loadConfig won't work first time.
// Since email depends on language, the email reload in NewConfig won't work first time.
// Email also handles its own proxying, as (SMTP atleast) doesn't use a HTTP transport.
app.email = NewEmailer(app)
app.loadStrftime()
app.email = NewEmailer(app.config, app.storage, app.LoggerSet)
var validatorConf ValidatorConf
@@ -579,13 +583,13 @@ func start(asDaemon, firstCall bool) {
)
// Updater proxy set in config.go, don't worry!
if app.proxyEnabled {
app.jf.SetTransport(app.proxyTransport)
if app.config.proxyConfig != nil {
app.jf.SetTransport(app.config.proxyTransport)
for _, c := range app.thirdPartyServices {
c.SetTransport(app.proxyTransport)
c.SetTransport(app.config.proxyTransport)
}
for _, c := range app.contactMethods {
c.SetTransport(app.proxyTransport)
c.SetTransport(app.config.proxyTransport)
}
}
} else {
@@ -601,7 +605,6 @@ func start(asDaemon, firstCall bool) {
app.host = "0.0.0.0"
}
address = fmt.Sprintf("%s:%d", app.host, app.port)
app.storage.lang.SetupPath = "setup"
err := app.storage.loadLangSetup(langFS)
if err != nil {
app.info.Fatalf(lm.FailedLangLoad, err)