diff --git a/cmd/branches.go b/cmd/branches.go index 3a3df328e..077d69cc3 100644 --- a/cmd/branches.go +++ b/cmd/branches.go @@ -187,7 +187,7 @@ var ( } else if err := promptBranchId(ctx, fsys); err != nil { return err } - return delete.Run(ctx, branchId) + return delete.Run(ctx, branchId, nil) }, } diff --git a/cmd/projects.go b/cmd/projects.go index fcf539af2..4ba28f08f 100644 --- a/cmd/projects.go +++ b/cmd/projects.go @@ -29,8 +29,7 @@ var ( projectName string orgId string dbPassword string - - region = utils.EnumFlag{ + region = utils.EnumFlag{ Allowed: awsRegions(), } size = utils.EnumFlag{ diff --git a/go.mod b/go.mod index 0d7d1fb63..72380e9a6 100644 --- a/go.mod +++ b/go.mod @@ -13,11 +13,11 @@ require ( github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 github.com/containerd/errdefs v1.0.0 github.com/containers/common v0.64.2 - github.com/docker/cli v28.5.1+incompatible - github.com/docker/docker v28.5.1+incompatible + github.com/docker/cli v28.5.2+incompatible + github.com/docker/docker v28.5.2+incompatible github.com/docker/go-connections v0.6.0 github.com/fsnotify/fsnotify v1.9.0 - github.com/getsentry/sentry-go v0.36.1 + github.com/getsentry/sentry-go v0.36.2 github.com/go-errors/errors v1.5.1 github.com/go-git/go-git/v5 v5.16.3 github.com/go-playground/validator/v10 v10.28.0 diff --git a/go.sum b/go.sum index 2809edcbf..adc3056a4 100644 --- a/go.sum +++ b/go.sum @@ -237,13 +237,13 @@ github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZ github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dnephin/pflag v1.0.7 h1:oxONGlWxhmUct0YzKTgrpQv9AUA1wtPBn7zuSjJqptk= github.com/dnephin/pflag v1.0.7/go.mod h1:uxE91IoWURlOiTUIA8Mq5ZZkAv3dPUfZNaT80Zm7OQE= -github.com/docker/cli v28.5.1+incompatible h1:ESutzBALAD6qyCLqbQSEf1a/U8Ybms5agw59yGVc+yY= -github.com/docker/cli v28.5.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v28.5.2+incompatible h1:XmG99IHcBmIAoC1PPg9eLBZPlTrNUAijsHLm8PjhBlg= +github.com/docker/cli v28.5.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v28.5.1+incompatible h1:Bm8DchhSD2J6PsFzxC35TZo4TLGR2PdW/E69rU45NhM= -github.com/docker/docker v28.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM= +github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8= github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo= github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0= @@ -300,8 +300,8 @@ github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIp github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/getkin/kin-openapi v0.131.0 h1:NO2UeHnFKRYhZ8wg6Nyh5Cq7dHk4suQQr72a4pMrDxE= github.com/getkin/kin-openapi v0.131.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58= -github.com/getsentry/sentry-go v0.36.1 h1:kMJt0WWsxWATUxkvFgVBZdIeHSk/Oiv5P0jZ9e5m/Lw= -github.com/getsentry/sentry-go v0.36.1/go.mod h1:p5Im24mJBeruET8Q4bbcMfCQ+F+Iadc4L48tB1apo2c= +github.com/getsentry/sentry-go v0.36.2 h1:uhuxRPTrUy0dnSzTd0LrYXlBYygLkKY0hhlG5LXarzM= +github.com/getsentry/sentry-go v0.36.2/go.mod h1:p5Im24mJBeruET8Q4bbcMfCQ+F+Iadc4L48tB1apo2c= github.com/ghostiam/protogetter v0.3.15 h1:1KF5sXel0HE48zh1/vn0Loiw25A9ApyseLzQuif1mLY= github.com/ghostiam/protogetter v0.3.15/go.mod h1:WZ0nw9pfzsgxuRsPOFQomgDVSWtDLJRfQJEhsGbmQMA= github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= diff --git a/internal/branches/delete/delete.go b/internal/branches/delete/delete.go index 95c309981..9b3730247 100644 --- a/internal/branches/delete/delete.go +++ b/internal/branches/delete/delete.go @@ -8,14 +8,17 @@ import ( "github.com/go-errors/errors" "github.com/supabase/cli/internal/branches/pause" "github.com/supabase/cli/internal/utils" + "github.com/supabase/cli/pkg/api" ) -func Run(ctx context.Context, branchId string) error { +func Run(ctx context.Context, branchId string, force *bool) error { projectRef, err := pause.GetBranchProjectRef(ctx, branchId) if err != nil { return err } - resp, err := utils.GetSupabase().V1DeleteABranchWithResponse(ctx, projectRef) + resp, err := utils.GetSupabase().V1DeleteABranchWithResponse(ctx, projectRef, &api.V1DeleteABranchParams{ + Force: force, + }) if err != nil { return errors.Errorf("failed to delete preview branch: %w", err) } diff --git a/internal/gen/types/templates/prod-ca-2025.crt b/internal/gen/types/templates/prod-ca-2025.crt new file mode 100644 index 000000000..da35f1f84 --- /dev/null +++ b/internal/gen/types/templates/prod-ca-2025.crt @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIIDxzCCAq+gAwIBAgIUeX+gpfmsRW9asFkRvjyXjHxbfgcwDQYJKoZIhvcNAQEL +BQAwazELMAkGA1UEBhMCVVMxEDAOBgNVBAgMB0RlbHdhcmUxEzARBgNVBAcMCk5l +dyBDYXN0bGUxFTATBgNVBAoMDFN1cGFiYXNlIEluYzEeMBwGA1UEAwwVU3VwYWJh +c2UgUm9vdCAyMDIxIENBMB4XDTI1MDkwMzA4MDEyNVoXDTM1MDkwMTA4MDEyNVow +azELMAkGA1UEBhMCVVMxEDAOBgNVBAgMB0RlbHdhcmUxEzARBgNVBAcMCk5ldyBD +YXN0bGUxFTATBgNVBAoMDFN1cGFiYXNlIEluYzEeMBwGA1UEAwwVU3VwYWJhc2Ug +Um9vdCAyMDIxIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5Ve7 +i9UAmc7luUilELPtqzEk8nGHxg7nY0aCStr625M7+K4OPO6RUllTsHh47k1jWyzm +LXLlyYwCsYCjQp+3vn06H+F/HRUxBt6CK2B7bNng230exTunk0xFvfkX6YgHR7B3 +1B7L25Rq3PhuRFPV4hnGYRam2XBZC4UNPqoAgrhV0HOYzXXAVoTr2yaBTMnB331Z +RwOmINh7eqTCk/JRZbb6vfZOhZRAVAe9AoRLoG8aKwmeoLGwlu0UuFx6z3E+6bmA +fSNa8Lx02GEoCdPLw9IRKUFq/SgBpQUKm44H1fDwTjH2CMM0N4p0mL/6wXnNeHvt +C40MmKZ0RcVmHE5wBwIDAQABo2MwYTAdBgNVHQ4EFgQUjvEE541toZcwtXQlZlcB +YOBRTnowHwYDVR0jBBgwFoAUjvEE541toZcwtXQlZlcBYOBRTnowDwYDVR0TAQH/ +BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQADggEBACD5IcGP +XKvS9qg0CgEQPFqYavt5c7P+0xxFgiZe+xoG8fUw58yNeK2APtgGPRpxEOGfAlNx +z9HDt4gcyHEE00B3qAVDm49pqNxioFWzNqU2LGfM/HL1QmN6urR7hCOkVCJddvOc +FhFX4nZDuRfaBboDvS5HlK3Pzxddp9hvrJi2bemr8HLqYc3HzmVckgPGSLML6t+h +4LRCXSlQsDgQ1LZ4KHsl4cq7K51N6FOXQBLB5q4lMKhs0VUhCT8Pdsj12+84laCV +c22q6p2mdT9SaernCSRnWazXWisgpjv3H7Ex4S1DCYjJIwn3PUToGFv1r8YRN2/S +O19yVSxxCIf64Sg= +-----END CERTIFICATE----- diff --git a/internal/gen/types/types.go b/internal/gen/types/types.go index aa22c04fa..8d616c733 100644 --- a/internal/gen/types/types.go +++ b/internal/gen/types/types.go @@ -114,6 +114,8 @@ var ( caStaging string //go:embed templates/prod-ca-2021.crt caProd string + //go:embed templates/prod-ca-2025.crt + caSnap string ) func GetRootCA(ctx context.Context, dbURL string, options ...func(*pgx.ConnConfig)) (string, error) { @@ -121,7 +123,8 @@ func GetRootCA(ctx context.Context, dbURL string, options ...func(*pgx.ConnConfi if require, err := isRequireSSL(ctx, dbURL, options...); !require { return "", err } - return caStaging + caProd, nil + // Merge all certs to support --db-url flag + return caStaging + caProd + caSnap, nil } func isRequireSSL(ctx context.Context, dbUrl string, options ...func(*pgx.ConnConfig)) (bool, error) { diff --git a/internal/inspect/vacuum_stats/vacuum_stats.go b/internal/inspect/vacuum_stats/vacuum_stats.go index a848f2272..71ec21e45 100644 --- a/internal/inspect/vacuum_stats/vacuum_stats.go +++ b/internal/inspect/vacuum_stats/vacuum_stats.go @@ -19,13 +19,17 @@ import ( var VacuumStatsQuery string type Result struct { - Name string - Last_vacuum string - Last_autovacuum string - Rowcount string - Dead_rowcount string - Autovacuum_threshold string - Expect_autovacuum string + Name string + Last_vacuum string + Last_autovacuum string + Last_analyze string + Last_autoanalyze string + Rowcount string + Dead_rowcount string + Autovacuum_threshold string + Autoanalyze_threshold string + Expect_autovacuum string + Expect_autoanalyze string } func Run(ctx context.Context, config pgconn.Config, fsys afero.Fs, options ...func(*pgx.ConnConfig)) error { @@ -43,10 +47,10 @@ func Run(ctx context.Context, config pgconn.Config, fsys afero.Fs, options ...fu return err } - table := "|Table|Last Vacuum|Last Auto Vacuum|Row count|Dead row count|Expect autovacuum?\n|-|-|-|-|-|-|\n" + table := "|Table|Last Vacuum|Last Auto Vacuum|Last Analyze|Last Auto Analyze|Row count|Dead row count|Expect autovacuum?|Expect autoanalyze?|\n|-|-|-|-|-|-|-|-|-|\n" for _, r := range result { rowcount := strings.Replace(r.Rowcount, "-1", "No stats", 1) - table += fmt.Sprintf("|`%s`|%s|%s|`%s`|`%s`|`%s`|\n", r.Name, r.Last_vacuum, r.Last_autovacuum, rowcount, r.Dead_rowcount, r.Expect_autovacuum) + table += fmt.Sprintf("|`%s`|%s|%s|%s|%s|`%s`|`%s`|`%s`|`%s`|\n", r.Name, r.Last_vacuum, r.Last_autovacuum, r.Last_analyze, r.Last_autoanalyze, rowcount, r.Dead_rowcount, r.Expect_autovacuum, r.Expect_autoanalyze) } return utils.RenderTable(table) } diff --git a/internal/inspect/vacuum_stats/vacuum_stats.sql b/internal/inspect/vacuum_stats/vacuum_stats.sql index 534fa4e64..73d32857f 100644 --- a/internal/inspect/vacuum_stats/vacuum_stats.sql +++ b/internal/inspect/vacuum_stats/vacuum_stats.sql @@ -15,7 +15,17 @@ WITH table_opts AS ( WHEN relopts LIKE '%autovacuum_vacuum_scale_factor%' THEN substring(relopts, '.*autovacuum_vacuum_scale_factor=([0-9.]+).*')::real ELSE current_setting('autovacuum_vacuum_scale_factor')::real - END AS autovacuum_vacuum_scale_factor + END AS autovacuum_vacuum_scale_factor, + CASE + WHEN relopts LIKE '%autovacuum_analyze_threshold%' + THEN substring(relopts, '.*autovacuum_analyze_threshold=([0-9.]+).*')::integer + ELSE current_setting('autovacuum_analyze_threshold')::integer + END AS autovacuum_analyze_threshold, + CASE + WHEN relopts LIKE '%autovacuum_analyze_scale_factor%' + THEN substring(relopts, '.*autovacuum_analyze_scale_factor=([0-9.]+).*')::real + ELSE current_setting('autovacuum_analyze_scale_factor')::real + END AS autovacuum_analyze_scale_factor FROM table_opts ) @@ -23,6 +33,8 @@ SELECT FORMAT('%I.%I', vacuum_settings.nspname, vacuum_settings.relname) AS name, coalesce(to_char(psut.last_vacuum, 'YYYY-MM-DD HH24:MI'), '') AS last_vacuum, coalesce(to_char(psut.last_autovacuum, 'YYYY-MM-DD HH24:MI'), '') AS last_autovacuum, + coalesce(to_char(psut.last_analyze, 'YYYY-MM-DD HH24:MI'), '') AS last_analyze, + coalesce(to_char(psut.last_autoanalyze, 'YYYY-MM-DD HH24:MI'), '') AS last_autoanalyze, to_char(pg_class.reltuples, '9G999G999G999') AS rowcount, to_char(psut.n_dead_tup, '9G999G999G999') AS dead_rowcount, to_char(autovacuum_vacuum_threshold @@ -31,7 +43,14 @@ SELECT WHEN autovacuum_vacuum_threshold + (autovacuum_vacuum_scale_factor::numeric * pg_class.reltuples) < psut.n_dead_tup THEN 'yes' ELSE 'no' - END AS expect_autovacuum + END AS expect_autovacuum, + to_char(autovacuum_analyze_threshold + + (autovacuum_analyze_scale_factor::numeric * pg_class.reltuples), '9G999G999G999') AS autoanalyze_threshold, + CASE + WHEN autovacuum_analyze_threshold + (autovacuum_analyze_scale_factor::numeric * pg_class.reltuples) < psut.n_dead_tup + THEN 'yes' + ELSE 'no' + END AS expect_autoanalyze FROM pg_stat_user_tables psut INNER JOIN pg_class ON psut.relid = pg_class.oid INNER JOIN vacuum_settings ON pg_class.oid = vacuum_settings.oid diff --git a/internal/inspect/vacuum_stats/vacuum_stats_test.go b/internal/inspect/vacuum_stats/vacuum_stats_test.go index 7872567b3..222da3493 100644 --- a/internal/inspect/vacuum_stats/vacuum_stats_test.go +++ b/internal/inspect/vacuum_stats/vacuum_stats_test.go @@ -32,10 +32,13 @@ func TestVacuumCommand(t *testing.T) { Name: "test_schema.test_table", Last_vacuum: "2021-01-01 00:00:00", Last_autovacuum: "2021-01-01 00:00:00", + Last_analyze: "2021-01-01 00:00:00", + Last_autoanalyze: "2021-01-01 00:00:00", Rowcount: "1000", Dead_rowcount: "100", Autovacuum_threshold: "100", Expect_autovacuum: "yes", + Expect_autoanalyze: "yes", }) // Run test err := Run(context.Background(), dbConfig, fsys, conn.Intercept) diff --git a/internal/link/link.go b/internal/link/link.go index 5c66f93c6..98e3b4814 100644 --- a/internal/link/link.go +++ b/internal/link/link.go @@ -69,6 +69,7 @@ func LinkServices(ctx context.Context, projectRef, serviceKey string, skipPooler func() error { return linkStorage(ctx, projectRef) }, func() error { if skipPooler { + utils.Config.Db.Pooler.ConnectionString = "" return fsys.RemoveAll(utils.PoolerUrlPath) } return linkPooler(ctx, projectRef, fsys) diff --git a/internal/start/start.go b/internal/start/start.go index 22b8fa694..2fadf7e0a 100644 --- a/internal/start/start.go +++ b/internal/start/start.go @@ -482,7 +482,7 @@ EOF "GOTRUE_JWT_DEFAULT_GROUP_NAME=authenticated", fmt.Sprintf("GOTRUE_JWT_EXP=%v", utils.Config.Auth.JwtExpiry), "GOTRUE_JWT_SECRET=" + utils.Config.Auth.JwtSecret.Value, - "GOTRUE_JWT_ISSUER=" + utils.GetApiUrl("/auth/v1"), + "GOTRUE_JWT_ISSUER=" + utils.Config.Auth.JwtIssuer, fmt.Sprintf("GOTRUE_EXTERNAL_EMAIL_ENABLED=%v", utils.Config.Auth.Email.EnableSignup), fmt.Sprintf("GOTRUE_MAILER_SECURE_EMAIL_CHANGE_ENABLED=%v", utils.Config.Auth.Email.DoubleConfirmChanges), @@ -494,10 +494,10 @@ EOF fmt.Sprintf("GOTRUE_SMTP_MAX_FREQUENCY=%v", utils.Config.Auth.Email.MaxFrequency), - "GOTRUE_MAILER_URLPATHS_INVITE=" + utils.GetApiUrl("/auth/v1/verify"), - "GOTRUE_MAILER_URLPATHS_CONFIRMATION=" + utils.GetApiUrl("/auth/v1/verify"), - "GOTRUE_MAILER_URLPATHS_RECOVERY=" + utils.GetApiUrl("/auth/v1/verify"), - "GOTRUE_MAILER_URLPATHS_EMAIL_CHANGE=" + utils.GetApiUrl("/auth/v1/verify"), + fmt.Sprintf("GOTRUE_MAILER_URLPATHS_INVITE=%s/verify", utils.Config.Auth.JwtIssuer), + fmt.Sprintf("GOTRUE_MAILER_URLPATHS_CONFIRMATION=%s/verify", utils.Config.Auth.JwtIssuer), + fmt.Sprintf("GOTRUE_MAILER_URLPATHS_RECOVERY=%s/verify", utils.Config.Auth.JwtIssuer), + fmt.Sprintf("GOTRUE_MAILER_URLPATHS_EMAIL_CHANGE=%s/verify", utils.Config.Auth.JwtIssuer), "GOTRUE_RATE_LIMIT_EMAIL_SENT=360000", fmt.Sprintf("GOTRUE_EXTERNAL_PHONE_ENABLED=%v", utils.Config.Auth.Sms.EnableSignup), @@ -699,7 +699,7 @@ EOF redirectUri := config.RedirectUri if redirectUri == "" { - redirectUri = utils.GetApiUrl("/auth/v1/callback") + redirectUri = utils.Config.Auth.JwtIssuer + "/callback" } env = append(env, fmt.Sprintf("GOTRUE_EXTERNAL_%s_REDIRECT_URI=%s", strings.ToUpper(name), redirectUri)) diff --git a/internal/utils/flags/config_path.go b/internal/utils/flags/config_path.go index bec77a432..4df1bff74 100644 --- a/internal/utils/flags/config_path.go +++ b/internal/utils/flags/config_path.go @@ -1,6 +1,8 @@ package flags import ( + "strings" + "github.com/spf13/afero" "github.com/supabase/cli/internal/utils" ) @@ -11,5 +13,14 @@ func LoadConfig(fsys afero.Fs) error { return err } utils.UpdateDockerIds() + // Apply profile specific overrides + if strings.EqualFold(utils.CurrentProfile.Name, "snap") { + ext := utils.Config.Auth.External["snapchat"] + ext.Enabled = true + ext.ClientId = utils.CurrentProfile.AuthClientID + // Any dummy value should work for local dev + ext.Secret.Value = utils.CurrentProfile.AuthClientID + utils.Config.Auth.External["snapchat"] = ext + } return nil } diff --git a/internal/utils/profile.go b/internal/utils/profile.go index eef9e5a0d..fcb089e3f 100644 --- a/internal/utils/profile.go +++ b/internal/utils/profile.go @@ -15,9 +15,10 @@ type Profile struct { Name string `mapstructure:"name" validate:"required"` APIURL string `mapstructure:"api_url" validate:"required,http_url"` DashboardURL string `mapstructure:"dashboard_url" validate:"required,http_url"` + DocsURL string `mapstructure:"docs_url" validate:"omitempty,http_url"` ProjectHost string `mapstructure:"project_host" validate:"required,hostname_rfc1123"` PoolerHost string `mapstructure:"pooler_host" validate:"omitempty,hostname_rfc1123"` - DocsURL string `mapstructure:"docs_url" validate:"omitempty,http_url"` + AuthClientID string `mapstructure:"client_id" validate:"omitempty,uuid4"` StudioImage string `mapstructure:"studio_image"` } @@ -48,6 +49,7 @@ var allProfiles = []Profile{{ DocsURL: "https://cloud.snap.com/docs", ProjectHost: "snapcloud.dev", PoolerHost: "snapcloud.co", + AuthClientID: "f7573b20-df47-48f1-b606-e8db4ec16252", }} var CurrentProfile Profile diff --git a/package.json b/package.json index 1bae6b518..27f3470fd 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "bin-links": "^6.0.0", "https-proxy-agent": "^7.0.2", "node-fetch": "^3.3.2", - "tar": "7.5.1" + "tar": "7.5.2" }, "release": { "branches": [ diff --git a/pkg/api/client.gen.go b/pkg/api/client.gen.go index b3c1a153e..59cf7da68 100644 --- a/pkg/api/client.gen.go +++ b/pkg/api/client.gen.go @@ -91,7 +91,7 @@ func WithRequestEditorFn(fn RequestEditorFn) ClientOption { // The interface specification for the client above. type ClientInterface interface { // V1DeleteABranch request - V1DeleteABranch(ctx context.Context, branchIdOrRef string, reqEditors ...RequestEditorFn) (*http.Response, error) + V1DeleteABranch(ctx context.Context, branchIdOrRef string, params *V1DeleteABranchParams, reqEditors ...RequestEditorFn) (*http.Response, error) // V1GetABranchConfig request V1GetABranchConfig(ctx context.Context, branchIdOrRef string, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -119,6 +119,9 @@ type ClientInterface interface { V1ResetABranch(ctx context.Context, branchIdOrRef string, body V1ResetABranchJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // V1RestoreABranch request + V1RestoreABranch(ctx context.Context, branchIdOrRef string, reqEditors ...RequestEditorFn) (*http.Response, error) + // V1AuthorizeUser request V1AuthorizeUser(ctx context.Context, params *V1AuthorizeUserParams, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -432,6 +435,9 @@ type ClientInterface interface { // V1DeleteJitAccess request V1DeleteJitAccess(ctx context.Context, ref string, userId openapi_types.UUID, reqEditors ...RequestEditorFn) (*http.Response, error) + // V1RollbackMigrations request + V1RollbackMigrations(ctx context.Context, ref string, params *V1RollbackMigrationsParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // V1ListMigrationHistory request V1ListMigrationHistory(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -445,6 +451,14 @@ type ClientInterface interface { V1UpsertAMigration(ctx context.Context, ref string, params *V1UpsertAMigrationParams, body V1UpsertAMigrationJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // V1GetAMigration request + V1GetAMigration(ctx context.Context, ref string, version string, reqEditors ...RequestEditorFn) (*http.Response, error) + + // V1PatchAMigrationWithBody request with any body + V1PatchAMigrationWithBody(ctx context.Context, ref string, version string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + V1PatchAMigration(ctx context.Context, ref string, version string, body V1PatchAMigrationJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // V1RunAQueryWithBody request with any body V1RunAQueryWithBody(ctx context.Context, ref string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -615,8 +629,8 @@ type ClientInterface interface { V1GetASnippet(ctx context.Context, id openapi_types.UUID, reqEditors ...RequestEditorFn) (*http.Response, error) } -func (c *Client) V1DeleteABranch(ctx context.Context, branchIdOrRef string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewV1DeleteABranchRequest(c.Server, branchIdOrRef) +func (c *Client) V1DeleteABranch(ctx context.Context, branchIdOrRef string, params *V1DeleteABranchParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewV1DeleteABranchRequest(c.Server, branchIdOrRef, params) if err != nil { return nil, err } @@ -747,6 +761,18 @@ func (c *Client) V1ResetABranch(ctx context.Context, branchIdOrRef string, body return c.Client.Do(req) } +func (c *Client) V1RestoreABranch(ctx context.Context, branchIdOrRef string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewV1RestoreABranchRequest(c.Server, branchIdOrRef) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) V1AuthorizeUser(ctx context.Context, params *V1AuthorizeUserParams, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewV1AuthorizeUserRequest(c.Server, params) if err != nil { @@ -2093,6 +2119,18 @@ func (c *Client) V1DeleteJitAccess(ctx context.Context, ref string, userId opena return c.Client.Do(req) } +func (c *Client) V1RollbackMigrations(ctx context.Context, ref string, params *V1RollbackMigrationsParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewV1RollbackMigrationsRequest(c.Server, ref, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) V1ListMigrationHistory(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewV1ListMigrationHistoryRequest(c.Server, ref) if err != nil { @@ -2153,6 +2191,42 @@ func (c *Client) V1UpsertAMigration(ctx context.Context, ref string, params *V1U return c.Client.Do(req) } +func (c *Client) V1GetAMigration(ctx context.Context, ref string, version string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewV1GetAMigrationRequest(c.Server, ref, version) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) V1PatchAMigrationWithBody(ctx context.Context, ref string, version string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewV1PatchAMigrationRequestWithBody(c.Server, ref, version, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) V1PatchAMigration(ctx context.Context, ref string, version string, body V1PatchAMigrationJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewV1PatchAMigrationRequest(c.Server, ref, version, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) V1RunAQueryWithBody(ctx context.Context, ref string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewV1RunAQueryRequestWithBody(c.Server, ref, contentType, body) if err != nil { @@ -2898,7 +2972,7 @@ func (c *Client) V1GetASnippet(ctx context.Context, id openapi_types.UUID, reqEd } // NewV1DeleteABranchRequest generates requests for V1DeleteABranch -func NewV1DeleteABranchRequest(server string, branchIdOrRef string) (*http.Request, error) { +func NewV1DeleteABranchRequest(server string, branchIdOrRef string, params *V1DeleteABranchParams) (*http.Request, error) { var err error var pathParam0 string @@ -2923,6 +2997,28 @@ func NewV1DeleteABranchRequest(server string, branchIdOrRef string) (*http.Reque return nil, err } + if params != nil { + queryValues := queryURL.Query() + + if params.Force != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "force", runtime.ParamLocationQuery, *params.Force); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() + } + req, err := http.NewRequest("DELETE", queryURL.String(), nil) if err != nil { return nil, err @@ -3209,6 +3305,40 @@ func NewV1ResetABranchRequestWithBody(server string, branchIdOrRef string, conte return req, nil } +// NewV1RestoreABranchRequest generates requests for V1RestoreABranch +func NewV1RestoreABranchRequest(server string, branchIdOrRef string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "branch_id_or_ref", runtime.ParamLocationPath, branchIdOrRef) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/v1/branches/%s/restore", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + // NewV1AuthorizeUserRequest generates requests for V1AuthorizeUser func NewV1AuthorizeUserRequest(server string, params *V1AuthorizeUserParams) (*http.Request, error) { var err error @@ -7320,6 +7450,58 @@ func NewV1DeleteJitAccessRequest(server string, ref string, userId openapi_types return req, nil } +// NewV1RollbackMigrationsRequest generates requests for V1RollbackMigrations +func NewV1RollbackMigrationsRequest(server string, ref string, params *V1RollbackMigrationsParams) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "ref", runtime.ParamLocationPath, ref) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/v1/projects/%s/database/migrations", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := queryURL.Query() + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "gte", runtime.ParamLocationQuery, params.Gte); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("DELETE", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + // NewV1ListMigrationHistoryRequest generates requests for V1ListMigrationHistory func NewV1ListMigrationHistoryRequest(server string, ref string) (*http.Request, error) { var err error @@ -7478,6 +7660,101 @@ func NewV1UpsertAMigrationRequestWithBody(server string, ref string, params *V1U return req, nil } +// NewV1GetAMigrationRequest generates requests for V1GetAMigration +func NewV1GetAMigrationRequest(server string, ref string, version string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "ref", runtime.ParamLocationPath, ref) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "version", runtime.ParamLocationPath, version) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/v1/projects/%s/database/migrations/%s", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewV1PatchAMigrationRequest calls the generic V1PatchAMigration builder with application/json body +func NewV1PatchAMigrationRequest(server string, ref string, version string, body V1PatchAMigrationJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewV1PatchAMigrationRequestWithBody(server, ref, version, "application/json", bodyReader) +} + +// NewV1PatchAMigrationRequestWithBody generates requests for V1PatchAMigration with any type of body +func NewV1PatchAMigrationRequestWithBody(server string, ref string, version string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "ref", runtime.ParamLocationPath, ref) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "version", runtime.ParamLocationPath, version) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/v1/projects/%s/database/migrations/%s", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("PATCH", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + // NewV1RunAQueryRequest calls the generic V1RunAQuery builder with application/json body func NewV1RunAQueryRequest(server string, ref string, body V1RunAQueryJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader @@ -9734,7 +10011,7 @@ func WithBaseURL(baseURL string) ClientOption { // ClientWithResponsesInterface is the interface specification for the client with responses above. type ClientWithResponsesInterface interface { // V1DeleteABranchWithResponse request - V1DeleteABranchWithResponse(ctx context.Context, branchIdOrRef string, reqEditors ...RequestEditorFn) (*V1DeleteABranchResponse, error) + V1DeleteABranchWithResponse(ctx context.Context, branchIdOrRef string, params *V1DeleteABranchParams, reqEditors ...RequestEditorFn) (*V1DeleteABranchResponse, error) // V1GetABranchConfigWithResponse request V1GetABranchConfigWithResponse(ctx context.Context, branchIdOrRef string, reqEditors ...RequestEditorFn) (*V1GetABranchConfigResponse, error) @@ -9762,6 +10039,9 @@ type ClientWithResponsesInterface interface { V1ResetABranchWithResponse(ctx context.Context, branchIdOrRef string, body V1ResetABranchJSONRequestBody, reqEditors ...RequestEditorFn) (*V1ResetABranchResponse, error) + // V1RestoreABranchWithResponse request + V1RestoreABranchWithResponse(ctx context.Context, branchIdOrRef string, reqEditors ...RequestEditorFn) (*V1RestoreABranchResponse, error) + // V1AuthorizeUserWithResponse request V1AuthorizeUserWithResponse(ctx context.Context, params *V1AuthorizeUserParams, reqEditors ...RequestEditorFn) (*V1AuthorizeUserResponse, error) @@ -10075,6 +10355,9 @@ type ClientWithResponsesInterface interface { // V1DeleteJitAccessWithResponse request V1DeleteJitAccessWithResponse(ctx context.Context, ref string, userId openapi_types.UUID, reqEditors ...RequestEditorFn) (*V1DeleteJitAccessResponse, error) + // V1RollbackMigrationsWithResponse request + V1RollbackMigrationsWithResponse(ctx context.Context, ref string, params *V1RollbackMigrationsParams, reqEditors ...RequestEditorFn) (*V1RollbackMigrationsResponse, error) + // V1ListMigrationHistoryWithResponse request V1ListMigrationHistoryWithResponse(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*V1ListMigrationHistoryResponse, error) @@ -10088,6 +10371,14 @@ type ClientWithResponsesInterface interface { V1UpsertAMigrationWithResponse(ctx context.Context, ref string, params *V1UpsertAMigrationParams, body V1UpsertAMigrationJSONRequestBody, reqEditors ...RequestEditorFn) (*V1UpsertAMigrationResponse, error) + // V1GetAMigrationWithResponse request + V1GetAMigrationWithResponse(ctx context.Context, ref string, version string, reqEditors ...RequestEditorFn) (*V1GetAMigrationResponse, error) + + // V1PatchAMigrationWithBodyWithResponse request with any body + V1PatchAMigrationWithBodyWithResponse(ctx context.Context, ref string, version string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*V1PatchAMigrationResponse, error) + + V1PatchAMigrationWithResponse(ctx context.Context, ref string, version string, body V1PatchAMigrationJSONRequestBody, reqEditors ...RequestEditorFn) (*V1PatchAMigrationResponse, error) + // V1RunAQueryWithBodyWithResponse request with any body V1RunAQueryWithBodyWithResponse(ctx context.Context, ref string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*V1RunAQueryResponse, error) @@ -10411,6 +10702,28 @@ func (r V1ResetABranchResponse) StatusCode() int { return 0 } +type V1RestoreABranchResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *BranchRestoreResponse +} + +// Status returns HTTPResponse.Status +func (r V1RestoreABranchResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r V1RestoreABranchResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type V1AuthorizeUserResponse struct { Body []byte HTTPResponse *http.Response @@ -12310,6 +12623,27 @@ func (r V1DeleteJitAccessResponse) StatusCode() int { return 0 } +type V1RollbackMigrationsResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r V1RollbackMigrationsResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r V1RollbackMigrationsResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type V1ListMigrationHistoryResponse struct { Body []byte HTTPResponse *http.Response @@ -12374,6 +12708,49 @@ func (r V1UpsertAMigrationResponse) StatusCode() int { return 0 } +type V1GetAMigrationResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *V1GetMigrationResponse +} + +// Status returns HTTPResponse.Status +func (r V1GetAMigrationResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r V1GetAMigrationResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type V1PatchAMigrationResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r V1PatchAMigrationResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r V1PatchAMigrationResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type V1RunAQueryResponse struct { Body []byte HTTPResponse *http.Response @@ -13352,8 +13729,8 @@ func (r V1GetASnippetResponse) StatusCode() int { } // V1DeleteABranchWithResponse request returning *V1DeleteABranchResponse -func (c *ClientWithResponses) V1DeleteABranchWithResponse(ctx context.Context, branchIdOrRef string, reqEditors ...RequestEditorFn) (*V1DeleteABranchResponse, error) { - rsp, err := c.V1DeleteABranch(ctx, branchIdOrRef, reqEditors...) +func (c *ClientWithResponses) V1DeleteABranchWithResponse(ctx context.Context, branchIdOrRef string, params *V1DeleteABranchParams, reqEditors ...RequestEditorFn) (*V1DeleteABranchResponse, error) { + rsp, err := c.V1DeleteABranch(ctx, branchIdOrRef, params, reqEditors...) if err != nil { return nil, err } @@ -13446,6 +13823,15 @@ func (c *ClientWithResponses) V1ResetABranchWithResponse(ctx context.Context, br return ParseV1ResetABranchResponse(rsp) } +// V1RestoreABranchWithResponse request returning *V1RestoreABranchResponse +func (c *ClientWithResponses) V1RestoreABranchWithResponse(ctx context.Context, branchIdOrRef string, reqEditors ...RequestEditorFn) (*V1RestoreABranchResponse, error) { + rsp, err := c.V1RestoreABranch(ctx, branchIdOrRef, reqEditors...) + if err != nil { + return nil, err + } + return ParseV1RestoreABranchResponse(rsp) +} + // V1AuthorizeUserWithResponse request returning *V1AuthorizeUserResponse func (c *ClientWithResponses) V1AuthorizeUserWithResponse(ctx context.Context, params *V1AuthorizeUserParams, reqEditors ...RequestEditorFn) (*V1AuthorizeUserResponse, error) { rsp, err := c.V1AuthorizeUser(ctx, params, reqEditors...) @@ -14431,6 +14817,15 @@ func (c *ClientWithResponses) V1DeleteJitAccessWithResponse(ctx context.Context, return ParseV1DeleteJitAccessResponse(rsp) } +// V1RollbackMigrationsWithResponse request returning *V1RollbackMigrationsResponse +func (c *ClientWithResponses) V1RollbackMigrationsWithResponse(ctx context.Context, ref string, params *V1RollbackMigrationsParams, reqEditors ...RequestEditorFn) (*V1RollbackMigrationsResponse, error) { + rsp, err := c.V1RollbackMigrations(ctx, ref, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseV1RollbackMigrationsResponse(rsp) +} + // V1ListMigrationHistoryWithResponse request returning *V1ListMigrationHistoryResponse func (c *ClientWithResponses) V1ListMigrationHistoryWithResponse(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*V1ListMigrationHistoryResponse, error) { rsp, err := c.V1ListMigrationHistory(ctx, ref, reqEditors...) @@ -14474,6 +14869,32 @@ func (c *ClientWithResponses) V1UpsertAMigrationWithResponse(ctx context.Context return ParseV1UpsertAMigrationResponse(rsp) } +// V1GetAMigrationWithResponse request returning *V1GetAMigrationResponse +func (c *ClientWithResponses) V1GetAMigrationWithResponse(ctx context.Context, ref string, version string, reqEditors ...RequestEditorFn) (*V1GetAMigrationResponse, error) { + rsp, err := c.V1GetAMigration(ctx, ref, version, reqEditors...) + if err != nil { + return nil, err + } + return ParseV1GetAMigrationResponse(rsp) +} + +// V1PatchAMigrationWithBodyWithResponse request with arbitrary body returning *V1PatchAMigrationResponse +func (c *ClientWithResponses) V1PatchAMigrationWithBodyWithResponse(ctx context.Context, ref string, version string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*V1PatchAMigrationResponse, error) { + rsp, err := c.V1PatchAMigrationWithBody(ctx, ref, version, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseV1PatchAMigrationResponse(rsp) +} + +func (c *ClientWithResponses) V1PatchAMigrationWithResponse(ctx context.Context, ref string, version string, body V1PatchAMigrationJSONRequestBody, reqEditors ...RequestEditorFn) (*V1PatchAMigrationResponse, error) { + rsp, err := c.V1PatchAMigration(ctx, ref, version, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseV1PatchAMigrationResponse(rsp) +} + // V1RunAQueryWithBodyWithResponse request with arbitrary body returning *V1RunAQueryResponse func (c *ClientWithResponses) V1RunAQueryWithBodyWithResponse(ctx context.Context, ref string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*V1RunAQueryResponse, error) { rsp, err := c.V1RunAQueryWithBody(ctx, ref, contentType, body, reqEditors...) @@ -15187,6 +15608,32 @@ func ParseV1ResetABranchResponse(rsp *http.Response) (*V1ResetABranchResponse, e return response, nil } +// ParseV1RestoreABranchResponse parses an HTTP response from a V1RestoreABranchWithResponse call +func ParseV1RestoreABranchResponse(rsp *http.Response) (*V1RestoreABranchResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &V1RestoreABranchResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest BranchRestoreResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + // ParseV1AuthorizeUserResponse parses an HTTP response from a V1AuthorizeUserWithResponse call func ParseV1AuthorizeUserResponse(rsp *http.Response) (*V1AuthorizeUserResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -17299,6 +17746,22 @@ func ParseV1DeleteJitAccessResponse(rsp *http.Response) (*V1DeleteJitAccessRespo return response, nil } +// ParseV1RollbackMigrationsResponse parses an HTTP response from a V1RollbackMigrationsWithResponse call +func ParseV1RollbackMigrationsResponse(rsp *http.Response) (*V1RollbackMigrationsResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &V1RollbackMigrationsResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + // ParseV1ListMigrationHistoryResponse parses an HTTP response from a V1ListMigrationHistoryWithResponse call func ParseV1ListMigrationHistoryResponse(rsp *http.Response) (*V1ListMigrationHistoryResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -17357,6 +17820,48 @@ func ParseV1UpsertAMigrationResponse(rsp *http.Response) (*V1UpsertAMigrationRes return response, nil } +// ParseV1GetAMigrationResponse parses an HTTP response from a V1GetAMigrationWithResponse call +func ParseV1GetAMigrationResponse(rsp *http.Response) (*V1GetAMigrationResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &V1GetAMigrationResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest V1GetMigrationResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseV1PatchAMigrationResponse parses an HTTP response from a V1PatchAMigrationWithResponse call +func ParseV1PatchAMigrationResponse(rsp *http.Response) (*V1PatchAMigrationResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &V1PatchAMigrationResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + // ParseV1RunAQueryResponse parses an HTTP response from a V1RunAQueryWithResponse call func ParseV1RunAQueryResponse(rsp *http.Response) (*V1RunAQueryResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) diff --git a/pkg/api/types.gen.go b/pkg/api/types.gen.go index d55f973ee..9c25cef2f 100644 --- a/pkg/api/types.gen.go +++ b/pkg/api/types.gen.go @@ -144,6 +144,25 @@ const ( BranchDetailResponseStatusUPGRADING BranchDetailResponseStatus = "UPGRADING" ) +// Defines values for BranchResponsePreviewProjectStatus. +const ( + BranchResponsePreviewProjectStatusACTIVEHEALTHY BranchResponsePreviewProjectStatus = "ACTIVE_HEALTHY" + BranchResponsePreviewProjectStatusACTIVEUNHEALTHY BranchResponsePreviewProjectStatus = "ACTIVE_UNHEALTHY" + BranchResponsePreviewProjectStatusCOMINGUP BranchResponsePreviewProjectStatus = "COMING_UP" + BranchResponsePreviewProjectStatusGOINGDOWN BranchResponsePreviewProjectStatus = "GOING_DOWN" + BranchResponsePreviewProjectStatusINACTIVE BranchResponsePreviewProjectStatus = "INACTIVE" + BranchResponsePreviewProjectStatusINITFAILED BranchResponsePreviewProjectStatus = "INIT_FAILED" + BranchResponsePreviewProjectStatusPAUSEFAILED BranchResponsePreviewProjectStatus = "PAUSE_FAILED" + BranchResponsePreviewProjectStatusPAUSING BranchResponsePreviewProjectStatus = "PAUSING" + BranchResponsePreviewProjectStatusREMOVED BranchResponsePreviewProjectStatus = "REMOVED" + BranchResponsePreviewProjectStatusRESIZING BranchResponsePreviewProjectStatus = "RESIZING" + BranchResponsePreviewProjectStatusRESTARTING BranchResponsePreviewProjectStatus = "RESTARTING" + BranchResponsePreviewProjectStatusRESTOREFAILED BranchResponsePreviewProjectStatus = "RESTORE_FAILED" + BranchResponsePreviewProjectStatusRESTORING BranchResponsePreviewProjectStatus = "RESTORING" + BranchResponsePreviewProjectStatusUNKNOWN BranchResponsePreviewProjectStatus = "UNKNOWN" + BranchResponsePreviewProjectStatusUPGRADING BranchResponsePreviewProjectStatus = "UPGRADING" +) + // Defines values for BranchResponseStatus. const ( BranchResponseStatusCREATINGPROJECT BranchResponseStatus = "CREATING_PROJECT" @@ -154,6 +173,11 @@ const ( BranchResponseStatusRUNNINGMIGRATIONS BranchResponseStatus = "RUNNING_MIGRATIONS" ) +// Defines values for BranchRestoreResponseMessage. +const ( + BranchRestorationInitiated BranchRestoreResponseMessage = "Branch restoration initiated" +) + // Defines values for BranchUpdateResponseMessage. const ( BranchUpdateResponseMessageOk BranchUpdateResponseMessage = "ok" @@ -1365,9 +1389,10 @@ const ( // Defines values for V1RestorePointResponseStatus. const ( - AVAILABLE V1RestorePointResponseStatus = "AVAILABLE" - PENDING V1RestorePointResponseStatus = "PENDING" - REMOVED V1RestorePointResponseStatus = "REMOVED" + V1RestorePointResponseStatusAVAILABLE V1RestorePointResponseStatus = "AVAILABLE" + V1RestorePointResponseStatusFAILED V1RestorePointResponseStatus = "FAILED" + V1RestorePointResponseStatusPENDING V1RestorePointResponseStatus = "PENDING" + V1RestorePointResponseStatusREMOVED V1RestorePointResponseStatus = "REMOVED" ) // Defines values for V1ServiceHealthResponseInfo0Name. @@ -1931,29 +1956,42 @@ type BranchDetailResponseStatus string // BranchResponse defines model for BranchResponse. type BranchResponse struct { - CreatedAt time.Time `json:"created_at"` - GitBranch *string `json:"git_branch,omitempty"` - Id openapi_types.UUID `json:"id"` - IsDefault bool `json:"is_default"` + CreatedAt time.Time `json:"created_at"` + DeletionScheduledAt *time.Time `json:"deletion_scheduled_at,omitempty"` + GitBranch *string `json:"git_branch,omitempty"` + Id openapi_types.UUID `json:"id"` + IsDefault bool `json:"is_default"` // LatestCheckRunId This field is deprecated and will not be populated. // Deprecated: - LatestCheckRunId *float32 `json:"latest_check_run_id,omitempty"` - Name string `json:"name"` - NotifyUrl *string `json:"notify_url,omitempty"` - ParentProjectRef string `json:"parent_project_ref"` - Persistent bool `json:"persistent"` - PrNumber *int32 `json:"pr_number,omitempty"` - ProjectRef string `json:"project_ref"` - ReviewRequestedAt *time.Time `json:"review_requested_at,omitempty"` - Status BranchResponseStatus `json:"status"` - UpdatedAt time.Time `json:"updated_at"` - WithData bool `json:"with_data"` -} + LatestCheckRunId *float32 `json:"latest_check_run_id,omitempty"` + Name string `json:"name"` + NotifyUrl *string `json:"notify_url,omitempty"` + ParentProjectRef string `json:"parent_project_ref"` + Persistent bool `json:"persistent"` + PrNumber *int32 `json:"pr_number,omitempty"` + PreviewProjectStatus *BranchResponsePreviewProjectStatus `json:"preview_project_status,omitempty"` + ProjectRef string `json:"project_ref"` + ReviewRequestedAt *time.Time `json:"review_requested_at,omitempty"` + Status BranchResponseStatus `json:"status"` + UpdatedAt time.Time `json:"updated_at"` + WithData bool `json:"with_data"` +} + +// BranchResponsePreviewProjectStatus defines model for BranchResponse.PreviewProjectStatus. +type BranchResponsePreviewProjectStatus string // BranchResponseStatus defines model for BranchResponse.Status. type BranchResponseStatus string +// BranchRestoreResponse defines model for BranchRestoreResponse. +type BranchRestoreResponse struct { + Message BranchRestoreResponseMessage `json:"message"` +} + +// BranchRestoreResponseMessage defines model for BranchRestoreResponse.Message. +type BranchRestoreResponseMessage string + // BranchUpdateResponse defines model for BranchUpdateResponse. type BranchUpdateResponse struct { Message BranchUpdateResponseMessage `json:"message"` @@ -2333,6 +2371,9 @@ type DeleteRolesResponse struct { // DeleteRolesResponseMessage defines model for DeleteRolesResponse.Message. type DeleteRolesResponseMessage string +// DeleteSecretsBody defines model for DeleteSecretsBody. +type DeleteSecretsBody = []string + // DeployFunctionResponse defines model for DeployFunctionResponse. type DeployFunctionResponse struct { CreatedAt *int64 `json:"created_at,omitempty"` @@ -3219,7 +3260,8 @@ type StorageConfigResponse struct { IcebergCatalog bool `json:"iceberg_catalog"` ListV2 bool `json:"list_v2"` } `json:"capabilities"` - External struct { + DatabasePoolMode string `json:"databasePoolMode"` + External struct { UpstreamTarget StorageConfigResponseExternalUpstreamTarget `json:"upstreamTarget"` } `json:"external"` Features struct { @@ -3233,7 +3275,8 @@ type StorageConfigResponse struct { Enabled bool `json:"enabled"` } `json:"s3Protocol"` } `json:"features"` - FileSizeLimit int64 `json:"fileSizeLimit"` + FileSizeLimit int64 `json:"fileSizeLimit"` + MigrationVersion string `json:"migrationVersion"` } // StorageConfigResponseExternalUpstreamTarget defines model for StorageConfigResponse.External.UpstreamTarget. @@ -3813,8 +3856,9 @@ type V1CreateFunctionBody struct { // V1CreateMigrationBody defines model for V1CreateMigrationBody. type V1CreateMigrationBody struct { - Name *string `json:"name,omitempty"` - Query string `json:"query"` + Name *string `json:"name,omitempty"` + Query string `json:"query"` + Rollback *string `json:"rollback,omitempty"` } // V1CreateProjectBody defines model for V1CreateProjectBody. @@ -3902,6 +3946,16 @@ type V1CreateProjectBody_RegionSelection struct { // V1CreateProjectBodyReleaseChannel Release channel. If not provided, GA will be used. type V1CreateProjectBodyReleaseChannel string +// V1GetMigrationResponse defines model for V1GetMigrationResponse. +type V1GetMigrationResponse struct { + CreatedBy *string `json:"created_by,omitempty"` + IdempotencyKey *string `json:"idempotency_key,omitempty"` + Name *string `json:"name,omitempty"` + Rollback *[]string `json:"rollback,omitempty"` + Statements *[]string `json:"statements,omitempty"` + Version string `json:"version"` +} + // V1GetUsageApiCountResponse defines model for V1GetUsageApiCountResponse. type V1GetUsageApiCountResponse struct { Error *V1GetUsageApiCountResponse_Error `json:"error,omitempty"` @@ -3999,6 +4053,12 @@ type V1OrganizationSlugResponseOptInTags string // V1OrganizationSlugResponsePlan defines model for V1OrganizationSlugResponse.Plan. type V1OrganizationSlugResponsePlan string +// V1PatchMigrationBody defines model for V1PatchMigrationBody. +type V1PatchMigrationBody struct { + Name *string `json:"name,omitempty"` + Rollback *string `json:"rollback,omitempty"` +} + // V1PgbouncerConfigResponse defines model for V1PgbouncerConfigResponse. type V1PgbouncerConfigResponse struct { ConnectionString *string `json:"connection_string,omitempty"` @@ -4227,8 +4287,9 @@ type V1UpdatePostgrestConfigBody struct { // V1UpsertMigrationBody defines model for V1UpsertMigrationBody. type V1UpsertMigrationBody struct { - Name *string `json:"name,omitempty"` - Query string `json:"query"` + Name *string `json:"name,omitempty"` + Query string `json:"query"` + Rollback *string `json:"rollback,omitempty"` } // VanitySubdomainBody defines model for VanitySubdomainBody. @@ -4245,6 +4306,12 @@ type VanitySubdomainConfigResponse struct { // VanitySubdomainConfigResponseStatus defines model for VanitySubdomainConfigResponse.Status. type VanitySubdomainConfigResponseStatus string +// V1DeleteABranchParams defines parameters for V1DeleteABranch. +type V1DeleteABranchParams struct { + // Force If set to false, schedule deletion with 1-hour grace period (only when soft deletion is enabled). + Force *bool `form:"force,omitempty" json:"force,omitempty"` +} + // V1DiffABranchParams defines parameters for V1DiffABranch. type V1DiffABranchParams struct { IncludedSchemas *string `form:"included_schemas,omitempty" json:"included_schemas,omitempty"` @@ -4359,6 +4426,7 @@ type V1GetProjectFunctionCombinedStatsParamsInterval string // V1GetProjectLogsParams defines parameters for V1GetProjectLogs. type V1GetProjectLogsParams struct { + // Sql Custom SQL query to execute on the logs. See [querying logs](/docs/guides/telemetry/logs?queryGroups=product&product=postgres&queryGroups=source&source=edge_logs#querying-with-the-logs-explorer) for more details. Sql *string `form:"sql,omitempty" json:"sql,omitempty"` IsoTimestampStart *time.Time `form:"iso_timestamp_start,omitempty" json:"iso_timestamp_start,omitempty"` IsoTimestampEnd *time.Time `form:"iso_timestamp_end,omitempty" json:"iso_timestamp_end,omitempty"` @@ -4429,6 +4497,12 @@ type V1GetRestorePointParams struct { Name *string `form:"name,omitempty" json:"name,omitempty"` } +// V1RollbackMigrationsParams defines parameters for V1RollbackMigrations. +type V1RollbackMigrationsParams struct { + // Gte Rollback migrations greater or equal to this version + Gte string `form:"gte" json:"gte"` +} + // V1ApplyAMigrationParams defines parameters for V1ApplyAMigration. type V1ApplyAMigrationParams struct { // IdempotencyKey A unique key to ensure the same migration is tracked only once. @@ -4488,9 +4562,6 @@ type V1GetServicesHealthParams struct { // V1GetServicesHealthParamsServices defines parameters for V1GetServicesHealth. type V1GetServicesHealthParamsServices string -// V1BulkDeleteSecretsJSONBody defines parameters for V1BulkDeleteSecrets. -type V1BulkDeleteSecretsJSONBody = []string - // V1GenerateTypescriptTypesParams defines parameters for V1GenerateTypescriptTypes. type V1GenerateTypescriptTypesParams struct { IncludedSchemas *string `form:"included_schemas,omitempty" json:"included_schemas,omitempty"` @@ -4610,6 +4681,9 @@ type V1ApplyAMigrationJSONRequestBody = V1CreateMigrationBody // V1UpsertAMigrationJSONRequestBody defines body for V1UpsertAMigration for application/json ContentType. type V1UpsertAMigrationJSONRequestBody = V1UpsertMigrationBody +// V1PatchAMigrationJSONRequestBody defines body for V1PatchAMigration for application/json ContentType. +type V1PatchAMigrationJSONRequestBody = V1PatchMigrationBody + // V1RunAQueryJSONRequestBody defines body for V1RunAQuery for application/json ContentType. type V1RunAQueryJSONRequestBody = V1RunQueryBody @@ -4647,7 +4721,7 @@ type V1RemoveAReadReplicaJSONRequestBody = RemoveReadReplicaBody type V1SetupAReadReplicaJSONRequestBody = SetUpReadReplicaBody // V1BulkDeleteSecretsJSONRequestBody defines body for V1BulkDeleteSecrets for application/json ContentType. -type V1BulkDeleteSecretsJSONRequestBody = V1BulkDeleteSecretsJSONBody +type V1BulkDeleteSecretsJSONRequestBody = DeleteSecretsBody // V1BulkCreateSecretsJSONRequestBody defines body for V1BulkCreateSecrets for application/json ContentType. type V1BulkCreateSecretsJSONRequestBody = CreateSecretBody diff --git a/pkg/config/auth.go b/pkg/config/auth.go index 82fc0eef7..56288b882 100644 --- a/pkg/config/auth.go +++ b/pkg/config/auth.go @@ -152,6 +152,7 @@ type ( SiteUrl string `toml:"site_url"` AdditionalRedirectUrls []string `toml:"additional_redirect_urls"` JwtExpiry uint `toml:"jwt_expiry"` + JwtIssuer string `toml:"jwt_issuer"` EnableRefreshTokenRotation bool `toml:"enable_refresh_token_rotation"` RefreshTokenReuseInterval uint `toml:"refresh_token_reuse_interval"` EnableManualLinking bool `toml:"enable_manual_linking"` @@ -241,6 +242,7 @@ type ( EnableConfirmations bool `toml:"enable_confirmations"` SecurePasswordChange bool `toml:"secure_password_change"` Template map[string]emailTemplate `toml:"template"` + Notification map[string]notification `toml:"notification"` Smtp *smtp `toml:"smtp"` MaxFrequency time.Duration `toml:"max_frequency"` OtpLength uint `toml:"otp_length"` @@ -264,6 +266,11 @@ type ( ContentPath string `toml:"content_path"` } + notification struct { + Enabled bool `toml:"enabled"` + emailTemplate + } + sms struct { EnableSignup bool `toml:"enable_signup"` EnableConfirmations bool `toml:"enable_confirmations"` @@ -643,167 +650,374 @@ func (e email) toAuthConfigBody(body *v1API.UpdateAuthConfigBody) { if e.Smtp != nil { e.Smtp.toAuthConfigBody(body) } - if len(e.Template) == 0 { - return - } - var tmpl *emailTemplate - tmpl = cast.Ptr(e.Template["invite"]) - if tmpl.Subject != nil { - body.MailerSubjectsInvite = nullable.NewNullableWithValue(*tmpl.Subject) - } - if tmpl.Content != nil { - body.MailerTemplatesInviteContent = nullable.NewNullableWithValue(*tmpl.Content) - } - tmpl = cast.Ptr(e.Template["confirmation"]) - if tmpl.Subject != nil { - body.MailerSubjectsConfirmation = nullable.NewNullableWithValue(*tmpl.Subject) - } - if tmpl.Content != nil { - body.MailerTemplatesConfirmationContent = nullable.NewNullableWithValue(*tmpl.Content) - } - tmpl = cast.Ptr(e.Template["recovery"]) - if tmpl.Subject != nil { - body.MailerSubjectsRecovery = nullable.NewNullableWithValue(*tmpl.Subject) - } - if tmpl.Content != nil { - body.MailerTemplatesRecoveryContent = nullable.NewNullableWithValue(*tmpl.Content) - } - tmpl = cast.Ptr(e.Template["magic_link"]) - if tmpl.Subject != nil { - body.MailerSubjectsMagicLink = nullable.NewNullableWithValue(*tmpl.Subject) - } - if tmpl.Content != nil { - body.MailerTemplatesMagicLinkContent = nullable.NewNullableWithValue(*tmpl.Content) - } - tmpl = cast.Ptr(e.Template["email_change"]) - if tmpl.Subject != nil { - body.MailerSubjectsEmailChange = nullable.NewNullableWithValue(*tmpl.Subject) - } - if tmpl.Content != nil { - body.MailerTemplatesEmailChangeContent = nullable.NewNullableWithValue(*tmpl.Content) - } - tmpl = cast.Ptr(e.Template["reauthentication"]) - if tmpl.Subject != nil { - body.MailerSubjectsReauthentication = nullable.NewNullableWithValue(*tmpl.Subject) - } - if tmpl.Content != nil { - body.MailerTemplatesReauthenticationContent = nullable.NewNullableWithValue(*tmpl.Content) - } -} - -func (e *email) fromAuthConfig(remoteConfig v1API.AuthConfigResponse) { - e.EnableSignup = ValOrDefault(remoteConfig.ExternalEmailEnabled, false) - e.DoubleConfirmChanges = ValOrDefault(remoteConfig.MailerSecureEmailChangeEnabled, false) - e.EnableConfirmations = !ValOrDefault(remoteConfig.MailerAutoconfirm, false) - e.OtpLength = cast.IntToUint(ValOrDefault(remoteConfig.MailerOtpLength, 0)) - e.OtpExpiry = cast.IntToUint(remoteConfig.MailerOtpExp) - e.SecurePasswordChange = ValOrDefault(remoteConfig.SecurityUpdatePasswordRequireReauthentication, false) - e.MaxFrequency = time.Duration(ValOrDefault(remoteConfig.SmtpMaxFrequency, 0)) * time.Second - e.Smtp.fromAuthConfig(remoteConfig) - if len(e.Template) == 0 { - return - } - if t, ok := e.Template["invite"]; ok { - if t.Subject != nil { - if value, err := remoteConfig.MailerSubjectsInvite.Get(); err == nil { - t.Subject = &value - } else { - t.Subject = nil - } + if len(e.Template) > 0 { + var tmpl *emailTemplate + tmpl = cast.Ptr(e.Template["invite"]) + if tmpl.Subject != nil { + body.MailerSubjectsInvite = nullable.NewNullableWithValue(*tmpl.Subject) } - if t.Content != nil { - if value, err := remoteConfig.MailerTemplatesInviteContent.Get(); err == nil { - t.Content = &value - } else { - t.Content = nil - } + if tmpl.Content != nil { + body.MailerTemplatesInviteContent = nullable.NewNullableWithValue(*tmpl.Content) } - e.Template["invite"] = t - } - if t, ok := e.Template["confirmation"]; ok { - if t.Subject != nil { - if value, err := remoteConfig.MailerSubjectsConfirmation.Get(); err == nil { - t.Subject = &value - } else { - t.Subject = nil - } + tmpl = cast.Ptr(e.Template["confirmation"]) + if tmpl.Subject != nil { + body.MailerSubjectsConfirmation = nullable.NewNullableWithValue(*tmpl.Subject) } - if t.Content != nil { - if value, err := remoteConfig.MailerTemplatesConfirmationContent.Get(); err == nil { - t.Content = &value - } else { - t.Content = nil - } + if tmpl.Content != nil { + body.MailerTemplatesConfirmationContent = nullable.NewNullableWithValue(*tmpl.Content) + } + tmpl = cast.Ptr(e.Template["recovery"]) + if tmpl.Subject != nil { + body.MailerSubjectsRecovery = nullable.NewNullableWithValue(*tmpl.Subject) + } + if tmpl.Content != nil { + body.MailerTemplatesRecoveryContent = nullable.NewNullableWithValue(*tmpl.Content) + } + tmpl = cast.Ptr(e.Template["magic_link"]) + if tmpl.Subject != nil { + body.MailerSubjectsMagicLink = nullable.NewNullableWithValue(*tmpl.Subject) + } + if tmpl.Content != nil { + body.MailerTemplatesMagicLinkContent = nullable.NewNullableWithValue(*tmpl.Content) + } + tmpl = cast.Ptr(e.Template["email_change"]) + if tmpl.Subject != nil { + body.MailerSubjectsEmailChange = nullable.NewNullableWithValue(*tmpl.Subject) + } + if tmpl.Content != nil { + body.MailerTemplatesEmailChangeContent = nullable.NewNullableWithValue(*tmpl.Content) + } + tmpl = cast.Ptr(e.Template["reauthentication"]) + if tmpl.Subject != nil { + body.MailerSubjectsReauthentication = nullable.NewNullableWithValue(*tmpl.Subject) + } + if tmpl.Content != nil { + body.MailerTemplatesReauthenticationContent = nullable.NewNullableWithValue(*tmpl.Content) } - e.Template["confirmation"] = t } - if t, ok := e.Template["recovery"]; ok { - if t.Subject != nil { - if value, err := remoteConfig.MailerSubjectsRecovery.Get(); err == nil { - t.Subject = &value - } else { - t.Subject = nil + // Notifications + if len(e.Notification) > 0 { + if n, ok := e.Notification["password_changed"]; ok { + body.MailerNotificationsPasswordChangedEnabled = nullable.NewNullableWithValue(n.Enabled) + if n.Subject != nil { + body.MailerSubjectsPasswordChangedNotification = nullable.NewNullableWithValue(*n.Subject) } - } - if t.Content != nil { - if value, err := remoteConfig.MailerTemplatesRecoveryContent.Get(); err == nil { - t.Content = &value - } else { - t.Content = nil + if n.Content != nil { + body.MailerTemplatesPasswordChangedNotificationContent = nullable.NewNullableWithValue(*n.Content) } } - e.Template["recovery"] = t - } - if t, ok := e.Template["magic_link"]; ok { - if t.Subject != nil { - if value, err := remoteConfig.MailerSubjectsMagicLink.Get(); err == nil { - t.Subject = &value - } else { - t.Subject = nil + if n, ok := e.Notification["email_changed"]; ok { + body.MailerNotificationsEmailChangedEnabled = nullable.NewNullableWithValue(n.Enabled) + if n.Subject != nil { + body.MailerSubjectsEmailChangedNotification = nullable.NewNullableWithValue(*n.Subject) + } + if n.Content != nil { + body.MailerTemplatesEmailChangedNotificationContent = nullable.NewNullableWithValue(*n.Content) } } - if t.Content != nil { - if value, err := remoteConfig.MailerTemplatesMagicLinkContent.Get(); err == nil { - t.Content = &value - } else { - t.Content = nil + if n, ok := e.Notification["phone_changed"]; ok { + body.MailerNotificationsPhoneChangedEnabled = nullable.NewNullableWithValue(n.Enabled) + if n.Subject != nil { + body.MailerSubjectsPhoneChangedNotification = nullable.NewNullableWithValue(*n.Subject) + } + if n.Content != nil { + body.MailerTemplatesPhoneChangedNotificationContent = nullable.NewNullableWithValue(*n.Content) } } - e.Template["magic_link"] = t - } - if t, ok := e.Template["email_change"]; ok { - if t.Subject != nil { - if value, err := remoteConfig.MailerSubjectsEmailChange.Get(); err == nil { - t.Subject = &value - } else { - t.Subject = nil + if n, ok := e.Notification["identity_linked"]; ok { + body.MailerNotificationsIdentityLinkedEnabled = nullable.NewNullableWithValue(n.Enabled) + if n.Subject != nil { + body.MailerSubjectsIdentityLinkedNotification = nullable.NewNullableWithValue(*n.Subject) + } + if n.Content != nil { + body.MailerTemplatesIdentityLinkedNotificationContent = nullable.NewNullableWithValue(*n.Content) } } - if t.Content != nil { - if value, err := remoteConfig.MailerTemplatesEmailChangeContent.Get(); err == nil { - t.Content = &value - } else { - t.Content = nil + if n, ok := e.Notification["identity_unlinked"]; ok { + body.MailerNotificationsIdentityUnlinkedEnabled = nullable.NewNullableWithValue(n.Enabled) + if n.Subject != nil { + body.MailerSubjectsIdentityUnlinkedNotification = nullable.NewNullableWithValue(*n.Subject) + } + if n.Content != nil { + body.MailerTemplatesIdentityUnlinkedNotificationContent = nullable.NewNullableWithValue(*n.Content) } } - e.Template["email_change"] = t - } - if t, ok := e.Template["reauthentication"]; ok { - if t.Subject != nil { - if value, err := remoteConfig.MailerSubjectsReauthentication.Get(); err == nil { - t.Subject = &value - } else { - t.Subject = nil + if n, ok := e.Notification["mfa_factor_enrolled"]; ok { + body.MailerNotificationsMfaFactorEnrolledEnabled = nullable.NewNullableWithValue(n.Enabled) + if n.Subject != nil { + body.MailerSubjectsMfaFactorEnrolledNotification = nullable.NewNullableWithValue(*n.Subject) + } + if n.Content != nil { + body.MailerTemplatesMfaFactorEnrolledNotificationContent = nullable.NewNullableWithValue(*n.Content) } } - if t.Content != nil { - if value, err := remoteConfig.MailerTemplatesReauthenticationContent.Get(); err == nil { - t.Content = &value - } else { - t.Content = nil + if n, ok := e.Notification["mfa_factor_unenrolled"]; ok { + body.MailerNotificationsMfaFactorUnenrolledEnabled = nullable.NewNullableWithValue(n.Enabled) + if n.Subject != nil { + body.MailerSubjectsMfaFactorUnenrolledNotification = nullable.NewNullableWithValue(*n.Subject) } + if n.Content != nil { + body.MailerTemplatesMfaFactorUnenrolledNotificationContent = nullable.NewNullableWithValue(*n.Content) + } + } + } +} + +func (e *email) fromAuthConfig(remoteConfig v1API.AuthConfigResponse) { + e.EnableSignup = ValOrDefault(remoteConfig.ExternalEmailEnabled, false) + e.DoubleConfirmChanges = ValOrDefault(remoteConfig.MailerSecureEmailChangeEnabled, false) + e.EnableConfirmations = !ValOrDefault(remoteConfig.MailerAutoconfirm, false) + e.OtpLength = cast.IntToUint(ValOrDefault(remoteConfig.MailerOtpLength, 0)) + e.OtpExpiry = cast.IntToUint(remoteConfig.MailerOtpExp) + e.SecurePasswordChange = ValOrDefault(remoteConfig.SecurityUpdatePasswordRequireReauthentication, false) + e.MaxFrequency = time.Duration(ValOrDefault(remoteConfig.SmtpMaxFrequency, 0)) * time.Second + e.Smtp.fromAuthConfig(remoteConfig) + if len(e.Template) > 0 { + if t, ok := e.Template["invite"]; ok { + if t.Subject != nil { + if value, err := remoteConfig.MailerSubjectsInvite.Get(); err == nil { + t.Subject = &value + } else { + t.Subject = nil + } + } + if t.Content != nil { + if value, err := remoteConfig.MailerTemplatesInviteContent.Get(); err == nil { + t.Content = &value + } else { + t.Content = nil + } + } + e.Template["invite"] = t + } + if t, ok := e.Template["confirmation"]; ok { + if t.Subject != nil { + if value, err := remoteConfig.MailerSubjectsConfirmation.Get(); err == nil { + t.Subject = &value + } else { + t.Subject = nil + } + } + if t.Content != nil { + if value, err := remoteConfig.MailerTemplatesConfirmationContent.Get(); err == nil { + t.Content = &value + } else { + t.Content = nil + } + } + e.Template["confirmation"] = t + } + if t, ok := e.Template["recovery"]; ok { + if t.Subject != nil { + if value, err := remoteConfig.MailerSubjectsRecovery.Get(); err == nil { + t.Subject = &value + } else { + t.Subject = nil + } + } + if t.Content != nil { + if value, err := remoteConfig.MailerTemplatesRecoveryContent.Get(); err == nil { + t.Content = &value + } else { + t.Content = nil + } + } + e.Template["recovery"] = t + } + if t, ok := e.Template["magic_link"]; ok { + if t.Subject != nil { + if value, err := remoteConfig.MailerSubjectsMagicLink.Get(); err == nil { + t.Subject = &value + } else { + t.Subject = nil + } + } + if t.Content != nil { + if value, err := remoteConfig.MailerTemplatesMagicLinkContent.Get(); err == nil { + t.Content = &value + } else { + t.Content = nil + } + } + e.Template["magic_link"] = t + } + if t, ok := e.Template["email_change"]; ok { + if t.Subject != nil { + if value, err := remoteConfig.MailerSubjectsEmailChange.Get(); err == nil { + t.Subject = &value + } else { + t.Subject = nil + } + } + if t.Content != nil { + if value, err := remoteConfig.MailerTemplatesEmailChangeContent.Get(); err == nil { + t.Content = &value + } else { + t.Content = nil + } + } + e.Template["email_change"] = t + } + if t, ok := e.Template["reauthentication"]; ok { + if t.Subject != nil { + if value, err := remoteConfig.MailerSubjectsReauthentication.Get(); err == nil { + t.Subject = &value + } else { + t.Subject = nil + } + } + if t.Content != nil { + if value, err := remoteConfig.MailerTemplatesReauthenticationContent.Get(); err == nil { + t.Content = &value + } else { + t.Content = nil + } + } + e.Template["reauthentication"] = t + } + } + // Notifications + if len(e.Notification) > 0 { + if n, ok := e.Notification["password_changed"]; ok { + if value, err := remoteConfig.MailerNotificationsPasswordChangedEnabled.Get(); err == nil { + n.Enabled = value + } + if n.Subject != nil { + if value, err := remoteConfig.MailerSubjectsPasswordChangedNotification.Get(); err == nil { + n.Subject = &value + } else { + n.Subject = nil + } + } + if n.Content != nil { + if value, err := remoteConfig.MailerTemplatesPasswordChangedNotificationContent.Get(); err == nil { + n.Content = &value + } else { + n.Content = nil + } + } + e.Notification["password_changed"] = n + } + if n, ok := e.Notification["email_changed"]; ok { + if value, err := remoteConfig.MailerNotificationsEmailChangedEnabled.Get(); err == nil { + n.Enabled = value + } + if n.Subject != nil { + if value, err := remoteConfig.MailerSubjectsEmailChangedNotification.Get(); err == nil { + n.Subject = &value + } else { + n.Subject = nil + } + } + if n.Content != nil { + if value, err := remoteConfig.MailerTemplatesEmailChangedNotificationContent.Get(); err == nil { + n.Content = &value + } else { + n.Content = nil + } + } + e.Notification["email_changed"] = n + } + if n, ok := e.Notification["phone_changed"]; ok { + if value, err := remoteConfig.MailerNotificationsPhoneChangedEnabled.Get(); err == nil { + n.Enabled = value + } + if n.Subject != nil { + if value, err := remoteConfig.MailerSubjectsPhoneChangedNotification.Get(); err == nil { + n.Subject = &value + } else { + n.Subject = nil + } + } + if n.Content != nil { + if value, err := remoteConfig.MailerTemplatesPhoneChangedNotificationContent.Get(); err == nil { + n.Content = &value + } else { + n.Content = nil + } + } + e.Notification["phone_changed"] = n + } + if n, ok := e.Notification["identity_linked"]; ok { + if value, err := remoteConfig.MailerNotificationsIdentityLinkedEnabled.Get(); err == nil { + n.Enabled = value + } + if n.Subject != nil { + if value, err := remoteConfig.MailerSubjectsIdentityLinkedNotification.Get(); err == nil { + n.Subject = &value + } else { + n.Subject = nil + } + } + if n.Content != nil { + if value, err := remoteConfig.MailerTemplatesIdentityLinkedNotificationContent.Get(); err == nil { + n.Content = &value + } else { + n.Content = nil + } + } + e.Notification["identity_linked"] = n + } + if n, ok := e.Notification["identity_unlinked"]; ok { + if value, err := remoteConfig.MailerNotificationsIdentityUnlinkedEnabled.Get(); err == nil { + n.Enabled = value + } + if n.Subject != nil { + if value, err := remoteConfig.MailerSubjectsIdentityUnlinkedNotification.Get(); err == nil { + n.Subject = &value + } else { + n.Subject = nil + } + } + if n.Content != nil { + if value, err := remoteConfig.MailerTemplatesIdentityUnlinkedNotificationContent.Get(); err == nil { + n.Content = &value + } else { + n.Content = nil + } + } + e.Notification["identity_unlinked"] = n + } + if n, ok := e.Notification["mfa_factor_enrolled"]; ok { + if value, err := remoteConfig.MailerNotificationsMfaFactorEnrolledEnabled.Get(); err == nil { + n.Enabled = value + } + if n.Subject != nil { + if value, err := remoteConfig.MailerSubjectsMfaFactorEnrolledNotification.Get(); err == nil { + n.Subject = &value + } else { + n.Subject = nil + } + } + if n.Content != nil { + if value, err := remoteConfig.MailerTemplatesMfaFactorEnrolledNotificationContent.Get(); err == nil { + n.Content = &value + } else { + n.Content = nil + } + } + e.Notification["mfa_factor_enrolled"] = n + } + if n, ok := e.Notification["mfa_factor_unenrolled"]; ok { + if value, err := remoteConfig.MailerNotificationsMfaFactorUnenrolledEnabled.Get(); err == nil { + n.Enabled = value + } + if n.Subject != nil { + if value, err := remoteConfig.MailerSubjectsMfaFactorUnenrolledNotification.Get(); err == nil { + n.Subject = &value + } else { + n.Subject = nil + } + } + if n.Content != nil { + if value, err := remoteConfig.MailerTemplatesMfaFactorUnenrolledNotificationContent.Get(); err == nil { + n.Content = &value + } else { + n.Content = nil + } + } + e.Notification["mfa_factor_unenrolled"] = n } - e.Template["reauthentication"] = t } } diff --git a/pkg/config/auth_test.go b/pkg/config/auth_test.go index 8ec52f0fa..0d04c7609 100644 --- a/pkg/config/auth_test.go +++ b/pkg/config/auth_test.go @@ -552,6 +552,57 @@ func TestEmailDiff(t *testing.T) { Content: cast.Ptr("reauthentication-content"), }, }, + Notification: map[string]notification{ + "password_changed": { + Enabled: true, + emailTemplate: emailTemplate{ + Subject: cast.Ptr("password-changed-subject"), + Content: cast.Ptr("password-changed-content"), + }, + }, + "email_changed": { + Enabled: true, + emailTemplate: emailTemplate{ + Subject: cast.Ptr("email-changed-subject"), + Content: cast.Ptr("email-changed-content"), + }, + }, + "phone_changed": { + Enabled: true, + emailTemplate: emailTemplate{ + Subject: cast.Ptr("phone-changed-subject"), + Content: cast.Ptr("phone-changed-content"), + }, + }, + "identity_linked": { + Enabled: true, + emailTemplate: emailTemplate{ + Subject: cast.Ptr("identity-linked-subject"), + Content: cast.Ptr("identity-linked-content"), + }, + }, + "identity_unlinked": { + Enabled: true, + emailTemplate: emailTemplate{ + Subject: cast.Ptr("identity-unlinked-subject"), + Content: cast.Ptr("identity-unlinked-content"), + }, + }, + "mfa_factor_enrolled": { + Enabled: true, + emailTemplate: emailTemplate{ + Subject: cast.Ptr("mfa-enrolled-subject"), + Content: cast.Ptr("mfa-enrolled-content"), + }, + }, + "mfa_factor_unenrolled": { + Enabled: true, + emailTemplate: emailTemplate{ + Subject: cast.Ptr("mfa-unenrolled-subject"), + Content: cast.Ptr("mfa-unenrolled-content"), + }, + }, + }, Smtp: &smtp{ Enabled: true, Host: "smtp.sendgrid.net", @@ -596,6 +647,28 @@ func TestEmailDiff(t *testing.T) { MailerTemplatesEmailChangeContent: nullable.NewNullableWithValue("email-change-content"), MailerSubjectsReauthentication: nullable.NewNullableWithValue("reauthentication-subject"), MailerTemplatesReauthenticationContent: nullable.NewNullableWithValue("reauthentication-content"), + // Notifications + MailerNotificationsPasswordChangedEnabled: nullable.NewNullableWithValue(true), + MailerSubjectsPasswordChangedNotification: nullable.NewNullableWithValue("password-changed-subject"), + MailerTemplatesPasswordChangedNotificationContent: nullable.NewNullableWithValue("password-changed-content"), + MailerNotificationsEmailChangedEnabled: nullable.NewNullableWithValue(true), + MailerSubjectsEmailChangedNotification: nullable.NewNullableWithValue("email-changed-subject"), + MailerTemplatesEmailChangedNotificationContent: nullable.NewNullableWithValue("email-changed-content"), + MailerNotificationsPhoneChangedEnabled: nullable.NewNullableWithValue(true), + MailerSubjectsPhoneChangedNotification: nullable.NewNullableWithValue("phone-changed-subject"), + MailerTemplatesPhoneChangedNotificationContent: nullable.NewNullableWithValue("phone-changed-content"), + MailerNotificationsIdentityLinkedEnabled: nullable.NewNullableWithValue(true), + MailerSubjectsIdentityLinkedNotification: nullable.NewNullableWithValue("identity-linked-subject"), + MailerTemplatesIdentityLinkedNotificationContent: nullable.NewNullableWithValue("identity-linked-content"), + MailerNotificationsIdentityUnlinkedEnabled: nullable.NewNullableWithValue(true), + MailerSubjectsIdentityUnlinkedNotification: nullable.NewNullableWithValue("identity-unlinked-subject"), + MailerTemplatesIdentityUnlinkedNotificationContent: nullable.NewNullableWithValue("identity-unlinked-content"), + MailerNotificationsMfaFactorEnrolledEnabled: nullable.NewNullableWithValue(true), + MailerSubjectsMfaFactorEnrolledNotification: nullable.NewNullableWithValue("mfa-enrolled-subject"), + MailerTemplatesMfaFactorEnrolledNotificationContent: nullable.NewNullableWithValue("mfa-enrolled-content"), + MailerNotificationsMfaFactorUnenrolledEnabled: nullable.NewNullableWithValue(true), + MailerSubjectsMfaFactorUnenrolledNotification: nullable.NewNullableWithValue("mfa-unenrolled-subject"), + MailerTemplatesMfaFactorUnenrolledNotificationContent: nullable.NewNullableWithValue("mfa-unenrolled-content"), }) // Check error assert.NoError(t, err) @@ -633,6 +706,45 @@ func TestEmailDiff(t *testing.T) { Content: cast.Ptr(""), }, }, + Notification: map[string]notification{ + "password_changed": { + Enabled: true, + emailTemplate: emailTemplate{ + Subject: cast.Ptr("password-changed-subject"), + Content: cast.Ptr("password-changed-content"), + }, + }, + "email_changed": { + Enabled: true, + emailTemplate: emailTemplate{ + Subject: cast.Ptr("email-changed-subject"), + Content: cast.Ptr("email-changed-content"), + }, + }, + "phone_changed": { + Enabled: true, + emailTemplate: emailTemplate{ + Subject: cast.Ptr("phone-changed-subject"), + Content: cast.Ptr("phone-changed-content"), + }, + }, + "identity_linked": { + Enabled: true, + emailTemplate: emailTemplate{ + Subject: cast.Ptr("identity-linked-subject"), + Content: cast.Ptr("identity-linked-content"), + }, + }, + "identity_unlinked": { + Enabled: true, + }, + "mfa_factor_enrolled": { + Enabled: true, + }, + "mfa_factor_unenrolled": { + Enabled: true, + }, + }, Smtp: &smtp{ Enabled: true, Host: "smtp.sendgrid.net", @@ -663,6 +775,21 @@ func TestEmailDiff(t *testing.T) { MailerSubjectsRecovery: nullable.NewNullableWithValue("recovery-subject"), MailerSubjectsMagicLink: nullable.NewNullableWithValue("magic-link-subject"), MailerTemplatesEmailChangeContent: nullable.NewNullableWithValue("email-change-content"), + // Notifications + MailerNotificationsPasswordChangedEnabled: nullable.NewNullableWithValue(false), + MailerSubjectsPasswordChangedNotification: nullable.NewNullableWithValue("password-changed-subject"), + MailerTemplatesPasswordChangedNotificationContent: nullable.NewNullableWithValue("password-changed-content"), + MailerNotificationsEmailChangedEnabled: nullable.NewNullableWithValue(false), + MailerSubjectsEmailChangedNotification: nullable.NewNullableWithValue("email-changed-subject"), + MailerNotificationsPhoneChangedEnabled: nullable.NewNullableWithValue(false), + MailerTemplatesPhoneChangedNotificationContent: nullable.NewNullableWithValue("phone-changed-content"), + MailerNotificationsIdentityLinkedEnabled: nullable.NewNullableWithValue(false), + MailerNotificationsIdentityUnlinkedEnabled: nullable.NewNullableWithValue(false), + MailerSubjectsIdentityUnlinkedNotification: nullable.NewNullableWithValue("identity-unlinked-subject"), + MailerTemplatesIdentityUnlinkedNotificationContent: nullable.NewNullableWithValue("identity-unlinked-content"), + MailerNotificationsMfaFactorEnrolledEnabled: nullable.NewNullableWithValue(false), + MailerSubjectsMfaFactorEnrolledNotification: nullable.NewNullableWithValue("mfa-enrolled-subject"), + MailerNotificationsMfaFactorUnenrolledEnabled: nullable.NewNullableWithValue(false), }) // Check error assert.NoError(t, err) @@ -681,6 +808,15 @@ func TestEmailDiff(t *testing.T) { "email_change": {}, "reauthentication": {}, }, + Notification: map[string]notification{ + "password_changed": {}, + "email_changed": {}, + "phone_changed": {}, + "identity_linked": {}, + "identity_unlinked": {}, + "mfa_factor_enrolled": {}, + "mfa_factor_unenrolled": {}, + }, MaxFrequency: time.Minute, OtpLength: 8, OtpExpiry: 86400, @@ -713,6 +849,24 @@ func TestEmailDiff(t *testing.T) { MailerTemplatesEmailChangeContent: nullable.NewNullableWithValue("email-change-content"), MailerSubjectsReauthentication: nullable.NewNullableWithValue("reauthentication-subject"), MailerTemplatesReauthenticationContent: nullable.NewNullableWithValue("reauthentication-content"), + // Notifications + MailerNotificationsPasswordChangedEnabled: nullable.NewNullableWithValue(true), + MailerSubjectsPasswordChangedNotification: nullable.NewNullableWithValue("password-changed-subject"), + MailerTemplatesPasswordChangedNotificationContent: nullable.NewNullableWithValue("password-changed-content"), + MailerNotificationsEmailChangedEnabled: nullable.NewNullableWithValue(true), + MailerSubjectsEmailChangedNotification: nullable.NewNullableWithValue("email-changed-subject"), + MailerNotificationsPhoneChangedEnabled: nullable.NewNullableWithValue(true), + MailerTemplatesPhoneChangedNotificationContent: nullable.NewNullableWithValue("phone-changed-content"), + MailerNotificationsIdentityLinkedEnabled: nullable.NewNullableWithValue(true), + MailerNotificationsIdentityUnlinkedEnabled: nullable.NewNullableWithValue(true), + MailerSubjectsIdentityUnlinkedNotification: nullable.NewNullableWithValue("identity-unlinked-subject"), + MailerTemplatesIdentityUnlinkedNotificationContent: nullable.NewNullableWithValue("identity-unlinked-content"), + MailerNotificationsMfaFactorEnrolledEnabled: nullable.NewNullableWithValue(true), + MailerSubjectsMfaFactorEnrolledNotification: nullable.NewNullableWithValue("mfa-enrolled-subject"), + MailerTemplatesMfaFactorEnrolledNotificationContent: nullable.NewNullableWithValue("mfa-enrolled-content"), + MailerNotificationsMfaFactorUnenrolledEnabled: nullable.NewNullableWithValue(true), + MailerSubjectsMfaFactorUnenrolledNotification: nullable.NewNullableWithValue("mfa-unenrolled-subject"), + MailerTemplatesMfaFactorUnenrolledNotificationContent: nullable.NewNullableWithValue("mfa-unenrolled-content"), }) // Check error assert.NoError(t, err) @@ -731,6 +885,15 @@ func TestEmailDiff(t *testing.T) { "email_change": {}, "reauthentication": {}, }, + Notification: map[string]notification{ + "password_changed": {}, + "email_changed": {}, + "phone_changed": {}, + "identity_linked": {}, + "identity_unlinked": {}, + "mfa_factor_enrolled": {}, + "mfa_factor_unenrolled": {}, + }, Smtp: &smtp{ Enabled: false, Host: "smtp.sendgrid.net", diff --git a/pkg/config/config.go b/pkg/config/config.go index 531bc3dea..0a6945d78 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -259,6 +259,7 @@ func (a *auth) Clone() auth { copy.Email.Smtp = &mailer } copy.Email.Template = maps.Clone(a.Email.Template) + copy.Email.Notification = maps.Clone(a.Email.Notification) if a.Hook.MFAVerificationAttempt != nil { hook := *a.Hook.MFAVerificationAttempt copy.Hook.MFAVerificationAttempt = &hook @@ -372,7 +373,8 @@ func NewConfig(editors ...ConfigEditor) config { Auth: auth{ Image: Images.Gotrue, Email: email{ - Template: map[string]emailTemplate{}, + Template: map[string]emailTemplate{}, + Notification: map[string]notification{}, }, Sms: sms{ TestOTP: map[string]string{}, @@ -583,6 +585,10 @@ func (c *config) Load(path string, fsys fs.FS, overrides ...ConfigEditor) error } c.Api.ExternalUrl = apiUrl.String() } + // Set default JWT issuer if not configured + if len(c.Auth.JwtIssuer) == 0 { + c.Auth.JwtIssuer = c.Api.ExternalUrl + "/auth/v1" + } // Update image versions switch c.Db.MajorVersion { case 13: @@ -669,6 +675,12 @@ func (c *baseConfig) resolve(builder pathBuilder, fsys fs.FS) error { } c.Auth.Email.Template[name] = tmpl } + for name, tmpl := range c.Auth.Email.Notification { + if len(tmpl.ContentPath) > 0 && !filepath.IsAbs(tmpl.ContentPath) { + tmpl.ContentPath = filepath.Join(builder.SupabaseDirPath, tmpl.ContentPath) + } + c.Auth.Email.Notification[name] = tmpl + } // Update fallback configs for name, bucket := range c.Storage.Buckets { if bucket.FileSizeLimit == 0 { @@ -1022,17 +1034,34 @@ func (e *email) validate(fsys fs.FS) (err error) { for name, tmpl := range e.Template { if len(tmpl.ContentPath) == 0 { if tmpl.Content != nil { - return errors.Errorf("Invalid config for auth.email.%s.content: please use content_path instead", name) + return errors.Errorf("Invalid config for auth.email.template.%s.content: please use content_path instead", name) } continue } if content, err := fs.ReadFile(fsys, tmpl.ContentPath); err != nil { - return errors.Errorf("Invalid config for auth.email.%s.content_path: %w", name, err) + return errors.Errorf("Invalid config for auth.email.template.%s.content_path: %w", name, err) } else { tmpl.Content = cast.Ptr(string(content)) } e.Template[name] = tmpl } + for name, tmpl := range e.Notification { + if !tmpl.Enabled { + continue + } + if len(tmpl.ContentPath) == 0 { + if tmpl.Content != nil { + return errors.Errorf("Invalid config for auth.email.notification.%s.content: please use content_path instead", name) + } + continue + } + if content, err := fs.ReadFile(fsys, tmpl.ContentPath); err != nil { + return errors.Errorf("Invalid config for auth.email.notification.%s.content_path: %w", name, err) + } else { + tmpl.Content = cast.Ptr(string(content)) + } + e.Notification[name] = tmpl + } if e.Smtp != nil && e.Smtp.Enabled { if len(e.Smtp.Host) == 0 { return errors.New("Missing required field in config: auth.email.smtp.host") diff --git a/pkg/config/templates/Dockerfile b/pkg/config/templates/Dockerfile index 45abad217..cdb54911a 100644 --- a/pkg/config/templates/Dockerfile +++ b/pkg/config/templates/Dockerfile @@ -1,19 +1,19 @@ # Exposed for updates by .github/dependabot.yml -FROM supabase/postgres:17.6.1.029 AS pg +FROM supabase/postgres:17.6.1.040 AS pg # Append to ServiceImages when adding new dependencies below FROM library/kong:2.8.1 AS kong FROM axllent/mailpit:v1.22.3 AS mailpit FROM postgrest/postgrest:v13.0.7 AS postgrest FROM supabase/postgres-meta:v0.93.1 AS pgmeta -FROM supabase/studio:2025.10.27-sha-85b84e0 AS studio +FROM supabase/studio:2025.11.03-sha-ddedae5 AS studio FROM darthsim/imgproxy:v3.8.0 AS imgproxy -FROM supabase/edge-runtime:v1.69.15 AS edgeruntime +FROM supabase/edge-runtime:v1.69.23 AS edgeruntime FROM timberio/vector:0.28.1-alpine AS vector -FROM supabase/supavisor:2.7.3 AS supavisor -FROM supabase/gotrue:v2.180.0 AS gotrue -FROM supabase/realtime:v2.57.3 AS realtime -FROM supabase/storage-api:v1.28.2 AS storage -FROM supabase/logflare:1.23.2 AS logflare +FROM supabase/supavisor:2.7.4 AS supavisor +FROM supabase/gotrue:v2.182.1 AS gotrue +FROM supabase/realtime:v2.61.1 AS realtime +FROM supabase/storage-api:v1.29.0 AS storage +FROM supabase/logflare:1.25.1 AS logflare # Append to JobImages when adding new dependencies below FROM supabase/pgadmin-schema-diff:cli-0.0.5 AS differ FROM supabase/migra:3.0.1663481299 AS migra diff --git a/pkg/config/templates/config.toml b/pkg/config/templates/config.toml index ebdb83103..134be6085 100644 --- a/pkg/config/templates/config.toml +++ b/pkg/config/templates/config.toml @@ -125,6 +125,8 @@ site_url = "http://127.0.0.1:3000" additional_redirect_urls = ["https://127.0.0.1:3000"] # How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 (1 week). jwt_expiry = 3600 +# JWT issuer URL. If not set, defaults to the local API URL (http://127.0.0.1:/auth/v1). +# jwt_issuer = "" # Path to JWT signing key. DO NOT commit your signing keys file to git. # signing_keys_path = "./signing_keys.json" # If disabled, the refresh token will never expire. @@ -198,6 +200,12 @@ otp_expiry = 3600 # subject = "You have been invited" # content_path = "./supabase/templates/invite.html" +# Uncomment to customize notification email template +# [auth.email.notification.password_changed] +# enabled = true +# subject = "Your password has been changed" +# content_path = "./templates/password_changed_notification.html" + [auth.sms] # Allow/disallow new user signups via SMS to your project. enable_signup = false diff --git a/pkg/config/testdata/TestEmailDiff/local_disabled_remote_enabled.diff b/pkg/config/testdata/TestEmailDiff/local_disabled_remote_enabled.diff index 42105313b..430759247 100644 --- a/pkg/config/testdata/TestEmailDiff/local_disabled_remote_enabled.diff +++ b/pkg/config/testdata/TestEmailDiff/local_disabled_remote_enabled.diff @@ -1,7 +1,7 @@ diff remote[auth] local[auth] --- remote[auth] +++ local[auth] -@@ -44,13 +44,13 @@ +@@ -47,13 +47,13 @@ inactivity_timeout = "0s" [email] @@ -22,3 +22,36 @@ diff remote[auth] local[auth] [email.template] [email.template.confirmation] content_path = "" +@@ -69,25 +69,25 @@ + content_path = "" + [email.notification] + [email.notification.email_changed] +-enabled = true ++enabled = false + content_path = "" + [email.notification.identity_linked] +-enabled = true ++enabled = false + content_path = "" + [email.notification.identity_unlinked] +-enabled = true ++enabled = false + content_path = "" + [email.notification.mfa_factor_enrolled] +-enabled = true ++enabled = false + content_path = "" + [email.notification.mfa_factor_unenrolled] +-enabled = true ++enabled = false + content_path = "" + [email.notification.password_changed] +-enabled = true ++enabled = false + content_path = "" + [email.notification.phone_changed] +-enabled = true ++enabled = false + content_path = "" + + [sms] diff --git a/pkg/config/testdata/TestEmailDiff/local_enabled_remote_disabled.diff b/pkg/config/testdata/TestEmailDiff/local_enabled_remote_disabled.diff index 536c34712..388c75ed3 100644 --- a/pkg/config/testdata/TestEmailDiff/local_enabled_remote_disabled.diff +++ b/pkg/config/testdata/TestEmailDiff/local_enabled_remote_disabled.diff @@ -1,7 +1,7 @@ diff remote[auth] local[auth] --- remote[auth] +++ local[auth] -@@ -44,36 +44,44 @@ +@@ -47,62 +47,74 @@ inactivity_timeout = "0s" [email] @@ -41,6 +41,43 @@ diff remote[auth] local[auth] content_path = "" [email.template.recovery] +content = "recovery-content" + content_path = "" + [email.notification] + [email.notification.email_changed] +-enabled = false ++enabled = true + subject = "email-changed-subject" ++content = "email-changed-content" + content_path = "" + [email.notification.identity_linked] +-enabled = false ++enabled = true ++subject = "identity-linked-subject" ++content = "identity-linked-content" + content_path = "" + [email.notification.identity_unlinked] +-enabled = false ++enabled = true + content_path = "" + [email.notification.mfa_factor_enrolled] +-enabled = false ++enabled = true + content_path = "" + [email.notification.mfa_factor_unenrolled] +-enabled = false ++enabled = true + content_path = "" + [email.notification.password_changed] +-enabled = false ++enabled = true + subject = "password-changed-subject" + content = "password-changed-content" + content_path = "" + [email.notification.phone_changed] +-enabled = false ++enabled = true ++subject = "phone-changed-subject" + content = "phone-changed-content" content_path = "" [email.smtp] -enabled = false diff --git a/pkg/config/testdata/config.toml b/pkg/config/testdata/config.toml index cc3e7e907..1b68b6d73 100644 --- a/pkg/config/testdata/config.toml +++ b/pkg/config/testdata/config.toml @@ -207,6 +207,12 @@ sender_name = "Admin" subject = "You have been invited" content_path = "./supabase/templates/invite.html" +# Uncomment to customize notification email template +[auth.email.notification.password_changed] +enabled = true +subject = "Your password has been changed" +content_path = "./templates/password_changed_notification.html" + [auth.sms] # Allow/disallow new user signups via SMS to your project. enable_signup = true diff --git a/pkg/migration/scripts/dump_schema.sh b/pkg/migration/scripts/dump_schema.sh index d7fa91f7f..0b7c8fb90 100755 --- a/pkg/migration/scripts/dump_schema.sh +++ b/pkg/migration/scripts/dump_schema.sh @@ -48,6 +48,7 @@ pg_dump \ | sed -E "s/^REVOKE (.+) ON (.+) \"(${EXCLUDED_SCHEMAS:-})\"/-- &/" \ | sed -E 's/^(CREATE EXTENSION IF NOT EXISTS "pg_tle").+/\1;/' \ | sed -E 's/^(CREATE EXTENSION IF NOT EXISTS "pgsodium").+/\1;/' \ +| sed -E 's/^(CREATE EXTENSION IF NOT EXISTS "pgmq").+/\1;/' \ | sed -E 's/^COMMENT ON EXTENSION (.+)/-- &/' \ | sed -E 's/^CREATE POLICY "cron_job_/-- &/' \ | sed -E 's/^ALTER TABLE "cron"/-- &/' \