mirror of
https://github.com/knadh/listmonk.git
synced 2024-09-20 07:16:33 +08:00
Merge 9b83ebb700
into 550cd3e1f8
This commit is contained in:
commit
40b5a883f5
|
@ -18,6 +18,7 @@ import (
|
|||
|
||||
"github.com/Masterminds/sprig/v3"
|
||||
"github.com/gdgvda/cron"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/jmoiron/sqlx/types"
|
||||
"github.com/knadh/goyesql/v2"
|
||||
|
@ -789,6 +790,8 @@ func initHTTPServer(app *App) *echo.Echo {
|
|||
srv.Static(ko.String("upload.filesystem.upload_uri"), ko.String("upload.filesystem.upload_path"))
|
||||
}
|
||||
|
||||
srv.Validator = &CustomValidator{Validator: validator.New()}
|
||||
|
||||
// Register all HTTP handlers.
|
||||
initHTTPHandlers(srv, app)
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
|
||||
"github.com/gdgvda/cron"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/jmoiron/sqlx/types"
|
||||
"github.com/knadh/koanf/parsers/json"
|
||||
"github.com/knadh/koanf/providers/rawbytes"
|
||||
|
@ -79,11 +80,15 @@ func handleGetSettings(c echo.Context) error {
|
|||
func handleUpdateSettings(c echo.Context) error {
|
||||
var (
|
||||
app = c.Get("app").(*App)
|
||||
set models.Settings
|
||||
req, set models.Settings
|
||||
)
|
||||
|
||||
// Unmarshal and marshal the fields once to sanitize the settings blob.
|
||||
if err := c.Bind(&set); err != nil {
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := c.Validate(req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -93,10 +98,13 @@ func handleUpdateSettings(c echo.Context) error {
|
|||
return err
|
||||
}
|
||||
|
||||
set = cur
|
||||
copier.CopyWithOption(&set, &req, copier.Option{IgnoreEmpty: true})
|
||||
|
||||
// There should be at least one SMTP block that's enabled.
|
||||
has := false
|
||||
for i, s := range set.SMTP {
|
||||
if s.Enabled {
|
||||
if *s.Enabled {
|
||||
has = true
|
||||
}
|
||||
|
||||
|
@ -157,6 +165,24 @@ func handleUpdateSettings(c echo.Context) error {
|
|||
}
|
||||
}
|
||||
|
||||
// handle bounce postmark
|
||||
if set.BouncePostmark.Enabled == nil {
|
||||
set.BouncePostmark.Enabled = cur.BouncePostmark.Enabled
|
||||
}
|
||||
if set.BouncePostmark.Username == "" {
|
||||
set.BouncePostmark.Username = cur.BouncePostmark.Username
|
||||
}
|
||||
if set.BouncePostmark.Password == "" {
|
||||
set.BouncePostmark.Password = cur.BouncePostmark.Password
|
||||
}
|
||||
|
||||
// handle bounce actions
|
||||
for name, action := range cur.BounceActions {
|
||||
if _, ok := set.BounceActions[name]; !ok {
|
||||
set.BounceActions[name] = action
|
||||
}
|
||||
}
|
||||
|
||||
// Validate and sanitize postback Messenger names. Duplicates are disallowed
|
||||
// and "email" is a reserved name.
|
||||
names := map[string]bool{emailMsgr: true}
|
||||
|
@ -188,20 +214,6 @@ func handleUpdateSettings(c echo.Context) error {
|
|||
names[name] = true
|
||||
}
|
||||
|
||||
// S3 password?
|
||||
if set.UploadS3AwsSecretAccessKey == "" {
|
||||
set.UploadS3AwsSecretAccessKey = cur.UploadS3AwsSecretAccessKey
|
||||
}
|
||||
if set.SendgridKey == "" {
|
||||
set.SendgridKey = cur.SendgridKey
|
||||
}
|
||||
if set.BouncePostmark.Password == "" {
|
||||
set.BouncePostmark.Password = cur.BouncePostmark.Password
|
||||
}
|
||||
if set.SecurityCaptchaSecret == "" {
|
||||
set.SecurityCaptchaSecret = cur.SecurityCaptchaSecret
|
||||
}
|
||||
|
||||
for n, v := range set.UploadExtensions {
|
||||
set.UploadExtensions[n] = strings.ToLower(strings.TrimPrefix(strings.TrimSpace(v), "."))
|
||||
}
|
||||
|
@ -217,7 +229,7 @@ func handleUpdateSettings(c echo.Context) error {
|
|||
set.DomainBlocklist = doms
|
||||
|
||||
// Validate slow query caching cron.
|
||||
if set.CacheSlowQueries {
|
||||
if *set.CacheSlowQueries {
|
||||
if _, err := cron.ParseStandard(set.CacheSlowQueriesInterval); err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, app.i18n.Ts("globals.messages.invalidData")+": slow query cron: "+err.Error())
|
||||
}
|
||||
|
|
19
cmd/validator.go
Normal file
19
cmd/validator.go
Normal file
|
@ -0,0 +1,19 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
type CustomValidator struct {
|
||||
Validator *validator.Validate
|
||||
}
|
||||
|
||||
func (c *CustomValidator) Validate(i interface{}) error {
|
||||
if err := c.Validator.Struct(i); err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
6
go.mod
6
go.mod
|
@ -7,8 +7,10 @@ require (
|
|||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/emersion/go-message v0.16.0
|
||||
github.com/gdgvda/cron v0.2.0
|
||||
github.com/go-playground/validator/v10 v10.22.0
|
||||
github.com/gofrs/uuid/v5 v5.0.0
|
||||
github.com/gorilla/feeds v1.1.1
|
||||
github.com/jinzhu/copier v0.4.0
|
||||
github.com/jmoiron/sqlx v1.3.5
|
||||
github.com/knadh/go-pop3 v0.3.0
|
||||
github.com/knadh/goyesql/v2 v2.2.0
|
||||
|
@ -40,6 +42,9 @@ require (
|
|||
github.com/Masterminds/semver/v3 v3.2.0 // indirect
|
||||
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/huandu/xstrings v1.4.0 // indirect
|
||||
|
@ -47,6 +52,7 @@ require (
|
|||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/labstack/gommon v0.4.2 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
|
|
13
go.sum
13
go.sum
|
@ -18,8 +18,17 @@ github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:
|
|||
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
|
||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||
github.com/gdgvda/cron v0.2.0 h1:oX8qdLZq4tC5StnCsZsTNs2BIzaRjcjmPZ4o+BArKX4=
|
||||
github.com/gdgvda/cron v0.2.0/go.mod h1:VEwidZXB255kESB5DcUGRWTYZS8KkOBYD1YBn8Wiyx8=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao=
|
||||
github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
|
@ -39,6 +48,8 @@ github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU
|
|||
github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ=
|
||||
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
|
||||
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
|
||||
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
|
||||
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
|
||||
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
|
||||
|
@ -80,6 +91,8 @@ github.com/labstack/echo/v4 v4.11.4 h1:vDZmA+qNeh1pd/cCkEicDMrjtrnMGQ1QFI9gWN1zG
|
|||
github.com/labstack/echo/v4 v4.11.4/go.mod h1:noh7EvLwqDsmh/X/HWKPUl1AjzJrhyptRyEbQJfxen8=
|
||||
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
|
||||
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
|
|
|
@ -8,35 +8,35 @@ type Settings struct {
|
|||
AppFaviconURL string `json:"app.favicon_url"`
|
||||
AppFromEmail string `json:"app.from_email"`
|
||||
AppNotifyEmails []string `json:"app.notify_emails"`
|
||||
EnablePublicSubPage bool `json:"app.enable_public_subscription_page"`
|
||||
EnablePublicArchive bool `json:"app.enable_public_archive"`
|
||||
EnablePublicArchiveRSSContent bool `json:"app.enable_public_archive_rss_content"`
|
||||
SendOptinConfirmation bool `json:"app.send_optin_confirmation"`
|
||||
CheckUpdates bool `json:"app.check_updates"`
|
||||
EnablePublicSubPage *bool `json:"app.enable_public_subscription_page"`
|
||||
EnablePublicArchive *bool `json:"app.enable_public_archive"`
|
||||
EnablePublicArchiveRSSContent *bool `json:"app.enable_public_archive_rss_content"`
|
||||
SendOptinConfirmation *bool `json:"app.send_optin_confirmation"`
|
||||
CheckUpdates *bool `json:"app.check_updates"`
|
||||
AppLang string `json:"app.lang"`
|
||||
|
||||
AppBatchSize int `json:"app.batch_size"`
|
||||
AppConcurrency int `json:"app.concurrency"`
|
||||
AppMaxSendErrors int `json:"app.max_send_errors"`
|
||||
AppMessageRate int `json:"app.message_rate"`
|
||||
CacheSlowQueries bool `json:"app.cache_slow_queries"`
|
||||
CacheSlowQueries *bool `json:"app.cache_slow_queries"`
|
||||
CacheSlowQueriesInterval string `json:"app.cache_slow_queries_interval"`
|
||||
|
||||
AppMessageSlidingWindow bool `json:"app.message_sliding_window"`
|
||||
AppMessageSlidingWindow *bool `json:"app.message_sliding_window"`
|
||||
AppMessageSlidingWindowDuration string `json:"app.message_sliding_window_duration"`
|
||||
AppMessageSlidingWindowRate int `json:"app.message_sliding_window_rate"`
|
||||
|
||||
PrivacyIndividualTracking bool `json:"privacy.individual_tracking"`
|
||||
PrivacyUnsubHeader bool `json:"privacy.unsubscribe_header"`
|
||||
PrivacyAllowBlocklist bool `json:"privacy.allow_blocklist"`
|
||||
PrivacyAllowPreferences bool `json:"privacy.allow_preferences"`
|
||||
PrivacyAllowExport bool `json:"privacy.allow_export"`
|
||||
PrivacyAllowWipe bool `json:"privacy.allow_wipe"`
|
||||
PrivacyIndividualTracking *bool `json:"privacy.individual_tracking"`
|
||||
PrivacyUnsubHeader *bool `json:"privacy.unsubscribe_header"`
|
||||
PrivacyAllowBlocklist *bool `json:"privacy.allow_blocklist"`
|
||||
PrivacyAllowPreferences *bool `json:"privacy.allow_preferences"`
|
||||
PrivacyAllowExport *bool `json:"privacy.allow_export"`
|
||||
PrivacyAllowWipe *bool `json:"privacy.allow_wipe"`
|
||||
PrivacyExportable []string `json:"privacy.exportable"`
|
||||
PrivacyRecordOptinIP bool `json:"privacy.record_optin_ip"`
|
||||
PrivacyRecordOptinIP *bool `json:"privacy.record_optin_ip"`
|
||||
DomainBlocklist []string `json:"privacy.domain_blocklist"`
|
||||
|
||||
SecurityEnableCaptcha bool `json:"security.enable_captcha"`
|
||||
SecurityEnableCaptcha *bool `json:"security.enable_captcha"`
|
||||
SecurityCaptchaKey string `json:"security.captcha_key"`
|
||||
SecurityCaptchaSecret string `json:"security.captcha_secret"`
|
||||
|
||||
|
@ -57,62 +57,62 @@ type Settings struct {
|
|||
|
||||
SMTP []struct {
|
||||
UUID string `json:"uuid"`
|
||||
Enabled bool `json:"enabled"`
|
||||
Host string `json:"host"`
|
||||
Enabled *bool `json:"enabled" validate:"required"`
|
||||
Host string `json:"host" validate:"required"`
|
||||
HelloHostname string `json:"hello_hostname"`
|
||||
Port int `json:"port"`
|
||||
AuthProtocol string `json:"auth_protocol"`
|
||||
Username string `json:"username"`
|
||||
Port int `json:"port" validate:"required"`
|
||||
AuthProtocol string `json:"auth_protocol" validate:"required"`
|
||||
Username string `json:"username" validate:"required"`
|
||||
Password string `json:"password,omitempty"`
|
||||
EmailHeaders []map[string]string `json:"email_headers"`
|
||||
MaxConns int `json:"max_conns"`
|
||||
MaxMsgRetries int `json:"max_msg_retries"`
|
||||
IdleTimeout string `json:"idle_timeout"`
|
||||
WaitTimeout string `json:"wait_timeout"`
|
||||
TLSType string `json:"tls_type"`
|
||||
TLSSkipVerify bool `json:"tls_skip_verify"`
|
||||
} `json:"smtp"`
|
||||
MaxConns int `json:"max_conns" validate:"required"`
|
||||
MaxMsgRetries int `json:"max_msg_retries" validate:"required"`
|
||||
IdleTimeout string `json:"idle_timeout" validate:"required"`
|
||||
WaitTimeout string `json:"wait_timeout" validate:"required"`
|
||||
TLSType string `json:"tls_type" validate:"required"`
|
||||
TLSSkipVerify *bool `json:"tls_skip_verify" validate:"required"`
|
||||
} `json:"smtp" validate:"omitempty,dive"`
|
||||
|
||||
Messengers []struct {
|
||||
UUID string `json:"uuid"`
|
||||
Enabled bool `json:"enabled"`
|
||||
Enabled *bool `json:"enabled" validate:"required"`
|
||||
Name string `json:"name"`
|
||||
RootURL string `json:"root_url"`
|
||||
Username string `json:"username"`
|
||||
RootURL string `json:"root_url" validate:"required"`
|
||||
Username string `json:"username" validate:"required"`
|
||||
Password string `json:"password,omitempty"`
|
||||
MaxConns int `json:"max_conns"`
|
||||
Timeout string `json:"timeout"`
|
||||
MaxMsgRetries int `json:"max_msg_retries"`
|
||||
} `json:"messengers"`
|
||||
MaxConns int `json:"max_conns" validate:"required"`
|
||||
Timeout string `json:"timeout" validate:"required"`
|
||||
MaxMsgRetries int `json:"max_msg_retries" validate:"required"`
|
||||
} `json:"messengers" validate:"omitempty,dive"`
|
||||
|
||||
BounceEnabled bool `json:"bounce.enabled"`
|
||||
BounceEnableWebhooks bool `json:"bounce.webhooks_enabled"`
|
||||
BounceEnabled *bool `json:"bounce.enabled"`
|
||||
BounceEnableWebhooks *bool `json:"bounce.webhooks_enabled"`
|
||||
BounceActions map[string]struct {
|
||||
Count int `json:"count"`
|
||||
Action string `json:"action"`
|
||||
} `json:"bounce.actions"`
|
||||
SESEnabled bool `json:"bounce.ses_enabled"`
|
||||
SendgridEnabled bool `json:"bounce.sendgrid_enabled"`
|
||||
Count int `json:"count" validate:"required"`
|
||||
Action string `json:"action" validate:"required"`
|
||||
} `json:"bounce.actions" validate:"omitempty,dive"`
|
||||
SESEnabled *bool `json:"bounce.ses_enabled"`
|
||||
SendgridEnabled *bool `json:"bounce.sendgrid_enabled"`
|
||||
SendgridKey string `json:"bounce.sendgrid_key"`
|
||||
BouncePostmark struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Enabled *bool `json:"enabled"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
} `json:"bounce.postmark"`
|
||||
BounceBoxes []struct {
|
||||
UUID string `json:"uuid"`
|
||||
Enabled bool `json:"enabled"`
|
||||
Type string `json:"type"`
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port"`
|
||||
AuthProtocol string `json:"auth_protocol"`
|
||||
ReturnPath string `json:"return_path"`
|
||||
Username string `json:"username"`
|
||||
Enabled *bool `json:"enabled" validate:"required"`
|
||||
Type string `json:"type" validate:"required"`
|
||||
Host string `json:"host" validate:"required"`
|
||||
Port int `json:"port" validate:"required"`
|
||||
AuthProtocol string `json:"auth_protocol" validate:"required"`
|
||||
ReturnPath string `json:"return_path" validate:"required"`
|
||||
Username string `json:"username" validate:"required"`
|
||||
Password string `json:"password,omitempty"`
|
||||
TLSEnabled bool `json:"tls_enabled"`
|
||||
TLSSkipVerify bool `json:"tls_skip_verify"`
|
||||
ScanInterval string `json:"scan_interval"`
|
||||
} `json:"bounce.mailboxes"`
|
||||
TLSEnabled *bool `json:"tls_enabled" validate:"required"`
|
||||
TLSSkipVerify *bool `json:"tls_skip_verify" validate:"required"`
|
||||
ScanInterval string `json:"scan_interval" validate:"required"`
|
||||
} `json:"bounce.mailboxes" validate:"omitempty,dive"`
|
||||
|
||||
AdminCustomCSS string `json:"appearance.admin.custom_css"`
|
||||
AdminCustomJS string `json:"appearance.admin.custom_js"`
|
||||
|
|
Loading…
Reference in a new issue