added custom validator and validation to put settings api

This commit is contained in:
nayanthulkar28 2024-07-02 18:12:55 +05:30
parent 22890a17a1
commit 9b83ebb700
6 changed files with 125 additions and 72 deletions

View file

@ -18,6 +18,7 @@ import (
"github.com/Masterminds/sprig/v3" "github.com/Masterminds/sprig/v3"
"github.com/gdgvda/cron" "github.com/gdgvda/cron"
"github.com/go-playground/validator/v10"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
"github.com/jmoiron/sqlx/types" "github.com/jmoiron/sqlx/types"
"github.com/knadh/goyesql/v2" "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.Static(ko.String("upload.filesystem.upload_uri"), ko.String("upload.filesystem.upload_path"))
} }
srv.Validator = &CustomValidator{Validator: validator.New()}
// Register all HTTP handlers. // Register all HTTP handlers.
initHTTPHandlers(srv, app) initHTTPHandlers(srv, app)

View file

@ -13,6 +13,7 @@ import (
"github.com/gdgvda/cron" "github.com/gdgvda/cron"
"github.com/gofrs/uuid/v5" "github.com/gofrs/uuid/v5"
"github.com/jinzhu/copier"
"github.com/jmoiron/sqlx/types" "github.com/jmoiron/sqlx/types"
"github.com/knadh/koanf/parsers/json" "github.com/knadh/koanf/parsers/json"
"github.com/knadh/koanf/providers/rawbytes" "github.com/knadh/koanf/providers/rawbytes"
@ -79,11 +80,15 @@ func handleGetSettings(c echo.Context) error {
func handleUpdateSettings(c echo.Context) error { func handleUpdateSettings(c echo.Context) error {
var ( var (
app = c.Get("app").(*App) app = c.Get("app").(*App)
set models.Settings req, set models.Settings
) )
// Unmarshal and marshal the fields once to sanitize the settings blob. // 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 return err
} }
@ -93,10 +98,13 @@ func handleUpdateSettings(c echo.Context) error {
return err return err
} }
set = cur
copier.CopyWithOption(&set, &req, copier.Option{IgnoreEmpty: true})
// There should be at least one SMTP block that's enabled. // There should be at least one SMTP block that's enabled.
has := false has := false
for i, s := range set.SMTP { for i, s := range set.SMTP {
if s.Enabled { if *s.Enabled {
has = true 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 // Validate and sanitize postback Messenger names. Duplicates are disallowed
// and "email" is a reserved name. // and "email" is a reserved name.
names := map[string]bool{emailMsgr: true} names := map[string]bool{emailMsgr: true}
@ -188,20 +214,6 @@ func handleUpdateSettings(c echo.Context) error {
names[name] = true 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 { for n, v := range set.UploadExtensions {
set.UploadExtensions[n] = strings.ToLower(strings.TrimPrefix(strings.TrimSpace(v), ".")) set.UploadExtensions[n] = strings.ToLower(strings.TrimPrefix(strings.TrimSpace(v), "."))
} }
@ -217,7 +229,7 @@ func handleUpdateSettings(c echo.Context) error {
set.DomainBlocklist = doms set.DomainBlocklist = doms
// Validate slow query caching cron. // Validate slow query caching cron.
if set.CacheSlowQueries { if *set.CacheSlowQueries {
if _, err := cron.ParseStandard(set.CacheSlowQueriesInterval); err != nil { if _, err := cron.ParseStandard(set.CacheSlowQueriesInterval); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, app.i18n.Ts("globals.messages.invalidData")+": slow query cron: "+err.Error()) return echo.NewHTTPError(http.StatusBadRequest, app.i18n.Ts("globals.messages.invalidData")+": slow query cron: "+err.Error())
} }

19
cmd/validator.go Normal file
View 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
View file

@ -7,8 +7,10 @@ require (
github.com/disintegration/imaging v1.6.2 github.com/disintegration/imaging v1.6.2
github.com/emersion/go-message v0.16.0 github.com/emersion/go-message v0.16.0
github.com/gdgvda/cron v0.2.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/gofrs/uuid/v5 v5.0.0
github.com/gorilla/feeds v1.1.1 github.com/gorilla/feeds v1.1.1
github.com/jinzhu/copier v0.4.0
github.com/jmoiron/sqlx v1.3.5 github.com/jmoiron/sqlx v1.3.5
github.com/knadh/go-pop3 v0.3.0 github.com/knadh/go-pop3 v0.3.0
github.com/knadh/goyesql/v2 v2.2.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/Masterminds/semver/v3 v3.2.0 // indirect
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 // indirect github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 // indirect
github.com/fsnotify/fsnotify v1.6.0 // 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/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/google/uuid v1.3.0 // indirect github.com/google/uuid v1.3.0 // indirect
github.com/huandu/xstrings v1.4.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/josharian/intern v1.0.0 // indirect
github.com/kr/pretty v0.3.1 // indirect github.com/kr/pretty v0.3.1 // indirect
github.com/labstack/gommon v0.4.2 // 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-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect

13
go.sum
View file

@ -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/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 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= 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 h1:oX8qdLZq4tC5StnCsZsTNs2BIzaRjcjmPZ4o+BArKX4=
github.com/gdgvda/cron v0.2.0/go.mod h1:VEwidZXB255kESB5DcUGRWTYZS8KkOBYD1YBn8Wiyx8= 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.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 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 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/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 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ=
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 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.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= 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/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 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= 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.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.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=

View file

@ -8,35 +8,35 @@ type Settings struct {
AppFaviconURL string `json:"app.favicon_url"` AppFaviconURL string `json:"app.favicon_url"`
AppFromEmail string `json:"app.from_email"` AppFromEmail string `json:"app.from_email"`
AppNotifyEmails []string `json:"app.notify_emails"` AppNotifyEmails []string `json:"app.notify_emails"`
EnablePublicSubPage bool `json:"app.enable_public_subscription_page"` EnablePublicSubPage *bool `json:"app.enable_public_subscription_page"`
EnablePublicArchive bool `json:"app.enable_public_archive"` EnablePublicArchive *bool `json:"app.enable_public_archive"`
EnablePublicArchiveRSSContent bool `json:"app.enable_public_archive_rss_content"` EnablePublicArchiveRSSContent *bool `json:"app.enable_public_archive_rss_content"`
SendOptinConfirmation bool `json:"app.send_optin_confirmation"` SendOptinConfirmation *bool `json:"app.send_optin_confirmation"`
CheckUpdates bool `json:"app.check_updates"` CheckUpdates *bool `json:"app.check_updates"`
AppLang string `json:"app.lang"` AppLang string `json:"app.lang"`
AppBatchSize int `json:"app.batch_size"` AppBatchSize int `json:"app.batch_size"`
AppConcurrency int `json:"app.concurrency"` AppConcurrency int `json:"app.concurrency"`
AppMaxSendErrors int `json:"app.max_send_errors"` AppMaxSendErrors int `json:"app.max_send_errors"`
AppMessageRate int `json:"app.message_rate"` 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"` 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"` AppMessageSlidingWindowDuration string `json:"app.message_sliding_window_duration"`
AppMessageSlidingWindowRate int `json:"app.message_sliding_window_rate"` AppMessageSlidingWindowRate int `json:"app.message_sliding_window_rate"`
PrivacyIndividualTracking bool `json:"privacy.individual_tracking"` PrivacyIndividualTracking *bool `json:"privacy.individual_tracking"`
PrivacyUnsubHeader bool `json:"privacy.unsubscribe_header"` PrivacyUnsubHeader *bool `json:"privacy.unsubscribe_header"`
PrivacyAllowBlocklist bool `json:"privacy.allow_blocklist"` PrivacyAllowBlocklist *bool `json:"privacy.allow_blocklist"`
PrivacyAllowPreferences bool `json:"privacy.allow_preferences"` PrivacyAllowPreferences *bool `json:"privacy.allow_preferences"`
PrivacyAllowExport bool `json:"privacy.allow_export"` PrivacyAllowExport *bool `json:"privacy.allow_export"`
PrivacyAllowWipe bool `json:"privacy.allow_wipe"` PrivacyAllowWipe *bool `json:"privacy.allow_wipe"`
PrivacyExportable []string `json:"privacy.exportable"` 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"` DomainBlocklist []string `json:"privacy.domain_blocklist"`
SecurityEnableCaptcha bool `json:"security.enable_captcha"` SecurityEnableCaptcha *bool `json:"security.enable_captcha"`
SecurityCaptchaKey string `json:"security.captcha_key"` SecurityCaptchaKey string `json:"security.captcha_key"`
SecurityCaptchaSecret string `json:"security.captcha_secret"` SecurityCaptchaSecret string `json:"security.captcha_secret"`
@ -57,62 +57,62 @@ type Settings struct {
SMTP []struct { SMTP []struct {
UUID string `json:"uuid"` UUID string `json:"uuid"`
Enabled bool `json:"enabled"` Enabled *bool `json:"enabled" validate:"required"`
Host string `json:"host"` Host string `json:"host" validate:"required"`
HelloHostname string `json:"hello_hostname"` HelloHostname string `json:"hello_hostname"`
Port int `json:"port"` Port int `json:"port" validate:"required"`
AuthProtocol string `json:"auth_protocol"` AuthProtocol string `json:"auth_protocol" validate:"required"`
Username string `json:"username"` Username string `json:"username" validate:"required"`
Password string `json:"password,omitempty"` Password string `json:"password,omitempty"`
EmailHeaders []map[string]string `json:"email_headers"` EmailHeaders []map[string]string `json:"email_headers"`
MaxConns int `json:"max_conns"` MaxConns int `json:"max_conns" validate:"required"`
MaxMsgRetries int `json:"max_msg_retries"` MaxMsgRetries int `json:"max_msg_retries" validate:"required"`
IdleTimeout string `json:"idle_timeout"` IdleTimeout string `json:"idle_timeout" validate:"required"`
WaitTimeout string `json:"wait_timeout"` WaitTimeout string `json:"wait_timeout" validate:"required"`
TLSType string `json:"tls_type"` TLSType string `json:"tls_type" validate:"required"`
TLSSkipVerify bool `json:"tls_skip_verify"` TLSSkipVerify *bool `json:"tls_skip_verify" validate:"required"`
} `json:"smtp"` } `json:"smtp" validate:"omitempty,dive"`
Messengers []struct { Messengers []struct {
UUID string `json:"uuid"` UUID string `json:"uuid"`
Enabled bool `json:"enabled"` Enabled *bool `json:"enabled" validate:"required"`
Name string `json:"name"` Name string `json:"name"`
RootURL string `json:"root_url"` RootURL string `json:"root_url" validate:"required"`
Username string `json:"username"` Username string `json:"username" validate:"required"`
Password string `json:"password,omitempty"` Password string `json:"password,omitempty"`
MaxConns int `json:"max_conns"` MaxConns int `json:"max_conns" validate:"required"`
Timeout string `json:"timeout"` Timeout string `json:"timeout" validate:"required"`
MaxMsgRetries int `json:"max_msg_retries"` MaxMsgRetries int `json:"max_msg_retries" validate:"required"`
} `json:"messengers"` } `json:"messengers" validate:"omitempty,dive"`
BounceEnabled bool `json:"bounce.enabled"` BounceEnabled *bool `json:"bounce.enabled"`
BounceEnableWebhooks bool `json:"bounce.webhooks_enabled"` BounceEnableWebhooks *bool `json:"bounce.webhooks_enabled"`
BounceActions map[string]struct { BounceActions map[string]struct {
Count int `json:"count"` Count int `json:"count" validate:"required"`
Action string `json:"action"` Action string `json:"action" validate:"required"`
} `json:"bounce.actions"` } `json:"bounce.actions" validate:"omitempty,dive"`
SESEnabled bool `json:"bounce.ses_enabled"` SESEnabled *bool `json:"bounce.ses_enabled"`
SendgridEnabled bool `json:"bounce.sendgrid_enabled"` SendgridEnabled *bool `json:"bounce.sendgrid_enabled"`
SendgridKey string `json:"bounce.sendgrid_key"` SendgridKey string `json:"bounce.sendgrid_key"`
BouncePostmark struct { BouncePostmark struct {
Enabled bool `json:"enabled"` Enabled *bool `json:"enabled"`
Username string `json:"username"` Username string `json:"username"`
Password string `json:"password"` Password string `json:"password"`
} `json:"bounce.postmark"` } `json:"bounce.postmark"`
BounceBoxes []struct { BounceBoxes []struct {
UUID string `json:"uuid"` UUID string `json:"uuid"`
Enabled bool `json:"enabled"` Enabled *bool `json:"enabled" validate:"required"`
Type string `json:"type"` Type string `json:"type" validate:"required"`
Host string `json:"host"` Host string `json:"host" validate:"required"`
Port int `json:"port"` Port int `json:"port" validate:"required"`
AuthProtocol string `json:"auth_protocol"` AuthProtocol string `json:"auth_protocol" validate:"required"`
ReturnPath string `json:"return_path"` ReturnPath string `json:"return_path" validate:"required"`
Username string `json:"username"` Username string `json:"username" validate:"required"`
Password string `json:"password,omitempty"` Password string `json:"password,omitempty"`
TLSEnabled bool `json:"tls_enabled"` TLSEnabled *bool `json:"tls_enabled" validate:"required"`
TLSSkipVerify bool `json:"tls_skip_verify"` TLSSkipVerify *bool `json:"tls_skip_verify" validate:"required"`
ScanInterval string `json:"scan_interval"` ScanInterval string `json:"scan_interval" validate:"required"`
} `json:"bounce.mailboxes"` } `json:"bounce.mailboxes" validate:"omitempty,dive"`
AdminCustomCSS string `json:"appearance.admin.custom_css"` AdminCustomCSS string `json:"appearance.admin.custom_css"`
AdminCustomJS string `json:"appearance.admin.custom_js"` AdminCustomJS string `json:"appearance.admin.custom_js"`