mirror of
https://github.com/knadh/listmonk.git
synced 2025-09-11 17:05:00 +08:00
Remove superfluous consts
dep in init functions by separating URL consts.
This commit is contained in:
parent
e2f24a140e
commit
88489223c9
15 changed files with 220 additions and 200 deletions
10
cmd/admin.go
10
cmd/admin.go
|
@ -26,11 +26,11 @@ type serverConfig struct {
|
|||
// GetServerConfig returns general server config.
|
||||
func (a *App) GetServerConfig(c echo.Context) error {
|
||||
out := serverConfig{
|
||||
RootURL: a.constants.RootURL,
|
||||
FromEmail: a.constants.FromEmail,
|
||||
Lang: a.constants.Lang,
|
||||
Permissions: a.constants.PermissionsRaw,
|
||||
HasLegacyUser: a.constants.HasLegacyUser,
|
||||
RootURL: a.urlCfg.RootURL,
|
||||
FromEmail: a.cfg.FromEmail,
|
||||
Lang: a.cfg.Lang,
|
||||
Permissions: a.cfg.PermissionsRaw,
|
||||
HasLegacyUser: a.cfg.HasLegacyUser,
|
||||
}
|
||||
|
||||
// Language list.
|
||||
|
|
|
@ -53,7 +53,7 @@ func (a *App) GetCampaignArchives(c echo.Context) error {
|
|||
func (a *App) GetCampaignArchivesFeed(c echo.Context) error {
|
||||
var (
|
||||
pg = a.paginator.NewFromURL(c.Request().URL.Query())
|
||||
showFullContent = a.constants.EnablePublicArchiveRSSContent
|
||||
showFullContent = a.cfg.EnablePublicArchiveRSSContent
|
||||
)
|
||||
|
||||
// Get archives from the DB.
|
||||
|
@ -81,8 +81,8 @@ func (a *App) GetCampaignArchivesFeed(c echo.Context) error {
|
|||
|
||||
// Generate the feed.
|
||||
feed := &feeds.Feed{
|
||||
Title: a.constants.SiteName,
|
||||
Link: &feeds.Link{Href: a.constants.RootURL},
|
||||
Title: a.cfg.SiteName,
|
||||
Link: &feeds.Link{Href: a.urlCfg.RootURL},
|
||||
Description: a.i18n.T("public.archiveTitle"),
|
||||
Items: out,
|
||||
}
|
||||
|
@ -215,9 +215,9 @@ func (a *App) getCampaignArchives(offset, limit int, renderBody bool) ([]campArc
|
|||
|
||||
// The campaign may have a custom slug.
|
||||
if camp.ArchiveSlug.Valid {
|
||||
archive.URL, _ = url.JoinPath(a.constants.ArchiveURL, camp.ArchiveSlug.String)
|
||||
archive.URL, _ = url.JoinPath(a.urlCfg.ArchiveURL, camp.ArchiveSlug.String)
|
||||
} else {
|
||||
archive.URL, _ = url.JoinPath(a.constants.ArchiveURL, camp.UUID)
|
||||
archive.URL, _ = url.JoinPath(a.urlCfg.ArchiveURL, camp.UUID)
|
||||
}
|
||||
|
||||
// Render the full template body if requested.
|
||||
|
|
|
@ -190,9 +190,9 @@ func (a *App) renderLoginPage(c echo.Context, loginErr error) error {
|
|||
oidcProvider = ""
|
||||
oidcLogo = ""
|
||||
)
|
||||
if a.constants.Security.OIDC.Enabled {
|
||||
if a.cfg.Security.OIDC.Enabled {
|
||||
oidcLogo = "oidc.png"
|
||||
u, err := url.Parse(a.constants.Security.OIDC.Provider)
|
||||
u, err := url.Parse(a.cfg.Security.OIDC.Provider)
|
||||
if err == nil {
|
||||
h := strings.Split(u.Hostname(), ".")
|
||||
|
||||
|
@ -330,7 +330,7 @@ func (a *App) doFirstTimeSetup(c echo.Context) error {
|
|||
Type: auth.RoleTypeUser,
|
||||
Name: null.NewString("Super Admin", true),
|
||||
}
|
||||
for p := range a.constants.Permissions {
|
||||
for p := range a.cfg.Permissions {
|
||||
r.Permissions = append(r.Permissions, p)
|
||||
}
|
||||
|
||||
|
|
|
@ -143,7 +143,7 @@ func (a *App) BounceWebhook(c echo.Context) error {
|
|||
bounces = append(bounces, b)
|
||||
|
||||
// Amazon SES.
|
||||
case service == "ses" && a.constants.BounceSESEnabled:
|
||||
case service == "ses" && a.cfg.BounceSESEnabled:
|
||||
switch c.Request().Header.Get("X-Amz-Sns-Message-Type") {
|
||||
// SNS webhook registration confirmation. Only after these are processed will the endpoint
|
||||
// start getting bounce notifications.
|
||||
|
@ -167,7 +167,7 @@ func (a *App) BounceWebhook(c echo.Context) error {
|
|||
}
|
||||
|
||||
// SendGrid.
|
||||
case service == "sendgrid" && a.constants.BounceSendgridEnabled:
|
||||
case service == "sendgrid" && a.cfg.BounceSendgridEnabled:
|
||||
var (
|
||||
sig = c.Request().Header.Get("X-Twilio-Email-Event-Webhook-Signature")
|
||||
ts = c.Request().Header.Get("X-Twilio-Email-Event-Webhook-Timestamp")
|
||||
|
@ -182,7 +182,7 @@ func (a *App) BounceWebhook(c echo.Context) error {
|
|||
bounces = append(bounces, bs...)
|
||||
|
||||
// Postmark.
|
||||
case service == "postmark" && a.constants.BouncePostmarkEnabled:
|
||||
case service == "postmark" && a.cfg.BouncePostmarkEnabled:
|
||||
bs, err := a.bounce.Postmark.ProcessBounce(rawReq, c)
|
||||
if err != nil {
|
||||
a.log.Printf("error processing postmark notification: %v", err)
|
||||
|
@ -195,7 +195,7 @@ func (a *App) BounceWebhook(c echo.Context) error {
|
|||
bounces = append(bounces, bs...)
|
||||
|
||||
// ForwardEmail.
|
||||
case service == "forwardemail" && a.constants.BounceForwardemailEnabled:
|
||||
case service == "forwardemail" && a.cfg.BounceForwardemailEnabled:
|
||||
var (
|
||||
sig = c.Request().Header.Get("X-Webhook-Signature")
|
||||
)
|
||||
|
|
|
@ -553,7 +553,7 @@ func (a *App) sendTestMessage(sub models.Subscriber, camp *models.Campaign) erro
|
|||
// validateCampaignFields validates incoming campaign field values.
|
||||
func (a *App) validateCampaignFields(c campReq) (campReq, error) {
|
||||
if c.FromEmail == "" {
|
||||
c.FromEmail = a.constants.FromEmail
|
||||
c.FromEmail = a.cfg.FromEmail
|
||||
} else if !reFromAddress.Match([]byte(c.FromEmail)) {
|
||||
if _, err := a.importer.SanitizeEmail(c.FromEmail); err != nil {
|
||||
return c, errors.New(a.i18n.T("campaigns.fieldInvalidFromEmail"))
|
||||
|
|
|
@ -49,7 +49,7 @@ func initHTTPHandlers(e *echo.Echo, a *App) {
|
|||
|
||||
// On no-auth, redirect to login page
|
||||
if _, ok := u.(*echo.HTTPError); ok {
|
||||
u, _ := url.Parse(a.constants.LoginURL)
|
||||
u, _ := url.Parse(a.urlCfg.LoginURL)
|
||||
q := url.Values{}
|
||||
q.Set("next", c.Request().RequestURI)
|
||||
u.RawQuery = q.Encode()
|
||||
|
@ -197,7 +197,7 @@ func initHTTPHandlers(e *echo.Echo, a *App) {
|
|||
g.PUT("/api/roles/lists/:id", pm(a.UpdateListRole, "roles:manage"))
|
||||
g.DELETE("/api/roles/:id", pm(a.DeleteRole, "roles:manage"))
|
||||
|
||||
if a.constants.BounceWebhooksEnabled {
|
||||
if a.cfg.BounceWebhooksEnabled {
|
||||
// Private authenticated bounce endpoint.
|
||||
g.POST("/webhooks/bounce", pm(a.BounceWebhook, "webhooks:post_bounce"))
|
||||
}
|
||||
|
@ -209,7 +209,7 @@ func initHTTPHandlers(e *echo.Echo, a *App) {
|
|||
// Public unauthenticated endpoints.
|
||||
g := e.Group("")
|
||||
|
||||
if a.constants.BounceWebhooksEnabled {
|
||||
if a.cfg.BounceWebhooksEnabled {
|
||||
// Public bounce endpoints for webservices like SES.
|
||||
g.POST("/webhooks/service/:service", a.BounceWebhook)
|
||||
}
|
||||
|
@ -223,7 +223,7 @@ func initHTTPHandlers(e *echo.Echo, a *App) {
|
|||
g.GET(path.Join(uriAdmin, "/login"), a.LoginPage)
|
||||
g.POST(path.Join(uriAdmin, "/login"), a.LoginPage)
|
||||
|
||||
if a.constants.Security.OIDC.Enabled {
|
||||
if a.cfg.Security.OIDC.Enabled {
|
||||
g.POST("/auth/oidc", a.OIDCLogin)
|
||||
g.GET("/auth/oidc", a.OIDCFinish)
|
||||
}
|
||||
|
@ -231,7 +231,7 @@ func initHTTPHandlers(e *echo.Echo, a *App) {
|
|||
// Public APIs.
|
||||
g.GET("/api/public/lists", a.GetPublicLists)
|
||||
g.POST("/api/public/subscription", a.PublicSubscription)
|
||||
if a.constants.EnablePublicArchive {
|
||||
if a.cfg.EnablePublicArchive {
|
||||
g.GET("/api/public/archive", a.GetCampaignArchives)
|
||||
}
|
||||
|
||||
|
@ -249,7 +249,7 @@ func initHTTPHandlers(e *echo.Echo, a *App) {
|
|||
g.GET("/campaign/:campUUID/:subUUID", noIndex(validateUUID(a.ViewCampaignMessage, "campUUID", "subUUID")))
|
||||
g.GET("/campaign/:campUUID/:subUUID/px.png", noIndex(validateUUID(a.RegisterCampaignView, "campUUID", "subUUID")))
|
||||
|
||||
if a.constants.EnablePublicArchive {
|
||||
if a.cfg.EnablePublicArchive {
|
||||
g.GET("/archive", a.CampaignArchivesPage)
|
||||
g.GET("/archive.xml", a.GetCampaignArchivesFeed)
|
||||
g.GET("/archive/:id", a.CampaignArchivePage)
|
||||
|
@ -283,7 +283,7 @@ func (a *App) AdminPage(c echo.Context) error {
|
|||
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
b = bytes.ReplaceAll(b, []byte("asset_version"), []byte(a.constants.AssetVersion))
|
||||
b = bytes.ReplaceAll(b, []byte("asset_version"), []byte(a.cfg.AssetVersion))
|
||||
|
||||
return c.HTMLBlob(http.StatusOK, b)
|
||||
}
|
||||
|
@ -306,19 +306,19 @@ func serveCustomAppearance(name string) echo.HandlerFunc {
|
|||
|
||||
switch name {
|
||||
case "admin.custom_css":
|
||||
out = app.constants.Appearance.AdminCSS
|
||||
out = app.cfg.Appearance.AdminCSS
|
||||
hdr = "text/css; charset=utf-8"
|
||||
|
||||
case "admin.custom_js":
|
||||
out = app.constants.Appearance.AdminJS
|
||||
out = app.cfg.Appearance.AdminJS
|
||||
hdr = "application/javascript; charset=utf-8"
|
||||
|
||||
case "public.custom_css":
|
||||
out = app.constants.Appearance.PublicCSS
|
||||
out = app.cfg.Appearance.PublicCSS
|
||||
hdr = "text/css; charset=utf-8"
|
||||
|
||||
case "public.custom_js":
|
||||
out = app.constants.Appearance.PublicJS
|
||||
out = app.cfg.Appearance.PublicJS
|
||||
hdr = "application/javascript; charset=utf-8"
|
||||
}
|
||||
|
||||
|
|
140
cmd/init.go
140
cmd/init.go
|
@ -57,19 +57,28 @@ const (
|
|||
queryFilePath = "queries.sql"
|
||||
)
|
||||
|
||||
// constants contains static, constant config values required by the app.
|
||||
type constants struct {
|
||||
// UrlConfig contains various URL constants used in the app.
|
||||
type UrlConfig struct {
|
||||
RootURL string `koanf:"root_url"`
|
||||
LogoURL string `koanf:"logo_url"`
|
||||
FaviconURL string `koanf:"favicon_url"`
|
||||
LoginURL string `koanf:"login_url"`
|
||||
UnsubURL string
|
||||
LinkTrackURL string
|
||||
ViewTrackURL string
|
||||
OptinURL string
|
||||
MessageURL string
|
||||
ArchiveURL string
|
||||
}
|
||||
|
||||
// Config contains static, constant config values required by arbitrary handlers and functions.
|
||||
type Config struct {
|
||||
SiteName string `koanf:"site_name"`
|
||||
RootURL string `koanf:"root_url"`
|
||||
LogoURL string `koanf:"logo_url"`
|
||||
FaviconURL string `koanf:"favicon_url"`
|
||||
LoginURL string `koanf:"login_url"`
|
||||
FromEmail string `koanf:"from_email"`
|
||||
NotifyEmails []string `koanf:"notify_emails"`
|
||||
EnablePublicSubPage bool `koanf:"enable_public_subscription_page"`
|
||||
EnablePublicArchive bool `koanf:"enable_public_archive"`
|
||||
EnablePublicArchiveRSSContent bool `koanf:"enable_public_archive_rss_content"`
|
||||
SendOptinConfirmation bool `koanf:"send_optin_confirmation"`
|
||||
Lang string `koanf:"lang"`
|
||||
DBBatchSize int `koanf:"batch_size"`
|
||||
Privacy struct {
|
||||
|
@ -105,12 +114,6 @@ type constants struct {
|
|||
}
|
||||
|
||||
HasLegacyUser bool
|
||||
UnsubURL string
|
||||
LinkTrackURL string
|
||||
ViewTrackURL string
|
||||
OptinURL string
|
||||
MessageURL string
|
||||
ArchiveURL string
|
||||
AssetVersion string
|
||||
|
||||
MediaUpload struct {
|
||||
|
@ -386,10 +389,40 @@ func initSettings(query string, db *sqlx.DB, ko *koanf.Koanf) {
|
|||
}
|
||||
}
|
||||
|
||||
// initConstants initializes the app's global constants from the given koanf instance.
|
||||
func initConstants(ko *koanf.Koanf) *constants {
|
||||
func initUrlConfig(ko *koanf.Koanf) *UrlConfig {
|
||||
root := strings.TrimSuffix(ko.String("app.root_url"), "/")
|
||||
|
||||
return &UrlConfig{
|
||||
RootURL: root,
|
||||
LogoURL: path.Join(root, ko.String("app.logo_url")),
|
||||
FaviconURL: path.Join(root, ko.String("app.favicon_url")),
|
||||
LoginURL: path.Join(uriAdmin, "/login"),
|
||||
|
||||
// Static URLS.
|
||||
// url.com/subscription/{campaign_uuid}/{subscriber_uuid}
|
||||
UnsubURL: fmt.Sprintf("%s/subscription/%%s/%%s", root),
|
||||
|
||||
// url.com/subscription/optin/{subscriber_uuid}
|
||||
OptinURL: fmt.Sprintf("%s/subscription/optin/%%s?%%s", root),
|
||||
|
||||
// url.com/link/{campaign_uuid}/{subscriber_uuid}/{link_uuid}
|
||||
LinkTrackURL: fmt.Sprintf("%s/link/%%s/%%s/%%s", root),
|
||||
|
||||
// url.com/link/{campaign_uuid}/{subscriber_uuid}
|
||||
MessageURL: fmt.Sprintf("%s/campaign/%%s/%%s", root),
|
||||
|
||||
// url.com/archive
|
||||
ArchiveURL: root + "/archive",
|
||||
|
||||
// url.com/campaign/{campaign_uuid}/{subscriber_uuid}/px.png
|
||||
ViewTrackURL: fmt.Sprintf("%s/campaign/%%s/%%s/px.png", root),
|
||||
}
|
||||
}
|
||||
|
||||
// initConstConfig initializes the app's global constants from the given koanf instance.
|
||||
func initConstConfig(ko *koanf.Koanf) *Config {
|
||||
// Read constants.
|
||||
var c constants
|
||||
var c Config
|
||||
if err := ko.Unmarshal("app", &c); err != nil {
|
||||
lo.Fatalf("error loading app config: %v", err)
|
||||
}
|
||||
|
@ -404,8 +437,6 @@ func initConstants(ko *koanf.Koanf) *constants {
|
|||
lo.Fatalf("error loading app.appearance config: %v", err)
|
||||
}
|
||||
|
||||
c.RootURL = strings.TrimRight(c.RootURL, "/")
|
||||
c.LoginURL = path.Join(uriAdmin, "/login")
|
||||
c.Lang = ko.String("app.lang")
|
||||
c.Privacy.Exportable = koanfmaps.StringSliceToLookupMap(ko.Strings("privacy.exportable"))
|
||||
c.MediaUpload.Provider = ko.String("upload.provider")
|
||||
|
@ -413,25 +444,6 @@ func initConstants(ko *koanf.Koanf) *constants {
|
|||
c.Privacy.DomainBlocklist = ko.Strings("privacy.domain_blocklist")
|
||||
c.Privacy.DomainAllowlist = ko.Strings("privacy.domain_allowlist")
|
||||
|
||||
// Static URLS.
|
||||
// url.com/subscription/{campaign_uuid}/{subscriber_uuid}
|
||||
c.UnsubURL = fmt.Sprintf("%s/subscription/%%s/%%s", c.RootURL)
|
||||
|
||||
// url.com/subscription/optin/{subscriber_uuid}
|
||||
c.OptinURL = fmt.Sprintf("%s/subscription/optin/%%s?%%s", c.RootURL)
|
||||
|
||||
// url.com/link/{campaign_uuid}/{subscriber_uuid}/{link_uuid}
|
||||
c.LinkTrackURL = fmt.Sprintf("%s/link/%%s/%%s/%%s", c.RootURL)
|
||||
|
||||
// url.com/link/{campaign_uuid}/{subscriber_uuid}
|
||||
c.MessageURL = fmt.Sprintf("%s/campaign/%%s/%%s", c.RootURL)
|
||||
|
||||
// url.com/archive
|
||||
c.ArchiveURL = c.RootURL + "/archive"
|
||||
|
||||
// url.com/campaign/{campaign_uuid}/{subscriber_uuid}/px.png
|
||||
c.ViewTrackURL = fmt.Sprintf("%s/campaign/%%s/%%s/px.png", c.RootURL)
|
||||
|
||||
c.BounceWebhooksEnabled = ko.Bool("bounce.webhooks_enabled")
|
||||
c.BounceSESEnabled = ko.Bool("bounce.ses_enabled")
|
||||
c.BounceSendgridEnabled = ko.Bool("bounce.sendgrid_enabled")
|
||||
|
@ -484,7 +496,7 @@ func initI18n(lang string, fs stuffbin.FileSystem) *i18n.I18n {
|
|||
}
|
||||
|
||||
// initCampaignManager initializes the campaign manager.
|
||||
func initCampaignManager(q *models.Queries, cs *constants, co *core.Core, md media.Store, i *i18n.I18n) *manager.Manager {
|
||||
func initCampaignManager(q *models.Queries, u *UrlConfig, co *core.Core, md media.Store, i *i18n.I18n) *manager.Manager {
|
||||
if ko.Bool("passive") {
|
||||
lo.Println("running in passive mode. won't process campaigns.")
|
||||
}
|
||||
|
@ -494,15 +506,15 @@ func initCampaignManager(q *models.Queries, cs *constants, co *core.Core, md med
|
|||
Concurrency: ko.Int("app.concurrency"),
|
||||
MessageRate: ko.Int("app.message_rate"),
|
||||
MaxSendErrors: ko.Int("app.max_send_errors"),
|
||||
FromEmail: cs.FromEmail,
|
||||
FromEmail: ko.MustString("app.from_email"),
|
||||
IndividualTracking: ko.Bool("privacy.individual_tracking"),
|
||||
UnsubURL: cs.UnsubURL,
|
||||
OptinURL: cs.OptinURL,
|
||||
LinkTrackURL: cs.LinkTrackURL,
|
||||
ViewTrackURL: cs.ViewTrackURL,
|
||||
MessageURL: cs.MessageURL,
|
||||
ArchiveURL: cs.ArchiveURL,
|
||||
RootURL: cs.RootURL,
|
||||
UnsubURL: u.UnsubURL,
|
||||
OptinURL: u.OptinURL,
|
||||
LinkTrackURL: u.LinkTrackURL,
|
||||
ViewTrackURL: u.ViewTrackURL,
|
||||
MessageURL: u.MessageURL,
|
||||
ArchiveURL: u.ArchiveURL,
|
||||
RootURL: u.RootURL,
|
||||
UnsubHeader: ko.Bool("privacy.unsubscribe_header"),
|
||||
SlidingWindow: ko.Bool("app.message_sliding_window"),
|
||||
SlidingWindowDuration: ko.Duration("app.message_sliding_window_duration"),
|
||||
|
@ -530,11 +542,11 @@ func initTxTemplates(m *manager.Manager, co *core.Core) {
|
|||
}
|
||||
|
||||
// initImporter initializes the bulk subscriber importer.
|
||||
func initImporter(q *models.Queries, db *sqlx.DB, core *core.Core, app *App) *subimporter.Importer {
|
||||
func initImporter(q *models.Queries, db *sqlx.DB, core *core.Core, i *i18n.I18n, ko *koanf.Koanf) *subimporter.Importer {
|
||||
return subimporter.New(
|
||||
subimporter.Options{
|
||||
DomainBlocklist: app.constants.Privacy.DomainBlocklist,
|
||||
DomainAllowlist: app.constants.Privacy.DomainAllowlist,
|
||||
DomainBlocklist: ko.Strings("privacy.domain_blocklist"),
|
||||
DomainAllowlist: ko.Strings("privacy.domain_allowlist"),
|
||||
UpsertStmt: q.UpsertSubscriber.Stmt,
|
||||
BlocklistStmt: q.UpsertBlocklistSubscriber.Stmt,
|
||||
UpdateListDateStmt: q.UpdateListsDate.Stmt,
|
||||
|
@ -549,7 +561,7 @@ func initImporter(q *models.Queries, db *sqlx.DB, core *core.Core, app *App) *su
|
|||
notifs.NotifySystem(subject, notifs.TplImport, data, nil)
|
||||
return nil
|
||||
},
|
||||
}, db.DB, app.i18n)
|
||||
}, db.DB, i)
|
||||
}
|
||||
|
||||
// initSMTPMessenger initializes the combined and individual SMTP messengers.
|
||||
|
@ -673,8 +685,8 @@ func initMediaStore(ko *koanf.Koanf) media.Store {
|
|||
}
|
||||
|
||||
// initNotifs initializes the notifier with the system e-mail templates.
|
||||
func initNotifs(fs stuffbin.FileSystem, i *i18n.I18n, em *email.Emailer, cs *constants, ko *koanf.Koanf) {
|
||||
tpls, err := stuffbin.ParseTemplatesGlob(initTplFuncs(i, cs), fs, "/static/email-templates/*.html")
|
||||
func initNotifs(fs stuffbin.FileSystem, i *i18n.I18n, em *email.Emailer, u *UrlConfig, ko *koanf.Koanf) {
|
||||
tpls, err := stuffbin.ParseTemplatesGlob(initTplFuncs(i, u), fs, "/static/email-templates/*.html")
|
||||
if err != nil {
|
||||
lo.Fatalf("error parsing e-mail notif templates: %v", err)
|
||||
}
|
||||
|
@ -808,20 +820,20 @@ func initHTTPServer(app *App) *echo.Echo {
|
|||
}
|
||||
})
|
||||
|
||||
tpl, err := stuffbin.ParseTemplatesGlob(initTplFuncs(app.i18n, app.constants), app.fs, "/public/templates/*.html")
|
||||
tpl, err := stuffbin.ParseTemplatesGlob(initTplFuncs(app.i18n, app.urlCfg), app.fs, "/public/templates/*.html")
|
||||
if err != nil {
|
||||
lo.Fatalf("error parsing public templates: %v", err)
|
||||
}
|
||||
srv.Renderer = &tplRenderer{
|
||||
templates: tpl,
|
||||
SiteName: app.constants.SiteName,
|
||||
RootURL: app.constants.RootURL,
|
||||
LogoURL: app.constants.LogoURL,
|
||||
FaviconURL: app.constants.FaviconURL,
|
||||
AssetVersion: app.constants.AssetVersion,
|
||||
EnablePublicSubPage: app.constants.EnablePublicSubPage,
|
||||
EnablePublicArchive: app.constants.EnablePublicArchive,
|
||||
IndividualTracking: app.constants.Privacy.IndividualTracking,
|
||||
SiteName: app.cfg.SiteName,
|
||||
RootURL: app.urlCfg.RootURL,
|
||||
LogoURL: app.urlCfg.LogoURL,
|
||||
FaviconURL: app.urlCfg.FaviconURL,
|
||||
AssetVersion: app.cfg.AssetVersion,
|
||||
EnablePublicSubPage: app.cfg.EnablePublicSubPage,
|
||||
EnablePublicArchive: app.cfg.EnablePublicArchive,
|
||||
IndividualTracking: app.cfg.Privacy.IndividualTracking,
|
||||
}
|
||||
|
||||
// Initialize the static file server.
|
||||
|
@ -928,13 +940,13 @@ func joinFSPaths(root string, paths []string) []string {
|
|||
|
||||
// initTplFuncs returns a generic template func map with custom template
|
||||
// functions and sprig template functions.
|
||||
func initTplFuncs(i *i18n.I18n, cs *constants) template.FuncMap {
|
||||
func initTplFuncs(i *i18n.I18n, u *UrlConfig) template.FuncMap {
|
||||
funcs := template.FuncMap{
|
||||
"RootURL": func() string {
|
||||
return cs.RootURL
|
||||
return u.RootURL
|
||||
},
|
||||
"LogoURL": func() string {
|
||||
return cs.LogoURL
|
||||
return u.LogoURL
|
||||
},
|
||||
"Date": func(layout string) string {
|
||||
if layout == "" {
|
||||
|
|
|
@ -270,7 +270,7 @@ func checkSchema(db *sqlx.DB) (bool, error) {
|
|||
}
|
||||
|
||||
func installUser(username, password string, q *models.Queries) {
|
||||
consts := initConstants(ko)
|
||||
consts := initConstConfig(ko)
|
||||
|
||||
// Super admin role.
|
||||
perms := []string{}
|
||||
|
|
61
cmd/main.go
61
cmd/main.go
|
@ -37,25 +37,27 @@ const (
|
|||
|
||||
// App contains the "global" shared components, controllers and fields.
|
||||
type App struct {
|
||||
core *core.Core
|
||||
fs stuffbin.FileSystem
|
||||
db *sqlx.DB
|
||||
queries *models.Queries
|
||||
constants *constants
|
||||
manager *manager.Manager
|
||||
importer *subimporter.Importer
|
||||
messengers []manager.Messenger
|
||||
emailMessenger manager.Messenger
|
||||
auth *auth.Auth
|
||||
media media.Store
|
||||
i18n *i18n.I18n
|
||||
bounce *bounce.Manager
|
||||
paginator *paginator.Paginator
|
||||
captcha *captcha.Captcha
|
||||
events *events.Events
|
||||
about about
|
||||
log *log.Logger
|
||||
bufLog *buflog.BufLog
|
||||
core *core.Core
|
||||
fs stuffbin.FileSystem
|
||||
db *sqlx.DB
|
||||
queries *models.Queries
|
||||
cfg *Config
|
||||
urlCfg *UrlConfig
|
||||
manager *manager.Manager
|
||||
importer *subimporter.Importer
|
||||
messengers []manager.Messenger
|
||||
emailMessenger manager.Messenger
|
||||
auth *auth.Auth
|
||||
media media.Store
|
||||
i18n *i18n.I18n
|
||||
bounce *bounce.Manager
|
||||
paginator *paginator.Paginator
|
||||
captcha *captcha.Captcha
|
||||
events *events.Events
|
||||
optinNotifyHook func(models.Subscriber, []int) (int, error)
|
||||
about about
|
||||
log *log.Logger
|
||||
bufLog *buflog.BufLog
|
||||
|
||||
// Channel for passing reload signals.
|
||||
chReload chan os.Signal
|
||||
|
@ -173,7 +175,8 @@ func main() {
|
|||
app := &App{
|
||||
fs: fs,
|
||||
db: db,
|
||||
constants: initConstants(ko),
|
||||
cfg: initConstConfig(ko),
|
||||
urlCfg: initUrlConfig(ko),
|
||||
media: initMediaStore(ko),
|
||||
messengers: []manager.Messenger{},
|
||||
log: lo,
|
||||
|
@ -192,10 +195,10 @@ func main() {
|
|||
}
|
||||
|
||||
// Load i18n language map.
|
||||
app.i18n = initI18n(app.constants.Lang, fs)
|
||||
app.i18n = initI18n(ko.MustString("app.lang"), fs)
|
||||
cOpt := &core.Opt{
|
||||
Constants: core.Constants{
|
||||
SendOptinConfirmation: app.constants.SendOptinConfirmation,
|
||||
SendOptinConfirmation: ko.Bool("app.send_optin_confirmation"),
|
||||
CacheSlowQueries: ko.Bool("app.cache_slow_queries"),
|
||||
},
|
||||
Queries: queries,
|
||||
|
@ -204,19 +207,19 @@ func main() {
|
|||
Log: lo,
|
||||
}
|
||||
|
||||
// Load bounce config.
|
||||
// Load bounce config into the core.
|
||||
if err := ko.Unmarshal("bounce.actions", &cOpt.Constants.BounceActions); err != nil {
|
||||
lo.Fatalf("error unmarshalling bounce config: %v", err)
|
||||
}
|
||||
|
||||
// Initialize the CRUD core.
|
||||
app.core = core.New(cOpt, &core.Hooks{
|
||||
SendOptinConfirmation: app.optinConfirmNotify(),
|
||||
})
|
||||
optinNotify := makeOptinNotifyHook(ko.Bool("app.send_optin_confirmation"), app.urlCfg, queries, app.i18n)
|
||||
app.optinNotifyHook = optinNotify
|
||||
app.core = core.New(cOpt, &core.Hooks{SendOptinConfirmation: optinNotify})
|
||||
|
||||
app.queries = queries
|
||||
app.manager = initCampaignManager(app.queries, app.constants, app.core, app.media, app.i18n)
|
||||
app.importer = initImporter(app.queries, db, app.core, app)
|
||||
app.manager = initCampaignManager(app.queries, app.urlCfg, app.core, app.media, app.i18n)
|
||||
app.importer = initImporter(app.queries, db, app.core, app.i18n, ko)
|
||||
|
||||
hasUsers, auth := initAuth(app.core, db.DB, ko)
|
||||
app.auth = auth
|
||||
|
@ -241,7 +244,7 @@ func main() {
|
|||
}
|
||||
|
||||
// Initialize admin email notification templates.
|
||||
initNotifs(app.fs, app.i18n, app.emailMessenger.(*email.Emailer), app.constants, ko)
|
||||
initNotifs(app.fs, app.i18n, app.emailMessenger.(*email.Emailer), app.urlCfg, ko)
|
||||
initTxTemplates(app.manager, app.core)
|
||||
|
||||
// Initialize any additional postback messengers.
|
||||
|
|
|
@ -46,8 +46,8 @@ func (a *App) UploadMedia(c echo.Context) error {
|
|||
)
|
||||
|
||||
// Validate file extension.
|
||||
if !inArray("*", a.constants.MediaUpload.Extensions) {
|
||||
if ok := inArray(ext, a.constants.MediaUpload.Extensions); !ok {
|
||||
if !inArray("*", a.cfg.MediaUpload.Extensions) {
|
||||
if ok := inArray(ext, a.cfg.MediaUpload.Extensions); !ok {
|
||||
return echo.NewHTTPError(http.StatusBadRequest,
|
||||
a.i18n.Ts("media.unsupportedFileType", "type", ext))
|
||||
}
|
||||
|
@ -131,7 +131,7 @@ func (a *App) UploadMedia(c echo.Context) error {
|
|||
}
|
||||
|
||||
// Insert the media into the DB.
|
||||
m, err := a.core.InsertMedia(fName, thumbfName, contentType, meta, a.constants.MediaUpload.Provider, a.media)
|
||||
m, err := a.core.InsertMedia(fName, thumbfName, contentType, meta, a.cfg.MediaUpload.Provider, a.media)
|
||||
if err != nil {
|
||||
cleanUp = true
|
||||
return err
|
||||
|
@ -158,7 +158,7 @@ func (a *App) GetMedia(c echo.Context) error {
|
|||
pg = a.paginator.NewFromURL(c.Request().URL.Query())
|
||||
query = c.FormValue("query")
|
||||
)
|
||||
res, total, err := a.core.QueryMedia(a.constants.MediaUpload.Provider, a.media, query, pg.Offset, pg.Limit)
|
||||
res, total, err := a.core.QueryMedia(a.cfg.MediaUpload.Provider, a.media, query, pg.Offset, pg.Limit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -206,10 +206,10 @@ func (a *App) SubscriptionPage(c echo.Context) error {
|
|||
Subscriber: s,
|
||||
SubUUID: subUUID,
|
||||
publicTpl: publicTpl{Title: a.i18n.T("public.unsubscribeTitle")},
|
||||
AllowBlocklist: a.constants.Privacy.AllowBlocklist,
|
||||
AllowExport: a.constants.Privacy.AllowExport,
|
||||
AllowWipe: a.constants.Privacy.AllowWipe,
|
||||
AllowPreferences: a.constants.Privacy.AllowPreferences,
|
||||
AllowBlocklist: a.cfg.Privacy.AllowBlocklist,
|
||||
AllowExport: a.cfg.Privacy.AllowExport,
|
||||
AllowWipe: a.cfg.Privacy.AllowWipe,
|
||||
AllowPreferences: a.cfg.Privacy.AllowPreferences,
|
||||
}
|
||||
|
||||
// If the subscriber is blocklisted, throw an error.
|
||||
|
@ -218,7 +218,7 @@ func (a *App) SubscriptionPage(c echo.Context) error {
|
|||
}
|
||||
|
||||
// Only show preference management if it's enabled in settings.
|
||||
if a.constants.Privacy.AllowPreferences {
|
||||
if a.cfg.Privacy.AllowPreferences {
|
||||
out.ShowManage = showManage
|
||||
|
||||
// Get the subscriber's lists from the DB to render in the template.
|
||||
|
@ -261,7 +261,7 @@ func (a *App) SubscriptionPrefs(c echo.Context) error {
|
|||
var (
|
||||
campUUID = c.Param("campUUID")
|
||||
subUUID = c.Param("subUUID")
|
||||
blocklist = a.constants.Privacy.AllowBlocklist && req.Blocklist
|
||||
blocklist = a.cfg.Privacy.AllowBlocklist && req.Blocklist
|
||||
)
|
||||
if !req.Manage || blocklist {
|
||||
if err := a.core.UnsubscribeByCampaign(subUUID, campUUID, blocklist); err != nil {
|
||||
|
@ -274,7 +274,7 @@ func (a *App) SubscriptionPrefs(c echo.Context) error {
|
|||
}
|
||||
|
||||
// Is preference management enabled?
|
||||
if !a.constants.Privacy.AllowPreferences {
|
||||
if !a.cfg.Privacy.AllowPreferences {
|
||||
return c.Render(http.StatusBadRequest, tplMessage,
|
||||
makeMsgTpl(a.i18n.T("public.errorTitle"), "", a.i18n.T("public.invalidFeature")))
|
||||
}
|
||||
|
@ -375,7 +375,7 @@ func (a *App) OptinPage(c echo.Context) error {
|
|||
// Confirm.
|
||||
if confirm {
|
||||
meta := models.JSON{}
|
||||
if a.constants.Privacy.RecordOptinIP {
|
||||
if a.cfg.Privacy.RecordOptinIP {
|
||||
if h := c.Request().Header.Get("X-Forwarded-For"); h != "" {
|
||||
meta["optin_ip"] = h
|
||||
} else if h := c.Request().RemoteAddr; h != "" {
|
||||
|
@ -405,7 +405,7 @@ func (a *App) OptinPage(c echo.Context) error {
|
|||
// SubscriptionFormPage handles subscription requests coming from public
|
||||
// HTML subscription forms.
|
||||
func (a *App) SubscriptionFormPage(c echo.Context) error {
|
||||
if !a.constants.EnablePublicSubPage {
|
||||
if !a.cfg.EnablePublicSubPage {
|
||||
return c.Render(http.StatusNotFound, tplMessage,
|
||||
makeMsgTpl(a.i18n.T("public.errorTitle"), "", a.i18n.Ts("public.invalidFeature")))
|
||||
}
|
||||
|
@ -428,8 +428,8 @@ func (a *App) SubscriptionFormPage(c echo.Context) error {
|
|||
out.Lists = lists
|
||||
|
||||
// Captcha is enabled. Set the key for the template to render.
|
||||
if a.constants.Security.EnableCaptcha {
|
||||
out.CaptchaKey = a.constants.Security.CaptchaKey
|
||||
if a.cfg.Security.EnableCaptcha {
|
||||
out.CaptchaKey = a.cfg.Security.CaptchaKey
|
||||
}
|
||||
|
||||
return c.Render(http.StatusOK, "subscription-form", out)
|
||||
|
@ -444,7 +444,7 @@ func (a *App) SubscriptionForm(c echo.Context) error {
|
|||
}
|
||||
|
||||
// Process CAPTCHA.
|
||||
if a.constants.Security.EnableCaptcha {
|
||||
if a.cfg.Security.EnableCaptcha {
|
||||
err, ok := a.captcha.Verify(c.FormValue("h-captcha-response"))
|
||||
if err != nil {
|
||||
a.log.Printf("Captcha request failed: %v", err)
|
||||
|
@ -479,7 +479,7 @@ func (a *App) SubscriptionForm(c echo.Context) error {
|
|||
// PublicSubscription handles subscription requests coming from public
|
||||
// API calls.
|
||||
func (a *App) PublicSubscription(c echo.Context) error {
|
||||
if !a.constants.EnablePublicSubPage {
|
||||
if !a.cfg.EnablePublicSubPage {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, a.i18n.T("public.invalidFeature"))
|
||||
}
|
||||
|
||||
|
@ -499,7 +499,7 @@ func (a *App) PublicSubscription(c echo.Context) error {
|
|||
func (a *App) LinkRedirect(c echo.Context) error {
|
||||
// If individual tracking is disabled, do not record the subscriber ID.
|
||||
subUUID := c.Param("subUUID")
|
||||
if !a.constants.Privacy.IndividualTracking {
|
||||
if !a.cfg.Privacy.IndividualTracking {
|
||||
subUUID = ""
|
||||
}
|
||||
|
||||
|
@ -524,7 +524,7 @@ func (a *App) LinkRedirect(c echo.Context) error {
|
|||
func (a *App) RegisterCampaignView(c echo.Context) error {
|
||||
// If individual tracking is disabled, do not record the subscriber ID.
|
||||
subUUID := c.Param("subUUID")
|
||||
if !a.constants.Privacy.IndividualTracking {
|
||||
if !a.cfg.Privacy.IndividualTracking {
|
||||
subUUID = ""
|
||||
}
|
||||
|
||||
|
@ -546,7 +546,7 @@ func (a *App) RegisterCampaignView(c echo.Context) error {
|
|||
// is dependent on the configuration.
|
||||
func (a *App) SelfExportSubscriberData(c echo.Context) error {
|
||||
// Is export allowed?
|
||||
if !a.constants.Privacy.AllowExport {
|
||||
if !a.cfg.Privacy.AllowExport {
|
||||
return c.Render(http.StatusBadRequest, tplMessage,
|
||||
makeMsgTpl(a.i18n.T("public.errorTitle"), "", a.i18n.Ts("public.invalidFeature")))
|
||||
}
|
||||
|
@ -555,7 +555,7 @@ func (a *App) SelfExportSubscriberData(c echo.Context) error {
|
|||
// list subscriptions, campaign views, and link clicks. Names of
|
||||
// private lists are replaced with "Private list".
|
||||
subUUID := c.Param("subUUID")
|
||||
data, b, err := a.exportSubscriberData(0, subUUID, a.constants.Privacy.Exportable)
|
||||
data, b, err := a.exportSubscriberData(0, subUUID, a.cfg.Privacy.Exportable)
|
||||
if err != nil {
|
||||
a.log.Printf("error exporting subscriber data: %s", err)
|
||||
return c.Render(http.StatusInternalServerError, tplMessage,
|
||||
|
@ -576,7 +576,7 @@ func (a *App) SelfExportSubscriberData(c echo.Context) error {
|
|||
// E-mail the data as a JSON attachment to the subscriber.
|
||||
const fname = "data.json"
|
||||
if err := a.emailMessenger.Push(models.Message{
|
||||
From: a.constants.FromEmail,
|
||||
From: a.cfg.FromEmail,
|
||||
To: []string{data.Email},
|
||||
Subject: subject,
|
||||
Body: body,
|
||||
|
@ -602,7 +602,7 @@ func (a *App) SelfExportSubscriberData(c echo.Context) error {
|
|||
// clicks remain as orphan data unconnected to any subscriber.
|
||||
func (a *App) WipeSubscriberData(c echo.Context) error {
|
||||
// Is wiping allowed?
|
||||
if !a.constants.Privacy.AllowWipe {
|
||||
if !a.cfg.Privacy.AllowWipe {
|
||||
return c.Render(http.StatusBadRequest, tplMessage,
|
||||
makeMsgTpl(a.i18n.T("public.errorTitle"), "", a.i18n.Ts("public.invalidFeature")))
|
||||
}
|
||||
|
|
|
@ -165,7 +165,7 @@ func (a *App) validateUserRole(r auth.Role) error {
|
|||
}
|
||||
|
||||
for _, p := range r.Permissions {
|
||||
if _, ok := a.constants.Permissions[p]; !ok {
|
||||
if _, ok := a.cfg.Permissions[p]; !ok {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, a.i18n.Ts("globals.messages.invalidFields", "name", fmt.Sprintf("permission: %s", p)))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -330,7 +330,7 @@ func (a *App) TestSMTPSettings(c echo.Context) error {
|
|||
}
|
||||
|
||||
m := models.Message{}
|
||||
m.From = a.constants.FromEmail
|
||||
m.From = a.cfg.FromEmail
|
||||
m.To = []string{to}
|
||||
m.Subject = a.i18n.T("settings.smtp.testConnection")
|
||||
m.Body = b.Bytes()
|
||||
|
|
|
@ -11,10 +11,12 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/knadh/listmonk/internal/auth"
|
||||
"github.com/knadh/listmonk/internal/i18n"
|
||||
"github.com/knadh/listmonk/internal/notifs"
|
||||
"github.com/knadh/listmonk/internal/subimporter"
|
||||
"github.com/knadh/listmonk/models"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/lib/pq"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -133,7 +135,7 @@ func (a *App) ExportSubscribers(c echo.Context) error {
|
|||
|
||||
// Get the batched export iterator.
|
||||
query := sanitizeSQLExp(c.FormValue("query"))
|
||||
exp, err := a.core.ExportSubscribers(query, subIDs, listIDs, subStatus, a.constants.DBBatchSize)
|
||||
exp, err := a.core.ExportSubscribers(query, subIDs, listIDs, subStatus, a.cfg.DBBatchSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -262,7 +264,7 @@ func (a *App) SubscriberSendOptin(c echo.Context) error {
|
|||
}
|
||||
|
||||
// Trigger the opt-in confirmation e-mail hook.
|
||||
if _, err := a.optinConfirmNotify()(out, nil); err != nil {
|
||||
if _, err := a.optinNotifyHook(out, nil); err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, a.i18n.T("subscribers.errorSendingOptin"))
|
||||
}
|
||||
|
||||
|
@ -510,7 +512,7 @@ func (a *App) ExportSubscriberData(c echo.Context) error {
|
|||
// Get the subscriber's data. A single query that gets the profile,
|
||||
// list subscriptions, campaign views, and link clicks. Names of
|
||||
// private lists are replaced with "Private list".
|
||||
_, b, err := a.exportSubscriberData(id, "", a.constants.Privacy.Exportable)
|
||||
_, b, err := a.exportSubscriberData(id, "", a.cfg.Privacy.Exportable)
|
||||
if err != nil {
|
||||
a.log.Printf("error exporting subscriber data: %s", err)
|
||||
return echo.NewHTTPError(http.StatusInternalServerError,
|
||||
|
@ -557,54 +559,6 @@ func (a *App) exportSubscriberData(id int, subUUID string, exportables map[strin
|
|||
return data, b, nil
|
||||
}
|
||||
|
||||
// optinConfirmNotify returns an enclosed callback that sends optin confirmation e-mails.
|
||||
// This is plugged into the 'core' package to send optin confirmations when a new subscriber is
|
||||
// created via `core.CreateSubscriber()`.
|
||||
func (app *App) optinConfirmNotify() func(sub models.Subscriber, listIDs []int) (int, error) {
|
||||
return func(sub models.Subscriber, listIDs []int) (int, error) {
|
||||
lists, err := app.core.GetSubscriberLists(sub.ID, "", listIDs, nil, models.SubscriptionStatusUnconfirmed, models.ListOptinDouble)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// None.
|
||||
if len(lists) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
var (
|
||||
out = subOptin{Subscriber: sub, Lists: lists}
|
||||
qListIDs = url.Values{}
|
||||
)
|
||||
|
||||
// Construct the opt-in URL with list IDs.
|
||||
for _, l := range out.Lists {
|
||||
qListIDs.Add("l", l.UUID)
|
||||
}
|
||||
out.OptinURL = fmt.Sprintf(app.constants.OptinURL, sub.UUID, qListIDs.Encode())
|
||||
out.UnsubURL = fmt.Sprintf(app.constants.UnsubURL, dummyUUID, sub.UUID)
|
||||
|
||||
// Unsub headers.
|
||||
hdr := textproto.MIMEHeader{}
|
||||
hdr.Set(models.EmailHeaderSubscriberUUID, sub.UUID)
|
||||
|
||||
// Attach List-Unsubscribe headers?
|
||||
if app.constants.Privacy.UnsubHeader {
|
||||
unsubURL := fmt.Sprintf(app.constants.UnsubURL, dummyUUID, sub.UUID)
|
||||
hdr.Set("List-Unsubscribe-Post", "List-Unsubscribe=One-Click")
|
||||
hdr.Set("List-Unsubscribe", `<`+unsubURL+`>`)
|
||||
}
|
||||
|
||||
// Send the e-mail.
|
||||
if err := notifs.Notify([]string{sub.Email}, app.i18n.T("subscribers.optinSubject"), notifs.TplSubscriberOptin, out, hdr); err != nil {
|
||||
app.log.Printf("error sending opt-in e-mail for subscriber %d (%s): %s", sub.ID, sub.UUID, err)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return len(lists), nil
|
||||
}
|
||||
}
|
||||
|
||||
// hasSubPerm checks whether the current user has permission to access the given list
|
||||
// of subscriber IDs.
|
||||
func (a *App) hasSubPerm(u auth.User, subIDs []int) error {
|
||||
|
@ -694,3 +648,54 @@ func getQueryInts(param string, qp url.Values) ([]int, error) {
|
|||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// makeOptinNotifyHook returns an enclosed callback that sends optin confirmation e-mails.
|
||||
// This is plugged into the 'core' package to send optin confirmations when a new subscriber is
|
||||
// created via `core.CreateSubscriber()`.
|
||||
func makeOptinNotifyHook(unsubHeader bool, u *UrlConfig, q *models.Queries, i *i18n.I18n) func(sub models.Subscriber, listIDs []int) (int, error) {
|
||||
return func(sub models.Subscriber, listIDs []int) (int, error) {
|
||||
// Fetch double opt-in lists from the given list IDs.
|
||||
// Get the list of subscription lists where the subscriber hasn't confirmed.
|
||||
var lists = []models.List{}
|
||||
if err := q.GetSubscriberLists.Select(&lists, sub.ID, "", pq.Array(listIDs), nil, models.SubscriptionStatusUnconfirmed, models.ListOptinDouble); err != nil {
|
||||
lo.Printf("error fetching lists for opt-in: %s", err)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// None.
|
||||
if len(lists) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
var (
|
||||
out = subOptin{Subscriber: sub, Lists: lists}
|
||||
qListIDs = url.Values{}
|
||||
)
|
||||
|
||||
// Construct the opt-in URL with list IDs.
|
||||
for _, l := range out.Lists {
|
||||
qListIDs.Add("l", l.UUID)
|
||||
}
|
||||
out.OptinURL = fmt.Sprintf(u.OptinURL, sub.UUID, qListIDs.Encode())
|
||||
out.UnsubURL = fmt.Sprintf(u.UnsubURL, dummyUUID, sub.UUID)
|
||||
|
||||
// Unsub headers.
|
||||
hdr := textproto.MIMEHeader{}
|
||||
hdr.Set(models.EmailHeaderSubscriberUUID, sub.UUID)
|
||||
|
||||
// Attach List-Unsubscribe headers?
|
||||
if unsubHeader {
|
||||
unsubURL := fmt.Sprintf(u.UnsubURL, dummyUUID, sub.UUID)
|
||||
hdr.Set("List-Unsubscribe-Post", "List-Unsubscribe=One-Click")
|
||||
hdr.Set("List-Unsubscribe", `<`+unsubURL+`>`)
|
||||
}
|
||||
|
||||
// Send the e-mail.
|
||||
if err := notifs.Notify([]string{sub.Email}, i.T("subscribers.optinSubject"), notifs.TplSubscriberOptin, out, hdr); err != nil {
|
||||
lo.Printf("error sending opt-in e-mail for subscriber %d (%s): %s", sub.ID, sub.UUID, err)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return len(lists), nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -191,7 +191,7 @@ func (a *App) validateTxMessage(m models.TxMessage) (models.TxMessage, error) {
|
|||
}
|
||||
|
||||
if m.FromEmail == "" {
|
||||
m.FromEmail = a.constants.FromEmail
|
||||
m.FromEmail = a.cfg.FromEmail
|
||||
}
|
||||
|
||||
if m.Messenger == "" {
|
||||
|
|
Loading…
Add table
Reference in a new issue