From c5d6325897f308d79683ff374954a32582e2ba2f Mon Sep 17 00:00:00 2001 From: Fabi <38692350+fgerschwiler@users.noreply.github.com> Date: Thu, 16 Dec 2021 15:21:37 +0100 Subject: [PATCH] feat: text query (#2735) * feat: change mail template to new query side * feat: adminapi message text * feat: adminapi message text * feat: adminapi message text * feat: message texts * feat: admin texts * feat: tests * feat: tests * feat: custom login text on adminapi * feat: custom login text * feat: custom login text * feat: message text prepare test * feat: login text texts * feat: custom login text * merge main * fix go.sum Co-authored-by: Livio Amstutz --- cmd/zitadel/main.go | 2 +- go.mod | 144 ++- .../eventsourcing/eventstore/iam.go | 105 +- .../eventsourcing/handler/custom_text.go | 133 -- .../eventsourcing/handler/handler.go | 6 - .../eventsourcing/handler/mail_template.go | 108 -- .../eventsourcing/handler/message_text.go | 121 -- .../eventsourcing/view/custom_texts.go | 65 - .../eventsourcing/view/mail_templates.go | 44 - .../eventsourcing/view/message_texts.go | 48 - internal/admin/repository/iam.go | 8 - internal/api/grpc/admin/custom_text.go | 52 +- internal/api/grpc/management/custom_text.go | 52 +- internal/api/grpc/text/custom_text.go | 19 + .../eventsourcing/eventstore/auth_request.go | 4 +- .../eventsourcing/eventstore/org.go | 6 +- .../eventsourcing/handler/custom_text.go | 132 -- .../eventsourcing/handler/handler.go | 1 - .../repository/eventsourcing/repository.go | 2 +- .../eventsourcing/view/custom_texts.go | 69 -- internal/command/iam_custom_message_text.go | 45 +- internal/iam/model/mail_template_view.go | 46 - .../iam/repository/view/mail_template_view.go | 32 - .../iam/repository/view/model/custom_text.go | 64 - .../repository/view/model/mail_template.go | 80 -- .../view/model/mail_template_query.go | 59 - .../eventsourcing/eventstore/org.go | 130 -- .../eventsourcing/handler/custom_text.go | 132 -- .../eventsourcing/handler/handler.go | 6 - .../eventsourcing/handler/mail_template.go | 110 -- .../eventsourcing/handler/message_text.go | 132 -- .../eventsourcing/view/custom_texts.go | 65 - .../eventsourcing/view/mail_templates.go | 53 - .../eventsourcing/view/message_texts.go | 57 - internal/management/repository/org.go | 10 - internal/notification/notification.go | 6 +- .../eventsourcing/handler/custom_text.go | 133 -- .../eventsourcing/handler/handler.go | 9 +- .../eventsourcing/handler/notification.go | 93 +- .../repository/eventsourcing/repository.go | 5 +- .../eventsourcing/spooler/spooler.go | 5 +- .../eventsourcing/view/custom_texts.go | 69 -- .../eventsourcing/view/mail_template.go | 10 - internal/query/custom_text.go | 1091 +++++++++++++++++ internal/query/custom_text_test.go | 221 ++++ internal/query/mail_template.go | 126 ++ internal/query/message_text.go | 324 +++++ internal/query/message_text_test.go | 167 +++ internal/query/projection/custom_text.go | 4 +- internal/query/projection/custom_text_test.go | 8 +- internal/query/projection/message_texts.go | 2 +- internal/query/query.go | 29 +- proto/zitadel/management.proto | 2 +- 53 files changed, 2250 insertions(+), 2196 deletions(-) delete mode 100644 internal/admin/repository/eventsourcing/handler/custom_text.go delete mode 100644 internal/admin/repository/eventsourcing/handler/mail_template.go delete mode 100644 internal/admin/repository/eventsourcing/handler/message_text.go delete mode 100644 internal/admin/repository/eventsourcing/view/custom_texts.go delete mode 100644 internal/admin/repository/eventsourcing/view/mail_templates.go delete mode 100644 internal/admin/repository/eventsourcing/view/message_texts.go delete mode 100644 internal/auth/repository/eventsourcing/handler/custom_text.go delete mode 100644 internal/auth/repository/eventsourcing/view/custom_texts.go delete mode 100644 internal/iam/model/mail_template_view.go delete mode 100644 internal/iam/repository/view/mail_template_view.go delete mode 100644 internal/iam/repository/view/model/mail_template.go delete mode 100644 internal/iam/repository/view/model/mail_template_query.go delete mode 100644 internal/management/repository/eventsourcing/handler/custom_text.go delete mode 100644 internal/management/repository/eventsourcing/handler/mail_template.go delete mode 100644 internal/management/repository/eventsourcing/handler/message_text.go delete mode 100644 internal/management/repository/eventsourcing/view/custom_texts.go delete mode 100644 internal/management/repository/eventsourcing/view/mail_templates.go delete mode 100644 internal/management/repository/eventsourcing/view/message_texts.go delete mode 100644 internal/notification/repository/eventsourcing/handler/custom_text.go delete mode 100644 internal/notification/repository/eventsourcing/view/custom_texts.go delete mode 100644 internal/notification/repository/eventsourcing/view/mail_template.go create mode 100644 internal/query/custom_text.go create mode 100644 internal/query/custom_text_test.go create mode 100644 internal/query/mail_template.go create mode 100644 internal/query/message_text.go create mode 100644 internal/query/message_text_test.go diff --git a/cmd/zitadel/main.go b/cmd/zitadel/main.go index 6944f2d367..d35a707ee9 100644 --- a/cmd/zitadel/main.go +++ b/cmd/zitadel/main.go @@ -193,7 +193,7 @@ func startZitadel(configPaths []string) { startUI(ctx, conf, authRepo, commands, queries, store) if *notificationEnabled { - notification.Start(ctx, conf.Notification, conf.SystemDefaults, commands, store != nil) + notification.Start(ctx, conf.Notification, conf.SystemDefaults, commands, queries, store != nil) } <-ctx.Done() diff --git a/go.mod b/go.mod index 1f7622d9c5..a9c08c654c 100644 --- a/go.mod +++ b/go.mod @@ -1,15 +1,12 @@ module github.com/caos/zitadel -go 1.16 +go 1.17 require ( cloud.google.com/go/storage v1.18.2 - cloud.google.com/go/trace v1.0.0 // indirect github.com/BurntSushi/toml v0.4.1 github.com/DATA-DOG/go-sqlmock v1.5.0 github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.0.0 - github.com/Masterminds/goutils v1.1.1 // indirect - github.com/Masterminds/semver v1.5.0 // indirect github.com/Masterminds/sprig v2.22.0+incompatible github.com/Masterminds/squirrel v1.5.2 github.com/VictoriaMetrics/fastcache v1.8.0 @@ -39,18 +36,12 @@ require ( github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 github.com/grpc-ecosystem/grpc-gateway v1.16.0 github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.1 - github.com/huandu/xstrings v1.3.2 // indirect github.com/jinzhu/gorm v1.9.16 - github.com/kevinburke/go-types v0.0.0-20210723172823-2deba1f80ba7 // indirect - github.com/kevinburke/rest v0.0.0-20210506044642-5611499aa33c // indirect github.com/kevinburke/twilio-go v0.0.0-20210327194925-1623146bcf73 github.com/lib/pq v1.10.4 github.com/lucasb-eyer/go-colorful v1.2.0 github.com/manifoldco/promptui v0.9.0 - github.com/mattn/go-colorable v0.1.12 // indirect; indirect github.com/mitchellh/copystructure v1.0.0 // indirect - github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect github.com/minio/minio-go/v7 v7.0.16 - github.com/mitchellh/copystructure v1.2.0 // indirect github.com/muesli/gamut v0.2.0 github.com/nicksnyder/go-i18n/v2 v2.1.2 github.com/nsf/jsondiff v0.0.0-20210926074059-1e845ec5d249 @@ -61,7 +52,6 @@ require ( github.com/sony/sonyflake v1.0.0 github.com/spf13/cobra v1.2.1 github.com/stretchr/testify v1.7.0 - github.com/ttacon/builder v0.0.0-20170518171403-c099f663e1c2 // indirect github.com/ttacon/libphonenumber v1.2.1 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.27.0 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.27.0 @@ -77,7 +67,6 @@ require ( golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c - golang.org/x/sys v0.0.0-20211205182925-97ca703d548d // indirect golang.org/x/text v0.3.7 golang.org/x/tools v0.1.8 google.golang.org/api v0.61.0 @@ -95,4 +84,135 @@ require ( sigs.k8s.io/yaml v1.3.0 ) +require ( + cloud.google.com/go v0.97.0 // indirect + cloud.google.com/go/trace v1.0.0 // indirect + github.com/AlecAivazis/survey/v2 v2.3.2 // indirect + github.com/AppsFlyer/go-sundheit v0.2.0 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver v1.5.0 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.0.0 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.8.2 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.2 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.0.2 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.3.2 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.5.0 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.5.2 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.9.2 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.6.2 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.11.1 // indirect + github.com/aws/smithy-go v1.9.0 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cenkalti/backoff/v4 v4.1.1 // indirect + github.com/census-instrumentation/opencensus-proto v0.2.1 // indirect + github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect + github.com/cloudflare/cfssl v0.0.0-20190726000631-633726f6bcb7 // indirect + github.com/cloudflare/cloudflare-go v0.23.0 // indirect + github.com/cloudscale-ch/cloudscale-go-sdk v1.7.1 // indirect + github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4 // indirect + github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect + github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 // indirect + github.com/dustin/go-humanize v1.0.0 // indirect + github.com/emirpasic/gods v1.12.0 // indirect + github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021 // indirect + github.com/evanphx/json-patch v4.11.0+incompatible // indirect + github.com/felixge/httpsnoop v1.0.2 // indirect + github.com/fsnotify/fsnotify v1.4.9 // indirect + github.com/fxamacker/cbor/v2 v2.2.0 // indirect + github.com/ghodss/yaml v1.0.0 // indirect + github.com/go-git/gcfg v1.5.0 // indirect + github.com/go-git/go-billy/v5 v5.0.0 // indirect + github.com/go-git/go-git/v5 v5.2.0 // indirect + github.com/go-logr/logr v0.4.0 // indirect + github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect + github.com/gobuffalo/flect v0.2.3 // indirect + github.com/gofrs/flock v0.8.1 // indirect + github.com/gofrs/uuid v3.2.0+incompatible // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/google/certificate-transparency-go v1.0.21 // indirect + github.com/google/go-cmp v0.5.6 // indirect + github.com/google/go-github/v31 v31.0.0 // indirect + github.com/google/go-querystring v1.0.0 // indirect + github.com/google/gofuzz v1.1.0 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/googleapis/gax-go/v2 v2.1.1 // indirect + github.com/googleapis/gnostic v0.5.5 // indirect + github.com/gorilla/handlers v1.5.1 // indirect + github.com/huandu/xstrings v1.3.2 // indirect + github.com/imdario/mergo v0.3.12 // indirect + github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/json-iterator/go v1.1.11 // indirect + github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect + github.com/kevinburke/go-types v0.0.0-20210723172823-2deba1f80ba7 // indirect + github.com/kevinburke/rest v0.0.0-20210506044642-5611499aa33c // indirect + github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd // indirect + github.com/klauspost/compress v1.13.5 // indirect + github.com/klauspost/cpuid v1.3.1 // indirect + github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect + github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect + github.com/mattn/go-colorable v0.1.12 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect + github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect + github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect + github.com/minio/md5-simd v1.1.0 // indirect + github.com/minio/sha256-simd v0.1.1 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/mapstructure v1.4.1 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/moby/spdystream v0.2.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.1 // indirect + github.com/muesli/clusters v0.0.0-20200529215643-2700303c1762 // indirect + github.com/muesli/kmeans v0.2.1 // indirect + github.com/pires/go-proxyproto v0.6.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_golang v1.11.0 // indirect + github.com/prometheus/client_model v0.2.0 // indirect + github.com/prometheus/common v0.26.0 // indirect + github.com/prometheus/procfs v0.6.0 // indirect + github.com/rs/xid v1.2.1 // indirect + github.com/satori/go.uuid v1.2.0 // indirect + github.com/sergi/go-diff v1.1.0 // indirect + github.com/sirupsen/logrus v1.8.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/ttacon/builder v0.0.0-20170518171403-c099f663e1c2 // indirect + github.com/wcharczuk/go-chart/v2 v2.1.0 // indirect + github.com/x448/float16 v0.8.4 // indirect + github.com/xanzy/ssh-agent v0.2.1 // indirect + github.com/xrash/smetrics v0.0.0-20200730060457-89a2a8a1fb0b // indirect + go.opencensus.io v0.23.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.2.0 // indirect + go.opentelemetry.io/otel/internal/metric v0.25.0 // indirect + go.opentelemetry.io/proto/otlp v0.10.0 // indirect + golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 // indirect + golang.org/x/mod v0.5.1 // indirect + golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect + golang.org/x/sys v0.0.0-20211205182925-97ca703d548d // indirect + golang.org/x/term v0.0.0-20210503060354-a79de5458b56 // indirect + golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect + google.golang.org/appengine v1.6.7 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/ini.v1 v1.62.0 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + k8s.io/component-base v0.22.2 // indirect + k8s.io/klog/v2 v2.9.0 // indirect + k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c // indirect + k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a // indirect + sigs.k8s.io/controller-tools v0.7.0 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.1.2 // indirect +) + replace github.com/gin-gonic/gin => github.com/gin-gonic/gin v1.7.4 diff --git a/internal/admin/repository/eventsourcing/eventstore/iam.go b/internal/admin/repository/eventsourcing/eventstore/iam.go index 4c88132f48..9e53490b14 100644 --- a/internal/admin/repository/eventsourcing/eventstore/iam.go +++ b/internal/admin/repository/eventsourcing/eventstore/iam.go @@ -2,34 +2,26 @@ package eventstore import ( "context" - "fmt" - "io/ioutil" "net/http" - "os" "strings" "sync" + "github.com/caos/logging" "golang.org/x/text/language" - "sigs.k8s.io/yaml" + admin_view "github.com/caos/zitadel/internal/admin/repository/eventsourcing/view" + "github.com/caos/zitadel/internal/config/systemdefaults" "github.com/caos/zitadel/internal/domain" v1 "github.com/caos/zitadel/internal/eventstore/v1" "github.com/caos/zitadel/internal/eventstore/v1/models" "github.com/caos/zitadel/internal/i18n" - iam_view "github.com/caos/zitadel/internal/iam/repository/view" - "github.com/caos/zitadel/internal/query" - "github.com/caos/zitadel/internal/user/repository/view/model" - - caos_errs "github.com/caos/zitadel/internal/errors" - - "github.com/caos/logging" - - admin_view "github.com/caos/zitadel/internal/admin/repository/eventsourcing/view" - "github.com/caos/zitadel/internal/config/systemdefaults" iam_model "github.com/caos/zitadel/internal/iam/model" + iam_view "github.com/caos/zitadel/internal/iam/repository/view" iam_es_model "github.com/caos/zitadel/internal/iam/repository/view/model" + "github.com/caos/zitadel/internal/query" "github.com/caos/zitadel/internal/telemetry/tracing" usr_model "github.com/caos/zitadel/internal/user/model" + "github.com/caos/zitadel/internal/user/repository/view/model" ) type IAMRepository struct { @@ -167,14 +159,6 @@ func (repo *IAMRepository) SearchDefaultIDPProviders(ctx context.Context, reques return result, nil } -func (repo *IAMRepository) GetDefaultMailTemplate(ctx context.Context) (*iam_model.MailTemplateView, error) { - template, err := repo.View.MailTemplateByAggregateID(repo.SystemDefaults.IamID) - if err != nil { - return nil, err - } - return iam_es_model.MailTemplateViewToModel(template), err -} - func (repo *IAMRepository) SearchIAMMembersx(ctx context.Context, request *iam_model.IAMMemberSearchRequest) (*iam_model.IAMMemberSearchResponse, error) { err := request.EnsureLimit(repo.SearchLimit) if err != nil { @@ -198,68 +182,6 @@ func (repo *IAMRepository) SearchIAMMembersx(ctx context.Context, request *iam_m return result, nil } -func (repo *IAMRepository) GetDefaultMessageText(ctx context.Context, textType, lang string) (*domain.CustomMessageText, error) { - repo.mutex.Lock() - defer repo.mutex.Unlock() - var err error - contents, ok := repo.NotificationTranslationFileContents[lang] - if !ok { - contents, err = repo.readTranslationFile(repo.NotificationDir, fmt.Sprintf("/i18n/%s.yaml", lang)) - if caos_errs.IsNotFound(err) { - contents, err = repo.readTranslationFile(repo.NotificationDir, fmt.Sprintf("/i18n/%s.yaml", repo.SystemDefaults.DefaultLanguage.String())) - } - if err != nil { - return nil, err - } - repo.NotificationTranslationFileContents[lang] = contents - } - messageTexts := new(domain.MessageTexts) - if err := yaml.Unmarshal(contents, messageTexts); err != nil { - return nil, caos_errs.ThrowInternal(err, "TEXT-3N9fs", "Errors.TranslationFile.ReadError") - } - return messageTexts.GetMessageTextByType(textType), nil -} - -func (repo *IAMRepository) GetCustomMessageText(ctx context.Context, textType, lang string) (*domain.CustomMessageText, error) { - texts, err := repo.View.CustomTextsByAggregateIDAndTemplateAndLand(repo.SystemDefaults.IamID, textType, lang) - if err != nil { - return nil, err - } - result := iam_es_model.CustomTextViewsToMessageDomain(repo.SystemDefaults.IamID, lang, texts) - result.Default = true - return result, err -} - -func (repo *IAMRepository) GetDefaultLoginTexts(ctx context.Context, lang string) (*domain.CustomLoginText, error) { - repo.mutex.Lock() - defer repo.mutex.Unlock() - contents, ok := repo.LoginTranslationFileContents[lang] - var err error - if !ok { - contents, err = repo.readTranslationFile(repo.LoginDir, fmt.Sprintf("/i18n/%s.yaml", lang)) - if caos_errs.IsNotFound(err) { - contents, err = repo.readTranslationFile(repo.LoginDir, fmt.Sprintf("/i18n/%s.yaml", repo.SystemDefaults.DefaultLanguage.String())) - } - if err != nil { - return nil, err - } - repo.LoginTranslationFileContents[lang] = contents - } - loginText := new(domain.CustomLoginText) - if err := yaml.Unmarshal(contents, loginText); err != nil { - return nil, caos_errs.ThrowInternal(err, "TEXT-GHR3Q", "Errors.TranslationFile.ReadError") - } - return loginText, nil -} - -func (repo *IAMRepository) GetCustomLoginTexts(ctx context.Context, lang string) (*domain.CustomLoginText, error) { - texts, err := repo.View.CustomTextsByAggregateIDAndTemplateAndLand(repo.SystemDefaults.IamID, domain.LoginCustomText, lang) - if err != nil { - return nil, err - } - return iam_es_model.CustomTextViewsToLoginDomain(repo.SystemDefaults.IamID, lang, texts), err -} - func (repo *IAMRepository) getIAMEvents(ctx context.Context, sequence uint64) ([]*models.Event, error) { query, err := iam_view.IAMByIDQuery(domain.IAMID, sequence) if err != nil { @@ -267,18 +189,3 @@ func (repo *IAMRepository) getIAMEvents(ctx context.Context, sequence uint64) ([ } return repo.Eventstore.FilterEvents(ctx, query) } - -func (repo *IAMRepository) readTranslationFile(dir http.FileSystem, filename string) ([]byte, error) { - r, err := dir.Open(filename) - if os.IsNotExist(err) { - return nil, caos_errs.ThrowNotFound(err, "TEXT-3n9fs", "Errors.TranslationFile.NotFound") - } - if err != nil { - return nil, caos_errs.ThrowInternal(err, "TEXT-93njw", "Errors.TranslationFile.ReadError") - } - contents, err := ioutil.ReadAll(r) - if err != nil { - return nil, caos_errs.ThrowInternal(err, "TEXT-l0fse", "Errors.TranslationFile.ReadError") - } - return contents, nil -} diff --git a/internal/admin/repository/eventsourcing/handler/custom_text.go b/internal/admin/repository/eventsourcing/handler/custom_text.go deleted file mode 100644 index 8e544b9944..0000000000 --- a/internal/admin/repository/eventsourcing/handler/custom_text.go +++ /dev/null @@ -1,133 +0,0 @@ -package handler - -import ( - "github.com/caos/logging" - - caos_errs "github.com/caos/zitadel/internal/errors" - "github.com/caos/zitadel/internal/eventstore/v1" - es_models "github.com/caos/zitadel/internal/eventstore/v1/models" - "github.com/caos/zitadel/internal/eventstore/v1/query" - "github.com/caos/zitadel/internal/eventstore/v1/spooler" - iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model" - iam_model "github.com/caos/zitadel/internal/iam/repository/view/model" - "github.com/caos/zitadel/internal/org/repository/eventsourcing/model" -) - -type CustomText struct { - handler - subscription *v1.Subscription -} - -func newCustomText(handler handler) *CustomText { - h := &CustomText{ - handler: handler, - } - - h.subscribe() - - return h -} - -func (m *CustomText) subscribe() { - m.subscription = m.es.Subscribe(m.AggregateTypes()...) - go func() { - for event := range m.subscription.Events { - query.ReduceEvent(m, event) - } - }() -} - -const ( - customTextTable = "adminapi.custom_texts" -) - -func (m *CustomText) Subscription() *v1.Subscription { - return m.subscription -} - -func (m *CustomText) ViewModel() string { - return customTextTable -} - -func (_ *CustomText) AggregateTypes() []es_models.AggregateType { - return []es_models.AggregateType{model.OrgAggregate, iam_es_model.IAMAggregate} -} - -func (p *CustomText) CurrentSequence() (uint64, error) { - sequence, err := p.view.GetLatestCustomTextSequence() - if err != nil { - return 0, err - } - return sequence.CurrentSequence, nil -} - -func (m *CustomText) EventQuery() (*es_models.SearchQuery, error) { - sequence, err := m.view.GetLatestCustomTextSequence() - if err != nil { - return nil, err - } - return es_models.NewSearchQuery(). - AggregateTypeFilter(m.AggregateTypes()...). - LatestSequenceFilter(sequence.CurrentSequence), nil -} - -func (m *CustomText) Reduce(event *es_models.Event) (err error) { - switch event.AggregateType { - case model.OrgAggregate, iam_es_model.IAMAggregate: - err = m.processCustomText(event) - } - return err -} - -func (m *CustomText) processCustomText(event *es_models.Event) (err error) { - customText := new(iam_model.CustomTextView) - switch event.Type { - case iam_es_model.CustomTextSet, model.CustomTextSet: - text := new(iam_model.CustomTextView) - err = text.SetData(event) - if err != nil { - return err - } - customText, err = m.view.CustomTextByIDs(event.AggregateID, text.Template, text.Key, text.Language) - if err != nil && !caos_errs.IsNotFound(err) { - return err - } - if caos_errs.IsNotFound(err) { - err = nil - customText = new(iam_model.CustomTextView) - customText.Language = text.Language - customText.Template = text.Template - customText.CreationDate = event.CreationDate - } - err = customText.AppendEvent(event) - case iam_es_model.CustomTextRemoved, model.CustomTextRemoved: - text := new(iam_model.CustomTextView) - err = text.SetData(event) - if err != nil { - return err - } - return m.view.DeleteCustomText(event.AggregateID, text.Template, text.Language, text.Key, event) - case iam_es_model.CustomTextMessageRemoved, model.CustomTextMessageRemoved: - text := new(iam_model.CustomTextView) - err = text.SetData(event) - if err != nil { - return err - } - return m.view.DeleteCustomTextTemplate(event.AggregateID, text.Template, text.Language, event) - default: - return m.view.ProcessedCustomTextSequence(event) - } - if err != nil { - return err - } - return m.view.PutCustomText(customText, event) -} - -func (m *CustomText) OnError(event *es_models.Event, err error) error { - logging.LogWithFields("SPOOL-3m912", "id", event.AggregateID).WithError(err).Warn("something went wrong in custom text handler") - return spooler.HandleError(event, err, m.view.GetLatestCustomTextFailedEvent, m.view.ProcessedCustomTextFailedEvent, m.view.ProcessedCustomTextSequence, m.errorCountUntilSkip) -} - -func (o *CustomText) OnSuccess() error { - return spooler.HandleSuccess(o.view.UpdateCustomTextSpoolerRunTimestamp) -} diff --git a/internal/admin/repository/eventsourcing/handler/handler.go b/internal/admin/repository/eventsourcing/handler/handler.go index 9aad062873..d4b2189767 100644 --- a/internal/admin/repository/eventsourcing/handler/handler.go +++ b/internal/admin/repository/eventsourcing/handler/handler.go @@ -46,12 +46,6 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es newExternalIDP( handler{view, bulkLimit, configs.cycleDuration("ExternalIDP"), errorCount, es}, defaults), - newMailTemplate( - handler{view, bulkLimit, configs.cycleDuration("MailTemplate"), errorCount, es}), - newMessageText( - handler{view, bulkLimit, configs.cycleDuration("MessageText"), errorCount, es}), - newCustomText( - handler{view, bulkLimit, configs.cycleDuration("CustomTexts"), errorCount, es}), } if static != nil { handlers = append(handlers, newStyling( diff --git a/internal/admin/repository/eventsourcing/handler/mail_template.go b/internal/admin/repository/eventsourcing/handler/mail_template.go deleted file mode 100644 index 8af4968d32..0000000000 --- a/internal/admin/repository/eventsourcing/handler/mail_template.go +++ /dev/null @@ -1,108 +0,0 @@ -package handler - -import ( - "github.com/caos/logging" - "github.com/caos/zitadel/internal/eventstore/v1" - - es_models "github.com/caos/zitadel/internal/eventstore/v1/models" - "github.com/caos/zitadel/internal/eventstore/v1/query" - "github.com/caos/zitadel/internal/eventstore/v1/spooler" - "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model" - iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model" - iam_model "github.com/caos/zitadel/internal/iam/repository/view/model" -) - -type MailTemplate struct { - handler - subscription *v1.Subscription -} - -func newMailTemplate(handler handler) *MailTemplate { - h := &MailTemplate{ - handler: handler, - } - - h.subscribe() - - return h -} - -func (m *MailTemplate) subscribe() { - m.subscription = m.es.Subscribe(m.AggregateTypes()...) - go func() { - for event := range m.subscription.Events { - query.ReduceEvent(m, event) - } - }() -} - -const ( - mailTemplateTable = "adminapi.mail_templates" -) - -func (m *MailTemplate) ViewModel() string { - return mailTemplateTable -} - -func (m *MailTemplate) Subscription() *v1.Subscription { - return m.subscription -} - -func (m *MailTemplate) AggregateTypes() []es_models.AggregateType { - return []es_models.AggregateType{iam_es_model.IAMAggregate} -} - -func (m *MailTemplate) CurrentSequence() (uint64, error) { - sequence, err := m.view.GetLatestMailTemplateSequence() - if err != nil { - return 0, err - } - return sequence.CurrentSequence, nil -} - -func (m *MailTemplate) EventQuery() (*es_models.SearchQuery, error) { - sequence, err := m.view.GetLatestMailTemplateSequence() - if err != nil { - return nil, err - } - return es_models.NewSearchQuery(). - AggregateTypeFilter(m.AggregateTypes()...). - LatestSequenceFilter(sequence.CurrentSequence), nil -} - -func (m *MailTemplate) Reduce(event *es_models.Event) (err error) { - switch event.AggregateType { - case model.IAMAggregate: - err = m.processMailTemplate(event) - } - return err -} - -func (m *MailTemplate) processMailTemplate(event *es_models.Event) (err error) { - template := new(iam_model.MailTemplateView) - switch event.Type { - case model.MailTemplateAdded: - err = template.AppendEvent(event) - case model.MailTemplateChanged: - template, err = m.view.MailTemplateByAggregateID(event.AggregateID) - if err != nil { - return err - } - err = template.AppendEvent(event) - default: - return m.view.ProcessedMailTemplateSequence(event) - } - if err != nil { - return err - } - return m.view.PutMailTemplate(template, event) -} - -func (m *MailTemplate) OnError(event *es_models.Event, err error) error { - logging.LogWithFields("SPOOL-Wj8sf", "id", event.AggregateID).WithError(err).Warn("something went wrong in label template handler") - return spooler.HandleError(event, err, m.view.GetLatestMailTemplateFailedEvent, m.view.ProcessedMailTemplateFailedEvent, m.view.ProcessedMailTemplateSequence, m.errorCountUntilSkip) -} - -func (m *MailTemplate) OnSuccess() error { - return spooler.HandleSuccess(m.view.UpdateMailTemplateSpoolerRunTimestamp) -} diff --git a/internal/admin/repository/eventsourcing/handler/message_text.go b/internal/admin/repository/eventsourcing/handler/message_text.go deleted file mode 100644 index 05083427e3..0000000000 --- a/internal/admin/repository/eventsourcing/handler/message_text.go +++ /dev/null @@ -1,121 +0,0 @@ -package handler - -import ( - "github.com/caos/logging" - - caos_errs "github.com/caos/zitadel/internal/errors" - "github.com/caos/zitadel/internal/eventstore/v1" - - es_models "github.com/caos/zitadel/internal/eventstore/v1/models" - "github.com/caos/zitadel/internal/eventstore/v1/query" - "github.com/caos/zitadel/internal/eventstore/v1/spooler" - "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model" - iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model" - iam_model "github.com/caos/zitadel/internal/iam/repository/view/model" -) - -type MessageText struct { - handler - subscription *v1.Subscription -} - -func newMessageText(handler handler) *MessageText { - h := &MessageText{ - handler: handler, - } - - h.subscribe() - - return h -} - -func (m *MessageText) subscribe() { - m.subscription = m.es.Subscribe(m.AggregateTypes()...) - go func() { - for event := range m.subscription.Events { - query.ReduceEvent(m, event) - } - }() -} - -const ( - mailTextTable = "adminapi.message_texts" -) - -func (m *MessageText) ViewModel() string { - return mailTextTable -} - -func (m *MessageText) Subscription() *v1.Subscription { - return m.subscription -} - -func (_ *MessageText) AggregateTypes() []es_models.AggregateType { - return []es_models.AggregateType{iam_es_model.IAMAggregate} -} - -func (p *MessageText) CurrentSequence() (uint64, error) { - sequence, err := p.view.GetLatestMessageTextSequence() - if err != nil { - return 0, err - } - return sequence.CurrentSequence, nil -} - -func (m *MessageText) EventQuery() (*es_models.SearchQuery, error) { - sequence, err := m.view.GetLatestMessageTextSequence() - if err != nil { - return nil, err - } - return es_models.NewSearchQuery(). - AggregateTypeFilter(m.AggregateTypes()...). - LatestSequenceFilter(sequence.CurrentSequence), nil -} - -func (m *MessageText) Reduce(event *es_models.Event) (err error) { - switch event.AggregateType { - case model.IAMAggregate: - err = m.processMessageText(event) - } - return err -} - -func (m *MessageText) processMessageText(event *es_models.Event) (err error) { - message := new(iam_model.MessageTextView) - switch event.Type { - case model.CustomTextSet, - model.CustomTextRemoved: - text := new(iam_model.CustomTextView) - err = text.SetData(event) - if err != nil { - return err - } - message, err = m.view.MessageTextByIDs(event.AggregateID, text.Template, text.Language) - if err != nil && !caos_errs.IsNotFound(err) { - return err - } - if caos_errs.IsNotFound(err) { - err = nil - message = new(iam_model.MessageTextView) - message.Language = text.Language - message.MessageTextType = text.Template - message.CreationDate = event.CreationDate - } - err = message.AppendEvent(event) - default: - return m.view.ProcessedMessageTextSequence(event) - } - if err != nil { - return err - } - return m.view.PutMessageText(message, event) -} - -func (m *MessageText) OnError(event *es_models.Event, err error) error { - logging.LogWithFields("HANDL-5jk84", "id", event.AggregateID).WithError(err).Warn("something went wrong in label mailText handler") - return spooler.HandleError(event, err, m.view.GetLatestMessageTextFailedEvent, m.view.ProcessedMessageTextFailedEvent, m.view.ProcessedMessageTextSequence, m.errorCountUntilSkip) -} - -func (o *MessageText) OnSuccess() error { - return spooler.HandleSuccess(o.view.UpdateMessageTextSpoolerRunTimestamp) -} diff --git a/internal/admin/repository/eventsourcing/view/custom_texts.go b/internal/admin/repository/eventsourcing/view/custom_texts.go deleted file mode 100644 index d9cbc0b564..0000000000 --- a/internal/admin/repository/eventsourcing/view/custom_texts.go +++ /dev/null @@ -1,65 +0,0 @@ -package view - -import ( - "github.com/caos/zitadel/internal/errors" - "github.com/caos/zitadel/internal/eventstore/v1/models" - "github.com/caos/zitadel/internal/iam/repository/view" - "github.com/caos/zitadel/internal/iam/repository/view/model" - global_view "github.com/caos/zitadel/internal/view/repository" -) - -const ( - customTextTable = "adminapi.custom_texts" -) - -func (v *View) CustomTextByIDs(aggregateID, template, lang, key string) (*model.CustomTextView, error) { - return view.CustomTextByIDs(v.Db, customTextTable, aggregateID, template, lang, key) -} - -func (v *View) CustomTextsByAggregateIDAndTemplateAndLand(aggregateID, template, lang string) ([]*model.CustomTextView, error) { - return view.GetCustomTexts(v.Db, customTextTable, aggregateID, template, lang) -} - -func (v *View) PutCustomText(template *model.CustomTextView, event *models.Event) error { - err := view.PutCustomText(v.Db, customTextTable, template) - if err != nil { - return err - } - return v.ProcessedCustomTextSequence(event) -} - -func (v *View) DeleteCustomText(aggregateID, textType, lang, key string, event *models.Event) error { - err := view.DeleteCustomText(v.Db, customTextTable, aggregateID, textType, lang, key) - if err != nil && !errors.IsNotFound(err) { - return err - } - return v.ProcessedCustomTextSequence(event) -} - -func (v *View) DeleteCustomTextTemplate(aggregateID, textType, lang string, event *models.Event) error { - err := view.DeleteCustomTextTemplate(v.Db, customTextTable, aggregateID, textType, lang) - if err != nil && !errors.IsNotFound(err) { - return err - } - return v.ProcessedCustomTextSequence(event) -} - -func (v *View) GetLatestCustomTextSequence() (*global_view.CurrentSequence, error) { - return v.latestSequence(customTextTable) -} - -func (v *View) ProcessedCustomTextSequence(event *models.Event) error { - return v.saveCurrentSequence(customTextTable, event) -} - -func (v *View) UpdateCustomTextSpoolerRunTimestamp() error { - return v.updateSpoolerRunSequence(customTextTable) -} - -func (v *View) GetLatestCustomTextFailedEvent(sequence uint64) (*global_view.FailedEvent, error) { - return v.latestFailedEvent(customTextTable, sequence) -} - -func (v *View) ProcessedCustomTextFailedEvent(failedEvent *global_view.FailedEvent) error { - return v.saveFailedEvent(failedEvent) -} diff --git a/internal/admin/repository/eventsourcing/view/mail_templates.go b/internal/admin/repository/eventsourcing/view/mail_templates.go deleted file mode 100644 index 8cc6f8cebd..0000000000 --- a/internal/admin/repository/eventsourcing/view/mail_templates.go +++ /dev/null @@ -1,44 +0,0 @@ -package view - -import ( - "github.com/caos/zitadel/internal/eventstore/v1/models" - "github.com/caos/zitadel/internal/iam/repository/view" - "github.com/caos/zitadel/internal/iam/repository/view/model" - global_view "github.com/caos/zitadel/internal/view/repository" -) - -const ( - mailTemplateTable = "adminapi.mail_templates" -) - -func (v *View) MailTemplateByAggregateID(aggregateID string) (*model.MailTemplateView, error) { - return view.GetMailTemplateByAggregateID(v.Db, mailTemplateTable, aggregateID) -} - -func (v *View) PutMailTemplate(template *model.MailTemplateView, event *models.Event) error { - err := view.PutMailTemplate(v.Db, mailTemplateTable, template) - if err != nil { - return err - } - return v.ProcessedMailTemplateSequence(event) -} - -func (v *View) GetLatestMailTemplateSequence() (*global_view.CurrentSequence, error) { - return v.latestSequence(mailTemplateTable) -} - -func (v *View) ProcessedMailTemplateSequence(event *models.Event) error { - return v.saveCurrentSequence(mailTemplateTable, event) -} - -func (v *View) UpdateMailTemplateSpoolerRunTimestamp() error { - return v.updateSpoolerRunSequence(mailTemplateTable) -} - -func (v *View) GetLatestMailTemplateFailedEvent(sequence uint64) (*global_view.FailedEvent, error) { - return v.latestFailedEvent(mailTemplateTable, sequence) -} - -func (v *View) ProcessedMailTemplateFailedEvent(failedEvent *global_view.FailedEvent) error { - return v.saveFailedEvent(failedEvent) -} diff --git a/internal/admin/repository/eventsourcing/view/message_texts.go b/internal/admin/repository/eventsourcing/view/message_texts.go deleted file mode 100644 index 17128d5e17..0000000000 --- a/internal/admin/repository/eventsourcing/view/message_texts.go +++ /dev/null @@ -1,48 +0,0 @@ -package view - -import ( - "github.com/caos/zitadel/internal/eventstore/v1/models" - "github.com/caos/zitadel/internal/iam/repository/view" - "github.com/caos/zitadel/internal/iam/repository/view/model" - global_view "github.com/caos/zitadel/internal/view/repository" -) - -const ( - messageTextTable = "adminapi.message_texts" -) - -func (v *View) MessageTexts(aggregateID string) ([]*model.MessageTextView, error) { - return view.GetMessageTexts(v.Db, messageTextTable, aggregateID) -} - -func (v *View) MessageTextByIDs(aggregateID, textType, lang string) (*model.MessageTextView, error) { - return view.GetMessageTextByIDs(v.Db, messageTextTable, aggregateID, textType, lang) -} - -func (v *View) PutMessageText(template *model.MessageTextView, event *models.Event) error { - err := view.PutMessageText(v.Db, messageTextTable, template) - if err != nil { - return err - } - return v.ProcessedMessageTextSequence(event) -} - -func (v *View) GetLatestMessageTextSequence() (*global_view.CurrentSequence, error) { - return v.latestSequence(messageTextTable) -} - -func (v *View) ProcessedMessageTextSequence(event *models.Event) error { - return v.saveCurrentSequence(messageTextTable, event) -} - -func (v *View) UpdateMessageTextSpoolerRunTimestamp() error { - return v.updateSpoolerRunSequence(messageTextTable) -} - -func (v *View) GetLatestMessageTextFailedEvent(sequence uint64) (*global_view.FailedEvent, error) { - return v.latestFailedEvent(messageTextTable, sequence) -} - -func (v *View) ProcessedMessageTextFailedEvent(failedEvent *global_view.FailedEvent) error { - return v.saveFailedEvent(failedEvent) -} diff --git a/internal/admin/repository/iam.go b/internal/admin/repository/iam.go index b2d978c2fd..bfa1a33969 100644 --- a/internal/admin/repository/iam.go +++ b/internal/admin/repository/iam.go @@ -5,7 +5,6 @@ import ( "golang.org/x/text/language" - "github.com/caos/zitadel/internal/domain" usr_model "github.com/caos/zitadel/internal/user/model" iam_model "github.com/caos/zitadel/internal/iam/model" @@ -24,11 +23,4 @@ type IAMRepository interface { IDPProvidersByIDPConfigID(ctx context.Context, idpConfigID string) ([]*iam_model.IDPProviderView, error) ExternalIDPsByIDPConfigID(ctx context.Context, idpConfigID string) ([]*usr_model.ExternalIDPView, error) - - GetDefaultMailTemplate(ctx context.Context) (*iam_model.MailTemplateView, error) - - GetDefaultMessageText(ctx context.Context, textType, language string) (*domain.CustomMessageText, error) - GetCustomMessageText(ctx context.Context, textType string, language string) (*domain.CustomMessageText, error) - GetDefaultLoginTexts(ctx context.Context, lang string) (*domain.CustomLoginText, error) - GetCustomLoginTexts(ctx context.Context, lang string) (*domain.CustomLoginText, error) } diff --git a/internal/api/grpc/admin/custom_text.go b/internal/api/grpc/admin/custom_text.go index ae43a003fc..5df7dc9a93 100644 --- a/internal/api/grpc/admin/custom_text.go +++ b/internal/api/grpc/admin/custom_text.go @@ -12,22 +12,22 @@ import ( ) func (s *Server) GetDefaultInitMessageText(ctx context.Context, req *admin_pb.GetDefaultInitMessageTextRequest) (*admin_pb.GetDefaultInitMessageTextResponse, error) { - msg, err := s.iam.GetDefaultMessageText(ctx, domain.InitCodeMessageType, req.Language) + msg, err := s.query.DefaultMessageTextByTypeAndLanguageFromFileSystem(domain.InitCodeMessageType, req.Language) if err != nil { return nil, err } return &admin_pb.GetDefaultInitMessageTextResponse{ - CustomText: text_grpc.DomainCustomMsgTextToPb(msg), + CustomText: text_grpc.ModelCustomMessageTextToPb(msg), }, nil } func (s *Server) GetCustomInitMessageText(ctx context.Context, req *admin_pb.GetCustomInitMessageTextRequest) (*admin_pb.GetCustomInitMessageTextResponse, error) { - msg, err := s.iam.GetCustomMessageText(ctx, domain.InitCodeMessageType, req.Language) + msg, err := s.query.CustomMessageTextByTypeAndLanguage(ctx, domain.IAMID, domain.InitCodeMessageType, req.Language) if err != nil { return nil, err } return &admin_pb.GetCustomInitMessageTextResponse{ - CustomText: text_grpc.DomainCustomMsgTextToPb(msg), + CustomText: text_grpc.ModelCustomMessageTextToPb(msg), }, nil } @@ -46,22 +46,22 @@ func (s *Server) SetDefaultInitMessageText(ctx context.Context, req *admin_pb.Se } func (s *Server) GetDefaultPasswordResetMessageText(ctx context.Context, req *admin_pb.GetDefaultPasswordResetMessageTextRequest) (*admin_pb.GetDefaultPasswordResetMessageTextResponse, error) { - msg, err := s.iam.GetDefaultMessageText(ctx, domain.PasswordResetMessageType, req.Language) + msg, err := s.query.DefaultMessageTextByTypeAndLanguageFromFileSystem(domain.PasswordResetMessageType, req.Language) if err != nil { return nil, err } return &admin_pb.GetDefaultPasswordResetMessageTextResponse{ - CustomText: text_grpc.DomainCustomMsgTextToPb(msg), + CustomText: text_grpc.ModelCustomMessageTextToPb(msg), }, nil } func (s *Server) GetCustomPasswordResetMessageText(ctx context.Context, req *admin_pb.GetCustomPasswordResetMessageTextRequest) (*admin_pb.GetCustomPasswordResetMessageTextResponse, error) { - msg, err := s.iam.GetCustomMessageText(ctx, domain.PasswordResetMessageType, req.Language) + msg, err := s.query.CustomMessageTextByTypeAndLanguage(ctx, domain.IAMID, domain.PasswordResetMessageType, req.Language) if err != nil { return nil, err } return &admin_pb.GetCustomPasswordResetMessageTextResponse{ - CustomText: text_grpc.DomainCustomMsgTextToPb(msg), + CustomText: text_grpc.ModelCustomMessageTextToPb(msg), }, nil } @@ -80,22 +80,22 @@ func (s *Server) SetDefaultPasswordResetMessageText(ctx context.Context, req *ad } func (s *Server) GetDefaultVerifyEmailMessageText(ctx context.Context, req *admin_pb.GetDefaultVerifyEmailMessageTextRequest) (*admin_pb.GetDefaultVerifyEmailMessageTextResponse, error) { - msg, err := s.iam.GetDefaultMessageText(ctx, domain.VerifyEmailMessageType, req.Language) + msg, err := s.query.DefaultMessageTextByTypeAndLanguageFromFileSystem(domain.VerifyEmailMessageType, req.Language) if err != nil { return nil, err } return &admin_pb.GetDefaultVerifyEmailMessageTextResponse{ - CustomText: text_grpc.DomainCustomMsgTextToPb(msg), + CustomText: text_grpc.ModelCustomMessageTextToPb(msg), }, nil } func (s *Server) GetCustomVerifyEmailMessageText(ctx context.Context, req *admin_pb.GetCustomVerifyEmailMessageTextRequest) (*admin_pb.GetCustomVerifyEmailMessageTextResponse, error) { - msg, err := s.iam.GetCustomMessageText(ctx, domain.VerifyEmailMessageType, req.Language) + msg, err := s.query.CustomMessageTextByTypeAndLanguage(ctx, domain.IAMID, domain.VerifyEmailMessageType, req.Language) if err != nil { return nil, err } return &admin_pb.GetCustomVerifyEmailMessageTextResponse{ - CustomText: text_grpc.DomainCustomMsgTextToPb(msg), + CustomText: text_grpc.ModelCustomMessageTextToPb(msg), }, nil } @@ -114,22 +114,22 @@ func (s *Server) SetDefaultVerifyEmailMessageText(ctx context.Context, req *admi } func (s *Server) GetDefaultVerifyPhoneMessageText(ctx context.Context, req *admin_pb.GetDefaultVerifyPhoneMessageTextRequest) (*admin_pb.GetDefaultVerifyPhoneMessageTextResponse, error) { - msg, err := s.iam.GetDefaultMessageText(ctx, domain.VerifyPhoneMessageType, req.Language) + msg, err := s.query.DefaultMessageTextByTypeAndLanguageFromFileSystem(domain.VerifyPhoneMessageType, req.Language) if err != nil { return nil, err } return &admin_pb.GetDefaultVerifyPhoneMessageTextResponse{ - CustomText: text_grpc.DomainCustomMsgTextToPb(msg), + CustomText: text_grpc.ModelCustomMessageTextToPb(msg), }, nil } func (s *Server) GetCustomVerifyPhoneMessageText(ctx context.Context, req *admin_pb.GetCustomVerifyPhoneMessageTextRequest) (*admin_pb.GetCustomVerifyPhoneMessageTextResponse, error) { - msg, err := s.iam.GetCustomMessageText(ctx, domain.VerifyPhoneMessageType, req.Language) + msg, err := s.query.CustomMessageTextByTypeAndLanguage(ctx, domain.IAMID, domain.VerifyPhoneMessageType, req.Language) if err != nil { return nil, err } return &admin_pb.GetCustomVerifyPhoneMessageTextResponse{ - CustomText: text_grpc.DomainCustomMsgTextToPb(msg), + CustomText: text_grpc.ModelCustomMessageTextToPb(msg), }, nil } @@ -148,22 +148,22 @@ func (s *Server) SetDefaultVerifyPhoneMessageText(ctx context.Context, req *admi } func (s *Server) GetDefaultDomainClaimedMessageText(ctx context.Context, req *admin_pb.GetDefaultDomainClaimedMessageTextRequest) (*admin_pb.GetDefaultDomainClaimedMessageTextResponse, error) { - msg, err := s.iam.GetDefaultMessageText(ctx, domain.DomainClaimedMessageType, req.Language) + msg, err := s.query.DefaultMessageTextByTypeAndLanguageFromFileSystem(domain.DomainClaimedMessageType, req.Language) if err != nil { return nil, err } return &admin_pb.GetDefaultDomainClaimedMessageTextResponse{ - CustomText: text_grpc.DomainCustomMsgTextToPb(msg), + CustomText: text_grpc.ModelCustomMessageTextToPb(msg), }, nil } func (s *Server) GetCustomDomainClaimedMessageText(ctx context.Context, req *admin_pb.GetCustomDomainClaimedMessageTextRequest) (*admin_pb.GetCustomDomainClaimedMessageTextResponse, error) { - msg, err := s.iam.GetCustomMessageText(ctx, domain.DomainClaimedMessageType, req.Language) + msg, err := s.query.CustomMessageTextByTypeAndLanguage(ctx, domain.IAMID, domain.DomainClaimedMessageType, req.Language) if err != nil { return nil, err } return &admin_pb.GetCustomDomainClaimedMessageTextResponse{ - CustomText: text_grpc.DomainCustomMsgTextToPb(msg), + CustomText: text_grpc.ModelCustomMessageTextToPb(msg), }, nil } @@ -182,22 +182,22 @@ func (s *Server) SetDefaultDomainClaimedMessageText(ctx context.Context, req *ad } func (s *Server) GetDefaultPasswordlessRegistrationMessageText(ctx context.Context, req *admin_pb.GetDefaultPasswordlessRegistrationMessageTextRequest) (*admin_pb.GetDefaultPasswordlessRegistrationMessageTextResponse, error) { - msg, err := s.iam.GetDefaultMessageText(ctx, domain.PasswordlessRegistrationMessageType, req.Language) + msg, err := s.query.DefaultMessageTextByTypeAndLanguageFromFileSystem(domain.PasswordlessRegistrationMessageType, req.Language) if err != nil { return nil, err } return &admin_pb.GetDefaultPasswordlessRegistrationMessageTextResponse{ - CustomText: text_grpc.DomainCustomMsgTextToPb(msg), + CustomText: text_grpc.ModelCustomMessageTextToPb(msg), }, nil } func (s *Server) GetCustomPasswordlessRegistrationMessageText(ctx context.Context, req *admin_pb.GetCustomPasswordlessRegistrationMessageTextRequest) (*admin_pb.GetCustomPasswordlessRegistrationMessageTextResponse, error) { - msg, err := s.iam.GetCustomMessageText(ctx, domain.PasswordlessRegistrationMessageType, req.Language) + msg, err := s.query.CustomMessageTextByTypeAndLanguage(ctx, domain.IAMID, domain.PasswordlessRegistrationMessageType, req.Language) if err != nil { return nil, err } return &admin_pb.GetCustomPasswordlessRegistrationMessageTextResponse{ - CustomText: text_grpc.DomainCustomMsgTextToPb(msg), + CustomText: text_grpc.ModelCustomMessageTextToPb(msg), }, nil } @@ -216,7 +216,7 @@ func (s *Server) SetDefaultPasswordlessRegistrationMessageText(ctx context.Conte } func (s *Server) GetDefaultLoginTexts(ctx context.Context, req *admin_pb.GetDefaultLoginTextsRequest) (*admin_pb.GetDefaultLoginTextsResponse, error) { - msg, err := s.iam.GetDefaultLoginTexts(ctx, req.Language) + msg, err := s.query.GetDefaultLoginTexts(ctx, req.Language) if err != nil { return nil, err } @@ -225,7 +225,7 @@ func (s *Server) GetDefaultLoginTexts(ctx context.Context, req *admin_pb.GetDefa }, nil } func (s *Server) GetCustomLoginTexts(ctx context.Context, req *admin_pb.GetCustomLoginTextsRequest) (*admin_pb.GetCustomLoginTextsResponse, error) { - msg, err := s.iam.GetCustomLoginTexts(ctx, req.Language) + msg, err := s.query.GetCustomLoginTexts(ctx, domain.IAMID, req.Language) if err != nil { return nil, err } diff --git a/internal/api/grpc/management/custom_text.go b/internal/api/grpc/management/custom_text.go index f737af0726..f539e8ab5d 100644 --- a/internal/api/grpc/management/custom_text.go +++ b/internal/api/grpc/management/custom_text.go @@ -13,22 +13,22 @@ import ( ) func (s *Server) GetCustomInitMessageText(ctx context.Context, req *mgmt_pb.GetCustomInitMessageTextRequest) (*mgmt_pb.GetCustomInitMessageTextResponse, error) { - msg, err := s.org.GetMessageText(ctx, authz.GetCtxData(ctx).OrgID, domain.InitCodeMessageType, req.Language) + msg, err := s.query.CustomMessageTextByTypeAndLanguage(ctx, authz.GetCtxData(ctx).OrgID, domain.InitCodeMessageType, req.Language) if err != nil { return nil, err } return &mgmt_pb.GetCustomInitMessageTextResponse{ - CustomText: text_grpc.DomainCustomMsgTextToPb(msg), + CustomText: text_grpc.ModelCustomMessageTextToPb(msg), }, nil } func (s *Server) GetDefaultInitMessageText(ctx context.Context, req *mgmt_pb.GetDefaultInitMessageTextRequest) (*mgmt_pb.GetDefaultInitMessageTextResponse, error) { - msg, err := s.org.GetDefaultMessageText(ctx, domain.InitCodeMessageType, req.Language) + msg, err := s.query.IAMMessageTextByTypeAndLanguage(ctx, domain.InitCodeMessageType, req.Language) if err != nil { return nil, err } return &mgmt_pb.GetDefaultInitMessageTextResponse{ - CustomText: text_grpc.DomainCustomMsgTextToPb(msg), + CustomText: text_grpc.ModelCustomMessageTextToPb(msg), }, nil } @@ -61,22 +61,22 @@ func (s *Server) ResetCustomInitMessageTextToDefault(ctx context.Context, req *m } func (s *Server) GetCustomPasswordResetMessageText(ctx context.Context, req *mgmt_pb.GetCustomPasswordResetMessageTextRequest) (*mgmt_pb.GetCustomPasswordResetMessageTextResponse, error) { - msg, err := s.org.GetMessageText(ctx, authz.GetCtxData(ctx).OrgID, domain.PasswordResetMessageType, req.Language) + msg, err := s.query.CustomMessageTextByTypeAndLanguage(ctx, authz.GetCtxData(ctx).OrgID, domain.PasswordResetMessageType, req.Language) if err != nil { return nil, err } return &mgmt_pb.GetCustomPasswordResetMessageTextResponse{ - CustomText: text_grpc.DomainCustomMsgTextToPb(msg), + CustomText: text_grpc.ModelCustomMessageTextToPb(msg), }, nil } func (s *Server) GetDefaultPasswordResetMessageText(ctx context.Context, req *mgmt_pb.GetDefaultPasswordResetMessageTextRequest) (*mgmt_pb.GetDefaultPasswordResetMessageTextResponse, error) { - msg, err := s.org.GetDefaultMessageText(ctx, domain.PasswordResetMessageType, req.Language) + msg, err := s.query.IAMMessageTextByTypeAndLanguage(ctx, domain.PasswordResetMessageType, req.Language) if err != nil { return nil, err } return &mgmt_pb.GetDefaultPasswordResetMessageTextResponse{ - CustomText: text_grpc.DomainCustomMsgTextToPb(msg), + CustomText: text_grpc.ModelCustomMessageTextToPb(msg), }, nil } @@ -109,22 +109,22 @@ func (s *Server) ResetCustomPasswordResetMessageTextToDefault(ctx context.Contex } func (s *Server) GetCustomVerifyEmailMessageText(ctx context.Context, req *mgmt_pb.GetCustomVerifyEmailMessageTextRequest) (*mgmt_pb.GetCustomVerifyEmailMessageTextResponse, error) { - msg, err := s.org.GetMessageText(ctx, authz.GetCtxData(ctx).OrgID, domain.VerifyEmailMessageType, req.Language) + msg, err := s.query.CustomMessageTextByTypeAndLanguage(ctx, authz.GetCtxData(ctx).OrgID, domain.VerifyEmailMessageType, req.Language) if err != nil { return nil, err } return &mgmt_pb.GetCustomVerifyEmailMessageTextResponse{ - CustomText: text_grpc.DomainCustomMsgTextToPb(msg), + CustomText: text_grpc.ModelCustomMessageTextToPb(msg), }, nil } func (s *Server) GetDefaultVerifyEmailMessageText(ctx context.Context, req *mgmt_pb.GetDefaultVerifyEmailMessageTextRequest) (*mgmt_pb.GetDefaultVerifyEmailMessageTextResponse, error) { - msg, err := s.org.GetDefaultMessageText(ctx, domain.VerifyEmailMessageType, req.Language) + msg, err := s.query.IAMMessageTextByTypeAndLanguage(ctx, domain.VerifyEmailMessageType, req.Language) if err != nil { return nil, err } return &mgmt_pb.GetDefaultVerifyEmailMessageTextResponse{ - CustomText: text_grpc.DomainCustomMsgTextToPb(msg), + CustomText: text_grpc.ModelCustomMessageTextToPb(msg), }, nil } @@ -157,22 +157,22 @@ func (s *Server) ResetCustomVerifyEmailMessageTextToDefault(ctx context.Context, } func (s *Server) GetCustomVerifyPhoneMessageText(ctx context.Context, req *mgmt_pb.GetCustomVerifyPhoneMessageTextRequest) (*mgmt_pb.GetCustomVerifyPhoneMessageTextResponse, error) { - msg, err := s.org.GetMessageText(ctx, authz.GetCtxData(ctx).OrgID, domain.VerifyPhoneMessageType, req.Language) + msg, err := s.query.CustomMessageTextByTypeAndLanguage(ctx, authz.GetCtxData(ctx).OrgID, domain.VerifyPhoneMessageType, req.Language) if err != nil { return nil, err } return &mgmt_pb.GetCustomVerifyPhoneMessageTextResponse{ - CustomText: text_grpc.DomainCustomMsgTextToPb(msg), + CustomText: text_grpc.ModelCustomMessageTextToPb(msg), }, nil } func (s *Server) GetDefaultVerifyPhoneMessageText(ctx context.Context, req *mgmt_pb.GetDefaultVerifyPhoneMessageTextRequest) (*mgmt_pb.GetDefaultVerifyPhoneMessageTextResponse, error) { - msg, err := s.org.GetDefaultMessageText(ctx, domain.VerifyPhoneMessageType, req.Language) + msg, err := s.query.IAMMessageTextByTypeAndLanguage(ctx, domain.VerifyPhoneMessageType, req.Language) if err != nil { return nil, err } return &mgmt_pb.GetDefaultVerifyPhoneMessageTextResponse{ - CustomText: text_grpc.DomainCustomMsgTextToPb(msg), + CustomText: text_grpc.ModelCustomMessageTextToPb(msg), }, nil } @@ -205,22 +205,22 @@ func (s *Server) ResetCustomVerifyPhoneMessageTextToDefault(ctx context.Context, } func (s *Server) GetCustomDomainClaimedMessageText(ctx context.Context, req *mgmt_pb.GetCustomDomainClaimedMessageTextRequest) (*mgmt_pb.GetCustomDomainClaimedMessageTextResponse, error) { - msg, err := s.org.GetMessageText(ctx, authz.GetCtxData(ctx).OrgID, domain.DomainClaimedMessageType, req.Language) + msg, err := s.query.CustomMessageTextByTypeAndLanguage(ctx, authz.GetCtxData(ctx).OrgID, domain.DomainClaimedMessageType, req.Language) if err != nil { return nil, err } return &mgmt_pb.GetCustomDomainClaimedMessageTextResponse{ - CustomText: text_grpc.DomainCustomMsgTextToPb(msg), + CustomText: text_grpc.ModelCustomMessageTextToPb(msg), }, nil } func (s *Server) GetDefaultDomainClaimedMessageText(ctx context.Context, req *mgmt_pb.GetDefaultDomainClaimedMessageTextRequest) (*mgmt_pb.GetDefaultDomainClaimedMessageTextResponse, error) { - msg, err := s.org.GetDefaultMessageText(ctx, domain.DomainClaimedMessageType, req.Language) + msg, err := s.query.IAMMessageTextByTypeAndLanguage(ctx, domain.DomainClaimedMessageType, req.Language) if err != nil { return nil, err } return &mgmt_pb.GetDefaultDomainClaimedMessageTextResponse{ - CustomText: text_grpc.DomainCustomMsgTextToPb(msg), + CustomText: text_grpc.ModelCustomMessageTextToPb(msg), }, nil } @@ -253,22 +253,22 @@ func (s *Server) ResetCustomDomainClaimedMessageTextToDefault(ctx context.Contex } func (s *Server) GetCustomPasswordlessRegistrationMessageText(ctx context.Context, req *mgmt_pb.GetCustomPasswordlessRegistrationMessageTextRequest) (*mgmt_pb.GetCustomPasswordlessRegistrationMessageTextResponse, error) { - msg, err := s.org.GetMessageText(ctx, authz.GetCtxData(ctx).OrgID, domain.PasswordlessRegistrationMessageType, req.Language) + msg, err := s.query.CustomMessageTextByTypeAndLanguage(ctx, authz.GetCtxData(ctx).OrgID, domain.PasswordlessRegistrationMessageType, req.Language) if err != nil { return nil, err } return &mgmt_pb.GetCustomPasswordlessRegistrationMessageTextResponse{ - CustomText: text_grpc.DomainCustomMsgTextToPb(msg), + CustomText: text_grpc.ModelCustomMessageTextToPb(msg), }, nil } func (s *Server) GetDefaultPasswordlessRegistrationMessageText(ctx context.Context, req *mgmt_pb.GetDefaultPasswordlessRegistrationMessageTextRequest) (*mgmt_pb.GetDefaultPasswordlessRegistrationMessageTextResponse, error) { - msg, err := s.org.GetDefaultMessageText(ctx, domain.PasswordlessRegistrationMessageType, req.Language) + msg, err := s.query.IAMMessageTextByTypeAndLanguage(ctx, domain.PasswordlessRegistrationMessageType, req.Language) if err != nil { return nil, err } return &mgmt_pb.GetDefaultPasswordlessRegistrationMessageTextResponse{ - CustomText: text_grpc.DomainCustomMsgTextToPb(msg), + CustomText: text_grpc.ModelCustomMessageTextToPb(msg), }, nil } @@ -301,7 +301,7 @@ func (s *Server) ResetCustomPasswordlessRegistrationMessageTextToDefault(ctx con } func (s *Server) GetCustomLoginTexts(ctx context.Context, req *mgmt_pb.GetCustomLoginTextsRequest) (*mgmt_pb.GetCustomLoginTextsResponse, error) { - msg, err := s.org.GetLoginTexts(ctx, authz.GetCtxData(ctx).OrgID, req.Language) + msg, err := s.query.GetCustomLoginTexts(ctx, authz.GetCtxData(ctx).OrgID, req.Language) if err != nil { return nil, err } @@ -311,7 +311,7 @@ func (s *Server) GetCustomLoginTexts(ctx context.Context, req *mgmt_pb.GetCustom } func (s *Server) GetDefaultLoginTexts(ctx context.Context, req *mgmt_pb.GetDefaultLoginTextsRequest) (*mgmt_pb.GetDefaultLoginTextsResponse, error) { - msg, err := s.org.GetDefaultLoginTexts(ctx, req.Language) + msg, err := s.query.IAMLoginTexts(ctx, req.Language) if err != nil { return nil, err } diff --git a/internal/api/grpc/text/custom_text.go b/internal/api/grpc/text/custom_text.go index 9b2a4720cd..523a30fd56 100644 --- a/internal/api/grpc/text/custom_text.go +++ b/internal/api/grpc/text/custom_text.go @@ -3,9 +3,28 @@ package text import ( "github.com/caos/zitadel/internal/api/grpc/object" "github.com/caos/zitadel/internal/domain" + "github.com/caos/zitadel/internal/query" text_pb "github.com/caos/zitadel/pkg/grpc/text" ) +func ModelCustomMessageTextToPb(msg *query.MessageText) *text_pb.MessageCustomText { + return &text_pb.MessageCustomText{ + Title: msg.Title, + PreHeader: msg.PreHeader, + Subject: msg.Subject, + Greeting: msg.Greeting, + Text: msg.Text, + ButtonText: msg.ButtonText, + FooterText: msg.Footer, + Details: object.ToViewDetailsPb( + msg.Sequence, + msg.CreationDate, + msg.ChangeDate, + msg.AggregateID, + ), + } +} + func DomainCustomMsgTextToPb(msg *domain.CustomMessageText) *text_pb.MessageCustomText { return &text_pb.MessageCustomText{ Title: msg.Title, diff --git a/internal/auth/repository/eventsourcing/eventstore/auth_request.go b/internal/auth/repository/eventsourcing/eventstore/auth_request.go index 02d151ea68..395848fe11 100644 --- a/internal/auth/repository/eventsourcing/eventstore/auth_request.go +++ b/internal/auth/repository/eventsourcing/eventstore/auth_request.go @@ -991,11 +991,11 @@ func labelPolicyToDomain(p *query.LabelPolicy) *domain.LabelPolicy { } func (repo *AuthRequestRepo) getLoginTexts(ctx context.Context, aggregateID string) ([]*domain.CustomText, error) { - loginTexts, err := repo.View.CustomTextsByAggregateIDAndTemplate(aggregateID, domain.LoginCustomText) + loginTexts, err := repo.Query.CustomTextListByTemplate(ctx, aggregateID, domain.LoginCustomText) if err != nil { return nil, err } - return iam_view_model.CustomTextViewsToDomain(loginTexts), err + return query.CustomTextsToDomain(loginTexts), err } func (repo *AuthRequestRepo) hasSucceededPage(ctx context.Context, request *domain.AuthRequest, provider applicationProvider) (bool, error) { diff --git a/internal/auth/repository/eventsourcing/eventstore/org.go b/internal/auth/repository/eventsourcing/eventstore/org.go index c0b620d9f3..678ee3be8e 100644 --- a/internal/auth/repository/eventsourcing/eventstore/org.go +++ b/internal/auth/repository/eventsourcing/eventstore/org.go @@ -41,15 +41,15 @@ func (repo *OrgRepository) GetMyPasswordComplexityPolicy(ctx context.Context) (* } func (repo *OrgRepository) GetLoginText(ctx context.Context, orgID string) ([]*domain.CustomText, error) { - loginTexts, err := repo.View.CustomTextsByAggregateIDAndTemplate(domain.IAMID, domain.LoginCustomText) + loginTexts, err := repo.Query.CustomTextListByTemplate(ctx, domain.IAMID, domain.LoginCustomText) if err != nil { return nil, err } - orgLoginTexts, err := repo.View.CustomTextsByAggregateIDAndTemplate(orgID, domain.LoginCustomText) + orgLoginTexts, err := repo.Query.CustomTextListByTemplate(ctx, orgID, domain.LoginCustomText) if err != nil { return nil, err } - return append(iam_view_model.CustomTextViewsToDomain(loginTexts), iam_view_model.CustomTextViewsToDomain(orgLoginTexts)...), nil + return append(query.CustomTextsToDomain(loginTexts), query.CustomTextsToDomain(orgLoginTexts)...), nil } func (p *OrgRepository) getIAMEvents(ctx context.Context, sequence uint64) ([]*models.Event, error) { diff --git a/internal/auth/repository/eventsourcing/handler/custom_text.go b/internal/auth/repository/eventsourcing/handler/custom_text.go deleted file mode 100644 index 93081ccd69..0000000000 --- a/internal/auth/repository/eventsourcing/handler/custom_text.go +++ /dev/null @@ -1,132 +0,0 @@ -package handler - -import ( - "github.com/caos/logging" - caos_errs "github.com/caos/zitadel/internal/errors" - "github.com/caos/zitadel/internal/eventstore/v1" - es_models "github.com/caos/zitadel/internal/eventstore/v1/models" - "github.com/caos/zitadel/internal/eventstore/v1/query" - "github.com/caos/zitadel/internal/eventstore/v1/spooler" - iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model" - iam_model "github.com/caos/zitadel/internal/iam/repository/view/model" - "github.com/caos/zitadel/internal/org/repository/eventsourcing/model" -) - -type CustomText struct { - handler - subscription *v1.Subscription -} - -func newCustomText(handler handler) *CustomText { - h := &CustomText{ - handler: handler, - } - - h.subscribe() - - return h -} - -func (m *CustomText) subscribe() { - m.subscription = m.es.Subscribe(m.AggregateTypes()...) - go func() { - for event := range m.subscription.Events { - query.ReduceEvent(m, event) - } - }() -} - -const ( - customTextTable = "auth.custom_texts" -) - -func (m *CustomText) ViewModel() string { - return customTextTable -} - -func (m *CustomText) Subscription() *v1.Subscription { - return m.subscription -} - -func (_ *CustomText) AggregateTypes() []es_models.AggregateType { - return []es_models.AggregateType{model.OrgAggregate, iam_es_model.IAMAggregate} -} - -func (p *CustomText) CurrentSequence() (uint64, error) { - sequence, err := p.view.GetLatestCustomTextSequence() - if err != nil { - return 0, err - } - return sequence.CurrentSequence, nil -} - -func (m *CustomText) EventQuery() (*es_models.SearchQuery, error) { - sequence, err := m.view.GetLatestCustomTextSequence() - if err != nil { - return nil, err - } - return es_models.NewSearchQuery(). - AggregateTypeFilter(m.AggregateTypes()...). - LatestSequenceFilter(sequence.CurrentSequence), nil -} - -func (m *CustomText) Reduce(event *es_models.Event) (err error) { - switch event.AggregateType { - case model.OrgAggregate, iam_es_model.IAMAggregate: - err = m.processCustomText(event) - } - return err -} - -func (m *CustomText) processCustomText(event *es_models.Event) (err error) { - customText := new(iam_model.CustomTextView) - switch event.Type { - case iam_es_model.CustomTextSet, model.CustomTextSet: - text := new(iam_model.CustomTextView) - err = text.SetData(event) - if err != nil { - return err - } - customText, err = m.view.CustomTextByIDs(event.AggregateID, text.Template, text.Key, text.Language) - if err != nil && !caos_errs.IsNotFound(err) { - return err - } - if caos_errs.IsNotFound(err) { - err = nil - customText = new(iam_model.CustomTextView) - customText.Language = text.Language - customText.Template = text.Template - customText.CreationDate = event.CreationDate - } - err = customText.AppendEvent(event) - case iam_es_model.CustomTextRemoved, model.CustomTextRemoved: - text := new(iam_model.CustomTextView) - err = text.SetData(event) - if err != nil { - return err - } - return m.view.DeleteCustomText(event.AggregateID, text.Template, text.Language, text.Key, event) - case iam_es_model.CustomTextMessageRemoved, model.CustomTextMessageRemoved: - text := new(iam_model.CustomTextView) - err = text.SetData(event) - if err != nil { - return err - } - return m.view.DeleteCustomTextTemplate(event.AggregateID, text.Template, text.Language, event) - default: - return m.view.ProcessedCustomTextSequence(event) - } - if err != nil { - return err - } - return m.view.PutCustomText(customText, event) -} - -func (m *CustomText) OnError(event *es_models.Event, err error) error { - logging.LogWithFields("SPOOL-3m0fs", "id", event.AggregateID).WithError(err).Warn("something went wrong in custom text handler") - return spooler.HandleError(event, err, m.view.GetLatestCustomTextFailedEvent, m.view.ProcessedCustomTextFailedEvent, m.view.ProcessedCustomTextSequence, m.errorCountUntilSkip) -} - -func (o *CustomText) OnSuccess() error { - return spooler.HandleSuccess(o.view.UpdateCustomTextSpoolerRunTimestamp) -} diff --git a/internal/auth/repository/eventsourcing/handler/handler.go b/internal/auth/repository/eventsourcing/handler/handler.go index 2ba7fe760f..fba698954e 100644 --- a/internal/auth/repository/eventsourcing/handler/handler.go +++ b/internal/auth/repository/eventsourcing/handler/handler.go @@ -56,7 +56,6 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es handler{view, bulkLimit, configs.cycleDuration("ExternalIDP"), errorCount, es}, systemDefaults), newRefreshToken(handler{view, bulkLimit, configs.cycleDuration("RefreshToken"), errorCount, es}), - newCustomText(handler{view, bulkLimit, configs.cycleDuration("CustomTexts"), errorCount, es}), newMetadata(handler{view, bulkLimit, configs.cycleDuration("Metadata"), errorCount, es}), newOrgProjectMapping(handler{view, bulkLimit, configs.cycleDuration("OrgProjectMapping"), errorCount, es}), } diff --git a/internal/auth/repository/eventsourcing/repository.go b/internal/auth/repository/eventsourcing/repository.go index 59b69aac7d..01f8946084 100644 --- a/internal/auth/repository/eventsourcing/repository.go +++ b/internal/auth/repository/eventsourcing/repository.go @@ -108,6 +108,7 @@ func Start(conf Config, authZ authz.Config, systemDefaults sd.SystemDefaults, co PrivacyPolicyProvider: queries, LabelPolicyProvider: queries, Command: command, + Query: queries, OrgViewProvider: queries, AuthRequests: authReq, View: view, @@ -119,7 +120,6 @@ func Start(conf Config, authZ authz.Config, systemDefaults sd.SystemDefaults, co IDPProviderViewProvider: view, LockoutPolicyViewProvider: queries, LoginPolicyViewProvider: queries, - Query: queries, UserGrantProvider: queryView, ProjectProvider: queryView, ApplicationProvider: queries, diff --git a/internal/auth/repository/eventsourcing/view/custom_texts.go b/internal/auth/repository/eventsourcing/view/custom_texts.go deleted file mode 100644 index 8d3acf6c19..0000000000 --- a/internal/auth/repository/eventsourcing/view/custom_texts.go +++ /dev/null @@ -1,69 +0,0 @@ -package view - -import ( - "github.com/caos/zitadel/internal/errors" - "github.com/caos/zitadel/internal/eventstore/v1/models" - "github.com/caos/zitadel/internal/iam/repository/view" - "github.com/caos/zitadel/internal/iam/repository/view/model" - global_view "github.com/caos/zitadel/internal/view/repository" -) - -const ( - customTextTable = "auth.custom_texts" -) - -func (v *View) CustomTextByIDs(aggregateID, template, lang, key string) (*model.CustomTextView, error) { - return view.CustomTextByIDs(v.Db, customTextTable, aggregateID, template, lang, key) -} - -func (v *View) CustomTextsByAggregateIDAndTemplateAndLand(aggregateID, template, lang string) ([]*model.CustomTextView, error) { - return view.GetCustomTexts(v.Db, customTextTable, aggregateID, template, lang) -} - -func (v *View) CustomTextsByAggregateIDAndTemplate(aggregateID, template string) ([]*model.CustomTextView, error) { - return view.GetCustomTextsByAggregateIDAndTemplate(v.Db, customTextTable, aggregateID, template) -} - -func (v *View) PutCustomText(template *model.CustomTextView, event *models.Event) error { - err := view.PutCustomText(v.Db, customTextTable, template) - if err != nil { - return err - } - return v.ProcessedCustomTextSequence(event) -} - -func (v *View) DeleteCustomText(aggregateID, textType, lang, key string, event *models.Event) error { - err := view.DeleteCustomText(v.Db, customTextTable, aggregateID, textType, lang, key) - if err != nil && !errors.IsNotFound(err) { - return err - } - return v.ProcessedCustomTextSequence(event) -} - -func (v *View) DeleteCustomTextTemplate(aggregateID, textType, lang string, event *models.Event) error { - err := view.DeleteCustomTextTemplate(v.Db, customTextTable, aggregateID, textType, lang) - if err != nil && !errors.IsNotFound(err) { - return err - } - return v.ProcessedCustomTextSequence(event) -} - -func (v *View) GetLatestCustomTextSequence() (*global_view.CurrentSequence, error) { - return v.latestSequence(customTextTable) -} - -func (v *View) ProcessedCustomTextSequence(event *models.Event) error { - return v.saveCurrentSequence(customTextTable, event) -} - -func (v *View) UpdateCustomTextSpoolerRunTimestamp() error { - return v.updateSpoolerRunSequence(customTextTable) -} - -func (v *View) GetLatestCustomTextFailedEvent(sequence uint64) (*global_view.FailedEvent, error) { - return v.latestFailedEvent(customTextTable, sequence) -} - -func (v *View) ProcessedCustomTextFailedEvent(failedEvent *global_view.FailedEvent) error { - return v.saveFailedEvent(failedEvent) -} diff --git a/internal/command/iam_custom_message_text.go b/internal/command/iam_custom_message_text.go index 96be6c8af9..41b3ac37be 100644 --- a/internal/command/iam_custom_message_text.go +++ b/internal/command/iam_custom_message_text.go @@ -3,12 +3,11 @@ package command import ( "context" - "golang.org/x/text/language" - "github.com/caos/zitadel/internal/domain" caos_errs "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/eventstore" "github.com/caos/zitadel/internal/repository/iam" + "golang.org/x/text/language" ) func (c *Commands) SetDefaultMessageText(ctx context.Context, messageText *domain.CustomMessageText) (*domain.ObjectDetails, error) { @@ -39,25 +38,53 @@ func (c *Commands) setDefaultMessageText(ctx context.Context, iamAgg *eventstore } events := make([]eventstore.EventPusher, 0) if existingMessageText.Greeting != msg.Greeting { - events = append(events, iam.NewCustomTextSetEvent(ctx, iamAgg, msg.MessageTextType, domain.MessageGreeting, msg.Greeting, msg.Language)) + if msg.Greeting != "" { + events = append(events, iam.NewCustomTextSetEvent(ctx, iamAgg, msg.MessageTextType, domain.MessageGreeting, msg.Greeting, msg.Language)) + } else { + events = append(events, iam.NewCustomTextRemovedEvent(ctx, iamAgg, msg.MessageTextType, domain.MessageGreeting, msg.Language)) + } } if existingMessageText.Subject != msg.Subject { - events = append(events, iam.NewCustomTextSetEvent(ctx, iamAgg, msg.MessageTextType, domain.MessageSubject, msg.Subject, msg.Language)) + if msg.Subject != "" { + events = append(events, iam.NewCustomTextSetEvent(ctx, iamAgg, msg.MessageTextType, domain.MessageSubject, msg.Subject, msg.Language)) + } else { + events = append(events, iam.NewCustomTextRemovedEvent(ctx, iamAgg, msg.MessageTextType, domain.MessageSubject, msg.Language)) + } } if existingMessageText.Title != msg.Title { - events = append(events, iam.NewCustomTextSetEvent(ctx, iamAgg, msg.MessageTextType, domain.MessageTitle, msg.Title, msg.Language)) + if msg.Title != "" { + events = append(events, iam.NewCustomTextSetEvent(ctx, iamAgg, msg.MessageTextType, domain.MessageTitle, msg.Title, msg.Language)) + } else { + events = append(events, iam.NewCustomTextRemovedEvent(ctx, iamAgg, msg.MessageTextType, domain.MessageTitle, msg.Language)) + } } if existingMessageText.PreHeader != msg.PreHeader { - events = append(events, iam.NewCustomTextSetEvent(ctx, iamAgg, msg.MessageTextType, domain.MessagePreHeader, msg.PreHeader, msg.Language)) + if msg.PreHeader != "" { + events = append(events, iam.NewCustomTextSetEvent(ctx, iamAgg, msg.MessageTextType, domain.MessagePreHeader, msg.PreHeader, msg.Language)) + } else { + events = append(events, iam.NewCustomTextRemovedEvent(ctx, iamAgg, msg.MessageTextType, domain.MessagePreHeader, msg.Language)) + } } if existingMessageText.Text != msg.Text { - events = append(events, iam.NewCustomTextSetEvent(ctx, iamAgg, msg.MessageTextType, domain.MessageText, msg.Text, msg.Language)) + if msg.Text != "" { + events = append(events, iam.NewCustomTextSetEvent(ctx, iamAgg, msg.MessageTextType, domain.MessageText, msg.Text, msg.Language)) + } else { + events = append(events, iam.NewCustomTextRemovedEvent(ctx, iamAgg, msg.MessageTextType, domain.MessageText, msg.Language)) + } } if existingMessageText.ButtonText != msg.ButtonText { - events = append(events, iam.NewCustomTextSetEvent(ctx, iamAgg, msg.MessageTextType, domain.MessageButtonText, msg.ButtonText, msg.Language)) + if msg.ButtonText != "" { + events = append(events, iam.NewCustomTextSetEvent(ctx, iamAgg, msg.MessageTextType, domain.MessageButtonText, msg.ButtonText, msg.Language)) + } else { + events = append(events, iam.NewCustomTextRemovedEvent(ctx, iamAgg, msg.MessageTextType, domain.MessageButtonText, msg.Language)) + } } if existingMessageText.FooterText != msg.FooterText { - events = append(events, iam.NewCustomTextSetEvent(ctx, iamAgg, msg.MessageTextType, domain.MessageFooterText, msg.FooterText, msg.Language)) + if msg.FooterText != "" { + events = append(events, iam.NewCustomTextSetEvent(ctx, iamAgg, msg.MessageTextType, domain.MessageFooterText, msg.FooterText, msg.Language)) + } else { + events = append(events, iam.NewCustomTextRemovedEvent(ctx, iamAgg, msg.MessageTextType, domain.MessageFooterText, msg.Language)) + } } return events, existingMessageText, nil } diff --git a/internal/iam/model/mail_template_view.go b/internal/iam/model/mail_template_view.go deleted file mode 100644 index b7a2532ab6..0000000000 --- a/internal/iam/model/mail_template_view.go +++ /dev/null @@ -1,46 +0,0 @@ -package model - -import ( - "github.com/caos/zitadel/internal/domain" - "time" -) - -type MailTemplateView struct { - AggregateID string - Template []byte - Default bool - - CreationDate time.Time - ChangeDate time.Time - Sequence uint64 -} - -type MailTemplateSearchRequest struct { - Offset uint64 - Limit uint64 - SortingColumn MailTemplateSearchKey - Asc bool - Queries []*MailTemplateSearchQuery -} - -type MailTemplateSearchKey int32 - -const ( - MailTemplateSearchKeyUnspecified MailTemplateSearchKey = iota - MailTemplateSearchKeyAggregateID -) - -type MailTemplateSearchQuery struct { - Key MailTemplateSearchKey - Method domain.SearchMethod - Value interface{} -} - -type MailTemplateSearchResponse struct { - Offset uint64 - Limit uint64 - TotalResult uint64 - Result []*MailTemplateView - Sequence uint64 - Timestamp time.Time -} diff --git a/internal/iam/repository/view/mail_template_view.go b/internal/iam/repository/view/mail_template_view.go deleted file mode 100644 index bf4f3a60e1..0000000000 --- a/internal/iam/repository/view/mail_template_view.go +++ /dev/null @@ -1,32 +0,0 @@ -package view - -import ( - "github.com/caos/zitadel/internal/domain" - caos_errs "github.com/caos/zitadel/internal/errors" - iam_model "github.com/caos/zitadel/internal/iam/model" - "github.com/caos/zitadel/internal/iam/repository/view/model" - "github.com/caos/zitadel/internal/view/repository" - "github.com/jinzhu/gorm" -) - -func GetMailTemplateByAggregateID(db *gorm.DB, table, aggregateID string) (*model.MailTemplateView, error) { - template := new(model.MailTemplateView) - aggregateIDQuery := &model.MailTemplateSearchQuery{Key: iam_model.MailTemplateSearchKeyAggregateID, Value: aggregateID, Method: domain.SearchMethodEquals} - query := repository.PrepareGetByQuery(table, aggregateIDQuery) - err := query(db, template) - if caos_errs.IsNotFound(err) { - return nil, caos_errs.ThrowNotFound(nil, "VIEW-iPnmU", "Errors.IAM.MailTemplate.NotExisting") - } - return template, err -} - -func PutMailTemplate(db *gorm.DB, table string, template *model.MailTemplateView) error { - save := repository.PrepareSave(table) - return save(db, template) -} - -func DeleteMailTemplate(db *gorm.DB, table, aggregateID string) error { - delete := repository.PrepareDeleteByKey(table, model.MailTemplateSearchKey(iam_model.MailTemplateSearchKeyAggregateID), aggregateID) - - return delete(db) -} diff --git a/internal/iam/repository/view/model/custom_text.go b/internal/iam/repository/view/model/custom_text.go index 3711e6afd3..93fa3b3b37 100644 --- a/internal/iam/repository/view/model/custom_text.go +++ b/internal/iam/repository/view/model/custom_text.go @@ -38,30 +38,6 @@ type CustomTextView struct { Sequence uint64 `json:"-" gorm:"column:sequence"` } -func CustomTextViewsToDomain(texts []*CustomTextView) []*domain.CustomText { - result := make([]*domain.CustomText, len(texts)) - for i, text := range texts { - result[i] = CustomTextViewToDomain(text) - } - return result -} - -func CustomTextViewToDomain(text *CustomTextView) *domain.CustomText { - lang := language.Make(text.Language) - return &domain.CustomText{ - ObjectRoot: models.ObjectRoot{ - AggregateID: text.AggregateID, - Sequence: text.Sequence, - CreationDate: text.CreationDate, - ChangeDate: text.ChangeDate, - }, - Template: text.Template, - Language: lang, - Key: text.Key, - Text: text.Text, - } -} - func (i *CustomTextView) AppendEvent(event *models.Event) (err error) { i.Sequence = event.Sequence switch event.Type { @@ -97,46 +73,6 @@ func (r *CustomTextView) IsMessageTemplate() bool { r.Template == domain.PasswordlessRegistrationMessageType } -func CustomTextViewsToMessageDomain(aggregateID, lang string, texts []*CustomTextView) *domain.CustomMessageText { - langTag := language.Make(lang) - result := &domain.CustomMessageText{ - ObjectRoot: models.ObjectRoot{ - AggregateID: aggregateID, - }, - Language: langTag, - } - for _, text := range texts { - if text.CreationDate.Before(result.CreationDate) { - result.CreationDate = text.CreationDate - } - if text.ChangeDate.After(result.ChangeDate) { - result.ChangeDate = text.ChangeDate - } - if text.Key == domain.MessageTitle { - result.Title = text.Text - } - if text.Key == domain.MessagePreHeader { - result.PreHeader = text.Text - } - if text.Key == domain.MessageSubject { - result.Subject = text.Text - } - if text.Key == domain.MessageGreeting { - result.Greeting = text.Text - } - if text.Key == domain.MessageText { - result.Text = text.Text - } - if text.Key == domain.MessageButtonText { - result.ButtonText = text.Text - } - if text.Key == domain.MessageFooterText { - result.FooterText = text.Text - } - } - return result -} - func CustomTextViewsToLoginDomain(aggregateID, lang string, texts []*CustomTextView) *domain.CustomLoginText { langTag := language.Make(lang) result := &domain.CustomLoginText{ diff --git a/internal/iam/repository/view/model/mail_template.go b/internal/iam/repository/view/model/mail_template.go deleted file mode 100644 index c2ff7758b7..0000000000 --- a/internal/iam/repository/view/model/mail_template.go +++ /dev/null @@ -1,80 +0,0 @@ -package model - -import ( - "encoding/json" - "time" - - org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model" - - es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model" - - "github.com/caos/logging" - caos_errs "github.com/caos/zitadel/internal/errors" - "github.com/caos/zitadel/internal/eventstore/v1/models" - "github.com/caos/zitadel/internal/iam/model" -) - -const ( - MailTemplateKeyAggregateID = "aggregate_id" -) - -type MailTemplateView struct { - AggregateID string `json:"-" gorm:"column:aggregate_id;primary_key"` - CreationDate time.Time `json:"-" gorm:"column:creation_date"` - ChangeDate time.Time `json:"-" gorm:"column:change_date"` - State int32 `json:"-" gorm:"column:mail_template_state"` - - Template []byte `json:"template" gorm:"column:template"` - Default bool `json:"-" gorm:"-"` - - Sequence uint64 `json:"-" gorm:"column:sequence"` -} - -func MailTemplateViewFromModel(template *model.MailTemplateView) *MailTemplateView { - return &MailTemplateView{ - AggregateID: template.AggregateID, - Sequence: template.Sequence, - CreationDate: template.CreationDate, - ChangeDate: template.ChangeDate, - Template: template.Template, - Default: template.Default, - } -} - -func MailTemplateViewToModel(template *MailTemplateView) *model.MailTemplateView { - return &model.MailTemplateView{ - AggregateID: template.AggregateID, - Sequence: template.Sequence, - CreationDate: template.CreationDate, - ChangeDate: template.ChangeDate, - Template: template.Template, - Default: template.Default, - } -} - -func (i *MailTemplateView) AppendEvent(event *models.Event) (err error) { - i.Sequence = event.Sequence - i.ChangeDate = event.CreationDate - switch event.Type { - case es_model.MailTemplateAdded, org_es_model.MailTemplateAdded: - i.setRootData(event) - i.CreationDate = event.CreationDate - err = i.SetData(event) - case es_model.MailTemplateChanged, org_es_model.MailTemplateChanged: - i.ChangeDate = event.CreationDate - err = i.SetData(event) - } - return err -} - -func (r *MailTemplateView) setRootData(event *models.Event) { - r.AggregateID = event.AggregateID -} - -func (r *MailTemplateView) SetData(event *models.Event) error { - if err := json.Unmarshal(event.Data, r); err != nil { - logging.Log("MODEL-YDZmZ").WithError(err).Error("could not unmarshal event data") - return caos_errs.ThrowInternal(err, "MODEL-sKWwO", "Could not unmarshal data") - } - return nil -} diff --git a/internal/iam/repository/view/model/mail_template_query.go b/internal/iam/repository/view/model/mail_template_query.go deleted file mode 100644 index 79b0838c85..0000000000 --- a/internal/iam/repository/view/model/mail_template_query.go +++ /dev/null @@ -1,59 +0,0 @@ -package model - -import ( - "github.com/caos/zitadel/internal/domain" - iam_model "github.com/caos/zitadel/internal/iam/model" - "github.com/caos/zitadel/internal/view/repository" -) - -type MailTemplateSearchRequest iam_model.MailTemplateSearchRequest -type MailTemplateSearchQuery iam_model.MailTemplateSearchQuery -type MailTemplateSearchKey iam_model.MailTemplateSearchKey - -func (req MailTemplateSearchRequest) GetLimit() uint64 { - return req.Limit -} - -func (req MailTemplateSearchRequest) GetOffset() uint64 { - return req.Offset -} - -func (req MailTemplateSearchRequest) GetSortingColumn() repository.ColumnKey { - if req.SortingColumn == iam_model.MailTemplateSearchKeyUnspecified { - return nil - } - return MailTemplateSearchKey(req.SortingColumn) -} - -func (req MailTemplateSearchRequest) GetAsc() bool { - return req.Asc -} - -func (req MailTemplateSearchRequest) GetQueries() []repository.SearchQuery { - result := make([]repository.SearchQuery, len(req.Queries)) - for i, q := range req.Queries { - result[i] = MailTemplateSearchQuery{Key: q.Key, Value: q.Value, Method: q.Method} - } - return result -} - -func (req MailTemplateSearchQuery) GetKey() repository.ColumnKey { - return MailTemplateSearchKey(req.Key) -} - -func (req MailTemplateSearchQuery) GetMethod() domain.SearchMethod { - return req.Method -} - -func (req MailTemplateSearchQuery) GetValue() interface{} { - return req.Value -} - -func (key MailTemplateSearchKey) ToColumnName() string { - switch iam_model.MailTemplateSearchKey(key) { - case iam_model.MailTemplateSearchKeyAggregateID: - return MailTemplateKeyAggregateID - default: - return "" - } -} diff --git a/internal/management/repository/eventsourcing/eventstore/org.go b/internal/management/repository/eventsourcing/eventstore/org.go index 4c3f8c2b40..6cf55fe1cf 100644 --- a/internal/management/repository/eventsourcing/eventstore/org.go +++ b/internal/management/repository/eventsourcing/eventstore/org.go @@ -3,7 +3,6 @@ package eventstore import ( "context" "encoding/json" - "fmt" "io/ioutil" "net/http" "os" @@ -14,7 +13,6 @@ import ( "github.com/caos/logging" "github.com/golang/protobuf/ptypes" "golang.org/x/text/language" - "sigs.k8s.io/yaml" "github.com/caos/zitadel/internal/api/authz" "github.com/caos/zitadel/internal/config/systemdefaults" @@ -206,134 +204,6 @@ func (repo *OrgRepository) SearchIDPProviders(ctx context.Context, request *iam_ return result, nil } -func (repo *OrgRepository) GetDefaultMailTemplate(ctx context.Context) (*iam_model.MailTemplateView, error) { - template, err := repo.View.MailTemplateByAggregateID(repo.SystemDefaults.IamID) - if err != nil { - return nil, err - } - template.Default = true - return iam_view_model.MailTemplateViewToModel(template), err -} - -func (repo *OrgRepository) GetMailTemplate(ctx context.Context) (*iam_model.MailTemplateView, error) { - template, err := repo.View.MailTemplateByAggregateID(authz.GetCtxData(ctx).OrgID) - if errors.IsNotFound(err) { - template, err = repo.View.MailTemplateByAggregateID(repo.SystemDefaults.IamID) - if err != nil { - return nil, err - } - template.Default = true - } - if err != nil { - return nil, err - } - return iam_view_model.MailTemplateViewToModel(template), err -} - -func (repo *OrgRepository) GetDefaultMessageText(ctx context.Context, textType, lang string) (*domain.CustomMessageText, error) { - repo.mutex.Lock() - defer repo.mutex.Unlock() - var err error - contents, ok := repo.NotificationTranslationFileContents[lang] - if !ok { - contents, err = repo.readTranslationFile(repo.NotificationDir, fmt.Sprintf("/i18n/%s.yaml", lang)) - if errors.IsNotFound(err) { - contents, err = repo.readTranslationFile(repo.NotificationDir, fmt.Sprintf("/i18n/%s.yaml", repo.SystemDefaults.DefaultLanguage.String())) - } - if err != nil { - return nil, err - } - repo.NotificationTranslationFileContents[lang] = contents - } - notificationTextMap := make(map[string]interface{}) - if err := yaml.Unmarshal(contents, ¬ificationTextMap); err != nil { - return nil, errors.ThrowInternal(err, "TEXT-093sd", "Errors.TranslationFile.ReadError") - } - texts, err := repo.View.CustomTextsByAggregateIDAndTemplateAndLand(repo.SystemDefaults.IamID, textType, lang) - if err != nil { - return nil, err - } - for _, text := range texts { - messageTextMap, ok := notificationTextMap[textType].(map[string]interface{}) - if !ok { - continue - } - messageTextMap[text.Key] = text.Text - } - jsonbody, err := json.Marshal(notificationTextMap) - if err != nil { - return nil, errors.ThrowInternal(err, "TEXT-02m8f", "Errors.TranslationFile.MergeError") - } - notificationText := new(domain.MessageTexts) - if err := json.Unmarshal(jsonbody, ¬ificationText); err != nil { - return nil, errors.ThrowInternal(err, "TEXT-20ops", "Errors.TranslationFile.MergeError") - } - result := notificationText.GetMessageTextByType(textType) - result.Default = true - return result, nil -} - -func (repo *OrgRepository) GetMessageText(ctx context.Context, orgID, textType, lang string) (*domain.CustomMessageText, error) { - texts, err := repo.View.CustomTextsByAggregateIDAndTemplateAndLand(orgID, textType, lang) - if err != nil { - return nil, err - } - if len(texts) == 0 { - return repo.GetDefaultMessageText(ctx, textType, lang) - } - return iam_view_model.CustomTextViewsToMessageDomain(repo.SystemDefaults.IamID, lang, texts), err -} - -func (repo *OrgRepository) GetDefaultLoginTexts(ctx context.Context, lang string) (*domain.CustomLoginText, error) { - repo.mutex.Lock() - defer repo.mutex.Unlock() - contents, ok := repo.LoginTranslationFileContents[lang] - var err error - if !ok { - contents, err = repo.readTranslationFile(repo.LoginDir, fmt.Sprintf("/i18n/%s.yaml", lang)) - if errors.IsNotFound(err) { - contents, err = repo.readTranslationFile(repo.LoginDir, fmt.Sprintf("/i18n/%s.yaml", repo.SystemDefaults.DefaultLanguage.String())) - } - if err != nil { - return nil, err - } - repo.LoginTranslationFileContents[lang] = contents - } - loginTextMap := make(map[string]interface{}) - if err := yaml.Unmarshal(contents, &loginTextMap); err != nil { - return nil, errors.ThrowInternal(err, "TEXT-l0fse", "Errors.TranslationFile.ReadError") - } - texts, err := repo.View.CustomTextsByAggregateIDAndTemplateAndLand(repo.SystemDefaults.IamID, domain.LoginCustomText, lang) - if err != nil { - return nil, err - } - for _, text := range texts { - keys := strings.Split(text.Key, ".") - screenTextMap, ok := loginTextMap[keys[0]].(map[string]interface{}) - if !ok { - continue - } - screenTextMap[keys[1]] = text.Text - } - jsonbody, err := json.Marshal(loginTextMap) - if err != nil { - return nil, errors.ThrowInternal(err, "TEXT-2n8fs", "Errors.TranslationFile.MergeError") - } - loginText := new(domain.CustomLoginText) - if err := json.Unmarshal(jsonbody, &loginText); err != nil { - return nil, errors.ThrowInternal(err, "TEXT-2n8fs", "Errors.TranslationFile.MergeError") - } - return loginText, nil -} - -func (repo *OrgRepository) GetLoginTexts(ctx context.Context, orgID, lang string) (*domain.CustomLoginText, error) { - texts, err := repo.View.CustomTextsByAggregateIDAndTemplateAndLand(orgID, domain.LoginCustomText, lang) - if err != nil { - return nil, err - } - return iam_view_model.CustomTextViewsToLoginDomain(repo.SystemDefaults.IamID, lang, texts), err -} - func (repo *OrgRepository) getOrgChanges(ctx context.Context, orgID string, lastSequence uint64, limit uint64, sortAscending bool, auditLogRetention time.Duration) (*org_model.OrgChanges, error) { query := org_view.ChangesQuery(orgID, lastSequence, limit, sortAscending, auditLogRetention) diff --git a/internal/management/repository/eventsourcing/handler/custom_text.go b/internal/management/repository/eventsourcing/handler/custom_text.go deleted file mode 100644 index 4d9bc7ec00..0000000000 --- a/internal/management/repository/eventsourcing/handler/custom_text.go +++ /dev/null @@ -1,132 +0,0 @@ -package handler - -import ( - "github.com/caos/logging" - caos_errs "github.com/caos/zitadel/internal/errors" - "github.com/caos/zitadel/internal/eventstore/v1" - es_models "github.com/caos/zitadel/internal/eventstore/v1/models" - "github.com/caos/zitadel/internal/eventstore/v1/query" - "github.com/caos/zitadel/internal/eventstore/v1/spooler" - iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model" - iam_model "github.com/caos/zitadel/internal/iam/repository/view/model" - "github.com/caos/zitadel/internal/org/repository/eventsourcing/model" -) - -type CustomText struct { - handler - subscription *v1.Subscription -} - -func newCustomText(handler handler) *CustomText { - h := &CustomText{ - handler: handler, - } - - h.subscribe() - - return h -} - -func (m *CustomText) subscribe() { - m.subscription = m.es.Subscribe(m.AggregateTypes()...) - go func() { - for event := range m.subscription.Events { - query.ReduceEvent(m, event) - } - }() -} - -const ( - customTextTable = "management.custom_texts" -) - -func (m *CustomText) ViewModel() string { - return customTextTable -} - -func (m *CustomText) Subscription() *v1.Subscription { - return m.subscription -} - -func (_ *CustomText) AggregateTypes() []es_models.AggregateType { - return []es_models.AggregateType{model.OrgAggregate, iam_es_model.IAMAggregate} -} - -func (p *CustomText) CurrentSequence() (uint64, error) { - sequence, err := p.view.GetLatestCustomTextSequence() - if err != nil { - return 0, err - } - return sequence.CurrentSequence, nil -} - -func (m *CustomText) EventQuery() (*es_models.SearchQuery, error) { - sequence, err := m.view.GetLatestCustomTextSequence() - if err != nil { - return nil, err - } - return es_models.NewSearchQuery(). - AggregateTypeFilter(m.AggregateTypes()...). - LatestSequenceFilter(sequence.CurrentSequence), nil -} - -func (m *CustomText) Reduce(event *es_models.Event) (err error) { - switch event.AggregateType { - case model.OrgAggregate, iam_es_model.IAMAggregate: - err = m.processCustomText(event) - } - return err -} - -func (m *CustomText) processCustomText(event *es_models.Event) (err error) { - customText := new(iam_model.CustomTextView) - switch event.Type { - case iam_es_model.CustomTextSet, model.CustomTextSet: - text := new(iam_model.CustomTextView) - err = text.SetData(event) - if err != nil { - return err - } - customText, err = m.view.CustomTextByIDs(event.AggregateID, text.Template, text.Key, text.Language) - if err != nil && !caos_errs.IsNotFound(err) { - return err - } - if caos_errs.IsNotFound(err) { - err = nil - customText = new(iam_model.CustomTextView) - customText.Language = text.Language - customText.Template = text.Template - customText.CreationDate = event.CreationDate - } - err = customText.AppendEvent(event) - case iam_es_model.CustomTextRemoved, model.CustomTextRemoved: - text := new(iam_model.CustomTextView) - err = text.SetData(event) - if err != nil { - return err - } - return m.view.DeleteCustomText(event.AggregateID, text.Template, text.Language, text.Key, event) - case iam_es_model.CustomTextMessageRemoved, model.CustomTextMessageRemoved: - text := new(iam_model.CustomTextView) - err = text.SetData(event) - if err != nil { - return err - } - return m.view.DeleteCustomTextTemplate(event.AggregateID, text.Template, text.Language, event) - default: - return m.view.ProcessedCustomTextSequence(event) - } - if err != nil { - return err - } - return m.view.PutCustomText(customText, event) -} - -func (m *CustomText) OnError(event *es_models.Event, err error) error { - logging.LogWithFields("SPOOL-3m912", "id", event.AggregateID).WithError(err).Warn("something went wrong in custom text handler") - return spooler.HandleError(event, err, m.view.GetLatestCustomTextFailedEvent, m.view.ProcessedCustomTextFailedEvent, m.view.ProcessedCustomTextSequence, m.errorCountUntilSkip) -} - -func (o *CustomText) OnSuccess() error { - return spooler.HandleSuccess(o.view.UpdateCustomTextSpoolerRunTimestamp) -} diff --git a/internal/management/repository/eventsourcing/handler/handler.go b/internal/management/repository/eventsourcing/handler/handler.go index a025c308ac..71c432835f 100644 --- a/internal/management/repository/eventsourcing/handler/handler.go +++ b/internal/management/repository/eventsourcing/handler/handler.go @@ -50,12 +50,6 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es newExternalIDP( handler{view, bulkLimit, configs.cycleDuration("ExternalIDP"), errorCount, es}, defaults), - newMailTemplate( - handler{view, bulkLimit, configs.cycleDuration("MailTemplate"), errorCount, es}), - newMessageText( - handler{view, bulkLimit, configs.cycleDuration("MessageText"), errorCount, es}), - newCustomText( - handler{view, bulkLimit, configs.cycleDuration("CustomText"), errorCount, es}), newMetadata( handler{view, bulkLimit, configs.cycleDuration("Metadata"), errorCount, es}), } diff --git a/internal/management/repository/eventsourcing/handler/mail_template.go b/internal/management/repository/eventsourcing/handler/mail_template.go deleted file mode 100644 index a165766f4f..0000000000 --- a/internal/management/repository/eventsourcing/handler/mail_template.go +++ /dev/null @@ -1,110 +0,0 @@ -package handler - -import ( - "github.com/caos/logging" - "github.com/caos/zitadel/internal/eventstore/v1" - iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model" - - es_models "github.com/caos/zitadel/internal/eventstore/v1/models" - "github.com/caos/zitadel/internal/eventstore/v1/query" - "github.com/caos/zitadel/internal/eventstore/v1/spooler" - iam_model "github.com/caos/zitadel/internal/iam/repository/view/model" - "github.com/caos/zitadel/internal/org/repository/eventsourcing/model" -) - -type MailTemplate struct { - handler - subscription *v1.Subscription -} - -func newMailTemplate(handler handler) *MailTemplate { - h := &MailTemplate{ - handler: handler, - } - - h.subscribe() - - return h -} - -func (m *MailTemplate) subscribe() { - m.subscription = m.es.Subscribe(m.AggregateTypes()...) - go func() { - for event := range m.subscription.Events { - query.ReduceEvent(m, event) - } - }() -} - -const ( - mailTemplateTable = "management.mail_templates" -) - -func (m *MailTemplate) ViewModel() string { - return mailTemplateTable -} - -func (m *MailTemplate) Subscription() *v1.Subscription { - return m.subscription -} - -func (_ *MailTemplate) AggregateTypes() []es_models.AggregateType { - return []es_models.AggregateType{model.OrgAggregate, iam_es_model.IAMAggregate} -} - -func (p *MailTemplate) CurrentSequence() (uint64, error) { - sequence, err := p.view.GetLatestMailTemplateSequence() - if err != nil { - return 0, err - } - return sequence.CurrentSequence, nil -} - -func (m *MailTemplate) EventQuery() (*es_models.SearchQuery, error) { - sequence, err := m.view.GetLatestMailTemplateSequence() - if err != nil { - return nil, err - } - return es_models.NewSearchQuery(). - AggregateTypeFilter(m.AggregateTypes()...). - LatestSequenceFilter(sequence.CurrentSequence), nil -} - -func (m *MailTemplate) Reduce(event *es_models.Event) (err error) { - switch event.AggregateType { - case model.OrgAggregate, iam_es_model.IAMAggregate: - err = m.processMailTemplate(event) - } - return err -} - -func (m *MailTemplate) processMailTemplate(event *es_models.Event) (err error) { - template := new(iam_model.MailTemplateView) - switch event.Type { - case iam_es_model.MailTemplateAdded, model.MailTemplateAdded: - err = template.AppendEvent(event) - case iam_es_model.MailTemplateChanged, model.MailTemplateChanged: - template, err = m.view.MailTemplateByAggregateID(event.AggregateID) - if err != nil { - return err - } - err = template.AppendEvent(event) - case model.MailTemplateRemoved: - return m.view.DeleteMailTemplate(event.AggregateID, event) - default: - return m.view.ProcessedMailTemplateSequence(event) - } - if err != nil { - return err - } - return m.view.PutMailTemplate(template, event) -} - -func (m *MailTemplate) OnError(event *es_models.Event, err error) error { - logging.LogWithFields("SPOOL-1n87f", "id", event.AggregateID).WithError(err).Warn("something went wrong in label template handler") - return spooler.HandleError(event, err, m.view.GetLatestMailTemplateFailedEvent, m.view.ProcessedMailTemplateFailedEvent, m.view.ProcessedMailTemplateSequence, m.errorCountUntilSkip) -} - -func (o *MailTemplate) OnSuccess() error { - return spooler.HandleSuccess(o.view.UpdateMailTemplateSpoolerRunTimestamp) -} diff --git a/internal/management/repository/eventsourcing/handler/message_text.go b/internal/management/repository/eventsourcing/handler/message_text.go deleted file mode 100644 index 4382ccbd6a..0000000000 --- a/internal/management/repository/eventsourcing/handler/message_text.go +++ /dev/null @@ -1,132 +0,0 @@ -package handler - -import ( - "github.com/caos/logging" - caos_errs "github.com/caos/zitadel/internal/errors" - "github.com/caos/zitadel/internal/eventstore/v1" - es_models "github.com/caos/zitadel/internal/eventstore/v1/models" - "github.com/caos/zitadel/internal/eventstore/v1/query" - "github.com/caos/zitadel/internal/eventstore/v1/spooler" - iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model" - iam_model "github.com/caos/zitadel/internal/iam/repository/view/model" - "github.com/caos/zitadel/internal/org/repository/eventsourcing/model" -) - -type MessageText struct { - handler - subscription *v1.Subscription -} - -func newMessageText(handler handler) *MessageText { - h := &MessageText{ - handler: handler, - } - - h.subscribe() - - return h -} - -func (m *MessageText) subscribe() { - m.subscription = m.es.Subscribe(m.AggregateTypes()...) - go func() { - for event := range m.subscription.Events { - query.ReduceEvent(m, event) - } - }() -} - -const ( - messageTextTable = "management.message_texts" -) - -func (m *MessageText) ViewModel() string { - return messageTextTable -} - -func (m *MessageText) Subscription() *v1.Subscription { - return m.subscription -} - -func (_ *MessageText) AggregateTypes() []es_models.AggregateType { - return []es_models.AggregateType{model.OrgAggregate, iam_es_model.IAMAggregate} -} - -func (p *MessageText) CurrentSequence() (uint64, error) { - sequence, err := p.view.GetLatestMessageTextSequence() - if err != nil { - return 0, err - } - return sequence.CurrentSequence, nil -} - -func (m *MessageText) EventQuery() (*es_models.SearchQuery, error) { - sequence, err := m.view.GetLatestMessageTextSequence() - if err != nil { - return nil, err - } - return es_models.NewSearchQuery(). - AggregateTypeFilter(m.AggregateTypes()...). - LatestSequenceFilter(sequence.CurrentSequence), nil -} - -func (m *MessageText) Reduce(event *es_models.Event) (err error) { - switch event.AggregateType { - case model.OrgAggregate, iam_es_model.IAMAggregate: - err = m.processMessageText(event) - } - return err -} - -func (m *MessageText) processMessageText(event *es_models.Event) (err error) { - message := new(iam_model.MessageTextView) - switch event.Type { - case iam_es_model.CustomTextSet, model.CustomTextSet, - iam_es_model.CustomTextRemoved, model.CustomTextRemoved: - text := new(iam_model.CustomTextView) - err = text.SetData(event) - if err != nil { - return err - } - if !text.IsMessageTemplate() { - return m.view.ProcessedMessageTextSequence(event) - } - message, err = m.view.MessageTextByIDs(event.AggregateID, text.Template, text.Language) - if err != nil && !caos_errs.IsNotFound(err) { - return err - } - if caos_errs.IsNotFound(err) { - err = nil - message = new(iam_model.MessageTextView) - message.Language = text.Language - message.MessageTextType = text.Template - message.CreationDate = event.CreationDate - } - err = message.AppendEvent(event) - case model.CustomTextMessageRemoved: - text := new(iam_model.CustomTextView) - err = text.SetData(event) - if err != nil { - return err - } - if !text.IsMessageTemplate() { - return m.view.ProcessedMessageTextSequence(event) - } - return m.view.DeleteMessageText(event.AggregateID, text.Template, text.Language, event) - default: - return m.view.ProcessedMessageTextSequence(event) - } - if err != nil { - return err - } - return m.view.PutMessageText(message, event) -} - -func (m *MessageText) OnError(event *es_models.Event, err error) error { - logging.LogWithFields("SPOOL-om8Hu", "id", event.AggregateID).WithError(err).Warn("something went wrong in label text handler") - return spooler.HandleError(event, err, m.view.GetLatestMessageTextFailedEvent, m.view.ProcessedMessageTextFailedEvent, m.view.ProcessedMessageTextSequence, m.errorCountUntilSkip) -} - -func (o *MessageText) OnSuccess() error { - return spooler.HandleSuccess(o.view.UpdateMessageTextSpoolerRunTimestamp) -} diff --git a/internal/management/repository/eventsourcing/view/custom_texts.go b/internal/management/repository/eventsourcing/view/custom_texts.go deleted file mode 100644 index c8e1bb5aad..0000000000 --- a/internal/management/repository/eventsourcing/view/custom_texts.go +++ /dev/null @@ -1,65 +0,0 @@ -package view - -import ( - "github.com/caos/zitadel/internal/errors" - "github.com/caos/zitadel/internal/eventstore/v1/models" - "github.com/caos/zitadel/internal/iam/repository/view" - "github.com/caos/zitadel/internal/iam/repository/view/model" - global_view "github.com/caos/zitadel/internal/view/repository" -) - -const ( - customTextTable = "management.custom_texts" -) - -func (v *View) CustomTextByIDs(aggregateID, template, lang, key string) (*model.CustomTextView, error) { - return view.CustomTextByIDs(v.Db, customTextTable, aggregateID, template, lang, key) -} - -func (v *View) CustomTextsByAggregateIDAndTemplateAndLand(aggregateID, template, lang string) ([]*model.CustomTextView, error) { - return view.GetCustomTexts(v.Db, customTextTable, aggregateID, template, lang) -} - -func (v *View) PutCustomText(template *model.CustomTextView, event *models.Event) error { - err := view.PutCustomText(v.Db, customTextTable, template) - if err != nil { - return err - } - return v.ProcessedCustomTextSequence(event) -} - -func (v *View) DeleteCustomText(aggregateID, textType, lang, key string, event *models.Event) error { - err := view.DeleteCustomText(v.Db, customTextTable, aggregateID, textType, lang, key) - if err != nil && !errors.IsNotFound(err) { - return err - } - return v.ProcessedCustomTextSequence(event) -} - -func (v *View) DeleteCustomTextTemplate(aggregateID, textType, lang string, event *models.Event) error { - err := view.DeleteCustomTextTemplate(v.Db, customTextTable, aggregateID, textType, lang) - if err != nil && !errors.IsNotFound(err) { - return err - } - return v.ProcessedCustomTextSequence(event) -} - -func (v *View) GetLatestCustomTextSequence() (*global_view.CurrentSequence, error) { - return v.latestSequence(customTextTable) -} - -func (v *View) ProcessedCustomTextSequence(event *models.Event) error { - return v.saveCurrentSequence(customTextTable, event) -} - -func (v *View) UpdateCustomTextSpoolerRunTimestamp() error { - return v.updateSpoolerRunSequence(customTextTable) -} - -func (v *View) GetLatestCustomTextFailedEvent(sequence uint64) (*global_view.FailedEvent, error) { - return v.latestFailedEvent(customTextTable, sequence) -} - -func (v *View) ProcessedCustomTextFailedEvent(failedEvent *global_view.FailedEvent) error { - return v.saveFailedEvent(failedEvent) -} diff --git a/internal/management/repository/eventsourcing/view/mail_templates.go b/internal/management/repository/eventsourcing/view/mail_templates.go deleted file mode 100644 index 3368d10ade..0000000000 --- a/internal/management/repository/eventsourcing/view/mail_templates.go +++ /dev/null @@ -1,53 +0,0 @@ -package view - -import ( - "github.com/caos/zitadel/internal/errors" - "github.com/caos/zitadel/internal/eventstore/v1/models" - "github.com/caos/zitadel/internal/iam/repository/view" - "github.com/caos/zitadel/internal/iam/repository/view/model" - global_view "github.com/caos/zitadel/internal/view/repository" -) - -const ( - mailTemplateTable = "management.mail_templates" -) - -func (v *View) MailTemplateByAggregateID(aggregateID string) (*model.MailTemplateView, error) { - return view.GetMailTemplateByAggregateID(v.Db, mailTemplateTable, aggregateID) -} - -func (v *View) PutMailTemplate(template *model.MailTemplateView, event *models.Event) error { - err := view.PutMailTemplate(v.Db, mailTemplateTable, template) - if err != nil { - return err - } - return v.ProcessedMailTemplateSequence(event) -} - -func (v *View) DeleteMailTemplate(aggregateID string, event *models.Event) error { - err := view.DeleteMailTemplate(v.Db, mailTemplateTable, aggregateID) - if err != nil && !errors.IsNotFound(err) { - return err - } - return v.ProcessedMailTemplateSequence(event) -} - -func (v *View) GetLatestMailTemplateSequence() (*global_view.CurrentSequence, error) { - return v.latestSequence(mailTemplateTable) -} - -func (v *View) ProcessedMailTemplateSequence(event *models.Event) error { - return v.saveCurrentSequence(mailTemplateTable, event) -} - -func (v *View) UpdateMailTemplateSpoolerRunTimestamp() error { - return v.updateSpoolerRunSequence(mailTemplateTable) -} - -func (v *View) GetLatestMailTemplateFailedEvent(sequence uint64) (*global_view.FailedEvent, error) { - return v.latestFailedEvent(mailTemplateTable, sequence) -} - -func (v *View) ProcessedMailTemplateFailedEvent(failedEvent *global_view.FailedEvent) error { - return v.saveFailedEvent(failedEvent) -} diff --git a/internal/management/repository/eventsourcing/view/message_texts.go b/internal/management/repository/eventsourcing/view/message_texts.go deleted file mode 100644 index 05345936a6..0000000000 --- a/internal/management/repository/eventsourcing/view/message_texts.go +++ /dev/null @@ -1,57 +0,0 @@ -package view - -import ( - "github.com/caos/zitadel/internal/errors" - "github.com/caos/zitadel/internal/eventstore/v1/models" - "github.com/caos/zitadel/internal/iam/repository/view" - "github.com/caos/zitadel/internal/iam/repository/view/model" - global_view "github.com/caos/zitadel/internal/view/repository" -) - -const ( - messageTextTable = "management.message_texts" -) - -func (v *View) MessageTextsByAggregateID(aggregateID string) ([]*model.MessageTextView, error) { - return view.GetMessageTexts(v.Db, messageTextTable, aggregateID) -} - -func (v *View) MessageTextByIDs(aggregateID, textType, lang string) (*model.MessageTextView, error) { - return view.GetMessageTextByIDs(v.Db, messageTextTable, aggregateID, textType, lang) -} - -func (v *View) PutMessageText(template *model.MessageTextView, event *models.Event) error { - err := view.PutMessageText(v.Db, messageTextTable, template) - if err != nil { - return err - } - return v.ProcessedMessageTextSequence(event) -} - -func (v *View) DeleteMessageText(aggregateID, textType, lang string, event *models.Event) error { - err := view.DeleteMessageText(v.Db, messageTextTable, aggregateID, textType, lang) - if err != nil && !errors.IsNotFound(err) { - return err - } - return v.ProcessedMessageTextSequence(event) -} - -func (v *View) GetLatestMessageTextSequence() (*global_view.CurrentSequence, error) { - return v.latestSequence(messageTextTable) -} - -func (v *View) ProcessedMessageTextSequence(event *models.Event) error { - return v.saveCurrentSequence(messageTextTable, event) -} - -func (v *View) UpdateMessageTextSpoolerRunTimestamp() error { - return v.updateSpoolerRunSequence(messageTextTable) -} - -func (v *View) GetLatestMessageTextFailedEvent(sequence uint64) (*global_view.FailedEvent, error) { - return v.latestFailedEvent(messageTextTable, sequence) -} - -func (v *View) ProcessedMessageTextFailedEvent(failedEvent *global_view.FailedEvent) error { - return v.saveFailedEvent(failedEvent) -} diff --git a/internal/management/repository/org.go b/internal/management/repository/org.go index ce564db525..41e18b898a 100644 --- a/internal/management/repository/org.go +++ b/internal/management/repository/org.go @@ -6,7 +6,6 @@ import ( "golang.org/x/text/language" - "github.com/caos/zitadel/internal/domain" iam_model "github.com/caos/zitadel/internal/iam/model" org_model "github.com/caos/zitadel/internal/org/model" @@ -25,13 +24,4 @@ type OrgRepository interface { SearchIDPProviders(ctx context.Context, request *iam_model.IDPProviderSearchRequest) (*iam_model.IDPProviderSearchResponse, error) GetIDPProvidersByIDPConfigID(ctx context.Context, aggregateID, idpConfigID string) ([]*iam_model.IDPProviderView, error) - - GetDefaultMailTemplate(ctx context.Context) (*iam_model.MailTemplateView, error) - GetMailTemplate(ctx context.Context) (*iam_model.MailTemplateView, error) - - GetDefaultMessageText(ctx context.Context, textType string, language string) (*domain.CustomMessageText, error) - GetMessageText(ctx context.Context, orgID, textType, lang string) (*domain.CustomMessageText, error) - - GetDefaultLoginTexts(ctx context.Context, lang string) (*domain.CustomLoginText, error) - GetLoginTexts(ctx context.Context, orgID, lang string) (*domain.CustomLoginText, error) } diff --git a/internal/notification/notification.go b/internal/notification/notification.go index 321e69ce7e..cd78b36fee 100644 --- a/internal/notification/notification.go +++ b/internal/notification/notification.go @@ -2,10 +2,12 @@ package notification import ( "context" + "github.com/caos/logging" "github.com/caos/zitadel/internal/command" sd "github.com/caos/zitadel/internal/config/systemdefaults" "github.com/caos/zitadel/internal/notification/repository/eventsourcing" + "github.com/caos/zitadel/internal/query" "github.com/rakyll/statik/fs" _ "github.com/caos/zitadel/internal/notification/statik" @@ -16,7 +18,7 @@ type Config struct { Repository eventsourcing.Config } -func Start(ctx context.Context, config Config, systemDefaults sd.SystemDefaults, command *command.Commands, hasStatics bool) { +func Start(ctx context.Context, config Config, systemDefaults sd.SystemDefaults, command *command.Commands, queries *query.Queries, hasStatics bool) { statikFS, err := fs.NewWithNamespace("notification") logging.Log("CONFI-7usEW").OnError(err).Panic("unable to start listener") @@ -24,6 +26,6 @@ func Start(ctx context.Context, config Config, systemDefaults sd.SystemDefaults, if !hasStatics { apiDomain = "" } - _, err = eventsourcing.Start(config.Repository, statikFS, systemDefaults, command, apiDomain) + _, err = eventsourcing.Start(config.Repository, statikFS, systemDefaults, command, queries, apiDomain) logging.Log("MAIN-9uBxp").OnError(err).Panic("unable to start app") } diff --git a/internal/notification/repository/eventsourcing/handler/custom_text.go b/internal/notification/repository/eventsourcing/handler/custom_text.go deleted file mode 100644 index 9250857f46..0000000000 --- a/internal/notification/repository/eventsourcing/handler/custom_text.go +++ /dev/null @@ -1,133 +0,0 @@ -package handler - -import ( - "github.com/caos/logging" - - caos_errs "github.com/caos/zitadel/internal/errors" - "github.com/caos/zitadel/internal/eventstore/v1" - es_models "github.com/caos/zitadel/internal/eventstore/v1/models" - "github.com/caos/zitadel/internal/eventstore/v1/query" - "github.com/caos/zitadel/internal/eventstore/v1/spooler" - iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model" - iam_model "github.com/caos/zitadel/internal/iam/repository/view/model" - "github.com/caos/zitadel/internal/org/repository/eventsourcing/model" -) - -type CustomText struct { - handler - subscription *v1.Subscription -} - -func newCustomText(handler handler) *CustomText { - h := &CustomText{ - handler: handler, - } - - h.subscribe() - - return h -} - -func (m *CustomText) subscribe() { - m.subscription = m.es.Subscribe(m.AggregateTypes()...) - go func() { - for event := range m.subscription.Events { - query.ReduceEvent(m, event) - } - }() -} - -const ( - customTextTable = "notification.custom_texts" -) - -func (m *CustomText) ViewModel() string { - return customTextTable -} - -func (m *CustomText) Subscription() *v1.Subscription { - return m.subscription -} - -func (_ *CustomText) AggregateTypes() []es_models.AggregateType { - return []es_models.AggregateType{model.OrgAggregate, iam_es_model.IAMAggregate} -} - -func (p *CustomText) CurrentSequence() (uint64, error) { - sequence, err := p.view.GetLatestCustomTextSequence() - if err != nil { - return 0, err - } - return sequence.CurrentSequence, nil -} - -func (m *CustomText) EventQuery() (*es_models.SearchQuery, error) { - sequence, err := m.view.GetLatestCustomTextSequence() - if err != nil { - return nil, err - } - return es_models.NewSearchQuery(). - AggregateTypeFilter(m.AggregateTypes()...). - LatestSequenceFilter(sequence.CurrentSequence), nil -} - -func (m *CustomText) Reduce(event *es_models.Event) (err error) { - switch event.AggregateType { - case model.OrgAggregate, iam_es_model.IAMAggregate: - err = m.processCustomText(event) - } - return err -} - -func (m *CustomText) processCustomText(event *es_models.Event) (err error) { - customText := new(iam_model.CustomTextView) - switch event.Type { - case iam_es_model.CustomTextSet, model.CustomTextSet: - text := new(iam_model.CustomTextView) - err = text.SetData(event) - if err != nil { - return err - } - customText, err = m.view.CustomTextByIDs(event.AggregateID, text.Template, text.Key, text.Language) - if err != nil && !caos_errs.IsNotFound(err) { - return err - } - if caos_errs.IsNotFound(err) { - err = nil - customText = new(iam_model.CustomTextView) - customText.Language = text.Language - customText.Template = text.Template - customText.CreationDate = event.CreationDate - } - err = customText.AppendEvent(event) - case iam_es_model.CustomTextRemoved, model.CustomTextRemoved: - text := new(iam_model.CustomTextView) - err = text.SetData(event) - if err != nil { - return err - } - return m.view.DeleteCustomText(event.AggregateID, text.Template, text.Language, text.Key, event) - case iam_es_model.CustomTextMessageRemoved, model.CustomTextMessageRemoved: - text := new(iam_model.CustomTextView) - err = text.SetData(event) - if err != nil { - return err - } - return m.view.DeleteCustomTextTemplate(event.AggregateID, text.Template, text.Language, event) - default: - return m.view.ProcessedCustomTextSequence(event) - } - if err != nil { - return err - } - return m.view.PutCustomText(customText, event) -} - -func (m *CustomText) OnError(event *es_models.Event, err error) error { - logging.LogWithFields("SPOOL-3m912", "id", event.AggregateID).WithError(err).Warn("something went wrong in custom text handler") - return spooler.HandleError(event, err, m.view.GetLatestCustomTextFailedEvent, m.view.ProcessedCustomTextFailedEvent, m.view.ProcessedCustomTextSequence, m.errorCountUntilSkip) -} - -func (o *CustomText) OnSuccess() error { - return spooler.HandleSuccess(o.view.UpdateCustomTextSpoolerRunTimestamp) -} diff --git a/internal/notification/repository/eventsourcing/handler/handler.go b/internal/notification/repository/eventsourcing/handler/handler.go index fc72dafc68..5ff85bae18 100644 --- a/internal/notification/repository/eventsourcing/handler/handler.go +++ b/internal/notification/repository/eventsourcing/handler/handler.go @@ -5,13 +5,14 @@ import ( "time" "github.com/caos/logging" + "github.com/caos/zitadel/internal/query" "github.com/caos/zitadel/internal/command" sd "github.com/caos/zitadel/internal/config/systemdefaults" "github.com/caos/zitadel/internal/config/types" "github.com/caos/zitadel/internal/crypto" v1 "github.com/caos/zitadel/internal/eventstore/v1" - "github.com/caos/zitadel/internal/eventstore/v1/query" + queryv1 "github.com/caos/zitadel/internal/eventstore/v1/query" "github.com/caos/zitadel/internal/notification/repository/eventsourcing/view" ) @@ -34,20 +35,20 @@ func (h *handler) Eventstore() v1.Eventstore { return h.es } -func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es v1.Eventstore, command *command.Commands, systemDefaults sd.SystemDefaults, dir http.FileSystem, apiDomain string) []query.Handler { +func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es v1.Eventstore, command *command.Commands, queries *query.Queries, systemDefaults sd.SystemDefaults, dir http.FileSystem, apiDomain string) []queryv1.Handler { aesCrypto, err := crypto.NewAESCrypto(systemDefaults.UserVerificationKey) if err != nil { logging.Log("HANDL-s90ew").WithError(err).Debug("error create new aes crypto") } - return []query.Handler{ + return []queryv1.Handler{ newNotifyUser( handler{view, bulkLimit, configs.cycleDuration("User"), errorCount, es}, systemDefaults.IamID, ), - newCustomText(handler{view, bulkLimit, configs.cycleDuration("CustomText"), errorCount, es}), newNotification( handler{view, bulkLimit, configs.cycleDuration("Notification"), errorCount, es}, command, + queries, systemDefaults, aesCrypto, dir, diff --git a/internal/notification/repository/eventsourcing/handler/notification.go b/internal/notification/repository/eventsourcing/handler/notification.go index 35304d9013..28f385a1d1 100644 --- a/internal/notification/repository/eventsourcing/handler/notification.go +++ b/internal/notification/repository/eventsourcing/handler/notification.go @@ -7,8 +7,6 @@ import ( "time" "github.com/caos/logging" - "golang.org/x/text/language" - "github.com/caos/zitadel/internal/api/authz" "github.com/caos/zitadel/internal/command" sd "github.com/caos/zitadel/internal/config/systemdefaults" @@ -17,12 +15,13 @@ import ( "github.com/caos/zitadel/internal/errors" v1 "github.com/caos/zitadel/internal/eventstore/v1" "github.com/caos/zitadel/internal/eventstore/v1/models" - "github.com/caos/zitadel/internal/eventstore/v1/query" + queryv1 "github.com/caos/zitadel/internal/eventstore/v1/query" "github.com/caos/zitadel/internal/eventstore/v1/spooler" "github.com/caos/zitadel/internal/i18n" iam_model "github.com/caos/zitadel/internal/iam/model" iam_es_model "github.com/caos/zitadel/internal/iam/repository/view/model" "github.com/caos/zitadel/internal/notification/types" + "github.com/caos/zitadel/internal/query" user_repo "github.com/caos/zitadel/internal/repository/user" es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model" "github.com/caos/zitadel/internal/user/repository/view" @@ -30,14 +29,10 @@ import ( ) const ( - notificationTable = "notification.notifications" - NotifyUserID = "NOTIFICATION" - labelPolicyTableOrg = "management.label_policies" - labelPolicyTableDef = "adminapi.label_policies" - mailTemplateTableOrg = "management.mail_templates" - mailTemplateTableDef = "adminapi.mail_templates" - messageTextTableOrg = "management.message_texts" - messageTextTableDef = "adminapi.message_texts" + notificationTable = "notification.notifications" + NotifyUserID = "NOTIFICATION" + labelPolicyTableOrg = "management.label_policies" + labelPolicyTableDef = "adminapi.label_policies" ) type Notification struct { @@ -48,11 +43,13 @@ type Notification struct { statikDir http.FileSystem subscription *v1.Subscription apiDomain string + queries *query.Queries } func newNotification( handler handler, command *command.Commands, + query *query.Queries, defaults sd.SystemDefaults, aesCrypto crypto.EncryptionAlgorithm, statikDir http.FileSystem, @@ -65,6 +62,7 @@ func newNotification( statikDir: statikDir, AesCrypto: aesCrypto, apiDomain: apiDomain, + queries: query, } h.subscribe() @@ -76,7 +74,7 @@ func (k *Notification) subscribe() { k.subscription = k.es.Subscribe(k.AggregateTypes()...) go func() { for event := range k.subscription.Events { - query.ReduceEvent(k, event) + queryv1.ReduceEvent(k, event) } }() } @@ -424,21 +422,20 @@ func (n *Notification) getLabelPolicy(ctx context.Context) (*iam_model.LabelPoli } // Read organization specific template -func (n *Notification) getMailTemplate(ctx context.Context) (*iam_model.MailTemplateView, error) { +func (n *Notification) getMailTemplate(ctx context.Context) (*query.MailTemplate, error) { // read from Org - template, err := n.view.MailTemplateByAggregateID(authz.GetCtxData(ctx).OrgID, mailTemplateTableOrg) + template, err := n.queries.MailTemplateByOrg(ctx, authz.GetCtxData(ctx).OrgID) if errors.IsNotFound(err) { // read from default - template, err = n.view.MailTemplateByAggregateID(n.systemDefaults.IamID, mailTemplateTableDef) + template, err = n.queries.DefaultMailTemplate(ctx) if err != nil { return nil, err } - template.Default = true } if err != nil { return nil, err } - return iam_es_model.MailTemplateViewToModel(template), err + return template, err } func (n *Notification) getTranslatorWithOrgTexts(orgID, textType string) (*i18n.Translator, error) { @@ -446,52 +443,27 @@ func (n *Notification) getTranslatorWithOrgTexts(orgID, textType string) (*i18n. if err != nil { return nil, err } - allCustomTexts, err := n.view.CustomTextsByAggregateIDAndTemplate(domain.IAMID, textType) + ctx := context.TODO() + allCustomTexts, err := n.queries.CustomTextListByTemplate(ctx, domain.IAMID, textType) if err != nil { return translator, nil } - customTexts, err := n.view.CustomTextsByAggregateIDAndTemplate(orgID, textType) + customTexts, err := n.queries.CustomTextListByTemplate(ctx, orgID, textType) if err != nil { return translator, nil } - allCustomTexts = append(allCustomTexts, customTexts...) + allCustomTexts.CustomTexts = append(allCustomTexts.CustomTexts, customTexts.CustomTexts...) - for _, text := range allCustomTexts { + for _, text := range allCustomTexts.CustomTexts { msg := i18n.Message{ ID: text.Template + "." + text.Key, Text: text.Text, } - translator.AddMessages(language.Make(text.Language), msg) + translator.AddMessages(text.Language, msg) } return translator, nil } -// Read organization specific texts -func (n *Notification) getMessageText(user *model.NotifyUser, textType, lang string) (*iam_model.MessageTextView, error) { - langTag := language.Make(lang) - if langTag == language.Und { - langTag = language.English - } - langBase, _ := langTag.Base() - - defaultMessageText, err := n.view.MessageTextByIDs(n.systemDefaults.IamID, textType, langBase.String(), messageTextTableDef) - if err != nil { - return nil, err - } - defaultMessageText.Default = true - - // read from Org - orgMessageText, err := n.view.MessageTextByIDs(user.ResourceOwner, textType, langBase.String(), messageTextTableOrg) - if errors.IsNotFound(err) { - return iam_es_model.MessageTextViewToModel(defaultMessageText), nil - } - if err != nil { - return nil, err - } - mergedText := mergeMessageTexts(defaultMessageText, orgMessageText) - return iam_es_model.MessageTextViewToModel(mergedText), err -} - func (n *Notification) getUserByID(userID string) (*model.NotifyUser, error) { user, usrErr := n.view.NotifyUserByID(userID) if usrErr != nil && !errors.IsNotFound(usrErr) { @@ -515,28 +487,3 @@ func (n *Notification) getUserByID(userID string) (*model.NotifyUser, error) { } return &userCopy, nil } - -func mergeMessageTexts(defaultText *iam_es_model.MessageTextView, orgText *iam_es_model.MessageTextView) *iam_es_model.MessageTextView { - if orgText.Subject == "" { - orgText.Subject = defaultText.Subject - } - if orgText.Title == "" { - orgText.Title = defaultText.Title - } - if orgText.PreHeader == "" { - orgText.PreHeader = defaultText.PreHeader - } - if orgText.Text == "" { - orgText.Text = defaultText.Text - } - if orgText.Greeting == "" { - orgText.Greeting = defaultText.Greeting - } - if orgText.ButtonText == "" { - orgText.ButtonText = defaultText.ButtonText - } - if orgText.FooterText == "" { - orgText.FooterText = defaultText.FooterText - } - return orgText -} diff --git a/internal/notification/repository/eventsourcing/repository.go b/internal/notification/repository/eventsourcing/repository.go index 3c9e02d057..fad83d42af 100644 --- a/internal/notification/repository/eventsourcing/repository.go +++ b/internal/notification/repository/eventsourcing/repository.go @@ -5,6 +5,7 @@ import ( "github.com/caos/zitadel/internal/command" "github.com/caos/zitadel/internal/eventstore/v1" + "github.com/caos/zitadel/internal/query" "golang.org/x/text/language" @@ -27,7 +28,7 @@ type EsRepository struct { spooler *es_spol.Spooler } -func Start(conf Config, dir http.FileSystem, systemDefaults sd.SystemDefaults, command *command.Commands, apiDomain string) (*EsRepository, error) { +func Start(conf Config, dir http.FileSystem, systemDefaults sd.SystemDefaults, command *command.Commands, queries *query.Queries, apiDomain string) (*EsRepository, error) { es, err := v1.Start(conf.Eventstore) if err != nil { return nil, err @@ -42,7 +43,7 @@ func Start(conf Config, dir http.FileSystem, systemDefaults sd.SystemDefaults, c return nil, err } - spool := spooler.StartSpooler(conf.Spooler, es, view, sqlClient, command, systemDefaults, dir, apiDomain) + spool := spooler.StartSpooler(conf.Spooler, es, view, sqlClient, command, queries, systemDefaults, dir, apiDomain) return &EsRepository{ spool, diff --git a/internal/notification/repository/eventsourcing/spooler/spooler.go b/internal/notification/repository/eventsourcing/spooler/spooler.go index 1ac2abf53c..f35c5bc89c 100644 --- a/internal/notification/repository/eventsourcing/spooler/spooler.go +++ b/internal/notification/repository/eventsourcing/spooler/spooler.go @@ -6,6 +6,7 @@ import ( "github.com/caos/zitadel/internal/command" "github.com/caos/zitadel/internal/eventstore/v1" + "github.com/caos/zitadel/internal/query" sd "github.com/caos/zitadel/internal/config/systemdefaults" "github.com/caos/zitadel/internal/eventstore/v1/spooler" @@ -20,12 +21,12 @@ type SpoolerConfig struct { Handlers handler.Configs } -func StartSpooler(c SpoolerConfig, es v1.Eventstore, view *view.View, sql *sql.DB, command *command.Commands, systemDefaults sd.SystemDefaults, dir http.FileSystem, apiDomain string) *spooler.Spooler { +func StartSpooler(c SpoolerConfig, es v1.Eventstore, view *view.View, sql *sql.DB, command *command.Commands, queries *query.Queries, systemDefaults sd.SystemDefaults, dir http.FileSystem, apiDomain string) *spooler.Spooler { spoolerConfig := spooler.Config{ Eventstore: es, Locker: &locker{dbClient: sql}, ConcurrentWorkers: c.ConcurrentWorkers, - ViewHandlers: handler.Register(c.Handlers, c.BulkLimit, c.FailureCountUntilSkip, view, es, command, systemDefaults, dir, apiDomain), + ViewHandlers: handler.Register(c.Handlers, c.BulkLimit, c.FailureCountUntilSkip, view, es, command, queries, systemDefaults, dir, apiDomain), } spool := spoolerConfig.New() spool.Start() diff --git a/internal/notification/repository/eventsourcing/view/custom_texts.go b/internal/notification/repository/eventsourcing/view/custom_texts.go deleted file mode 100644 index 02313b216c..0000000000 --- a/internal/notification/repository/eventsourcing/view/custom_texts.go +++ /dev/null @@ -1,69 +0,0 @@ -package view - -import ( - "github.com/caos/zitadel/internal/errors" - "github.com/caos/zitadel/internal/eventstore/v1/models" - "github.com/caos/zitadel/internal/iam/repository/view" - "github.com/caos/zitadel/internal/iam/repository/view/model" - global_view "github.com/caos/zitadel/internal/view/repository" -) - -const ( - customTextTable = "notification.custom_texts" -) - -func (v *View) CustomTextByIDs(aggregateID, template, lang, key string) (*model.CustomTextView, error) { - return view.CustomTextByIDs(v.Db, customTextTable, aggregateID, template, lang, key) -} - -func (v *View) CustomTextsByAggregateIDAndTemplate(aggregateID, template string) ([]*model.CustomTextView, error) { - return view.GetCustomTextsByAggregateIDAndTemplate(v.Db, customTextTable, aggregateID, template) -} - -func (v *View) CustomTextsByAggregateIDAndTemplateAndLang(aggregateID, template, lang string) ([]*model.CustomTextView, error) { - return view.GetCustomTexts(v.Db, customTextTable, aggregateID, template, lang) -} - -func (v *View) PutCustomText(template *model.CustomTextView, event *models.Event) error { - err := view.PutCustomText(v.Db, customTextTable, template) - if err != nil { - return err - } - return v.ProcessedCustomTextSequence(event) -} - -func (v *View) DeleteCustomText(aggregateID, textType, lang, key string, event *models.Event) error { - err := view.DeleteCustomText(v.Db, customTextTable, aggregateID, textType, lang, key) - if err != nil && !errors.IsNotFound(err) { - return err - } - return v.ProcessedCustomTextSequence(event) -} - -func (v *View) DeleteCustomTextTemplate(aggregateID, textType, lang string, event *models.Event) error { - err := view.DeleteCustomTextTemplate(v.Db, customTextTable, aggregateID, textType, lang) - if err != nil && !errors.IsNotFound(err) { - return err - } - return v.ProcessedCustomTextSequence(event) -} - -func (v *View) GetLatestCustomTextSequence() (*global_view.CurrentSequence, error) { - return v.latestSequence(customTextTable) -} - -func (v *View) ProcessedCustomTextSequence(event *models.Event) error { - return v.saveCurrentSequence(customTextTable, event) -} - -func (v *View) UpdateCustomTextSpoolerRunTimestamp() error { - return v.updateSpoolerRunSequence(customTextTable) -} - -func (v *View) GetLatestCustomTextFailedEvent(sequence uint64) (*global_view.FailedEvent, error) { - return v.latestFailedEvent(customTextTable, sequence) -} - -func (v *View) ProcessedCustomTextFailedEvent(failedEvent *global_view.FailedEvent) error { - return v.saveFailedEvent(failedEvent) -} diff --git a/internal/notification/repository/eventsourcing/view/mail_template.go b/internal/notification/repository/eventsourcing/view/mail_template.go deleted file mode 100644 index 0e1ddf8941..0000000000 --- a/internal/notification/repository/eventsourcing/view/mail_template.go +++ /dev/null @@ -1,10 +0,0 @@ -package view - -import ( - "github.com/caos/zitadel/internal/iam/repository/view" - "github.com/caos/zitadel/internal/iam/repository/view/model" -) - -func (v *View) MailTemplateByAggregateID(aggregateID string, mailTemplateTableVar string) (*model.MailTemplateView, error) { - return view.GetMailTemplateByAggregateID(v.Db, mailTemplateTableVar, aggregateID) -} diff --git a/internal/query/custom_text.go b/internal/query/custom_text.go new file mode 100644 index 0000000000..76684d29d9 --- /dev/null +++ b/internal/query/custom_text.go @@ -0,0 +1,1091 @@ +package query + +import ( + "context" + "database/sql" + "encoding/json" + "fmt" + "strings" + "time" + + sq "github.com/Masterminds/squirrel" + "golang.org/x/text/language" + "sigs.k8s.io/yaml" + + "github.com/caos/zitadel/internal/domain" + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore/v1/models" + "github.com/caos/zitadel/internal/query/projection" +) + +type CustomTexts struct { + SearchResponse + CustomTexts []*CustomText +} + +type CustomText struct { + AggregateID string + Sequence uint64 + CreationDate time.Time + ChangeDate time.Time + + Template string + Language language.Tag + Key string + Text string +} + +var ( + customTextTable = table{ + name: projection.CustomTextTable, + } + CustomTextColAggregateID = Column{ + name: projection.CustomTextAggregateIDCol, + table: customTextTable, + } + CustomTextColSequence = Column{ + name: projection.CustomTextSequenceCol, + table: customTextTable, + } + CustomTextColCreationDate = Column{ + name: projection.CustomTextCreationDateCol, + table: customTextTable, + } + CustomTextColChangeDate = Column{ + name: projection.CustomTextChangeDateCol, + table: customTextTable, + } + CustomTextColTemplate = Column{ + name: projection.CustomTextTemplateCol, + table: customTextTable, + } + CustomTextColLanguage = Column{ + name: projection.CustomTextLanguageCol, + table: customTextTable, + } + CustomTextColKey = Column{ + name: projection.CustomTextKeyCol, + table: customTextTable, + } + CustomTextColText = Column{ + name: projection.CustomTextTextCol, + table: customTextTable, + } +) + +func (q *Queries) CustomTextList(ctx context.Context, aggregateID, template, language string) (texts *CustomTexts, err error) { + stmt, scan := prepareCustomTextsQuery() + query, args, err := stmt.Where( + sq.Eq{ + CustomTextColAggregateID.identifier(): aggregateID, + CustomTextColTemplate.identifier(): template, + CustomTextColLanguage.identifier(): language, + }, + ).ToSql() + if err != nil { + return nil, errors.ThrowInternal(err, "QUERY-M9gse", "Errors.Query.SQLStatement") + } + + rows, err := q.client.QueryContext(ctx, query, args...) + if err != nil { + return nil, errors.ThrowInternal(err, "QUERY-2j00f", "Errors.Internal") + } + texts, err = scan(rows) + if err != nil { + return nil, err + } + texts.LatestSequence, err = q.latestSequence(ctx, projectsTable) + return texts, err +} + +func (q *Queries) CustomTextListByTemplate(ctx context.Context, aggregateID, template string) (texts *CustomTexts, err error) { + stmt, scan := prepareCustomTextsQuery() + query, args, err := stmt.Where( + sq.Eq{ + CustomTextColAggregateID.identifier(): aggregateID, + CustomTextColTemplate.identifier(): template, + }, + ).ToSql() + if err != nil { + return nil, errors.ThrowInternal(err, "QUERY-M49fs", "Errors.Query.SQLStatement") + } + + rows, err := q.client.QueryContext(ctx, query, args...) + if err != nil { + return nil, errors.ThrowInternal(err, "QUERY-3n9ge", "Errors.Internal") + } + texts, err = scan(rows) + if err != nil { + return nil, err + } + texts.LatestSequence, err = q.latestSequence(ctx, projectsTable) + return texts, err +} + +func (q *Queries) GetDefaultLoginTexts(ctx context.Context, lang string) (*domain.CustomLoginText, error) { + contents, err := q.readLoginTranslationFile(lang) + if err != nil { + return nil, err + } + loginText := new(domain.CustomLoginText) + if err := yaml.Unmarshal(contents, loginText); err != nil { + return nil, errors.ThrowInternal(err, "TEXT-M0p4s", "Errors.TranslationFile.ReadError") + } + return loginText, nil +} + +func (q *Queries) GetCustomLoginTexts(ctx context.Context, aggregateID, lang string) (*domain.CustomLoginText, error) { + texts, err := q.CustomTextList(ctx, aggregateID, domain.LoginCustomText, lang) + if err != nil { + return nil, err + } + return CustomTextsToLoginDomain(domain.IAMID, lang, texts), err +} + +func (q *Queries) IAMLoginTexts(ctx context.Context, lang string) (*domain.CustomLoginText, error) { + contents, err := q.readLoginTranslationFile(lang) + if err != nil { + return nil, err + } + loginTextMap := make(map[string]interface{}) + if err := yaml.Unmarshal(contents, &loginTextMap); err != nil { + return nil, errors.ThrowInternal(err, "QUERY-m0Jf3", "Errors.TranslationFile.ReadError") + } + texts, err := q.CustomTextList(ctx, domain.IAMID, domain.LoginCustomText, lang) + if err != nil { + return nil, err + } + for _, text := range texts.CustomTexts { + keys := strings.Split(text.Key, ".") + screenTextMap, ok := loginTextMap[keys[0]].(map[string]interface{}) + if !ok { + continue + } + screenTextMap[keys[1]] = text.Text + } + jsonbody, err := json.Marshal(loginTextMap) + if err != nil { + return nil, errors.ThrowInternal(err, "QUERY-0nJ3f", "Errors.TranslationFile.MergeError") + } + loginText := new(domain.CustomLoginText) + if err := json.Unmarshal(jsonbody, &loginText); err != nil { + return nil, errors.ThrowInternal(err, "QUERY-m93Jf", "Errors.TranslationFile.MergeError") + } + return loginText, nil +} + +func (q *Queries) readLoginTranslationFile(lang string) ([]byte, error) { + q.mutex.Lock() + defer q.mutex.Unlock() + contents, ok := q.LoginTranslationFileContents[lang] + var err error + if !ok { + contents, err = q.readTranslationFile(q.LoginDir, fmt.Sprintf("/i18n/%s.yaml", lang)) + if errors.IsNotFound(err) { + contents, err = q.readTranslationFile(q.LoginDir, fmt.Sprintf("/i18n/%s.yaml", q.DefaultLanguage.String())) + } + if err != nil { + return nil, err + } + q.LoginTranslationFileContents[lang] = contents + } + return contents, nil +} + +func prepareCustomTextsQuery() (sq.SelectBuilder, func(*sql.Rows) (*CustomTexts, error)) { + return sq.Select( + CustomTextColAggregateID.identifier(), + CustomTextColSequence.identifier(), + CustomTextColCreationDate.identifier(), + CustomTextColChangeDate.identifier(), + CustomTextColLanguage.identifier(), + CustomTextColTemplate.identifier(), + CustomTextColKey.identifier(), + CustomTextColText.identifier(), + countColumn.identifier()). + From(customTextTable.identifier()).PlaceholderFormat(sq.Dollar), + func(rows *sql.Rows) (*CustomTexts, error) { + customTexts := make([]*CustomText, 0) + var count uint64 + for rows.Next() { + customText := new(CustomText) + lang := "" + err := rows.Scan( + &customText.AggregateID, + &customText.Sequence, + &customText.CreationDate, + &customText.ChangeDate, + &lang, + &customText.Template, + &customText.Key, + &customText.Text, + &count, + ) + if err != nil { + return nil, err + } + customText.Language = language.Make(lang) + customTexts = append(customTexts, customText) + } + + if err := rows.Close(); err != nil { + return nil, errors.ThrowInternal(err, "QUERY-3n9fs", "Errors.Query.CloseRows") + } + + return &CustomTexts{ + CustomTexts: customTexts, + SearchResponse: SearchResponse{ + Count: count, + }, + }, nil + } +} + +func CustomTextsToDomain(texts *CustomTexts) []*domain.CustomText { + result := make([]*domain.CustomText, len(texts.CustomTexts)) + for i, text := range texts.CustomTexts { + result[i] = CustomTextToDomain(text) + } + return result +} + +func CustomTextToDomain(text *CustomText) *domain.CustomText { + return &domain.CustomText{ + ObjectRoot: models.ObjectRoot{ + AggregateID: text.AggregateID, + Sequence: text.Sequence, + CreationDate: text.CreationDate, + ChangeDate: text.ChangeDate, + }, + Template: text.Template, + Language: text.Language, + Key: text.Key, + Text: text.Text, + } +} + +func CustomTextsToLoginDomain(aggregateID, lang string, texts *CustomTexts) *domain.CustomLoginText { + langTag := language.Make(lang) + result := &domain.CustomLoginText{ + ObjectRoot: models.ObjectRoot{ + AggregateID: aggregateID, + }, + Language: langTag, + } + for _, text := range texts.CustomTexts { + if text.CreationDate.Before(result.CreationDate) { + result.CreationDate = text.CreationDate + } + if text.ChangeDate.After(result.ChangeDate) { + result.ChangeDate = text.ChangeDate + } + if strings.HasPrefix(text.Key, domain.LoginKeySelectAccount) { + selectAccountKeyToDomain(text, result) + } + if strings.HasPrefix(text.Key, domain.LoginKeyLogin) { + loginKeyToDomain(text, result) + } + if strings.HasPrefix(text.Key, domain.LoginKeyPassword) { + passwordKeyToDomain(text, result) + } + if strings.HasPrefix(text.Key, domain.LoginKeyUsernameChange) { + usernameChangeKeyToDomain(text, result) + } + if strings.HasPrefix(text.Key, domain.LoginKeyUsernameChangeDone) { + usernameChangeDoneKeyToDomain(text, result) + } + if strings.HasPrefix(text.Key, domain.LoginKeyInitPassword) { + initPasswordKeyToDomain(text, result) + } + if strings.HasPrefix(text.Key, domain.LoginKeyInitPasswordDone) { + initPasswordDoneKeyToDomain(text, result) + } + if strings.HasPrefix(text.Key, domain.LoginKeyEmailVerification) { + emailVerificationKeyToDomain(text, result) + } + if strings.HasPrefix(text.Key, domain.LoginKeyEmailVerificationDone) { + emailVerificationDoneKeyToDomain(text, result) + } + if strings.HasPrefix(text.Key, domain.LoginKeyInitializeUser) { + initializeUserKeyToDomain(text, result) + } + if strings.HasPrefix(text.Key, domain.LoginKeyInitUserDone) { + initializeUserDoneKeyToDomain(text, result) + } + if strings.HasPrefix(text.Key, domain.LoginKeyInitMFAPrompt) { + initMFAPromptKeyToDomain(text, result) + } + if strings.HasPrefix(text.Key, domain.LoginKeyInitMFAOTP) { + initMFAOTPKeyToDomain(text, result) + } + if strings.HasPrefix(text.Key, domain.LoginKeyInitMFAU2F) { + initMFAU2FKeyToDomain(text, result) + } + if strings.HasPrefix(text.Key, domain.LoginKeyInitMFADone) { + initMFADoneKeyToDomain(text, result) + } + if strings.HasPrefix(text.Key, domain.LoginKeyMFAProviders) { + mfaProvidersKeyToDomain(text, result) + } + if strings.HasPrefix(text.Key, domain.LoginKeyVerifyMFAOTP) { + verifyMFAOTPKeyToDomain(text, result) + } + if strings.HasPrefix(text.Key, domain.LoginKeyVerifyMFAU2F) { + verifyMFAU2FKeyToDomain(text, result) + } + if strings.HasPrefix(text.Key, domain.LoginKeyPasswordless) { + passwordlessKeyToDomain(text, result) + } + if strings.HasPrefix(text.Key, domain.LoginKeyPasswordlessPrompt) { + passwordlessPromptKeyToDomain(text, result) + } + if strings.HasPrefix(text.Key, domain.LoginKeyPasswordlessRegistration) { + passwordlessRegistrationKeyToDomain(text, result) + } + if strings.HasPrefix(text.Key, domain.LoginKeyPasswordlessRegistrationDone) { + passwordlessRegistrationDoneKeyToDomain(text, result) + } + if strings.HasPrefix(text.Key, domain.LoginKeyPasswordChange) { + passwordChangeKeyToDomain(text, result) + } + if strings.HasPrefix(text.Key, domain.LoginKeyPasswordChangeDone) { + passwordChangeDoneKeyToDomain(text, result) + } + if strings.HasPrefix(text.Key, domain.LoginKeyPasswordResetDone) { + passwordResetDoneKeyToDomain(text, result) + } + if strings.HasPrefix(text.Key, domain.LoginKeyRegistrationOption) { + registrationOptionKeyToDomain(text, result) + } + if strings.HasPrefix(text.Key, domain.LoginKeyRegistrationUser) { + registrationUserKeyToDomain(text, result) + } + if strings.HasPrefix(text.Key, domain.LoginKeyRegistrationOrg) { + registrationOrgKeyToDomain(text, result) + } + if strings.HasPrefix(text.Key, domain.LoginKeyLinkingUserDone) { + linkingUserKeyToDomain(text, result) + } + if strings.HasPrefix(text.Key, domain.LoginKeyExternalNotFound) { + externalUserNotFoundKeyToDomain(text, result) + } + if strings.HasPrefix(text.Key, domain.LoginKeySuccessLogin) { + successLoginKeyToDomain(text, result) + } + if strings.HasPrefix(text.Key, domain.LoginKeyLogoutDone) { + logoutDoneKeyToDomain(text, result) + } + if strings.HasPrefix(text.Key, domain.LoginKeyFooter) { + footerKeyToDomain(text, result) + } + } + return result +} + +func selectAccountKeyToDomain(text *CustomText, result *domain.CustomLoginText) { + if text.Key == domain.LoginKeySelectAccountTitle { + result.SelectAccount.Title = text.Text + } + if text.Key == domain.LoginKeySelectAccountDescription { + result.SelectAccount.Description = text.Text + } + if text.Key == domain.LoginKeySelectAccountTitleLinkingProcess { + result.SelectAccount.TitleLinking = text.Text + } + if text.Key == domain.LoginKeySelectAccountDescriptionLinkingProcess { + result.SelectAccount.DescriptionLinking = text.Text + } + if text.Key == domain.LoginKeySelectAccountOtherUser { + result.SelectAccount.OtherUser = text.Text + } + if text.Key == domain.LoginKeySelectAccountSessionStateActive { + result.SelectAccount.SessionState0 = text.Text + } + if text.Key == domain.LoginKeySelectAccountSessionStateInactive { + result.SelectAccount.SessionState1 = text.Text + } + if text.Key == domain.LoginKeySelectAccountUserMustBeMemberOfOrg { + result.SelectAccount.MustBeMemberOfOrg = text.Text + } +} + +func loginKeyToDomain(text *CustomText, result *domain.CustomLoginText) { + if text.Key == domain.LoginKeyLoginTitle { + result.Login.Title = text.Text + } + if text.Key == domain.LoginKeyLoginDescription { + result.Login.Description = text.Text + } + if text.Key == domain.LoginKeyLoginTitleLinkingProcess { + result.Login.TitleLinking = text.Text + } + if text.Key == domain.LoginKeyLoginDescriptionLinkingProcess { + result.Login.DescriptionLinking = text.Text + } + if text.Key == domain.LoginKeyLoginNameLabel { + result.Login.LoginNameLabel = text.Text + } + if text.Key == domain.LoginKeyLoginUsernamePlaceHolder { + result.Login.UsernamePlaceholder = text.Text + } + if text.Key == domain.LoginKeyLoginLoginnamePlaceHolder { + result.Login.LoginnamePlaceholder = text.Text + } + if text.Key == domain.LoginKeyLoginExternalUserDescription { + result.Login.ExternalUserDescription = text.Text + } + if text.Key == domain.LoginKeyLoginUserMustBeMemberOfOrg { + result.Login.MustBeMemberOfOrg = text.Text + } + if text.Key == domain.LoginKeyLoginRegisterButtonText { + result.Login.RegisterButtonText = text.Text + } + if text.Key == domain.LoginKeyLoginNextButtonText { + result.Login.NextButtonText = text.Text + } +} + +func passwordKeyToDomain(text *CustomText, result *domain.CustomLoginText) { + if text.Key == domain.LoginKeyPasswordTitle { + result.Password.Title = text.Text + } + if text.Key == domain.LoginKeyPasswordDescription { + result.Password.Description = text.Text + } + if text.Key == domain.LoginKeyPasswordLabel { + result.Password.PasswordLabel = text.Text + } + if text.Key == domain.LoginKeyPasswordResetLinkText { + result.Password.ResetLinkText = text.Text + } + if text.Key == domain.LoginKeyPasswordBackButtonText { + result.Password.BackButtonText = text.Text + } + if text.Key == domain.LoginKeyPasswordNextButtonText { + result.Password.NextButtonText = text.Text + } + if text.Key == domain.LoginKeyPasswordMinLength { + result.Password.MinLength = text.Text + } + if text.Key == domain.LoginKeyPasswordHasUppercase { + result.Password.HasUppercase = text.Text + } + if text.Key == domain.LoginKeyPasswordHasLowercase { + result.Password.HasLowercase = text.Text + } + if text.Key == domain.LoginKeyPasswordHasNumber { + result.Password.HasNumber = text.Text + } + if text.Key == domain.LoginKeyPasswordHasSymbol { + result.Password.HasSymbol = text.Text + } + if text.Key == domain.LoginKeyPasswordConfirmation { + result.Password.Confirmation = text.Text + } +} + +func usernameChangeKeyToDomain(text *CustomText, result *domain.CustomLoginText) { + if text.Key == domain.LoginKeyUsernameChangeTitle { + result.UsernameChange.Title = text.Text + } + if text.Key == domain.LoginKeyUsernameChangeDescription { + result.UsernameChange.Description = text.Text + } + if text.Key == domain.LoginKeyUsernameChangeUsernameLabel { + result.UsernameChange.UsernameLabel = text.Text + } + if text.Key == domain.LoginKeyUsernameChangeCancelButtonText { + result.UsernameChange.CancelButtonText = text.Text + } + if text.Key == domain.LoginKeyUsernameChangeNextButtonText { + result.UsernameChange.NextButtonText = text.Text + } +} + +func usernameChangeDoneKeyToDomain(text *CustomText, result *domain.CustomLoginText) { + if text.Key == domain.LoginKeyUsernameChangeDoneTitle { + result.UsernameChangeDone.Title = text.Text + } + if text.Key == domain.LoginKeyUsernameChangeDoneDescription { + result.UsernameChangeDone.Description = text.Text + } + if text.Key == domain.LoginKeyUsernameChangeDoneNextButtonText { + result.UsernameChangeDone.NextButtonText = text.Text + } +} + +func initPasswordKeyToDomain(text *CustomText, result *domain.CustomLoginText) { + if text.Key == domain.LoginKeyInitPasswordTitle { + result.InitPassword.Title = text.Text + } + if text.Key == domain.LoginKeyInitPasswordDescription { + result.InitPassword.Description = text.Text + } + if text.Key == domain.LoginKeyInitPasswordCodeLabel { + result.InitPassword.CodeLabel = text.Text + } + if text.Key == domain.LoginKeyInitPasswordNewPasswordLabel { + result.InitPassword.NewPasswordLabel = text.Text + } + if text.Key == domain.LoginKeyInitPasswordNewPasswordConfirmLabel { + result.InitPassword.NewPasswordConfirmLabel = text.Text + } + if text.Key == domain.LoginKeyInitPasswordNextButtonText { + result.InitPassword.NextButtonText = text.Text + } + if text.Key == domain.LoginKeyInitPasswordResendButtonText { + result.InitPassword.ResendButtonText = text.Text + } +} + +func initPasswordDoneKeyToDomain(text *CustomText, result *domain.CustomLoginText) { + if text.Key == domain.LoginKeyInitPasswordDoneTitle { + result.InitPasswordDone.Title = text.Text + } + if text.Key == domain.LoginKeyInitPasswordDoneDescription { + result.InitPasswordDone.Description = text.Text + } + if text.Key == domain.LoginKeyInitPasswordDoneNextButtonText { + result.InitPasswordDone.NextButtonText = text.Text + } + if text.Key == domain.LoginKeyInitPasswordDoneCancelButtonText { + result.InitPasswordDone.CancelButtonText = text.Text + } +} + +func emailVerificationKeyToDomain(text *CustomText, result *domain.CustomLoginText) { + if text.Key == domain.LoginKeyEmailVerificationTitle { + result.EmailVerification.Title = text.Text + } + if text.Key == domain.LoginKeyEmailVerificationDescription { + result.EmailVerification.Description = text.Text + } + if text.Key == domain.LoginKeyEmailVerificationCodeLabel { + result.EmailVerification.CodeLabel = text.Text + } + if text.Key == domain.LoginKeyEmailVerificationNextButtonText { + result.EmailVerification.NextButtonText = text.Text + } + if text.Key == domain.LoginKeyEmailVerificationResendButtonText { + result.EmailVerification.ResendButtonText = text.Text + } +} + +func emailVerificationDoneKeyToDomain(text *CustomText, result *domain.CustomLoginText) { + if text.Key == domain.LoginKeyEmailVerificationDoneTitle { + result.EmailVerificationDone.Title = text.Text + } + if text.Key == domain.LoginKeyEmailVerificationDoneDescription { + result.EmailVerificationDone.Description = text.Text + } + if text.Key == domain.LoginKeyEmailVerificationDoneNextButtonText { + result.EmailVerificationDone.NextButtonText = text.Text + } + if text.Key == domain.LoginKeyEmailVerificationDoneCancelButtonText { + result.EmailVerificationDone.CancelButtonText = text.Text + } + if text.Key == domain.LoginKeyEmailVerificationDoneLoginButtonText { + result.EmailVerificationDone.LoginButtonText = text.Text + } +} + +func initializeUserKeyToDomain(text *CustomText, result *domain.CustomLoginText) { + if text.Key == domain.LoginKeyInitializeUserTitle { + result.InitUser.Title = text.Text + } + if text.Key == domain.LoginKeyInitializeUserDescription { + result.InitUser.Description = text.Text + } + if text.Key == domain.LoginKeyInitializeUserCodeLabel { + result.InitUser.CodeLabel = text.Text + } + if text.Key == domain.LoginKeyInitializeUserNewPasswordLabel { + result.InitUser.NewPasswordLabel = text.Text + } + if text.Key == domain.LoginKeyInitializeUserNewPasswordConfirmLabel { + result.InitUser.NewPasswordConfirmLabel = text.Text + } + if text.Key == domain.LoginKeyInitializeUserResendButtonText { + result.InitUser.ResendButtonText = text.Text + } + if text.Key == domain.LoginKeyInitializeUserNextButtonText { + result.InitUser.NextButtonText = text.Text + } +} + +func initializeUserDoneKeyToDomain(text *CustomText, result *domain.CustomLoginText) { + if text.Key == domain.LoginKeyInitUserDoneTitle { + result.InitUserDone.Title = text.Text + } + if text.Key == domain.LoginKeyInitUserDoneDescription { + result.InitUserDone.Description = text.Text + } + if text.Key == domain.LoginKeyInitUserDoneCancelButtonText { + result.InitUserDone.CancelButtonText = text.Text + } + if text.Key == domain.LoginKeyInitUserDoneNextButtonText { + result.InitUserDone.NextButtonText = text.Text + } +} + +func initMFAPromptKeyToDomain(text *CustomText, result *domain.CustomLoginText) { + if text.Key == domain.LoginKeyInitMFAPromptTitle { + result.InitMFAPrompt.Title = text.Text + } + if text.Key == domain.LoginKeyInitMFAPromptDescription { + result.InitMFAPrompt.Description = text.Text + } + if text.Key == domain.LoginKeyInitMFAPromptOTPOption { + result.InitMFAPrompt.Provider0 = text.Text + } + if text.Key == domain.LoginKeyInitMFAPromptU2FOption { + result.InitMFAPrompt.Provider1 = text.Text + } + if text.Key == domain.LoginKeyInitMFAPromptSkipButtonText { + result.InitMFAPrompt.SkipButtonText = text.Text + } + if text.Key == domain.LoginKeyInitMFAPromptNextButtonText { + result.InitMFAPrompt.NextButtonText = text.Text + } +} + +func initMFAOTPKeyToDomain(text *CustomText, result *domain.CustomLoginText) { + if text.Key == domain.LoginKeyInitMFAOTPTitle { + result.InitMFAOTP.Title = text.Text + } + if text.Key == domain.LoginKeyInitMFAOTPDescription { + result.InitMFAOTP.Description = text.Text + } + if text.Key == domain.LoginKeyInitMFAOTPDescriptionOTP { + result.InitMFAOTP.OTPDescription = text.Text + } + if text.Key == domain.LoginKeyInitMFAOTPCodeLabel { + result.InitMFAOTP.CodeLabel = text.Text + } + if text.Key == domain.LoginKeyInitMFAOTPSecretLabel { + result.InitMFAOTP.SecretLabel = text.Text + } + if text.Key == domain.LoginKeyInitMFAOTPNextButtonText { + result.InitMFAOTP.NextButtonText = text.Text + } + if text.Key == domain.LoginKeyInitMFAOTPCancelButtonText { + result.InitMFAOTP.CancelButtonText = text.Text + } +} + +func initMFAU2FKeyToDomain(text *CustomText, result *domain.CustomLoginText) { + if text.Key == domain.LoginKeyInitMFAU2FTitle { + result.InitMFAU2F.Title = text.Text + } + if text.Key == domain.LoginKeyInitMFAU2FDescription { + result.InitMFAU2F.Description = text.Text + } + if text.Key == domain.LoginKeyInitMFAU2FTokenNameLabel { + result.InitMFAU2F.TokenNameLabel = text.Text + } + if text.Key == domain.LoginKeyInitMFAU2FRegisterTokenButtonText { + result.InitMFAU2F.RegisterTokenButtonText = text.Text + } + if text.Key == domain.LoginKeyInitMFAU2FNotSupported { + result.InitMFAU2F.NotSupported = text.Text + } + if text.Key == domain.LoginKeyInitMFAU2FErrorRetry { + result.InitMFAU2F.ErrorRetry = text.Text + } +} + +func initMFADoneKeyToDomain(text *CustomText, result *domain.CustomLoginText) { + if text.Key == domain.LoginKeyInitMFADoneTitle { + result.InitMFADone.Title = text.Text + } + if text.Key == domain.LoginKeyInitMFADoneDescription { + result.InitMFADone.Description = text.Text + } + if text.Key == domain.LoginKeyInitMFADoneCancelButtonText { + result.InitMFADone.CancelButtonText = text.Text + } + if text.Key == domain.LoginKeyInitMFADoneNextButtonText { + result.InitMFADone.NextButtonText = text.Text + } +} + +func mfaProvidersKeyToDomain(text *CustomText, result *domain.CustomLoginText) { + if text.Key == domain.LoginKeyMFAProvidersChooseOther { + result.MFAProvider.ChooseOther = text.Text + } + if text.Key == domain.LoginKeyMFAProvidersOTP { + result.MFAProvider.Provider0 = text.Text + } + if text.Key == domain.LoginKeyMFAProvidersU2F { + result.MFAProvider.Provider1 = text.Text + } +} + +func verifyMFAOTPKeyToDomain(text *CustomText, result *domain.CustomLoginText) { + if text.Key == domain.LoginKeyVerifyMFAOTPTitle { + result.VerifyMFAOTP.Title = text.Text + } + if text.Key == domain.LoginKeyVerifyMFAOTPDescription { + result.VerifyMFAOTP.Description = text.Text + } + if text.Key == domain.LoginKeyVerifyMFAOTPCodeLabel { + result.VerifyMFAOTP.CodeLabel = text.Text + } + if text.Key == domain.LoginKeyVerifyMFAOTPNextButtonText { + result.VerifyMFAOTP.NextButtonText = text.Text + } +} + +func verifyMFAU2FKeyToDomain(text *CustomText, result *domain.CustomLoginText) { + if text.Key == domain.LoginKeyVerifyMFAU2FTitle { + result.VerifyMFAU2F.Title = text.Text + } + if text.Key == domain.LoginKeyVerifyMFAU2FDescription { + result.VerifyMFAU2F.Description = text.Text + } + if text.Key == domain.LoginKeyVerifyMFAU2FValidateTokenText { + result.VerifyMFAU2F.ValidateTokenButtonText = text.Text + } + if text.Key == domain.LoginKeyVerifyMFAU2FNotSupported { + result.VerifyMFAU2F.NotSupported = text.Text + } + if text.Key == domain.LoginKeyVerifyMFAU2FErrorRetry { + result.VerifyMFAU2F.ErrorRetry = text.Text + } +} + +func passwordlessKeyToDomain(text *CustomText, result *domain.CustomLoginText) { + if text.Key == domain.LoginKeyPasswordlessTitle { + result.Passwordless.Title = text.Text + } + if text.Key == domain.LoginKeyPasswordlessDescription { + result.Passwordless.Description = text.Text + } + if text.Key == domain.LoginKeyPasswordlessLoginWithPwButtonText { + result.Passwordless.LoginWithPwButtonText = text.Text + } + if text.Key == domain.LoginKeyPasswordlessValidateTokenButtonText { + result.Passwordless.ValidateTokenButtonText = text.Text + } + if text.Key == domain.LoginKeyPasswordlessNotSupported { + result.Passwordless.NotSupported = text.Text + } + if text.Key == domain.LoginKeyPasswordlessErrorRetry { + result.Passwordless.ErrorRetry = text.Text + } +} + +func passwordlessPromptKeyToDomain(text *CustomText, result *domain.CustomLoginText) { + if text.Key == domain.LoginKeyPasswordlessPromptTitle { + result.PasswordlessPrompt.Title = text.Text + } + if text.Key == domain.LoginKeyPasswordlessPromptDescription { + result.PasswordlessPrompt.Description = text.Text + } + if text.Key == domain.LoginKeyPasswordlessPromptDescriptionInit { + result.PasswordlessPrompt.DescriptionInit = text.Text + } + if text.Key == domain.LoginKeyPasswordlessPromptPasswordlessButtonText { + result.PasswordlessPrompt.PasswordlessButtonText = text.Text + } + if text.Key == domain.LoginKeyPasswordlessPromptNextButtonText { + result.PasswordlessPrompt.NextButtonText = text.Text + } + if text.Key == domain.LoginKeyPasswordlessPromptSkipButtonText { + result.PasswordlessPrompt.SkipButtonText = text.Text + } +} + +func passwordlessRegistrationKeyToDomain(text *CustomText, result *domain.CustomLoginText) { + if text.Key == domain.LoginKeyPasswordlessRegistrationTitle { + result.PasswordlessRegistration.Title = text.Text + } + if text.Key == domain.LoginKeyPasswordlessRegistrationDescription { + result.PasswordlessRegistration.Description = text.Text + } + if text.Key == domain.LoginKeyPasswordlessRegistrationRegisterTokenButtonText { + result.PasswordlessRegistration.RegisterTokenButtonText = text.Text + } + if text.Key == domain.LoginKeyPasswordlessRegistrationTokenNameLabel { + result.PasswordlessRegistration.TokenNameLabel = text.Text + } + if text.Key == domain.LoginKeyPasswordlessRegistrationNotSupported { + result.PasswordlessRegistration.NotSupported = text.Text + } + if text.Key == domain.LoginKeyPasswordlessRegistrationErrorRetry { + result.PasswordlessRegistration.ErrorRetry = text.Text + } +} + +func passwordlessRegistrationDoneKeyToDomain(text *CustomText, result *domain.CustomLoginText) { + if text.Key == domain.LoginKeyPasswordlessRegistrationDoneTitle { + result.PasswordlessRegistrationDone.Title = text.Text + } + if text.Key == domain.LoginKeyPasswordlessRegistrationDoneDescription { + result.PasswordlessRegistrationDone.Description = text.Text + } + if text.Key == domain.LoginKeyPasswordlessRegistrationDoneDescriptionClose { + result.PasswordlessRegistrationDone.DescriptionClose = text.Text + } + if text.Key == domain.LoginKeyPasswordlessRegistrationDoneNextButtonText { + result.PasswordlessRegistrationDone.NextButtonText = text.Text + } + if text.Key == domain.LoginKeyPasswordlessRegistrationDoneCancelButtonText { + result.PasswordlessRegistrationDone.CancelButtonText = text.Text + } +} + +func passwordChangeKeyToDomain(text *CustomText, result *domain.CustomLoginText) { + if text.Key == domain.LoginKeyPasswordChangeTitle { + result.PasswordChange.Title = text.Text + } + if text.Key == domain.LoginKeyPasswordChangeDescription { + result.PasswordChange.Description = text.Text + } + if text.Key == domain.LoginKeyPasswordChangeOldPasswordLabel { + result.PasswordChange.OldPasswordLabel = text.Text + } + if text.Key == domain.LoginKeyPasswordChangeNewPasswordLabel { + result.PasswordChange.NewPasswordLabel = text.Text + } + if text.Key == domain.LoginKeyPasswordChangeNewPasswordConfirmLabel { + result.PasswordChange.NewPasswordConfirmLabel = text.Text + } + if text.Key == domain.LoginKeyPasswordChangeCancelButtonText { + result.PasswordChange.CancelButtonText = text.Text + } + if text.Key == domain.LoginKeyPasswordChangeNextButtonText { + result.PasswordChange.NextButtonText = text.Text + } +} + +func passwordChangeDoneKeyToDomain(text *CustomText, result *domain.CustomLoginText) { + if text.Key == domain.LoginKeyPasswordChangeDoneTitle { + result.PasswordChangeDone.Title = text.Text + } + if text.Key == domain.LoginKeyPasswordChangeDoneDescription { + result.PasswordChangeDone.Description = text.Text + } + if text.Key == domain.LoginKeyPasswordChangeDoneNextButtonText { + result.PasswordChangeDone.NextButtonText = text.Text + } +} + +func passwordResetDoneKeyToDomain(text *CustomText, result *domain.CustomLoginText) { + if text.Key == domain.LoginKeyPasswordResetDoneTitle { + result.PasswordResetDone.Title = text.Text + } + if text.Key == domain.LoginKeyPasswordResetDoneDescription { + result.PasswordResetDone.Description = text.Text + } + if text.Key == domain.LoginKeyPasswordResetDoneNextButtonText { + result.PasswordResetDone.NextButtonText = text.Text + } +} + +func registrationOptionKeyToDomain(text *CustomText, result *domain.CustomLoginText) { + if text.Key == domain.LoginKeyRegistrationOptionTitle { + result.RegisterOption.Title = text.Text + } + if text.Key == domain.LoginKeyRegistrationOptionDescription { + result.RegisterOption.Description = text.Text + } + if text.Key == domain.LoginKeyRegistrationOptionExternalLoginDescription { + result.RegisterOption.ExternalLoginDescription = text.Text + } + if text.Key == domain.LoginKeyRegistrationOptionUserNameButtonText { + result.RegisterOption.RegisterUsernamePasswordButtonText = text.Text + } +} + +func registrationUserKeyToDomain(text *CustomText, result *domain.CustomLoginText) { + if text.Key == domain.LoginKeyRegistrationUserTitle { + result.RegistrationUser.Title = text.Text + } + if text.Key == domain.LoginKeyRegistrationUserDescription { + result.RegistrationUser.Description = text.Text + } + if text.Key == domain.LoginKeyRegistrationUserDescriptionOrgRegister { + result.RegistrationUser.DescriptionOrgRegister = text.Text + } + if text.Key == domain.LoginKeyRegistrationUserFirstnameLabel { + result.RegistrationUser.FirstnameLabel = text.Text + } + if text.Key == domain.LoginKeyRegistrationUserLastnameLabel { + result.RegistrationUser.LastnameLabel = text.Text + } + if text.Key == domain.LoginKeyRegistrationUserEmailLabel { + result.RegistrationUser.EmailLabel = text.Text + } + if text.Key == domain.LoginKeyRegistrationUserUsernameLabel { + result.RegistrationUser.UsernameLabel = text.Text + } + if text.Key == domain.LoginKeyRegistrationUserLanguageLabel { + result.RegistrationUser.LanguageLabel = text.Text + } + if text.Key == domain.LoginKeyRegistrationUserGenderLabel { + result.RegistrationUser.GenderLabel = text.Text + } + if text.Key == domain.LoginKeyRegistrationUserPasswordLabel { + result.RegistrationUser.PasswordLabel = text.Text + } + if text.Key == domain.LoginKeyRegistrationUserPasswordConfirmLabel { + result.RegistrationUser.PasswordConfirmLabel = text.Text + } + if text.Key == domain.LoginKeyRegistrationUserTOSAndPrivacyLabel { + result.RegistrationUser.TOSAndPrivacyLabel = text.Text + } + if text.Key == domain.LoginKeyRegistrationUserTOSConfirm { + result.RegistrationUser.TOSConfirm = text.Text + } + if text.Key == domain.LoginKeyRegistrationUserTOSLinkText { + result.RegistrationUser.TOSLinkText = text.Text + } + if text.Key == domain.LoginKeyRegistrationUserTOSConfirmAnd { + result.RegistrationUser.TOSConfirmAnd = text.Text + } + if text.Key == domain.LoginKeyRegistrationUserPrivacyLinkText { + result.RegistrationUser.PrivacyLinkText = text.Text + } + if text.Key == domain.LoginKeyRegistrationUserNextButtonText { + result.RegistrationUser.NextButtonText = text.Text + } + if text.Key == domain.LoginKeyRegistrationUserBackButtonText { + result.RegistrationUser.BackButtonText = text.Text + } +} + +func registrationOrgKeyToDomain(text *CustomText, result *domain.CustomLoginText) { + if text.Key == domain.LoginKeyRegisterOrgTitle { + result.RegistrationOrg.Title = text.Text + } + if text.Key == domain.LoginKeyRegisterOrgDescription { + result.RegistrationOrg.Description = text.Text + } + if text.Key == domain.LoginKeyRegisterOrgOrgNameLabel { + result.RegistrationOrg.OrgNameLabel = text.Text + } + if text.Key == domain.LoginKeyRegisterOrgFirstnameLabel { + result.RegistrationOrg.FirstnameLabel = text.Text + } + if text.Key == domain.LoginKeyRegisterOrgLastnameLabel { + result.RegistrationOrg.LastnameLabel = text.Text + } + if text.Key == domain.LoginKeyRegisterOrgUsernameLabel { + result.RegistrationOrg.UsernameLabel = text.Text + } + if text.Key == domain.LoginKeyRegisterOrgEmailLabel { + result.RegistrationOrg.EmailLabel = text.Text + } + if text.Key == domain.LoginKeyRegisterOrgPasswordLabel { + result.RegistrationOrg.PasswordLabel = text.Text + } + if text.Key == domain.LoginKeyRegisterOrgPasswordConfirmLabel { + result.RegistrationOrg.PasswordConfirmLabel = text.Text + } + if text.Key == domain.LoginKeyRegisterOrgTOSAndPrivacyLabel { + result.RegistrationOrg.TOSAndPrivacyLabel = text.Text + } + if text.Key == domain.LoginKeyRegisterOrgTOSConfirm { + result.RegistrationOrg.TOSConfirm = text.Text + } + if text.Key == domain.LoginKeyRegisterOrgTOSLinkText { + result.RegistrationOrg.TOSLinkText = text.Text + } + if text.Key == domain.LoginKeyRegisterOrgTosConfirmAnd { + result.RegistrationOrg.TOSConfirmAnd = text.Text + } + if text.Key == domain.LoginKeyRegisterOrgPrivacyLinkText { + result.RegistrationOrg.PrivacyLinkText = text.Text + } + if text.Key == domain.LoginKeyRegisterOrgSaveButtonText { + result.RegistrationOrg.SaveButtonText = text.Text + } +} + +func linkingUserKeyToDomain(text *CustomText, result *domain.CustomLoginText) { + if text.Key == domain.LoginKeyLinkingUserDoneTitle { + result.LinkingUsersDone.Title = text.Text + } + if text.Key == domain.LoginKeyLinkingUserDoneDescription { + result.LinkingUsersDone.Description = text.Text + } + if text.Key == domain.LoginKeyLinkingUserDoneCancelButtonText { + result.LinkingUsersDone.CancelButtonText = text.Text + } + if text.Key == domain.LoginKeyLinkingUserDoneNextButtonText { + result.LinkingUsersDone.NextButtonText = text.Text + } +} + +func externalUserNotFoundKeyToDomain(text *CustomText, result *domain.CustomLoginText) { + if text.Key == domain.LoginKeyExternalNotFoundTitle { + result.ExternalNotFoundOption.Title = text.Text + } + if text.Key == domain.LoginKeyExternalNotFoundDescription { + result.ExternalNotFoundOption.Description = text.Text + } + if text.Key == domain.LoginKeyExternalNotFoundLinkButtonText { + result.ExternalNotFoundOption.LinkButtonText = text.Text + } + if text.Key == domain.LoginKeyExternalNotFoundAutoRegisterButtonText { + result.ExternalNotFoundOption.AutoRegisterButtonText = text.Text + } + if text.Key == domain.LoginKeyExternalNotFoundTOSAndPrivacyLabel { + result.ExternalNotFoundOption.TOSAndPrivacyLabel = text.Text + } + if text.Key == domain.LoginKeyExternalNotFoundTOSConfirm { + result.ExternalNotFoundOption.TOSConfirm = text.Text + } + if text.Key == domain.LoginKeyExternalNotFoundTOSLinkText { + result.ExternalNotFoundOption.TOSLinkText = text.Text + } + if text.Key == domain.LoginKeyExternalNotFoundTOSConfirmAnd { + result.ExternalNotFoundOption.TOSConfirmAnd = text.Text + } + if text.Key == domain.LoginKeyExternalNotFoundPrivacyLinkText { + result.ExternalNotFoundOption.PrivacyLinkText = text.Text + } +} + +func successLoginKeyToDomain(text *CustomText, result *domain.CustomLoginText) { + if text.Key == domain.LoginKeySuccessLoginTitle { + result.LoginSuccess.Title = text.Text + } + if text.Key == domain.LoginKeySuccessLoginAutoRedirectDescription { + result.LoginSuccess.AutoRedirectDescription = text.Text + } + if text.Key == domain.LoginKeySuccessLoginRedirectedDescription { + result.LoginSuccess.RedirectedDescription = text.Text + } + if text.Key == domain.LoginKeySuccessLoginNextButtonText { + result.LoginSuccess.NextButtonText = text.Text + } +} + +func logoutDoneKeyToDomain(text *CustomText, result *domain.CustomLoginText) { + if text.Key == domain.LoginKeyLogoutDoneTitle { + result.LogoutDone.Title = text.Text + } + if text.Key == domain.LoginKeyLogoutDoneDescription { + result.LogoutDone.Description = text.Text + } + if text.Key == domain.LoginKeyLogoutDoneLoginButtonText { + result.LogoutDone.LoginButtonText = text.Text + } +} + +func footerKeyToDomain(text *CustomText, result *domain.CustomLoginText) { + if text.Key == domain.LoginKeyFooterTOS { + result.Footer.TOS = text.Text + } + if text.Key == domain.LoginKeyFooterPrivacyPolicy { + result.Footer.PrivacyPolicy = text.Text + } + if text.Key == domain.LoginKeyFooterHelp { + result.Footer.Help = text.Text + } + if text.Key == domain.LoginKeyFooterHelpLink { + result.Footer.HelpLink = text.Text + } +} diff --git a/internal/query/custom_text_test.go b/internal/query/custom_text_test.go new file mode 100644 index 0000000000..a66e4f0839 --- /dev/null +++ b/internal/query/custom_text_test.go @@ -0,0 +1,221 @@ +package query + +import ( + "database/sql" + "database/sql/driver" + "errors" + "fmt" + "regexp" + "testing" + + errs "github.com/caos/zitadel/internal/errors" + "golang.org/x/text/language" +) + +func Test_CustomTextPrepares(t *testing.T) { + type want struct { + sqlExpectations sqlExpectation + err checkErr + } + tests := []struct { + name string + prepare interface{} + want want + object interface{} + }{ + { + name: "prepareCustomTextQuery no result", + prepare: prepareCustomTextsQuery, + want: want{ + sqlExpectations: mockQueries( + regexp.QuoteMeta(`SELECT zitadel.projections.custom_texts.aggregate_id,`+ + ` zitadel.projections.custom_texts.sequence,`+ + ` zitadel.projections.custom_texts.creation_date,`+ + ` zitadel.projections.custom_texts.change_date,`+ + ` zitadel.projections.custom_texts.language,`+ + ` zitadel.projections.custom_texts.template,`+ + ` zitadel.projections.custom_texts.key,`+ + ` zitadel.projections.custom_texts.text,`+ + ` COUNT(*) OVER ()`+ + ` FROM zitadel.projections.custom_texts`), + nil, + nil, + ), + err: func(err error) (error, bool) { + if !errs.IsNotFound(err) { + return fmt.Errorf("err should be zitadel.NotFoundError got: %w", err), false + } + return nil, true + }, + }, + object: &CustomTexts{CustomTexts: []*CustomText{}}, + }, + { + name: "prepareCustomTextQuery one result", + prepare: prepareCustomTextsQuery, + want: want{ + sqlExpectations: mockQueries( + regexp.QuoteMeta(`SELECT zitadel.projections.custom_texts.aggregate_id,`+ + ` zitadel.projections.custom_texts.sequence,`+ + ` zitadel.projections.custom_texts.creation_date,`+ + ` zitadel.projections.custom_texts.change_date,`+ + ` zitadel.projections.custom_texts.language,`+ + ` zitadel.projections.custom_texts.template,`+ + ` zitadel.projections.custom_texts.key,`+ + ` zitadel.projections.custom_texts.text,`+ + ` COUNT(*) OVER ()`+ + ` FROM zitadel.projections.custom_texts`), + []string{ + "aggregate_id", + "sequence", + "creation_date", + "change_date", + "language", + "template", + "key", + "text", + "count", + }, + [][]driver.Value{ + { + "agg-id", + uint64(20211109), + testNow, + testNow, + "en", + "template", + "key", + "text", + }, + }, + ), + }, + object: &CustomTexts{ + SearchResponse: SearchResponse{ + Count: 1, + }, + CustomTexts: []*CustomText{ + { + AggregateID: "agg-id", + CreationDate: testNow, + ChangeDate: testNow, + Sequence: 20211109, + Language: language.English, + Template: "template", + Key: "key", + Text: "text", + }, + }, + }, + }, + { + name: "prepareCustomTextQuery multiple result", + prepare: prepareCustomTextsQuery, + want: want{ + sqlExpectations: mockQueries( + regexp.QuoteMeta(`SELECT zitadel.projections.custom_texts.aggregate_id,`+ + ` zitadel.projections.custom_texts.sequence,`+ + ` zitadel.projections.custom_texts.creation_date,`+ + ` zitadel.projections.custom_texts.change_date,`+ + ` zitadel.projections.custom_texts.language,`+ + ` zitadel.projections.custom_texts.template,`+ + ` zitadel.projections.custom_texts.key,`+ + ` zitadel.projections.custom_texts.text,`+ + ` COUNT(*) OVER ()`+ + ` FROM zitadel.projections.custom_texts`), + []string{ + "aggregate_id", + "sequence", + "creation_date", + "change_date", + "language", + "template", + "key", + "text", + "count", + }, + [][]driver.Value{ + { + "agg-id", + uint64(20211109), + testNow, + testNow, + "en", + "template", + "key", + "text", + }, + { + "agg-id", + uint64(20211109), + testNow, + testNow, + "en", + "template", + "key2", + "text", + }, + }, + ), + }, + object: &CustomTexts{ + SearchResponse: SearchResponse{ + Count: 2, + }, + CustomTexts: []*CustomText{ + { + AggregateID: "agg-id", + CreationDate: testNow, + ChangeDate: testNow, + Sequence: 20211109, + Language: language.English, + Template: "template", + Key: "key", + Text: "text", + }, + { + AggregateID: "agg-id", + CreationDate: testNow, + ChangeDate: testNow, + Sequence: 20211109, + Language: language.English, + Template: "template", + Key: "key2", + Text: "text", + }, + }, + }, + }, + { + name: "prepareCustomTextQuery sql err", + prepare: prepareCustomTextsQuery, + want: want{ + sqlExpectations: mockQueryErr( + regexp.QuoteMeta(`SELECT zitadel.projections.custom_texts.aggregate_id,`+ + ` zitadel.projections.custom_texts.sequence,`+ + ` zitadel.projections.custom_texts.creation_date,`+ + ` zitadel.projections.custom_texts.change_date,`+ + ` zitadel.projections.custom_texts.language,`+ + ` zitadel.projections.custom_texts.template,`+ + ` zitadel.projections.custom_texts.key,`+ + ` zitadel.projections.custom_texts.text,`+ + ` COUNT(*) OVER ()`+ + ` FROM zitadel.projections.custom_texts`), + sql.ErrConnDone, + ), + err: func(err error) (error, bool) { + if !errors.Is(err, sql.ErrConnDone) { + return fmt.Errorf("err should be sql.ErrConnDone got: %w", err), false + } + return nil, true + }, + }, + object: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assertPrepare(t, tt.prepare, tt.object, tt.want.sqlExpectations, tt.want.err) + }) + } +} diff --git a/internal/query/mail_template.go b/internal/query/mail_template.go new file mode 100644 index 0000000000..b55ad9c8c7 --- /dev/null +++ b/internal/query/mail_template.go @@ -0,0 +1,126 @@ +package query + +import ( + "context" + "database/sql" + errs "errors" + "time" + + sq "github.com/Masterminds/squirrel" + "github.com/caos/zitadel/internal/domain" + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/query/projection" +) + +type MailTemplate struct { + AggregateID string + Sequence uint64 + CreationDate time.Time + ChangeDate time.Time + State domain.PolicyState + + Template []byte + IsDefault bool +} + +var ( + mailTemplateTable = table{ + name: projection.MailTemplateTable, + } + MailTemplateColAggregateID = Column{ + name: projection.MailTemplateAggregateIDCol, + table: mailTemplateTable, + } + MailTemplateColSequence = Column{ + name: projection.MailTemplateSequenceCol, + table: mailTemplateTable, + } + MailTemplateColCreationDate = Column{ + name: projection.MailTemplateCreationDateCol, + table: mailTemplateTable, + } + MailTemplateColChangeDate = Column{ + name: projection.MailTemplateChangeDateCol, + table: mailTemplateTable, + } + MailTemplateColTemplate = Column{ + name: projection.MailTemplateTemplateCol, + table: mailTemplateTable, + } + MailTemplateColIsDefault = Column{ + name: projection.MailTemplateIsDefaultCol, + table: mailTemplateTable, + } + MailTemplateColState = Column{ + name: projection.MailTemplateStateCol, + table: mailTemplateTable, + } +) + +func (q *Queries) MailTemplateByOrg(ctx context.Context, orgID string) (*MailTemplate, error) { + stmt, scan := prepareMailTemplateQuery() + query, args, err := stmt.Where( + sq.Or{ + sq.Eq{ + MailTemplateColAggregateID.identifier(): orgID, + }, + sq.Eq{ + MailTemplateColAggregateID.identifier(): q.iamID, + }, + }). + OrderBy(MailTemplateColIsDefault.identifier()). + Limit(1).ToSql() + if err != nil { + return nil, errors.ThrowInternal(err, "QUERY-m0sJg", "Errors.Query.SQLStatement") + } + + row := q.client.QueryRowContext(ctx, query, args...) + return scan(row) +} + +func (q *Queries) DefaultMailTemplate(ctx context.Context) (*MailTemplate, error) { + stmt, scan := prepareMailTemplateQuery() + query, args, err := stmt.Where(sq.Eq{ + MailTemplateColAggregateID.identifier(): q.iamID, + }). + OrderBy(MailTemplateColIsDefault.identifier()). + Limit(1).ToSql() + if err != nil { + return nil, errors.ThrowInternal(err, "QUERY-2m0fH", "Errors.Query.SQLStatement") + } + + row := q.client.QueryRowContext(ctx, query, args...) + return scan(row) +} + +func prepareMailTemplateQuery() (sq.SelectBuilder, func(*sql.Row) (*MailTemplate, error)) { + return sq.Select( + MailTemplateColAggregateID.identifier(), + MailTemplateColSequence.identifier(), + MailTemplateColCreationDate.identifier(), + MailTemplateColChangeDate.identifier(), + MailTemplateColTemplate.identifier(), + MailTemplateColIsDefault.identifier(), + MailTemplateColState.identifier(), + ). + From(mailTemplateTable.identifier()).PlaceholderFormat(sq.Dollar), + func(row *sql.Row) (*MailTemplate, error) { + policy := new(MailTemplate) + err := row.Scan( + &policy.AggregateID, + &policy.Sequence, + &policy.CreationDate, + &policy.ChangeDate, + &policy.Template, + &policy.IsDefault, + &policy.State, + ) + if err != nil { + if errs.Is(err, sql.ErrNoRows) { + return nil, errors.ThrowNotFound(err, "QUERY-2NO0g", "Errors.MailTemplate.NotFound") + } + return nil, errors.ThrowInternal(err, "QUERY-4Nisf", "Errors.Internal") + } + return policy, nil + } +} diff --git a/internal/query/message_text.go b/internal/query/message_text.go new file mode 100644 index 0000000000..f98bffb22d --- /dev/null +++ b/internal/query/message_text.go @@ -0,0 +1,324 @@ +package query + +import ( + "context" + "database/sql" + "encoding/json" + errs "errors" + "fmt" + "io/ioutil" + "net/http" + "os" + "time" + + sq "github.com/Masterminds/squirrel" + "golang.org/x/text/language" + "sigs.k8s.io/yaml" + + "github.com/caos/zitadel/internal/domain" + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/query/projection" +) + +type MessageTexts struct { + InitCode MessageText + PasswordReset MessageText + VerifyEmail MessageText + VerifyPhone MessageText + DomainClaimed MessageText + PasswordlessRegistration MessageText +} + +type MessageText struct { + AggregateID string + Sequence uint64 + CreationDate time.Time + ChangeDate time.Time + State domain.PolicyState + + IsDefault bool + + Type string + Language language.Tag + Title string + PreHeader string + Subject string + Greeting string + Text string + ButtonText string + Footer string +} + +var ( + messageTextTable = table{ + name: projection.MessageTextTable, + } + MessageTextColAggregateID = Column{ + name: projection.MessageTextAggregateIDCol, + table: messageTextTable, + } + MessageTextColSequence = Column{ + name: projection.MessageTextSequenceCol, + table: messageTextTable, + } + MessageTextColCreationDate = Column{ + name: projection.MessageTextCreationDateCol, + table: messageTextTable, + } + MessageTextColChangeDate = Column{ + name: projection.MessageTextChangeDateCol, + table: messageTextTable, + } + MessageTextColState = Column{ + name: projection.MessageTextStateCol, + table: messageTextTable, + } + MessageTextColType = Column{ + name: projection.MessageTextTypeCol, + table: messageTextTable, + } + MessageTextColLanguage = Column{ + name: projection.MessageTextLanguageCol, + table: messageTextTable, + } + MessageTextColTitle = Column{ + name: projection.MessageTextTitleCol, + table: messageTextTable, + } + MessageTextColPreHeader = Column{ + name: projection.MessageTextPreHeaderCol, + table: messageTextTable, + } + MessageTextColSubject = Column{ + name: projection.MessageTextSubjectCol, + table: messageTextTable, + } + MessageTextColGreeting = Column{ + name: projection.MessageTextGreetingCol, + table: messageTextTable, + } + MessageTextColText = Column{ + name: projection.MessageTextTextCol, + table: messageTextTable, + } + MessageTextColButtonText = Column{ + name: projection.MessageTextButtonTextCol, + table: messageTextTable, + } + MessageTextColFooter = Column{ + name: projection.MessageTextFooterCol, + table: messageTextTable, + } +) + +func (q *Queries) MessageTextByOrg(ctx context.Context, orgID string) (*MessageText, error) { + stmt, scan := prepareMessageTextQuery() + query, args, err := stmt.Where( + sq.Or{ + sq.Eq{ + MessageTextColAggregateID.identifier(): orgID, + }, + sq.Eq{ + MessageTextColAggregateID.identifier(): q.iamID, + }, + }). + OrderBy(MessageTextColAggregateID.identifier()). + Limit(1).ToSql() + if err != nil { + return nil, errors.ThrowInternal(err, "QUERY-90n3N", "Errors.Query.SQLStatement") + } + + row := q.client.QueryRowContext(ctx, query, args...) + return scan(row) +} + +func (q *Queries) DefaultMessageText(ctx context.Context) (*MessageText, error) { + stmt, scan := prepareMessageTextQuery() + query, args, err := stmt.Where(sq.Eq{ + MessageTextColAggregateID.identifier(): q.iamID, + }). + Limit(1).ToSql() + if err != nil { + return nil, errors.ThrowInternal(err, "QUERY-1b9mf", "Errors.Query.SQLStatement") + } + + row := q.client.QueryRowContext(ctx, query, args...) + return scan(row) +} + +func (q *Queries) DefaultMessageTextByTypeAndLanguageFromFileSystem(messageType, language string) (*MessageText, error) { + contents, err := q.readNotificationTextMessages(language) + if err != nil { + return nil, err + } + messageTexts := new(MessageTexts) + if err := yaml.Unmarshal(contents, messageTexts); err != nil { + return nil, errors.ThrowInternal(err, "TEXT-3N9fs", "Errors.TranslationFile.ReadError") + } + return messageTexts.GetMessageTextByType(messageType), nil +} + +func (q *Queries) CustomMessageTextByTypeAndLanguage(ctx context.Context, aggregateID, messageType, language string) (*MessageText, error) { + stmt, scan := prepareMessageTextQuery() + query, args, err := stmt.Where( + sq.Eq{ + MessageTextColAggregateID.identifier(): aggregateID, + MessageTextColType.identifier(): messageType, + MessageTextColLanguage.identifier(): language, + }, + ). + Limit(1).ToSql() + if err != nil { + return nil, errors.ThrowInternal(err, "QUERY-1b9mf", "Errors.Query.SQLStatement") + } + + row := q.client.QueryRowContext(ctx, query, args...) + return scan(row) +} + +func (q *Queries) IAMMessageTextByTypeAndLanguage(ctx context.Context, messageType, language string) (*MessageText, error) { + contents, err := q.readNotificationTextMessages(language) + if err != nil { + return nil, err + } + notificationTextMap := make(map[string]interface{}) + if err := yaml.Unmarshal(contents, ¬ificationTextMap); err != nil { + return nil, errors.ThrowInternal(err, "QUERY-ekjFF", "Errors.TranslationFile.ReadError") + } + texts, err := q.CustomTextList(ctx, domain.IAMID, messageType, language) + if err != nil { + return nil, err + } + for _, text := range texts.CustomTexts { + messageTextMap, ok := notificationTextMap[messageType].(map[string]interface{}) + if !ok { + continue + } + messageTextMap[text.Key] = text.Text + } + jsonbody, err := json.Marshal(notificationTextMap) + + if err != nil { + return nil, errors.ThrowInternal(err, "QUERY-3m8fJ", "Errors.TranslationFile.MergeError") + } + notificationText := new(MessageTexts) + if err := json.Unmarshal(jsonbody, ¬ificationText); err != nil { + return nil, errors.ThrowInternal(err, "QUERY-9MkfD", "Errors.TranslationFile.MergeError") + } + result := notificationText.GetMessageTextByType(messageType) + result.IsDefault = true + return result, nil +} + +func (q *Queries) readNotificationTextMessages(language string) ([]byte, error) { + q.mutex.Lock() + defer q.mutex.Unlock() + var err error + contents, ok := q.NotificationTranslationFileContents[language] + if !ok { + contents, err = q.readTranslationFile(q.NotificationDir, fmt.Sprintf("/i18n/%s.yaml", language)) + if errors.IsNotFound(err) { + contents, err = q.readTranslationFile(q.NotificationDir, fmt.Sprintf("/i18n/%s.yaml", q.DefaultLanguage.String())) + } + if err != nil { + return nil, err + } + q.NotificationTranslationFileContents[language] = contents + } + return contents, nil +} + +func prepareMessageTextQuery() (sq.SelectBuilder, func(*sql.Row) (*MessageText, error)) { + return sq.Select( + MessageTextColAggregateID.identifier(), + MessageTextColSequence.identifier(), + MessageTextColCreationDate.identifier(), + MessageTextColChangeDate.identifier(), + MessageTextColState.identifier(), + MessageTextColType.identifier(), + MessageTextColLanguage.identifier(), + MessageTextColTitle.identifier(), + MessageTextColPreHeader.identifier(), + MessageTextColSubject.identifier(), + MessageTextColGreeting.identifier(), + MessageTextColText.identifier(), + MessageTextColButtonText.identifier(), + MessageTextColFooter.identifier(), + ). + From(messageTextTable.identifier()).PlaceholderFormat(sq.Dollar), + func(row *sql.Row) (*MessageText, error) { + msg := new(MessageText) + lang := "" + title := sql.NullString{} + preHeader := sql.NullString{} + subject := sql.NullString{} + greeting := sql.NullString{} + text := sql.NullString{} + buttonText := sql.NullString{} + footer := sql.NullString{} + err := row.Scan( + &msg.AggregateID, + &msg.Sequence, + &msg.CreationDate, + &msg.ChangeDate, + &msg.State, + &msg.Type, + &lang, + &title, + &preHeader, + &subject, + &greeting, + &text, + &buttonText, + &footer, + ) + if err != nil { + if errs.Is(err, sql.ErrNoRows) { + return nil, errors.ThrowNotFound(err, "QUERY-3nlrS", "Errors.MessageText.NotFound") + } + return nil, errors.ThrowInternal(err, "QUERY-499gJ", "Errors.Internal") + } + msg.Language = language.Make(lang) + msg.Title = title.String + msg.PreHeader = preHeader.String + msg.Subject = subject.String + msg.Greeting = greeting.String + msg.Text = text.String + msg.ButtonText = buttonText.String + msg.Footer = footer.String + return msg, nil + } +} + +func (q *Queries) readTranslationFile(dir http.FileSystem, filename string) ([]byte, error) { + r, err := dir.Open(filename) + if os.IsNotExist(err) { + return nil, errors.ThrowNotFound(err, "QUERY-sN9wg", "Errors.TranslationFile.NotFound") + } + if err != nil { + return nil, errors.ThrowInternal(err, "QUERY-93njw", "Errors.TranslationFile.ReadError") + } + contents, err := ioutil.ReadAll(r) + if err != nil { + return nil, errors.ThrowInternal(err, "QUERY-l0fse", "Errors.TranslationFile.ReadError") + } + return contents, nil +} + +func (m *MessageTexts) GetMessageTextByType(msgType string) *MessageText { + switch msgType { + case domain.InitCodeMessageType: + return &m.InitCode + case domain.PasswordResetMessageType: + return &m.PasswordReset + case domain.VerifyEmailMessageType: + return &m.VerifyEmail + case domain.VerifyPhoneMessageType: + return &m.VerifyPhone + case domain.DomainClaimedMessageType: + return &m.DomainClaimed + case domain.PasswordlessRegistrationMessageType: + return &m.PasswordlessRegistration + } + return nil +} diff --git a/internal/query/message_text_test.go b/internal/query/message_text_test.go new file mode 100644 index 0000000000..ebc0131d3e --- /dev/null +++ b/internal/query/message_text_test.go @@ -0,0 +1,167 @@ +package query + +import ( + "database/sql" + "database/sql/driver" + "errors" + "fmt" + "regexp" + "testing" + + "github.com/caos/zitadel/internal/domain" + errs "github.com/caos/zitadel/internal/errors" + "golang.org/x/text/language" +) + +func Test_MessageTextPrepares(t *testing.T) { + type want struct { + sqlExpectations sqlExpectation + err checkErr + } + tests := []struct { + name string + prepare interface{} + want want + object interface{} + }{ + { + name: "prepareMessageTextQuery no result", + prepare: prepareMessageTextQuery, + want: want{ + sqlExpectations: mockQueries( + regexp.QuoteMeta(`SELECT zitadel.projections.message_texts.aggregate_id,`+ + ` zitadel.projections.message_texts.sequence,`+ + ` zitadel.projections.message_texts.creation_date,`+ + ` zitadel.projections.message_texts.change_date,`+ + ` zitadel.projections.message_texts.state,`+ + ` zitadel.projections.message_texts.type,`+ + ` zitadel.projections.message_texts.language,`+ + ` zitadel.projections.message_texts.title,`+ + ` zitadel.projections.message_texts.pre_header,`+ + ` zitadel.projections.message_texts.subject,`+ + ` zitadel.projections.message_texts.greeting,`+ + ` zitadel.projections.message_texts.text,`+ + ` zitadel.projections.message_texts.button_text,`+ + ` zitadel.projections.message_texts.footer_text`+ + ` FROM zitadel.projections.message_texts`), + nil, + nil, + ), + err: func(err error) (error, bool) { + if !errs.IsNotFound(err) { + return fmt.Errorf("err should be zitadel.NotFoundError got: %w", err), false + } + return nil, true + }, + }, + object: (*MessageText)(nil), + }, + { + name: "prepareMesssageTextQuery found", + prepare: prepareMessageTextQuery, + want: want{ + sqlExpectations: mockQuery( + regexp.QuoteMeta(`SELECT zitadel.projections.message_texts.aggregate_id,`+ + ` zitadel.projections.message_texts.sequence,`+ + ` zitadel.projections.message_texts.creation_date,`+ + ` zitadel.projections.message_texts.change_date,`+ + ` zitadel.projections.message_texts.state,`+ + ` zitadel.projections.message_texts.type,`+ + ` zitadel.projections.message_texts.language,`+ + ` zitadel.projections.message_texts.title,`+ + ` zitadel.projections.message_texts.pre_header,`+ + ` zitadel.projections.message_texts.subject,`+ + ` zitadel.projections.message_texts.greeting,`+ + ` zitadel.projections.message_texts.text,`+ + ` zitadel.projections.message_texts.button_text,`+ + ` zitadel.projections.message_texts.footer_text`+ + ` FROM zitadel.projections.message_texts`), + []string{ + "aggregate_id", + "sequence", + "creation_date", + "change_date", + "state", + "type", + "language", + "title", + "pre_header", + "subject", + "greeting", + "text", + "button_text", + "footer_text", + }, + []driver.Value{ + "agg-id", + uint64(20211109), + testNow, + testNow, + domain.PolicyStateActive, + "type", + "en", + "title", + "pre_header", + "subject", + "greeting", + "text", + "button_text", + "footer_text", + }, + ), + }, + object: &MessageText{ + AggregateID: "agg-id", + CreationDate: testNow, + ChangeDate: testNow, + Sequence: 20211109, + State: domain.PolicyStateActive, + Type: "type", + Language: language.English, + Title: "title", + PreHeader: "pre_header", + Subject: "subject", + Greeting: "greeting", + Text: "text", + ButtonText: "button_text", + Footer: "footer_text", + }, + }, + { + name: "prepareMessageTextQuery sql err", + prepare: prepareMessageTextQuery, + want: want{ + sqlExpectations: mockQueryErr( + regexp.QuoteMeta(`SELECT zitadel.projections.message_texts.aggregate_id,`+ + ` zitadel.projections.message_texts.sequence,`+ + ` zitadel.projections.message_texts.creation_date,`+ + ` zitadel.projections.message_texts.change_date,`+ + ` zitadel.projections.message_texts.state,`+ + ` zitadel.projections.message_texts.type,`+ + ` zitadel.projections.message_texts.language,`+ + ` zitadel.projections.message_texts.title,`+ + ` zitadel.projections.message_texts.pre_header,`+ + ` zitadel.projections.message_texts.subject,`+ + ` zitadel.projections.message_texts.greeting,`+ + ` zitadel.projections.message_texts.text,`+ + ` zitadel.projections.message_texts.button_text,`+ + ` zitadel.projections.message_texts.footer_text`+ + ` FROM zitadel.projections.message_texts`), + sql.ErrConnDone, + ), + err: func(err error) (error, bool) { + if !errors.Is(err, sql.ErrConnDone) { + return fmt.Errorf("err should be sql.ErrConnDone got: %w", err), false + } + return nil, true + }, + }, + object: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assertPrepare(t, tt.prepare, tt.object, tt.want.sqlExpectations, tt.want.err) + }) + } +} diff --git a/internal/query/projection/custom_text.go b/internal/query/projection/custom_text.go index 69337901d1..a3c609de9b 100644 --- a/internal/query/projection/custom_text.go +++ b/internal/query/projection/custom_text.go @@ -92,7 +92,7 @@ func (p *CustomTextProjection) reduceSet(event eventstore.EventReader) (*handler logging.LogWithFields("PROJE-g0Jfs", "seq", event.Sequence(), "expectedTypes", []eventstore.EventType{org.CustomTextSetEventType, iam.CustomTextSetEventType}).Error("wrong event type") return nil, errors.ThrowInvalidArgument(nil, "PROJE-KKfw4", "reduce.wrong.event.type") } - return crdb.NewCreateStatement( + return crdb.NewUpsertStatement( &customTextEvent, []handler.Column{ handler.NewCol(CustomTextAggregateIDCol, customTextEvent.Aggregate().ID), @@ -124,7 +124,7 @@ func (p *CustomTextProjection) reduceRemoved(event eventstore.EventReader) (*han handler.NewCond(CustomTextAggregateIDCol, customTextEvent.Aggregate().ID), handler.NewCond(CustomTextTemplateCol, customTextEvent.Template), handler.NewCond(CustomTextKeyCol, customTextEvent.Key), - handler.NewCond(CustomTextLanguageCol, customTextEvent.Language), + handler.NewCond(CustomTextLanguageCol, customTextEvent.Language.String()), }), nil } diff --git a/internal/query/projection/custom_text_test.go b/internal/query/projection/custom_text_test.go index 733728f073..88eee0fcbc 100644 --- a/internal/query/projection/custom_text_test.go +++ b/internal/query/projection/custom_text_test.go @@ -45,7 +45,7 @@ func TestCustomTextProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO zitadel.projections.custom_texts (aggregate_id, creation_date, change_date, sequence, is_default, template, language, key, text) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", + expectedStmt: "UPSERT INTO zitadel.projections.custom_texts (aggregate_id, creation_date, change_date, sequence, is_default, template, language, key, text) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", expectedArgs: []interface{}{ "agg-id", anyArg{}, @@ -89,7 +89,7 @@ func TestCustomTextProjection_reduces(t *testing.T) { "agg-id", "InitCode", "Text", - language.English, + "en", }, }, }, @@ -152,7 +152,7 @@ func TestCustomTextProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO zitadel.projections.custom_texts (aggregate_id, creation_date, change_date, sequence, is_default, template, language, key, text) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", + expectedStmt: "UPSERT INTO zitadel.projections.custom_texts (aggregate_id, creation_date, change_date, sequence, is_default, template, language, key, text) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", expectedArgs: []interface{}{ "agg-id", anyArg{}, @@ -196,7 +196,7 @@ func TestCustomTextProjection_reduces(t *testing.T) { "agg-id", "InitCode", "Text", - language.English, + "en", }, }, }, diff --git a/internal/query/projection/message_texts.go b/internal/query/projection/message_texts.go index 6ab339e30e..8f78cd1ef9 100644 --- a/internal/query/projection/message_texts.go +++ b/internal/query/projection/message_texts.go @@ -96,7 +96,7 @@ func (p *MessageTextProjection) reduceAdded(event eventstore.EventReader) (*hand return nil, errors.ThrowInvalidArgument(nil, "PROJE-2n90r", "reduce.wrong.event.type") } if !isMessageTemplate(templateEvent.Template) { - return nil, nil + return crdb.NewNoOpStatement(event), nil } cols := []handler.Column{ diff --git a/internal/query/query.go b/internal/query/query.go index 8383fc4f9f..e5c5d9dd7e 100644 --- a/internal/query/query.go +++ b/internal/query/query.go @@ -3,7 +3,10 @@ package query import ( "context" "database/sql" + "net/http" + "sync" + "github.com/caos/logging" sd "github.com/caos/zitadel/internal/config/systemdefaults" "github.com/caos/zitadel/internal/config/types" "github.com/caos/zitadel/internal/eventstore" @@ -16,12 +19,21 @@ import ( "github.com/caos/zitadel/internal/repository/project" usr_repo "github.com/caos/zitadel/internal/repository/user" "github.com/caos/zitadel/internal/telemetry/tracing" + "github.com/rakyll/statik/fs" + "golang.org/x/text/language" ) type Queries struct { iamID string eventstore *eventstore.Eventstore client *sql.DB + + DefaultLanguage language.Tag + LoginDir http.FileSystem + NotificationDir http.FileSystem + mutex sync.Mutex + LoginTranslationFileContents map[string][]byte + NotificationTranslationFileContents map[string][]byte } type Config struct { @@ -34,10 +46,21 @@ func StartQueries(ctx context.Context, es *eventstore.Eventstore, projections pr return nil, err } + statikLoginFS, err := fs.NewWithNamespace("login") + logging.Log("CONFI-7usEW").OnError(err).Panic("unable to start login statik dir") + + statikNotificationFS, err := fs.NewWithNamespace("notification") + logging.Log("CONFI-7usEW").OnError(err).Panic("unable to start notification statik dir") + repo = &Queries{ - iamID: defaults.IamID, - eventstore: es, - client: sqlClient, + iamID: defaults.IamID, + eventstore: es, + client: sqlClient, + DefaultLanguage: defaults.DefaultLanguage, + LoginDir: statikLoginFS, + NotificationDir: statikNotificationFS, + LoginTranslationFileContents: make(map[string][]byte), + NotificationTranslationFileContents: make(map[string][]byte), } iam_repo.RegisterEventMappers(repo.eventstore) usr_repo.RegisterEventMappers(repo.eventstore) diff --git a/proto/zitadel/management.proto b/proto/zitadel/management.proto index 0c7e2acfd7..840bfcd310 100644 --- a/proto/zitadel/management.proto +++ b/proto/zitadel/management.proto @@ -289,7 +289,7 @@ service ManagementService { // Changes the username rpc UpdateUserName(UpdateUserNameRequest) returns (UpdateUserNameResponse) { option (google.api.http) = { - get: "/users/{user_id}/username" + put: "/users/{user_id}/username" }; option (zitadel.v1.auth_option) = {