From 76346cb0709f24b1a0847f2e9c40cf97ba393944 Mon Sep 17 00:00:00 2001 From: Fabi <38692350+fgerschwiler@users.noreply.github.com> Date: Sun, 21 Nov 2021 20:22:25 +0100 Subject: [PATCH] fix: features query (#2610) --- .../eventsourcing/eventstore/features.go | 71 ---- .../eventsourcing/handler/features.go | 169 --------- .../eventsourcing/handler/handler.go | 2 - .../repository/eventsourcing/repository.go | 7 - .../repository/eventsourcing/view/features.go | 56 --- internal/admin/repository/features.go | 12 - internal/admin/repository/repository.go | 1 - internal/api/grpc/admin/features.go | 8 +- internal/api/grpc/admin/server.go | 2 - internal/api/grpc/auth/features.go | 4 +- internal/api/grpc/auth/user.go | 2 +- internal/api/grpc/features/features.go | 15 +- internal/api/grpc/management/features.go | 4 +- internal/api/grpc/management/org.go | 2 +- internal/api/grpc/management/project.go | 2 +- .../grpc/management/project_application.go | 2 +- internal/api/grpc/management/server.go | 2 - internal/api/grpc/management/user.go | 2 +- .../eventsourcing/eventstore/features.go | 66 ---- .../eventsourcing/handler/features.go | 169 --------- .../eventsourcing/handler/handler.go | 1 - .../repository/eventsourcing/repository.go | 5 - .../repository/eventsourcing/view/features.go | 56 --- internal/auth/repository/features.go | 11 - internal/auth/repository/repository.go | 1 - .../eventstore/token_verifier.go | 39 +- .../eventsourcing/handler/features.go | 169 --------- .../eventsourcing/handler/handler.go | 2 - .../repository/eventsourcing/repository.go | 1 + .../repository/eventsourcing/view/features.go | 56 --- internal/features/model/features_view.go | 121 ------ .../features/repository/view/features_view.go | 48 --- .../repository/view/model/features.go | 117 ------ .../repository/view/model/features_query.go | 61 --- .../eventsourcing/eventstore/features.go | 70 ---- .../eventsourcing/handler/features.go | 169 --------- .../eventsourcing/handler/handler.go | 2 - .../repository/eventsourcing/repository.go | 2 - .../repository/eventsourcing/view/features.go | 56 --- internal/management/repository/features.go | 11 - internal/management/repository/repository.go | 1 - internal/query/features.go | 172 ++++++--- internal/query/features_test.go | 348 ++++++++++++++++++ internal/query/projection/feature.go | 4 +- internal/query/projection/feature_test.go | 38 +- .../cockroach/V1.92__features_not_null.sql | 64 ++++ 46 files changed, 604 insertions(+), 1619 deletions(-) delete mode 100644 internal/admin/repository/eventsourcing/eventstore/features.go delete mode 100644 internal/admin/repository/eventsourcing/handler/features.go delete mode 100644 internal/admin/repository/eventsourcing/view/features.go delete mode 100644 internal/admin/repository/features.go delete mode 100644 internal/auth/repository/eventsourcing/eventstore/features.go delete mode 100644 internal/auth/repository/eventsourcing/handler/features.go delete mode 100644 internal/auth/repository/eventsourcing/view/features.go delete mode 100644 internal/auth/repository/features.go delete mode 100644 internal/authz/repository/eventsourcing/handler/features.go delete mode 100644 internal/authz/repository/eventsourcing/view/features.go delete mode 100644 internal/features/model/features_view.go delete mode 100644 internal/features/repository/view/features_view.go delete mode 100644 internal/features/repository/view/model/features.go delete mode 100644 internal/features/repository/view/model/features_query.go delete mode 100644 internal/management/repository/eventsourcing/eventstore/features.go delete mode 100644 internal/management/repository/eventsourcing/handler/features.go delete mode 100644 internal/management/repository/eventsourcing/view/features.go delete mode 100644 internal/management/repository/features.go create mode 100644 internal/query/features_test.go create mode 100644 migrations/cockroach/V1.92__features_not_null.sql diff --git a/internal/admin/repository/eventsourcing/eventstore/features.go b/internal/admin/repository/eventsourcing/eventstore/features.go deleted file mode 100644 index cf161b088e..0000000000 --- a/internal/admin/repository/eventsourcing/eventstore/features.go +++ /dev/null @@ -1,71 +0,0 @@ -package eventstore - -import ( - "context" - - "github.com/caos/logging" - - admin_view "github.com/caos/zitadel/internal/admin/repository/eventsourcing/view" - "github.com/caos/zitadel/internal/config/systemdefaults" - "github.com/caos/zitadel/internal/domain" - "github.com/caos/zitadel/internal/errors" - v1 "github.com/caos/zitadel/internal/eventstore/v1" - "github.com/caos/zitadel/internal/eventstore/v1/models" - features_model "github.com/caos/zitadel/internal/features/model" - "github.com/caos/zitadel/internal/features/repository/view/model" - iam_view "github.com/caos/zitadel/internal/iam/repository/view" -) - -type FeaturesRepo struct { - Eventstore v1.Eventstore - - View *admin_view.View - - SearchLimit uint64 - SystemDefaults systemdefaults.SystemDefaults -} - -func (repo *FeaturesRepo) GetDefaultFeatures(ctx context.Context) (*features_model.FeaturesView, error) { - features, viewErr := repo.View.FeaturesByAggregateID(domain.IAMID) - if viewErr != nil && !errors.IsNotFound(viewErr) { - return nil, viewErr - } - if errors.IsNotFound(viewErr) { - features = new(model.FeaturesView) - } - - events, esErr := repo.getIAMEvents(ctx, features.Sequence) - if errors.IsNotFound(viewErr) && len(events) == 0 { - return nil, errors.ThrowNotFound(nil, "EVENT-Lsoj7", "Errors.Org.NotFound") - } - if esErr != nil { - logging.Log("EVENT-PSoc3").WithError(esErr).Debug("error retrieving new events") - return model.FeaturesToModel(features), nil - } - featuresCopy := *features - for _, event := range events { - if err := featuresCopy.AppendEvent(event); err != nil { - return model.FeaturesToModel(&featuresCopy), nil - } - } - return model.FeaturesToModel(&featuresCopy), nil -} - -func (repo *FeaturesRepo) GetOrgFeatures(ctx context.Context, orgID string) (*features_model.FeaturesView, error) { - features, err := repo.View.FeaturesByAggregateID(orgID) - if errors.IsNotFound(err) { - return repo.GetDefaultFeatures(ctx) - } - if err != nil { - return nil, err - } - return model.FeaturesToModel(features), nil -} - -func (repo *FeaturesRepo) getIAMEvents(ctx context.Context, sequence uint64) ([]*models.Event, error) { - query, err := iam_view.IAMByIDQuery(domain.IAMID, sequence) - if err != nil { - return nil, err - } - return repo.Eventstore.FilterEvents(ctx, query) -} diff --git a/internal/admin/repository/eventsourcing/handler/features.go b/internal/admin/repository/eventsourcing/handler/features.go deleted file mode 100644 index 408864e48c..0000000000 --- a/internal/admin/repository/eventsourcing/handler/features.go +++ /dev/null @@ -1,169 +0,0 @@ -package handler - -import ( - "context" - - "github.com/caos/logging" - - "github.com/caos/zitadel/internal/domain" - 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/features/repository/view/model" - "github.com/caos/zitadel/internal/iam/repository/eventsourcing" - iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model" - org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model" - iam_repo "github.com/caos/zitadel/internal/repository/iam" - org_repo "github.com/caos/zitadel/internal/repository/org" -) - -const ( - featuresTable = "adminapi.features" -) - -type Features struct { - handler - subscription *v1.Subscription -} - -func newFeatures(handler handler) *Features { - h := &Features{ - handler: handler, - } - - h.subscribe() - - return h -} - -func (p *Features) subscribe() { - p.subscription = p.es.Subscribe(p.AggregateTypes()...) - go func() { - for event := range p.subscription.Events { - query.ReduceEvent(p, event) - } - }() -} - -func (p *Features) Subscription() *v1.Subscription { - return p.subscription -} - -func (p *Features) ViewModel() string { - return featuresTable -} - -func (p *Features) AggregateTypes() []es_models.AggregateType { - return []es_models.AggregateType{iam_es_model.IAMAggregate, org_es_model.OrgAggregate} -} - -func (p *Features) EventQuery() (*es_models.SearchQuery, error) { - sequence, err := p.view.GetLatestFeaturesSequence() - if err != nil { - return nil, err - } - return es_models.NewSearchQuery(). - AggregateTypeFilter(p.AggregateTypes()...). - LatestSequenceFilter(sequence.CurrentSequence), nil -} - -func (p *Features) CurrentSequence() (uint64, error) { - sequence, err := p.view.GetLatestFeaturesSequence() - if err != nil { - return 0, err - } - return sequence.CurrentSequence, nil -} - -func (p *Features) Reduce(event *es_models.Event) (err error) { - switch event.AggregateType { - case org_es_model.OrgAggregate, iam_es_model.IAMAggregate: - err = p.processFeatures(event) - } - return err -} - -func (p *Features) processFeatures(event *es_models.Event) (err error) { - features := new(model.FeaturesView) - switch string(event.Type) { - case string(org_es_model.OrgAdded): - features, err = p.getDefaultFeatures() - if err != nil { - return err - } - features.AggregateID = event.AggregateID - features.Default = true - case string(iam_repo.FeaturesSetEventType): - defaultFeatures, err := p.view.AllDefaultFeatures() - if err != nil { - return err - } - for _, features := range defaultFeatures { - err = features.AppendEvent(event) - if err != nil { - return err - } - } - return p.view.PutFeaturesList(defaultFeatures, event) - case string(org_repo.FeaturesSetEventType): - features, err = p.view.FeaturesByAggregateID(event.AggregateID) - if err != nil { - return err - } - err = features.AppendEvent(event) - case string(org_repo.FeaturesRemovedEventType): - features, err = p.getDefaultFeatures() - if err != nil { - return err - } - features.AggregateID = event.AggregateID - features.Default = true - default: - return p.view.ProcessedFeaturesSequence(event) - } - if err != nil { - return err - } - return p.view.PutFeatures(features, event) -} - -func (p *Features) OnError(event *es_models.Event, err error) error { - logging.LogWithFields("SPOOL-Wj8sf", "id", event.AggregateID).WithError(err).Warn("something went wrong in login features handler") - return spooler.HandleError(event, err, p.view.GetLatestFeaturesFailedEvent, p.view.ProcessedFeaturesFailedEvent, p.view.ProcessedFeaturesSequence, p.errorCountUntilSkip) -} - -func (p *Features) OnSuccess() error { - return spooler.HandleSuccess(p.view.UpdateFeaturesSpoolerRunTimestamp) -} - -func (p *Features) getDefaultFeatures() (*model.FeaturesView, error) { - features, featuresErr := p.view.FeaturesByAggregateID(domain.IAMID) - if featuresErr != nil && !caos_errs.IsNotFound(featuresErr) { - return nil, featuresErr - } - if features == nil { - features = &model.FeaturesView{} - } - events, err := p.getIAMEvents(features.Sequence) - if err != nil { - return features, featuresErr - } - featuresCopy := *features - for _, event := range events { - if err := featuresCopy.AppendEvent(event); err != nil { - return features, nil - } - } - return &featuresCopy, nil -} - -func (p *Features) getIAMEvents(sequence uint64) ([]*es_models.Event, error) { - query, err := eventsourcing.IAMByIDQuery(domain.IAMID, sequence) - if err != nil { - return nil, err - } - - return p.es.FilterEvents(context.Background(), query) -} diff --git a/internal/admin/repository/eventsourcing/handler/handler.go b/internal/admin/repository/eventsourcing/handler/handler.go index 34a72d1abf..6fdb301587 100644 --- a/internal/admin/repository/eventsourcing/handler/handler.go +++ b/internal/admin/repository/eventsourcing/handler/handler.go @@ -52,8 +52,6 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es handler{view, bulkLimit, configs.cycleDuration("MailTemplate"), errorCount, es}), newMessageText( handler{view, bulkLimit, configs.cycleDuration("MessageText"), errorCount, es}), - newFeatures( - handler{view, bulkLimit, configs.cycleDuration("Features"), errorCount, es}), newCustomText( handler{view, bulkLimit, configs.cycleDuration("CustomTexts"), errorCount, es}), } diff --git a/internal/admin/repository/eventsourcing/repository.go b/internal/admin/repository/eventsourcing/repository.go index cec4e52118..43ec7ad143 100644 --- a/internal/admin/repository/eventsourcing/repository.go +++ b/internal/admin/repository/eventsourcing/repository.go @@ -30,7 +30,6 @@ type EsRepository struct { spooler *es_spol.Spooler eventstore.IAMRepository eventstore.AdministratorRepo - eventstore.FeaturesRepo eventstore.UserRepo } @@ -74,12 +73,6 @@ func Start(ctx context.Context, conf Config, systemDefaults sd.SystemDefaults, c AdministratorRepo: eventstore.AdministratorRepo{ View: view, }, - FeaturesRepo: eventstore.FeaturesRepo{ - Eventstore: es, - View: view, - SearchLimit: conf.SearchLimit, - SystemDefaults: systemDefaults, - }, UserRepo: eventstore.UserRepo{ Eventstore: es, View: view, diff --git a/internal/admin/repository/eventsourcing/view/features.go b/internal/admin/repository/eventsourcing/view/features.go deleted file mode 100644 index 7ea2629da6..0000000000 --- a/internal/admin/repository/eventsourcing/view/features.go +++ /dev/null @@ -1,56 +0,0 @@ -package view - -import ( - "github.com/caos/zitadel/internal/eventstore/v1/models" - "github.com/caos/zitadel/internal/features/repository/view" - "github.com/caos/zitadel/internal/features/repository/view/model" - global_view "github.com/caos/zitadel/internal/view/repository" -) - -const ( - featuresTable = "adminapi.features" -) - -func (v *View) AllDefaultFeatures() ([]*model.FeaturesView, error) { - return view.GetDefaultFeatures(v.Db, featuresTable) -} - -func (v *View) FeaturesByAggregateID(aggregateID string) (*model.FeaturesView, error) { - return view.GetFeaturesByAggregateID(v.Db, featuresTable, aggregateID) -} - -func (v *View) PutFeatures(features *model.FeaturesView, event *models.Event) error { - err := view.PutFeatures(v.Db, featuresTable, features) - if err != nil { - return err - } - return v.ProcessedFeaturesSequence(event) -} - -func (v *View) PutFeaturesList(features []*model.FeaturesView, event *models.Event) error { - err := view.PutFeaturesList(v.Db, featuresTable, features...) - if err != nil { - return err - } - return v.ProcessedFeaturesSequence(event) -} - -func (v *View) GetLatestFeaturesSequence() (*global_view.CurrentSequence, error) { - return v.latestSequence(featuresTable) -} - -func (v *View) ProcessedFeaturesSequence(event *models.Event) error { - return v.saveCurrentSequence(featuresTable, event) -} - -func (v *View) UpdateFeaturesSpoolerRunTimestamp() error { - return v.updateSpoolerRunSequence(featuresTable) -} - -func (v *View) GetLatestFeaturesFailedEvent(sequence uint64) (*global_view.FailedEvent, error) { - return v.latestFailedEvent(featuresTable, sequence) -} - -func (v *View) ProcessedFeaturesFailedEvent(failedEvent *global_view.FailedEvent) error { - return v.saveFailedEvent(failedEvent) -} diff --git a/internal/admin/repository/features.go b/internal/admin/repository/features.go deleted file mode 100644 index c7ecda8782..0000000000 --- a/internal/admin/repository/features.go +++ /dev/null @@ -1,12 +0,0 @@ -package repository - -import ( - "context" - - features_model "github.com/caos/zitadel/internal/features/model" -) - -type FeaturesRepository interface { - GetDefaultFeatures(ctx context.Context) (*features_model.FeaturesView, error) - GetOrgFeatures(ctx context.Context, id string) (*features_model.FeaturesView, error) -} diff --git a/internal/admin/repository/repository.go b/internal/admin/repository/repository.go index 61fbdad53f..8d39417778 100644 --- a/internal/admin/repository/repository.go +++ b/internal/admin/repository/repository.go @@ -6,6 +6,5 @@ type Repository interface { Health(ctx context.Context) error IAMRepository AdministratorRepository - FeaturesRepository UserRepository } diff --git a/internal/api/grpc/admin/features.go b/internal/api/grpc/admin/features.go index 25487de2da..412653e261 100644 --- a/internal/api/grpc/admin/features.go +++ b/internal/api/grpc/admin/features.go @@ -10,12 +10,12 @@ import ( ) func (s *Server) GetDefaultFeatures(ctx context.Context, _ *admin_pb.GetDefaultFeaturesRequest) (*admin_pb.GetDefaultFeaturesResponse, error) { - features, err := s.features.GetDefaultFeatures(ctx) + features, err := s.query.DefaultFeatures(ctx) if err != nil { return nil, err } return &admin_pb.GetDefaultFeaturesResponse{ - Features: features_grpc.FeaturesFromModel(features), + Features: features_grpc.ModelFeaturesToPb(features), }, nil } @@ -30,12 +30,12 @@ func (s *Server) SetDefaultFeatures(ctx context.Context, req *admin_pb.SetDefaul } func (s *Server) GetOrgFeatures(ctx context.Context, req *admin_pb.GetOrgFeaturesRequest) (*admin_pb.GetOrgFeaturesResponse, error) { - features, err := s.features.GetOrgFeatures(ctx, req.OrgId) + features, err := s.query.FeaturesByOrgID(ctx, req.OrgId) if err != nil { return nil, err } return &admin_pb.GetOrgFeaturesResponse{ - Features: features_grpc.FeaturesFromModel(features), + Features: features_grpc.ModelFeaturesToPb(features), }, nil } diff --git a/internal/api/grpc/admin/server.go b/internal/api/grpc/admin/server.go index 45fb45d326..e5ff49a9da 100644 --- a/internal/api/grpc/admin/server.go +++ b/internal/api/grpc/admin/server.go @@ -25,7 +25,6 @@ type Server struct { iam repository.IAMRepository administrator repository.AdministratorRepository repo repository.Repository - features repository.FeaturesRepository users repository.UserRepository iamDomain string } @@ -41,7 +40,6 @@ func CreateServer(command *command.Commands, query *query.Queries, repo reposito iam: repo, administrator: repo, repo: repo, - features: repo, users: repo, iamDomain: iamDomain, } diff --git a/internal/api/grpc/auth/features.go b/internal/api/grpc/auth/features.go index 4fcf6d24d1..41e801e613 100644 --- a/internal/api/grpc/auth/features.go +++ b/internal/api/grpc/auth/features.go @@ -8,11 +8,11 @@ import ( ) func (s *Server) ListMyZitadelFeatures(ctx context.Context, _ *auth_pb.ListMyZitadelFeaturesRequest) (*auth_pb.ListMyZitadelFeaturesResponse, error) { - features, err := s.repo.GetOrgFeatures(ctx, authz.GetCtxData(ctx).OrgID) + features, err := s.query.FeaturesByOrgID(ctx, authz.GetCtxData(ctx).OrgID) if err != nil { return nil, err } return &auth_pb.ListMyZitadelFeaturesResponse{ - Result: features.FeatureList(), + Result: features.EnabledFeatureTypes(), }, nil } diff --git a/internal/api/grpc/auth/user.go b/internal/api/grpc/auth/user.go index 3fb2cdaf16..33c78dde52 100644 --- a/internal/api/grpc/auth/user.go +++ b/internal/api/grpc/auth/user.go @@ -26,7 +26,7 @@ func (s *Server) GetMyUser(ctx context.Context, _ *auth_pb.GetMyUserRequest) (*a func (s *Server) ListMyUserChanges(ctx context.Context, req *auth_pb.ListMyUserChangesRequest) (*auth_pb.ListMyUserChangesResponse, error) { sequence, limit, asc := change.ChangeQueryToModel(req.Query) - features, err := s.repo.GetOrgFeatures(ctx, authz.GetCtxData(ctx).ResourceOwner) + features, err := s.query.FeaturesByOrgID(ctx, authz.GetCtxData(ctx).ResourceOwner) if err != nil { return nil, err } diff --git a/internal/api/grpc/features/features.go b/internal/api/grpc/features/features.go index f6045cb2cf..24026f6711 100644 --- a/internal/api/grpc/features/features.go +++ b/internal/api/grpc/features/features.go @@ -5,16 +5,14 @@ import ( object_grpc "github.com/caos/zitadel/internal/api/grpc/object" "github.com/caos/zitadel/internal/domain" - features_model "github.com/caos/zitadel/internal/features/model" + "github.com/caos/zitadel/internal/query" features_pb "github.com/caos/zitadel/pkg/grpc/features" ) -func FeaturesFromModel(features *features_model.FeaturesView) *features_pb.Features { +func ModelFeaturesToPb(features *query.Features) *features_pb.Features { return &features_pb.Features{ - Details: object_grpc.ToViewDetailsPb(features.Sequence, features.CreationDate, features.ChangeDate, features.AggregateID), - Tier: FeatureTierToPb(features.TierName, features.TierDescription, features.State, features.StateDescription), - IsDefault: features.Default, - + IsDefault: features.IsDefault, + Tier: FeatureTierToPb(features.TierName, features.TierDescription, features.State, features.StateDescription), AuditLogRetention: durationpb.New(features.AuditLogRetention), LoginPolicyFactors: features.LoginPolicyFactors, LoginPolicyIdp: features.LoginPolicyIDP, @@ -34,6 +32,11 @@ func FeaturesFromModel(features *features_model.FeaturesView) *features_pb.Featu MetadataUser: features.MetadataUser, LockoutPolicy: features.LockoutPolicy, Actions: features.Actions, + Details: object_grpc.ChangeToDetailsPb( + features.Sequence, + features.ChangeDate, + features.AggregateID, + ), } } diff --git a/internal/api/grpc/management/features.go b/internal/api/grpc/management/features.go index 9a7551b4b1..98eab8b393 100644 --- a/internal/api/grpc/management/features.go +++ b/internal/api/grpc/management/features.go @@ -9,11 +9,11 @@ import ( ) func (s *Server) GetFeatures(ctx context.Context, req *mgmt_pb.GetFeaturesRequest) (*mgmt_pb.GetFeaturesResponse, error) { - features, err := s.features.GetOrgFeatures(ctx, authz.GetCtxData(ctx).OrgID) + features, err := s.query.FeaturesByOrgID(ctx, authz.GetCtxData(ctx).OrgID) if err != nil { return nil, err } return &mgmt_pb.GetFeaturesResponse{ - Features: features_grpc.FeaturesFromModel(features), + Features: features_grpc.ModelFeaturesToPb(features), }, nil } diff --git a/internal/api/grpc/management/org.go b/internal/api/grpc/management/org.go index b05ab03dbe..a5d8a2e55c 100644 --- a/internal/api/grpc/management/org.go +++ b/internal/api/grpc/management/org.go @@ -36,7 +36,7 @@ func (s *Server) GetOrgByDomainGlobal(ctx context.Context, req *mgmt_pb.GetOrgBy func (s *Server) ListOrgChanges(ctx context.Context, req *mgmt_pb.ListOrgChangesRequest) (*mgmt_pb.ListOrgChangesResponse, error) { sequence, limit, asc := change_grpc.ChangeQueryToModel(req.Query) - features, err := s.features.GetOrgFeatures(ctx, authz.GetCtxData(ctx).OrgID) + features, err := s.query.FeaturesByOrgID(ctx, authz.GetCtxData(ctx).OrgID) if err != nil { return nil, err } diff --git a/internal/api/grpc/management/project.go b/internal/api/grpc/management/project.go index bd35fec66c..c04d975c64 100644 --- a/internal/api/grpc/management/project.go +++ b/internal/api/grpc/management/project.go @@ -110,7 +110,7 @@ func (s *Server) ListGrantedProjectRoles(ctx context.Context, req *mgmt_pb.ListG func (s *Server) ListProjectChanges(ctx context.Context, req *mgmt_pb.ListProjectChangesRequest) (*mgmt_pb.ListProjectChangesResponse, error) { sequence, limit, asc := change_grpc.ChangeQueryToModel(req.Query) - features, err := s.features.GetOrgFeatures(ctx, authz.GetCtxData(ctx).OrgID) + features, err := s.query.FeaturesByOrgID(ctx, authz.GetCtxData(ctx).OrgID) if err != nil { return nil, err } diff --git a/internal/api/grpc/management/project_application.go b/internal/api/grpc/management/project_application.go index b546bd3adf..1c9cb1d934 100644 --- a/internal/api/grpc/management/project_application.go +++ b/internal/api/grpc/management/project_application.go @@ -42,7 +42,7 @@ func (s *Server) ListApps(ctx context.Context, req *mgmt_pb.ListAppsRequest) (*m func (s *Server) ListAppChanges(ctx context.Context, req *mgmt_pb.ListAppChangesRequest) (*mgmt_pb.ListAppChangesResponse, error) { sequence, limit, asc := change_grpc.ChangeQueryToModel(req.Query) - features, err := s.features.GetOrgFeatures(ctx, authz.GetCtxData(ctx).OrgID) + features, err := s.query.FeaturesByOrgID(ctx, authz.GetCtxData(ctx).OrgID) if err != nil { return nil, err } diff --git a/internal/api/grpc/management/server.go b/internal/api/grpc/management/server.go index 8a43faf8b6..53d3cc6d45 100644 --- a/internal/api/grpc/management/server.go +++ b/internal/api/grpc/management/server.go @@ -28,7 +28,6 @@ type Server struct { user repository.UserRepository usergrant repository.UserGrantRepository iam repository.IamRepository - features repository.FeaturesRepository authZ authz.Config systemDefaults systemdefaults.SystemDefaults } @@ -46,7 +45,6 @@ func CreateServer(command *command.Commands, query *query.Queries, repo reposito user: repo, usergrant: repo, iam: repo, - features: repo, systemDefaults: sd, } } diff --git a/internal/api/grpc/management/user.go b/internal/api/grpc/management/user.go index d2fc082673..3ef8d3e612 100644 --- a/internal/api/grpc/management/user.go +++ b/internal/api/grpc/management/user.go @@ -57,7 +57,7 @@ func (s *Server) ListUsers(ctx context.Context, req *mgmt_pb.ListUsersRequest) ( func (s *Server) ListUserChanges(ctx context.Context, req *mgmt_pb.ListUserChangesRequest) (*mgmt_pb.ListUserChangesResponse, error) { sequence, limit, asc := change_grpc.ChangeQueryToModel(req.Query) - features, err := s.features.GetOrgFeatures(ctx, authz.GetCtxData(ctx).OrgID) + features, err := s.query.FeaturesByOrgID(ctx, authz.GetCtxData(ctx).OrgID) if err != nil { return nil, err } diff --git a/internal/auth/repository/eventsourcing/eventstore/features.go b/internal/auth/repository/eventsourcing/eventstore/features.go deleted file mode 100644 index b3cf660393..0000000000 --- a/internal/auth/repository/eventsourcing/eventstore/features.go +++ /dev/null @@ -1,66 +0,0 @@ -package eventstore - -import ( - "context" - - "github.com/caos/logging" - - auth_view "github.com/caos/zitadel/internal/auth/repository/eventsourcing/view" - "github.com/caos/zitadel/internal/domain" - "github.com/caos/zitadel/internal/errors" - v1 "github.com/caos/zitadel/internal/eventstore/v1" - "github.com/caos/zitadel/internal/eventstore/v1/models" - features_model "github.com/caos/zitadel/internal/features/model" - "github.com/caos/zitadel/internal/features/repository/view/model" - iam_view "github.com/caos/zitadel/internal/iam/repository/view" -) - -type FeaturesRepo struct { - Eventstore v1.Eventstore - - View *auth_view.View -} - -func (repo *FeaturesRepo) GetDefaultFeatures(ctx context.Context) (*features_model.FeaturesView, error) { - features, viewErr := repo.View.FeaturesByAggregateID(domain.IAMID) - if viewErr != nil && !errors.IsNotFound(viewErr) { - return nil, viewErr - } - if errors.IsNotFound(viewErr) { - features = new(model.FeaturesView) - } - events, esErr := repo.getIAMEvents(ctx, features.Sequence) - if errors.IsNotFound(viewErr) && len(events) == 0 { - return nil, errors.ThrowNotFound(nil, "EVENT-Lsoj7", "Errors.Org.NotFound") - } - if esErr != nil { - logging.Log("EVENT-PSoc3").WithError(esErr).Debug("error retrieving new events") - return model.FeaturesToModel(features), nil - } - featuresCopy := *features - for _, event := range events { - if err := featuresCopy.AppendEvent(event); err != nil { - return model.FeaturesToModel(&featuresCopy), nil - } - } - return model.FeaturesToModel(&featuresCopy), nil -} - -func (repo *FeaturesRepo) GetOrgFeatures(ctx context.Context, orgID string) (*features_model.FeaturesView, error) { - features, err := repo.View.FeaturesByAggregateID(orgID) - if errors.IsNotFound(err) { - return repo.GetDefaultFeatures(ctx) - } - if err != nil { - return nil, err - } - return model.FeaturesToModel(features), nil -} - -func (repo *FeaturesRepo) getIAMEvents(ctx context.Context, sequence uint64) ([]*models.Event, error) { - query, err := iam_view.IAMByIDQuery(domain.IAMID, sequence) - if err != nil { - return nil, err - } - return repo.Eventstore.FilterEvents(ctx, query) -} diff --git a/internal/auth/repository/eventsourcing/handler/features.go b/internal/auth/repository/eventsourcing/handler/features.go deleted file mode 100644 index 5775f8845d..0000000000 --- a/internal/auth/repository/eventsourcing/handler/features.go +++ /dev/null @@ -1,169 +0,0 @@ -package handler - -import ( - "context" - - "github.com/caos/logging" - - "github.com/caos/zitadel/internal/domain" - 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/features/repository/view/model" - "github.com/caos/zitadel/internal/iam/repository/eventsourcing" - iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model" - org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model" - iam_repo "github.com/caos/zitadel/internal/repository/iam" - org_repo "github.com/caos/zitadel/internal/repository/org" -) - -const ( - featuresTable = "auth.features" -) - -type Features struct { - handler - subscription *v1.Subscription -} - -func newFeatures(handler handler) *Features { - h := &Features{ - handler: handler, - } - - h.subscribe() - - return h -} - -func (p *Features) subscribe() { - p.subscription = p.es.Subscribe(p.AggregateTypes()...) - go func() { - for event := range p.subscription.Events { - query.ReduceEvent(p, event) - } - }() -} - -func (p *Features) ViewModel() string { - return featuresTable -} - -func (p *Features) Subscription() *v1.Subscription { - return p.subscription -} - -func (p *Features) AggregateTypes() []es_models.AggregateType { - return []es_models.AggregateType{iam_es_model.IAMAggregate, org_es_model.OrgAggregate} -} - -func (p *Features) EventQuery() (*es_models.SearchQuery, error) { - sequence, err := p.view.GetLatestFeaturesSequence() - if err != nil { - return nil, err - } - return es_models.NewSearchQuery(). - AggregateTypeFilter(p.AggregateTypes()...). - LatestSequenceFilter(sequence.CurrentSequence), nil -} - -func (p *Features) CurrentSequence() (uint64, error) { - sequence, err := p.view.GetLatestFeaturesSequence() - if err != nil { - return 0, err - } - return sequence.CurrentSequence, nil -} - -func (p *Features) Reduce(event *es_models.Event) (err error) { - switch event.AggregateType { - case org_es_model.OrgAggregate, iam_es_model.IAMAggregate: - err = p.processFeatures(event) - } - return err -} - -func (p *Features) processFeatures(event *es_models.Event) (err error) { - features := new(model.FeaturesView) - switch string(event.Type) { - case string(org_es_model.OrgAdded): - features, err = p.getDefaultFeatures() - if err != nil { - return err - } - features.AggregateID = event.AggregateID - features.Default = true - case string(iam_repo.FeaturesSetEventType): - defaultFeatures, err := p.view.AllDefaultFeatures() - if err != nil { - return err - } - for _, features := range defaultFeatures { - err = features.AppendEvent(event) - if err != nil { - return err - } - } - return p.view.PutFeaturesList(defaultFeatures, event) - case string(org_repo.FeaturesSetEventType): - features, err = p.view.FeaturesByAggregateID(event.AggregateID) - if err != nil { - return err - } - err = features.AppendEvent(event) - case string(org_repo.FeaturesRemovedEventType): - features, err = p.getDefaultFeatures() - if err != nil { - return err - } - features.AggregateID = event.AggregateID - features.Default = true - default: - return p.view.ProcessedFeaturesSequence(event) - } - if err != nil { - return err - } - return p.view.PutFeatures(features, event) -} - -func (p *Features) OnError(event *es_models.Event, err error) error { - logging.LogWithFields("SPOOL-Wj8sf", "id", event.AggregateID).WithError(err).Warn("something went wrong in login features handler") - return spooler.HandleError(event, err, p.view.GetLatestFeaturesFailedEvent, p.view.ProcessedFeaturesFailedEvent, p.view.ProcessedFeaturesSequence, p.errorCountUntilSkip) -} - -func (p *Features) OnSuccess() error { - return spooler.HandleSuccess(p.view.UpdateFeaturesSpoolerRunTimestamp) -} - -func (p *Features) getDefaultFeatures() (*model.FeaturesView, error) { - features, featuresErr := p.view.FeaturesByAggregateID(domain.IAMID) - if featuresErr != nil && !caos_errs.IsNotFound(featuresErr) { - return nil, featuresErr - } - if features == nil { - features = &model.FeaturesView{} - } - events, err := p.getIAMEvents(features.Sequence) - if err != nil { - return features, featuresErr - } - featuresCopy := *features - for _, event := range events { - if err := featuresCopy.AppendEvent(event); err != nil { - return features, nil - } - } - return &featuresCopy, nil -} - -func (p *Features) getIAMEvents(sequence uint64) ([]*es_models.Event, error) { - query, err := eventsourcing.IAMByIDQuery(domain.IAMID, sequence) - if err != nil { - return nil, err - } - - return p.es.FilterEvents(context.Background(), query) -} diff --git a/internal/auth/repository/eventsourcing/handler/handler.go b/internal/auth/repository/eventsourcing/handler/handler.go index 78251190d0..ac3cf5a554 100644 --- a/internal/auth/repository/eventsourcing/handler/handler.go +++ b/internal/auth/repository/eventsourcing/handler/handler.go @@ -59,7 +59,6 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es handler{view, bulkLimit, configs.cycleDuration("ExternalIDP"), errorCount, es}, systemDefaults), newLabelPolicy(handler{view, bulkLimit, configs.cycleDuration("LabelPolicy"), errorCount, es}), - newFeatures(handler{view, bulkLimit, configs.cycleDuration("Features"), errorCount, es}), 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}), diff --git a/internal/auth/repository/eventsourcing/repository.go b/internal/auth/repository/eventsourcing/repository.go index 187a8e9f6e..c44fc8d276 100644 --- a/internal/auth/repository/eventsourcing/repository.go +++ b/internal/auth/repository/eventsourcing/repository.go @@ -47,7 +47,6 @@ type EsRepository struct { eventstore.UserGrantRepo eventstore.OrgRepository eventstore.IAMRepository - eventstore.FeaturesRepo } func Start(conf Config, authZ authz.Config, systemDefaults sd.SystemDefaults, command *command.Commands, queries *query.Queries, authZRepo *authz_repo.EsRepository, esV2 *es2.Eventstore) (*EsRepository, error) { @@ -169,10 +168,6 @@ func Start(conf Config, authZ authz.Config, systemDefaults sd.SystemDefaults, co LoginDir: statikLoginFS, IAMV2QuerySide: queries, }, - eventstore.FeaturesRepo{ - Eventstore: es, - View: view, - }, }, nil } diff --git a/internal/auth/repository/eventsourcing/view/features.go b/internal/auth/repository/eventsourcing/view/features.go deleted file mode 100644 index 2849f84ad1..0000000000 --- a/internal/auth/repository/eventsourcing/view/features.go +++ /dev/null @@ -1,56 +0,0 @@ -package view - -import ( - "github.com/caos/zitadel/internal/eventstore/v1/models" - "github.com/caos/zitadel/internal/features/repository/view" - "github.com/caos/zitadel/internal/features/repository/view/model" - global_view "github.com/caos/zitadel/internal/view/repository" -) - -const ( - featuresTable = "auth.features" -) - -func (v *View) AllDefaultFeatures() ([]*model.FeaturesView, error) { - return view.GetDefaultFeatures(v.Db, featuresTable) -} - -func (v *View) FeaturesByAggregateID(aggregateID string) (*model.FeaturesView, error) { - return view.GetFeaturesByAggregateID(v.Db, featuresTable, aggregateID) -} - -func (v *View) PutFeatures(features *model.FeaturesView, event *models.Event) error { - err := view.PutFeatures(v.Db, featuresTable, features) - if err != nil { - return err - } - return v.ProcessedFeaturesSequence(event) -} - -func (v *View) PutFeaturesList(features []*model.FeaturesView, event *models.Event) error { - err := view.PutFeaturesList(v.Db, featuresTable, features...) - if err != nil { - return err - } - return v.ProcessedFeaturesSequence(event) -} - -func (v *View) GetLatestFeaturesSequence() (*global_view.CurrentSequence, error) { - return v.latestSequence(featuresTable) -} - -func (v *View) ProcessedFeaturesSequence(event *models.Event) error { - return v.saveCurrentSequence(featuresTable, event) -} - -func (v *View) UpdateFeaturesSpoolerRunTimestamp() error { - return v.updateSpoolerRunSequence(featuresTable) -} - -func (v *View) GetLatestFeaturesFailedEvent(sequence uint64) (*global_view.FailedEvent, error) { - return v.latestFailedEvent(featuresTable, sequence) -} - -func (v *View) ProcessedFeaturesFailedEvent(failedEvent *global_view.FailedEvent) error { - return v.saveFailedEvent(failedEvent) -} diff --git a/internal/auth/repository/features.go b/internal/auth/repository/features.go deleted file mode 100644 index 1c01ab4d00..0000000000 --- a/internal/auth/repository/features.go +++ /dev/null @@ -1,11 +0,0 @@ -package repository - -import ( - "context" - - features_model "github.com/caos/zitadel/internal/features/model" -) - -type FeaturesRepository interface { - GetOrgFeatures(ctx context.Context, id string) (*features_model.FeaturesView, error) -} diff --git a/internal/auth/repository/repository.go b/internal/auth/repository/repository.go index 03c3a2e898..d8c3edd824 100644 --- a/internal/auth/repository/repository.go +++ b/internal/auth/repository/repository.go @@ -15,6 +15,5 @@ type Repository interface { UserGrantRepository OrgRepository IAMRepository - FeaturesRepository RefreshTokenRepository } diff --git a/internal/authz/repository/eventsourcing/eventstore/token_verifier.go b/internal/authz/repository/eventsourcing/eventstore/token_verifier.go index 977acf5d52..1d1d59780d 100644 --- a/internal/authz/repository/eventsourcing/eventstore/token_verifier.go +++ b/internal/authz/repository/eventsourcing/eventstore/token_verifier.go @@ -15,10 +15,10 @@ import ( v1 "github.com/caos/zitadel/internal/eventstore/v1" "github.com/caos/zitadel/internal/eventstore/v1/models" es_sdk "github.com/caos/zitadel/internal/eventstore/v1/sdk" - features_view_model "github.com/caos/zitadel/internal/features/repository/view/model" iam_model "github.com/caos/zitadel/internal/iam/model" iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model" iam_view "github.com/caos/zitadel/internal/iam/repository/view" + "github.com/caos/zitadel/internal/query" "github.com/caos/zitadel/internal/telemetry/tracing" usr_model "github.com/caos/zitadel/internal/user/model" usr_view "github.com/caos/zitadel/internal/user/repository/view" @@ -30,6 +30,7 @@ type TokenVerifierRepo struct { IAMID string Eventstore v1.Eventstore View *view.View + Query *query.Queries } func (repo *TokenVerifierRepo) TokenByID(ctx context.Context, tokenID, userID string) (*usr_model.TokenView, error) { @@ -110,17 +111,14 @@ func (repo *TokenVerifierRepo) ProjectIDAndOriginsByClientID(ctx context.Context } func (repo *TokenVerifierRepo) CheckOrgFeatures(ctx context.Context, orgID string, requiredFeatures ...string) error { - features, err := repo.View.FeaturesByAggregateID(orgID) - if caos_errs.IsNotFound(err) { - return repo.checkDefaultFeatures(ctx, requiredFeatures...) - } + features, err := repo.Query.FeaturesByOrgID(ctx, orgID) if err != nil { return err } return checkFeatures(features, requiredFeatures...) } -func checkFeatures(features *features_view_model.FeaturesView, requiredFeatures ...string) error { +func checkFeatures(features *query.Features, requiredFeatures ...string) error { for _, requiredFeature := range requiredFeatures { if strings.HasPrefix(requiredFeature, domain.FeatureLoginPolicy) { if err := checkLoginPolicyFeatures(features, requiredFeature); err != nil { @@ -187,7 +185,7 @@ func checkFeatures(features *features_view_model.FeaturesView, requiredFeatures return nil } -func checkLoginPolicyFeatures(features *features_view_model.FeaturesView, requiredFeature string) error { +func checkLoginPolicyFeatures(features *query.Features, requiredFeature string) error { switch requiredFeature { case domain.FeatureLoginPolicyFactors: if !features.LoginPolicyFactors { @@ -221,7 +219,7 @@ func checkLoginPolicyFeatures(features *features_view_model.FeaturesView, requir return nil } -func checkLabelPolicyFeatures(features *features_view_model.FeaturesView, requiredFeature string) error { +func checkLabelPolicyFeatures(features *query.Features, requiredFeature string) error { switch requiredFeature { case domain.FeatureLabelPolicyPrivateLabel: if !features.LabelPolicyPrivateLabel { @@ -280,28 +278,11 @@ func (u *TokenVerifierRepo) getIAMByID(ctx context.Context) (*iam_model.IAM, err } func (repo *TokenVerifierRepo) checkDefaultFeatures(ctx context.Context, requiredFeatures ...string) error { - features, viewErr := repo.View.FeaturesByAggregateID(domain.IAMID) - if viewErr != nil && !caos_errs.IsNotFound(viewErr) { - return viewErr + features, err := repo.Query.DefaultFeatures(ctx) + if err != nil { + return err } - if caos_errs.IsNotFound(viewErr) { - features = new(features_view_model.FeaturesView) - } - events, esErr := repo.getIAMEvents(ctx, features.Sequence) - if caos_errs.IsNotFound(viewErr) && len(events) == 0 { - return checkFeatures(features, requiredFeatures...) - } - if esErr != nil { - logging.Log("EVENT-PSoc3").WithError(esErr).Debug("error retrieving new events") - return esErr - } - featuresCopy := *features - for _, event := range events { - if err := featuresCopy.AppendEvent(event); err != nil { - return checkFeatures(features, requiredFeatures...) - } - } - return checkFeatures(&featuresCopy, requiredFeatures...) + return checkFeatures(features, requiredFeatures...) } func (repo *TokenVerifierRepo) getIAMEvents(ctx context.Context, sequence uint64) ([]*models.Event, error) { diff --git a/internal/authz/repository/eventsourcing/handler/features.go b/internal/authz/repository/eventsourcing/handler/features.go deleted file mode 100644 index 86b2d0bdc9..0000000000 --- a/internal/authz/repository/eventsourcing/handler/features.go +++ /dev/null @@ -1,169 +0,0 @@ -package handler - -import ( - "context" - - "github.com/caos/logging" - - "github.com/caos/zitadel/internal/domain" - 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/features/repository/view/model" - "github.com/caos/zitadel/internal/iam/repository/eventsourcing" - iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model" - org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model" - iam_repo "github.com/caos/zitadel/internal/repository/iam" - org_repo "github.com/caos/zitadel/internal/repository/org" -) - -const ( - featuresTable = "authz.features" -) - -type Features struct { - handler - subscription *v1.Subscription -} - -func newFeatures(handler handler) *Features { - h := &Features{ - handler: handler, - } - - h.subscribe() - - return h -} - -func (p *Features) subscribe() { - p.subscription = p.es.Subscribe(p.AggregateTypes()...) - go func() { - for event := range p.subscription.Events { - query.ReduceEvent(p, event) - } - }() -} - -func (p *Features) ViewModel() string { - return featuresTable -} - -func (p *Features) Subscription() *v1.Subscription { - return p.subscription -} - -func (p *Features) AggregateTypes() []es_models.AggregateType { - return []es_models.AggregateType{iam_es_model.IAMAggregate, org_es_model.OrgAggregate} -} - -func (p *Features) EventQuery() (*es_models.SearchQuery, error) { - sequence, err := p.view.GetLatestFeaturesSequence() - if err != nil { - return nil, err - } - return es_models.NewSearchQuery(). - AggregateTypeFilter(p.AggregateTypes()...). - LatestSequenceFilter(sequence.CurrentSequence), nil -} - -func (p *Features) CurrentSequence() (uint64, error) { - sequence, err := p.view.GetLatestFeaturesSequence() - if err != nil { - return 0, err - } - return sequence.CurrentSequence, nil -} - -func (p *Features) Reduce(event *es_models.Event) (err error) { - switch event.AggregateType { - case org_es_model.OrgAggregate, iam_es_model.IAMAggregate: - err = p.processFeatures(event) - } - return err -} - -func (p *Features) processFeatures(event *es_models.Event) (err error) { - features := new(model.FeaturesView) - switch string(event.Type) { - case string(org_es_model.OrgAdded): - features, err = p.getDefaultFeatures() - if err != nil { - return err - } - features.AggregateID = event.AggregateID - features.Default = true - case string(iam_repo.FeaturesSetEventType): - defaultFeatures, err := p.view.AllDefaultFeatures() - if err != nil { - return err - } - for _, features := range defaultFeatures { - err = features.AppendEvent(event) - if err != nil { - return err - } - } - return p.view.PutFeaturesList(defaultFeatures, event) - case string(org_repo.FeaturesSetEventType): - features, err = p.view.FeaturesByAggregateID(event.AggregateID) - if err != nil { - return err - } - err = features.AppendEvent(event) - case string(org_repo.FeaturesRemovedEventType): - features, err = p.getDefaultFeatures() - if err != nil { - return err - } - features.AggregateID = event.AggregateID - features.Default = true - default: - return p.view.ProcessedFeaturesSequence(event) - } - if err != nil { - return err - } - return p.view.PutFeatures(features, event) -} - -func (p *Features) OnError(event *es_models.Event, err error) error { - logging.LogWithFields("SPOOL-Wj8sf", "id", event.AggregateID).WithError(err).Warn("something went wrong in login features handler") - return spooler.HandleError(event, err, p.view.GetLatestFeaturesFailedEvent, p.view.ProcessedFeaturesFailedEvent, p.view.ProcessedFeaturesSequence, p.errorCountUntilSkip) -} - -func (p *Features) OnSuccess() error { - return spooler.HandleSuccess(p.view.UpdateFeaturesSpoolerRunTimestamp) -} - -func (p *Features) getDefaultFeatures() (*model.FeaturesView, error) { - features, featuresErr := p.view.FeaturesByAggregateID(domain.IAMID) - if featuresErr != nil && !caos_errs.IsNotFound(featuresErr) { - return nil, featuresErr - } - if features == nil { - features = &model.FeaturesView{} - } - events, err := p.getIAMEvents(features.Sequence) - if err != nil { - return features, featuresErr - } - featuresCopy := *features - for _, event := range events { - if err := featuresCopy.AppendEvent(event); err != nil { - return features, nil - } - } - return &featuresCopy, nil -} - -func (p *Features) getIAMEvents(sequence uint64) ([]*es_models.Event, error) { - query, err := eventsourcing.IAMByIDQuery(domain.IAMID, sequence) - if err != nil { - return nil, err - } - - return p.es.FilterEvents(context.Background(), query) -} diff --git a/internal/authz/repository/eventsourcing/handler/handler.go b/internal/authz/repository/eventsourcing/handler/handler.go index a16b27d485..f8fb96153f 100644 --- a/internal/authz/repository/eventsourcing/handler/handler.go +++ b/internal/authz/repository/eventsourcing/handler/handler.go @@ -38,8 +38,6 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es handler{view, bulkLimit, configs.cycleDuration("UserMemberships"), errorCount, es}), newApplication( handler{view, bulkLimit, configs.cycleDuration("Application"), errorCount, es}), - newFeatures( - handler{view, bulkLimit, configs.cycleDuration("Features"), errorCount, es}), } } diff --git a/internal/authz/repository/eventsourcing/repository.go b/internal/authz/repository/eventsourcing/repository.go index 4136a32f2f..79c7b07a05 100644 --- a/internal/authz/repository/eventsourcing/repository.go +++ b/internal/authz/repository/eventsourcing/repository.go @@ -72,6 +72,7 @@ func Start(conf Config, authZ authz.Config, systemDefaults sd.SystemDefaults, qu Eventstore: es, IAMID: systemDefaults.IamID, View: view, + Query: queries, }, }, nil } diff --git a/internal/authz/repository/eventsourcing/view/features.go b/internal/authz/repository/eventsourcing/view/features.go deleted file mode 100644 index 65cb93e99d..0000000000 --- a/internal/authz/repository/eventsourcing/view/features.go +++ /dev/null @@ -1,56 +0,0 @@ -package view - -import ( - "github.com/caos/zitadel/internal/eventstore/v1/models" - "github.com/caos/zitadel/internal/features/repository/view" - "github.com/caos/zitadel/internal/features/repository/view/model" - global_view "github.com/caos/zitadel/internal/view/repository" -) - -const ( - featuresTable = "authz.features" -) - -func (v *View) AllDefaultFeatures() ([]*model.FeaturesView, error) { - return view.GetDefaultFeatures(v.Db, featuresTable) -} - -func (v *View) FeaturesByAggregateID(aggregateID string) (*model.FeaturesView, error) { - return view.GetFeaturesByAggregateID(v.Db, featuresTable, aggregateID) -} - -func (v *View) PutFeatures(features *model.FeaturesView, event *models.Event) error { - err := view.PutFeatures(v.Db, featuresTable, features) - if err != nil { - return err - } - return v.ProcessedFeaturesSequence(event) -} - -func (v *View) PutFeaturesList(features []*model.FeaturesView, event *models.Event) error { - err := view.PutFeaturesList(v.Db, featuresTable, features...) - if err != nil { - return err - } - return v.ProcessedFeaturesSequence(event) -} - -func (v *View) GetLatestFeaturesSequence() (*global_view.CurrentSequence, error) { - return v.latestSequence(featuresTable) -} - -func (v *View) ProcessedFeaturesSequence(event *models.Event) error { - return v.saveCurrentSequence(featuresTable, event) -} - -func (v *View) UpdateFeaturesSpoolerRunTimestamp() error { - return v.updateSpoolerRunSequence(featuresTable) -} - -func (v *View) GetLatestFeaturesFailedEvent(sequence uint64) (*global_view.FailedEvent, error) { - return v.latestFailedEvent(featuresTable, sequence) -} - -func (v *View) ProcessedFeaturesFailedEvent(failedEvent *global_view.FailedEvent) error { - return v.saveFailedEvent(failedEvent) -} diff --git a/internal/features/model/features_view.go b/internal/features/model/features_view.go deleted file mode 100644 index 3aed63a022..0000000000 --- a/internal/features/model/features_view.go +++ /dev/null @@ -1,121 +0,0 @@ -package model - -import ( - "time" - - "github.com/caos/zitadel/internal/domain" -) - -type FeaturesView struct { - AggregateID string - CreationDate time.Time - ChangeDate time.Time - Sequence uint64 - Default bool - - TierName string - TierDescription string - State domain.FeaturesState - StateDescription string - AuditLogRetention time.Duration - LoginPolicyFactors bool - LoginPolicyIDP bool - LoginPolicyPasswordless bool - LoginPolicyRegistration bool - LoginPolicyUsernameLogin bool - LoginPolicyPasswordReset bool - PasswordComplexityPolicy bool - LabelPolicyPrivateLabel bool - LabelPolicyWatermark bool - CustomDomain bool - PrivacyPolicy bool - MetadataUser bool - CustomTextMessage bool - CustomTextLogin bool - LockoutPolicy bool - Actions bool -} - -func (f *FeaturesView) FeatureList() []string { - list := make([]string, 0) - if f.LoginPolicyFactors { - list = append(list, domain.FeatureLoginPolicyFactors) - } - if f.LoginPolicyIDP { - list = append(list, domain.FeatureLoginPolicyIDP) - } - if f.LoginPolicyPasswordless { - list = append(list, domain.FeatureLoginPolicyPasswordless) - } - if f.LoginPolicyRegistration { - list = append(list, domain.FeatureLoginPolicyRegistration) - } - if f.LoginPolicyUsernameLogin { - list = append(list, domain.FeatureLoginPolicyUsernameLogin) - } - if f.LoginPolicyPasswordReset { - list = append(list, domain.FeatureLoginPolicyPasswordReset) - } - if f.PasswordComplexityPolicy { - list = append(list, domain.FeaturePasswordComplexityPolicy) - } - if f.LabelPolicyPrivateLabel { - list = append(list, domain.FeatureLabelPolicyPrivateLabel) - } - if f.LabelPolicyWatermark { - list = append(list, domain.FeatureLabelPolicyWatermark) - } - if f.CustomDomain { - list = append(list, domain.FeatureCustomDomain) - } - if f.PrivacyPolicy { - list = append(list, domain.FeaturePrivacyPolicy) - } - if f.MetadataUser { - list = append(list, domain.FeatureMetadataUser) - } - if f.CustomTextMessage { - list = append(list, domain.FeatureCustomTextMessage) - } - if f.CustomTextLogin { - list = append(list, domain.FeatureCustomTextLogin) - } - if f.LockoutPolicy { - list = append(list, domain.FeatureLockoutPolicy) - } - if f.Actions { - list = append(list, domain.FeatureActions) - } - return list -} - -type FeaturesSearchRequest struct { - Offset uint64 - Limit uint64 - SortingColumn FeaturesSearchKey - Asc bool - Queries []*FeaturesSearchQuery -} - -type FeaturesSearchKey int32 - -const ( - FeaturesSearchKeyUnspecified FeaturesSearchKey = iota - FeaturesSearchKeyAggregateID - FeaturesSearchKeyDefault -) - -type FeaturesSearchQuery struct { - Key FeaturesSearchKey - Method domain.SearchMethod - Value interface{} -} - -type FeaturesSearchResult struct { - Offset uint64 - Limit uint64 - TotalResult uint64 - Result []*FeaturesView - Sequence uint64 - Timestamp time.Time -} diff --git a/internal/features/repository/view/features_view.go b/internal/features/repository/view/features_view.go deleted file mode 100644 index fdd6ae29f0..0000000000 --- a/internal/features/repository/view/features_view.go +++ /dev/null @@ -1,48 +0,0 @@ -package view - -import ( - "github.com/jinzhu/gorm" - - "github.com/caos/zitadel/internal/domain" - caos_errs "github.com/caos/zitadel/internal/errors" - "github.com/caos/zitadel/internal/features/model" - view_model "github.com/caos/zitadel/internal/features/repository/view/model" - "github.com/caos/zitadel/internal/view/repository" -) - -func GetDefaultFeatures(db *gorm.DB, table string) ([]*view_model.FeaturesView, error) { - features := make([]*view_model.FeaturesView, 0) - queries := []*model.FeaturesSearchQuery{ - {Key: model.FeaturesSearchKeyDefault, Value: true, Method: domain.SearchMethodEquals}, - } - query := repository.PrepareSearchQuery(table, view_model.FeaturesSearchRequest{Queries: queries}) - _, err := query(db, &features) - if err != nil { - return nil, err - } - return features, nil -} - -func GetFeaturesByAggregateID(db *gorm.DB, table, aggregateID string) (*view_model.FeaturesView, error) { - features := new(view_model.FeaturesView) - query := repository.PrepareGetByKey(table, view_model.FeaturesSearchKey(model.FeaturesSearchKeyAggregateID), aggregateID) - err := query(db, features) - if caos_errs.IsNotFound(err) { - return nil, caos_errs.ThrowNotFound(nil, "VIEW-Dbf3h", "Errors.Features.NotFound") - } - return features, err -} - -func PutFeatures(db *gorm.DB, table string, features *view_model.FeaturesView) error { - save := repository.PrepareSave(table) - return save(db, features) -} - -func PutFeaturesList(db *gorm.DB, table string, featuresList ...*view_model.FeaturesView) error { - save := repository.PrepareBulkSave(table) - f := make([]interface{}, len(featuresList)) - for i, features := range featuresList { - f[i] = features - } - return save(db, f...) -} diff --git a/internal/features/repository/view/model/features.go b/internal/features/repository/view/model/features.go deleted file mode 100644 index 13f2463bbe..0000000000 --- a/internal/features/repository/view/model/features.go +++ /dev/null @@ -1,117 +0,0 @@ -package model - -import ( - "encoding/json" - "time" - - "github.com/caos/logging" - - "github.com/caos/zitadel/internal/domain" - caos_errs "github.com/caos/zitadel/internal/errors" - "github.com/caos/zitadel/internal/eventstore/v1/models" - features_model "github.com/caos/zitadel/internal/features/model" - iam_repo "github.com/caos/zitadel/internal/repository/iam" - org_repo "github.com/caos/zitadel/internal/repository/org" -) - -const ( - FeaturesKeyAggregateID = "aggregate_id" - FeaturesKeyDefault = "default_features" -) - -type FeaturesView 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"` - Sequence uint64 `json:"-" gorm:"column:sequence"` - Default bool `json:"-" gorm:"column:default_features"` - - TierName string `json:"tierName" gorm:"column:tier_name"` - TierDescription string `json:"tierDescription" gorm:"column:tier_description"` - State int32 `json:"state" gorm:"column:state"` - StateDescription string `json:"stateDescription" gorm:"column:state_description"` - AuditLogRetention time.Duration `json:"auditLogRetention" gorm:"column:audit_log_retention"` - LoginPolicyFactors bool `json:"loginPolicyFactors" gorm:"column:login_policy_factors"` - LoginPolicyIDP bool `json:"loginPolicyIDP" gorm:"column:login_policy_idp"` - LoginPolicyPasswordless bool `json:"loginPolicyPasswordless" gorm:"column:login_policy_passwordless"` - LoginPolicyRegistration bool `json:"loginPolicyRegistration" gorm:"column:login_policy_registration"` - LoginPolicyUsernameLogin bool `json:"loginPolicyUsernameLogin" gorm:"column:login_policy_username_login"` - LoginPolicyPasswordReset bool `json:"loginPolicyPasswordReset" gorm:"column:login_policy_password_reset"` - PasswordComplexityPolicy bool `json:"passwordComplexityPolicy" gorm:"column:password_complexity_policy"` - LabelPolicy *bool `json:"labelPolicy" gorm:"-"` - LabelPolicyPrivateLabel bool `json:"labelPolicyPrivateLabel" gorm:"column:label_policy_private_label"` - LabelPolicyWatermark bool `json:"labelPolicyWatermark" gorm:"column:label_policy_watermark"` - CustomDomain bool `json:"customDomain" gorm:"column:custom_domain"` - PrivacyPolicy bool `json:"privacyPolicy" gorm:"column:privacy_policy"` - MetadataUser bool `json:"metadataUser" gorm:"column:metadata_user"` - CustomTextMessage bool `json:"customTextMessage" gorm:"column:custom_text_message"` - CustomTextLogin bool `json:"customTextLogin" gorm:"column:custom_text_login"` - LockoutPolicy bool `json:"lockoutPolicy" gorm:"column:lockout_policy"` - Actions bool `json:"actions" gorm:"column:actions"` -} - -func FeaturesToModel(features *FeaturesView) *features_model.FeaturesView { - return &features_model.FeaturesView{ - AggregateID: features.AggregateID, - CreationDate: features.CreationDate, - ChangeDate: features.ChangeDate, - Sequence: features.Sequence, - Default: features.Default, - TierName: features.TierName, - TierDescription: features.TierDescription, - State: domain.FeaturesState(features.State), - StateDescription: features.StateDescription, - AuditLogRetention: features.AuditLogRetention, - LoginPolicyFactors: features.LoginPolicyFactors, - LoginPolicyIDP: features.LoginPolicyIDP, - LoginPolicyPasswordless: features.LoginPolicyPasswordless, - LoginPolicyRegistration: features.LoginPolicyRegistration, - LoginPolicyUsernameLogin: features.LoginPolicyUsernameLogin, - LoginPolicyPasswordReset: features.LoginPolicyPasswordReset, - PasswordComplexityPolicy: features.PasswordComplexityPolicy, - LabelPolicyPrivateLabel: features.LabelPolicyPrivateLabel, - LabelPolicyWatermark: features.LabelPolicyWatermark, - CustomDomain: features.CustomDomain, - PrivacyPolicy: features.PrivacyPolicy, - MetadataUser: features.MetadataUser, - CustomTextMessage: features.CustomTextMessage, - CustomTextLogin: features.CustomTextLogin, - LockoutPolicy: features.LockoutPolicy, - Actions: features.Actions, - } -} - -func (f *FeaturesView) AppendEvent(event *models.Event) (err error) { - f.Sequence = event.Sequence - f.ChangeDate = event.CreationDate - switch string(event.Type) { - case string(iam_repo.FeaturesSetEventType): - f.SetRootData(event) - f.CreationDate = event.CreationDate - f.Default = true - err = f.SetData(event) - case string(org_repo.FeaturesSetEventType): - f.SetRootData(event) - f.CreationDate = event.CreationDate - err = f.SetData(event) - f.Default = false - } - return err -} - -func (f *FeaturesView) SetRootData(event *models.Event) { - if f.AggregateID == "" { - f.AggregateID = event.AggregateID - } -} - -func (f *FeaturesView) SetData(event *models.Event) error { - if err := json.Unmarshal(event.Data, f); err != nil { - logging.Log("EVEN-DVsf2").WithError(err).Error("could not unmarshal event data") - return caos_errs.ThrowInternal(err, "MODEL-Bfg31", "Could not unmarshal data") - } - if f.LabelPolicy != nil { - f.LabelPolicyPrivateLabel = *f.LabelPolicy - } - return nil -} diff --git a/internal/features/repository/view/model/features_query.go b/internal/features/repository/view/model/features_query.go deleted file mode 100644 index c5035827ab..0000000000 --- a/internal/features/repository/view/model/features_query.go +++ /dev/null @@ -1,61 +0,0 @@ -package model - -import ( - "github.com/caos/zitadel/internal/domain" - "github.com/caos/zitadel/internal/features/model" - "github.com/caos/zitadel/internal/view/repository" -) - -type FeaturesSearchRequest model.FeaturesSearchRequest -type FeaturesSearchQuery model.FeaturesSearchQuery -type FeaturesSearchKey model.FeaturesSearchKey - -func (req FeaturesSearchRequest) GetLimit() uint64 { - return req.Limit -} - -func (req FeaturesSearchRequest) GetOffset() uint64 { - return req.Offset -} - -func (req FeaturesSearchRequest) GetSortingColumn() repository.ColumnKey { - if req.SortingColumn == model.FeaturesSearchKeyUnspecified { - return nil - } - return FeaturesSearchKey(req.SortingColumn) -} - -func (req FeaturesSearchRequest) GetAsc() bool { - return req.Asc -} - -func (req FeaturesSearchRequest) GetQueries() []repository.SearchQuery { - result := make([]repository.SearchQuery, len(req.Queries)) - for i, q := range req.Queries { - result[i] = FeaturesSearchQuery{Key: q.Key, Value: q.Value, Method: q.Method} - } - return result -} - -func (req FeaturesSearchQuery) GetKey() repository.ColumnKey { - return FeaturesSearchKey(req.Key) -} - -func (req FeaturesSearchQuery) GetMethod() domain.SearchMethod { - return req.Method -} - -func (req FeaturesSearchQuery) GetValue() interface{} { - return req.Value -} - -func (key FeaturesSearchKey) ToColumnName() string { - switch model.FeaturesSearchKey(key) { - case model.FeaturesSearchKeyAggregateID: - return FeaturesKeyAggregateID - case model.FeaturesSearchKeyDefault: - return FeaturesKeyDefault - default: - return "" - } -} diff --git a/internal/management/repository/eventsourcing/eventstore/features.go b/internal/management/repository/eventsourcing/eventstore/features.go deleted file mode 100644 index 07705e48c5..0000000000 --- a/internal/management/repository/eventsourcing/eventstore/features.go +++ /dev/null @@ -1,70 +0,0 @@ -package eventstore - -import ( - "context" - - "github.com/caos/logging" - - "github.com/caos/zitadel/internal/config/systemdefaults" - "github.com/caos/zitadel/internal/domain" - "github.com/caos/zitadel/internal/errors" - v1 "github.com/caos/zitadel/internal/eventstore/v1" - "github.com/caos/zitadel/internal/eventstore/v1/models" - features_model "github.com/caos/zitadel/internal/features/model" - "github.com/caos/zitadel/internal/features/repository/view/model" - iam_view "github.com/caos/zitadel/internal/iam/repository/view" - mgmt_view "github.com/caos/zitadel/internal/management/repository/eventsourcing/view" -) - -type FeaturesRepo struct { - Eventstore v1.Eventstore - - View *mgmt_view.View - - SearchLimit uint64 - SystemDefaults systemdefaults.SystemDefaults -} - -func (repo *FeaturesRepo) GetDefaultFeatures(ctx context.Context) (*features_model.FeaturesView, error) { - features, viewErr := repo.View.FeaturesByAggregateID(domain.IAMID) - if viewErr != nil && !errors.IsNotFound(viewErr) { - return nil, viewErr - } - if errors.IsNotFound(viewErr) { - features = new(model.FeaturesView) - } - events, esErr := repo.getIAMEvents(ctx, features.Sequence) - if errors.IsNotFound(viewErr) && len(events) == 0 { - return nil, errors.ThrowNotFound(nil, "EVENT-Lsoj7", "Errors.Org.NotFound") - } - if esErr != nil { - logging.Log("EVENT-PSoc3").WithError(esErr).Debug("error retrieving new events") - return model.FeaturesToModel(features), nil - } - featuresCopy := *features - for _, event := range events { - if err := featuresCopy.AppendEvent(event); err != nil { - return model.FeaturesToModel(&featuresCopy), nil - } - } - return model.FeaturesToModel(&featuresCopy), nil -} - -func (repo *FeaturesRepo) GetOrgFeatures(ctx context.Context, orgID string) (*features_model.FeaturesView, error) { - features, err := repo.View.FeaturesByAggregateID(orgID) - if errors.IsNotFound(err) { - return repo.GetDefaultFeatures(ctx) - } - if err != nil { - return nil, err - } - return model.FeaturesToModel(features), err -} - -func (repo *FeaturesRepo) getIAMEvents(ctx context.Context, sequence uint64) ([]*models.Event, error) { - query, err := iam_view.IAMByIDQuery(domain.IAMID, sequence) - if err != nil { - return nil, err - } - return repo.Eventstore.FilterEvents(ctx, query) -} diff --git a/internal/management/repository/eventsourcing/handler/features.go b/internal/management/repository/eventsourcing/handler/features.go deleted file mode 100644 index 44abb12df0..0000000000 --- a/internal/management/repository/eventsourcing/handler/features.go +++ /dev/null @@ -1,169 +0,0 @@ -package handler - -import ( - "context" - - "github.com/caos/logging" - - "github.com/caos/zitadel/internal/domain" - 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/features/repository/view/model" - "github.com/caos/zitadel/internal/iam/repository/eventsourcing" - iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model" - org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model" - iam_repo "github.com/caos/zitadel/internal/repository/iam" - org_repo "github.com/caos/zitadel/internal/repository/org" -) - -const ( - featuresTable = "management.features" -) - -type Features struct { - handler - subscription *v1.Subscription -} - -func newFeatures(handler handler) *Features { - h := &Features{ - handler: handler, - } - - h.subscribe() - - return h -} - -func (p *Features) subscribe() { - p.subscription = p.es.Subscribe(p.AggregateTypes()...) - go func() { - for event := range p.subscription.Events { - query.ReduceEvent(p, event) - } - }() -} - -func (p *Features) ViewModel() string { - return featuresTable -} - -func (p *Features) Subscription() *v1.Subscription { - return p.subscription -} - -func (p *Features) AggregateTypes() []es_models.AggregateType { - return []es_models.AggregateType{iam_es_model.IAMAggregate, org_es_model.OrgAggregate} -} - -func (p *Features) EventQuery() (*es_models.SearchQuery, error) { - sequence, err := p.view.GetLatestFeaturesSequence() - if err != nil { - return nil, err - } - return es_models.NewSearchQuery(). - AggregateTypeFilter(p.AggregateTypes()...). - LatestSequenceFilter(sequence.CurrentSequence), nil -} - -func (p *Features) CurrentSequence() (uint64, error) { - sequence, err := p.view.GetLatestFeaturesSequence() - if err != nil { - return 0, err - } - return sequence.CurrentSequence, nil -} - -func (p *Features) Reduce(event *es_models.Event) (err error) { - switch event.AggregateType { - case org_es_model.OrgAggregate, iam_es_model.IAMAggregate: - err = p.processFeatures(event) - } - return err -} - -func (p *Features) processFeatures(event *es_models.Event) (err error) { - features := new(model.FeaturesView) - switch string(event.Type) { - case string(org_es_model.OrgAdded): - features, err = p.getDefaultFeatures() - if err != nil { - return err - } - features.AggregateID = event.AggregateID - features.Default = true - case string(iam_repo.FeaturesSetEventType): - defaultFeatures, err := p.view.AllDefaultFeatures() - if err != nil { - return err - } - for _, features := range defaultFeatures { - err = features.AppendEvent(event) - if err != nil { - return err - } - } - return p.view.PutFeaturesList(defaultFeatures, event) - case string(org_repo.FeaturesSetEventType): - features, err = p.view.FeaturesByAggregateID(event.AggregateID) - if err != nil { - return err - } - err = features.AppendEvent(event) - case string(org_repo.FeaturesRemovedEventType): - features, err = p.getDefaultFeatures() - if err != nil { - return err - } - features.AggregateID = event.AggregateID - features.Default = true - default: - return p.view.ProcessedFeaturesSequence(event) - } - if err != nil { - return err - } - return p.view.PutFeatures(features, event) -} - -func (p *Features) OnError(event *es_models.Event, err error) error { - logging.LogWithFields("SPOOL-Wj8sf", "id", event.AggregateID).WithError(err).Warn("something went wrong in login features handler") - return spooler.HandleError(event, err, p.view.GetLatestFeaturesFailedEvent, p.view.ProcessedFeaturesFailedEvent, p.view.ProcessedFeaturesSequence, p.errorCountUntilSkip) -} - -func (p *Features) OnSuccess() error { - return spooler.HandleSuccess(p.view.UpdateFeaturesSpoolerRunTimestamp) -} - -func (p *Features) getDefaultFeatures() (*model.FeaturesView, error) { - features, featuresErr := p.view.FeaturesByAggregateID(domain.IAMID) - if featuresErr != nil && !caos_errs.IsNotFound(featuresErr) { - return nil, featuresErr - } - if features == nil { - features = &model.FeaturesView{} - } - events, err := p.getIAMEvents(features.Sequence) - if err != nil { - return features, featuresErr - } - featuresCopy := *features - for _, event := range events { - if err := featuresCopy.AppendEvent(event); err != nil { - return features, nil - } - } - return &featuresCopy, nil -} - -func (p *Features) getIAMEvents(sequence uint64) ([]*es_models.Event, error) { - query, err := eventsourcing.IAMByIDQuery(domain.IAMID, sequence) - if err != nil { - return nil, err - } - - return p.es.FilterEvents(context.Background(), query) -} diff --git a/internal/management/repository/eventsourcing/handler/handler.go b/internal/management/repository/eventsourcing/handler/handler.go index c0b444735e..fa2dd1f7b5 100644 --- a/internal/management/repository/eventsourcing/handler/handler.go +++ b/internal/management/repository/eventsourcing/handler/handler.go @@ -60,8 +60,6 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es handler{view, bulkLimit, configs.cycleDuration("MailTemplate"), errorCount, es}), newMessageText( handler{view, bulkLimit, configs.cycleDuration("MessageText"), errorCount, es}), - newFeatures( - handler{view, bulkLimit, configs.cycleDuration("Features"), errorCount, es}), newCustomText( handler{view, bulkLimit, configs.cycleDuration("CustomText"), errorCount, es}), newMetadata( diff --git a/internal/management/repository/eventsourcing/repository.go b/internal/management/repository/eventsourcing/repository.go index 49d3847c53..04f2c210f3 100644 --- a/internal/management/repository/eventsourcing/repository.go +++ b/internal/management/repository/eventsourcing/repository.go @@ -31,7 +31,6 @@ type EsRepository struct { eventstore.UserRepo eventstore.UserGrantRepo eventstore.IAMRepository - eventstore.FeaturesRepo view *mgmt_view.View } @@ -79,7 +78,6 @@ func Start(conf Config, systemDefaults sd.SystemDefaults, roles []string, querie UserRepo: eventstore.UserRepo{es, conf.SearchLimit, view, systemDefaults, assetsAPI}, UserGrantRepo: eventstore.UserGrantRepo{conf.SearchLimit, view, assetsAPI}, IAMRepository: eventstore.IAMRepository{IAMV2Query: queries}, - FeaturesRepo: eventstore.FeaturesRepo{es, view, conf.SearchLimit, systemDefaults}, view: view, }, nil } diff --git a/internal/management/repository/eventsourcing/view/features.go b/internal/management/repository/eventsourcing/view/features.go deleted file mode 100644 index da966f5c7d..0000000000 --- a/internal/management/repository/eventsourcing/view/features.go +++ /dev/null @@ -1,56 +0,0 @@ -package view - -import ( - "github.com/caos/zitadel/internal/eventstore/v1/models" - "github.com/caos/zitadel/internal/features/repository/view" - "github.com/caos/zitadel/internal/features/repository/view/model" - global_view "github.com/caos/zitadel/internal/view/repository" -) - -const ( - featuresTable = "management.features" -) - -func (v *View) AllDefaultFeatures() ([]*model.FeaturesView, error) { - return view.GetDefaultFeatures(v.Db, featuresTable) -} - -func (v *View) FeaturesByAggregateID(aggregateID string) (*model.FeaturesView, error) { - return view.GetFeaturesByAggregateID(v.Db, featuresTable, aggregateID) -} - -func (v *View) PutFeatures(features *model.FeaturesView, event *models.Event) error { - err := view.PutFeatures(v.Db, featuresTable, features) - if err != nil { - return err - } - return v.ProcessedFeaturesSequence(event) -} - -func (v *View) PutFeaturesList(features []*model.FeaturesView, event *models.Event) error { - err := view.PutFeaturesList(v.Db, featuresTable, features...) - if err != nil { - return err - } - return v.ProcessedFeaturesSequence(event) -} - -func (v *View) GetLatestFeaturesSequence() (*global_view.CurrentSequence, error) { - return v.latestSequence(featuresTable) -} - -func (v *View) ProcessedFeaturesSequence(event *models.Event) error { - return v.saveCurrentSequence(featuresTable, event) -} - -func (v *View) UpdateFeaturesSpoolerRunTimestamp() error { - return v.updateSpoolerRunSequence(featuresTable) -} - -func (v *View) GetLatestFeaturesFailedEvent(sequence uint64) (*global_view.FailedEvent, error) { - return v.latestFailedEvent(featuresTable, sequence) -} - -func (v *View) ProcessedFeaturesFailedEvent(failedEvent *global_view.FailedEvent) error { - return v.saveFailedEvent(failedEvent) -} diff --git a/internal/management/repository/features.go b/internal/management/repository/features.go deleted file mode 100644 index 1c01ab4d00..0000000000 --- a/internal/management/repository/features.go +++ /dev/null @@ -1,11 +0,0 @@ -package repository - -import ( - "context" - - features_model "github.com/caos/zitadel/internal/features/model" -) - -type FeaturesRepository interface { - GetOrgFeatures(ctx context.Context, id string) (*features_model.FeaturesView, error) -} diff --git a/internal/management/repository/repository.go b/internal/management/repository/repository.go index cb45972e00..0ed2044712 100644 --- a/internal/management/repository/repository.go +++ b/internal/management/repository/repository.go @@ -7,5 +7,4 @@ type Repository interface { UserRepository UserGrantRepository IamRepository - FeaturesRepository } diff --git a/internal/query/features.go b/internal/query/features.go index 4208407e5b..dba30e3cf7 100644 --- a/internal/query/features.go +++ b/internal/query/features.go @@ -7,14 +7,14 @@ import ( "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 Feature struct { +type Features struct { AggregateID string - CreationDate time.Time ChangeDate time.Time Sequence uint64 IsDefault bool @@ -42,91 +42,113 @@ type Feature struct { } var ( - feautureTable = table{ + featureTable = table{ name: projection.FeatureTable, } FeatureColumnAggregateID = Column{ - name: projection.FeatureAggregateIDCol, - } - FeatureColumnCreationDate = Column{ - name: projection.FeatureCreationDateCol, + name: projection.FeatureAggregateIDCol, + table: featureTable, } FeatureColumnChangeDate = Column{ - name: projection.FeatureChangeDateCol, + name: projection.FeatureChangeDateCol, + table: featureTable, } FeatureColumnSequence = Column{ - name: projection.FeatureSequenceCol, + name: projection.FeatureSequenceCol, + table: featureTable, } FeatureColumnIsDefault = Column{ - name: projection.FeatureIsDefaultCol, + name: projection.FeatureIsDefaultCol, + table: featureTable, } FeatureTierName = Column{ - name: projection.FeatureTierNameCol, + name: projection.FeatureTierNameCol, + table: featureTable, } FeatureTierDescription = Column{ - name: projection.FeatureTierDescriptionCol, + name: projection.FeatureTierDescriptionCol, + table: featureTable, } FeatureState = Column{ - name: projection.FeatureStateCol, + name: projection.FeatureStateCol, + table: featureTable, } FeatureStateDescription = Column{ - name: projection.FeatureStateDescriptionCol, + name: projection.FeatureStateDescriptionCol, + table: featureTable, } FeatureAuditLogRetention = Column{ - name: projection.FeatureAuditLogRetentionCol, + name: projection.FeatureAuditLogRetentionCol, + table: featureTable, } FeatureLoginPolicyFactors = Column{ - name: projection.FeatureLoginPolicyFactorsCol, + name: projection.FeatureLoginPolicyFactorsCol, + table: featureTable, } FeatureLoginPolicyIDP = Column{ - name: projection.FeatureLoginPolicyIDPCol, + name: projection.FeatureLoginPolicyIDPCol, + table: featureTable, } FeatureLoginPolicyPasswordless = Column{ - name: projection.FeatureLoginPolicyPasswordlessCol, + name: projection.FeatureLoginPolicyPasswordlessCol, + table: featureTable, } FeatureLoginPolicyRegistration = Column{ - name: projection.FeatureLoginPolicyRegistrationCol, + name: projection.FeatureLoginPolicyRegistrationCol, + table: featureTable, } FeatureLoginPolicyUsernameLogin = Column{ - name: projection.FeatureLoginPolicyUsernameLoginCol, + name: projection.FeatureLoginPolicyUsernameLoginCol, + table: featureTable, } FeatureLoginPolicyPasswordReset = Column{ - name: projection.FeatureLoginPolicyPasswordResetCol, + name: projection.FeatureLoginPolicyPasswordResetCol, + table: featureTable, } FeaturePasswordComplexityPolicy = Column{ - name: projection.FeaturePasswordComplexityPolicyCol, + name: projection.FeaturePasswordComplexityPolicyCol, + table: featureTable, } FeatureLabelPolicyPrivateLabel = Column{ - name: projection.FeatureLabelPolicyPrivateLabelCol, + name: projection.FeatureLabelPolicyPrivateLabelCol, + table: featureTable, } FeatureLabelPolicyWatermark = Column{ - name: projection.FeatureLabelPolicyWatermarkCol, + name: projection.FeatureLabelPolicyWatermarkCol, + table: featureTable, } FeatureCustomDomain = Column{ - name: projection.FeatureCustomDomainCol, + name: projection.FeatureCustomDomainCol, + table: featureTable, } FeaturePrivacyPolicy = Column{ - name: projection.FeaturePrivacyPolicyCol, + name: projection.FeaturePrivacyPolicyCol, + table: featureTable, } FeatureMetadataUser = Column{ - name: projection.FeatureMetadataUserCol, + name: projection.FeatureMetadataUserCol, + table: featureTable, } FeatureCustomTextMessage = Column{ - name: projection.FeatureCustomTextMessageCol, + name: projection.FeatureCustomTextMessageCol, + table: featureTable, } FeatureCustomTextLogin = Column{ - name: projection.FeatureCustomTextLoginCol, + name: projection.FeatureCustomTextLoginCol, + table: featureTable, } FeatureLockoutPolicy = Column{ - name: projection.FeatureLockoutPolicyCol, + name: projection.FeatureLockoutPolicyCol, + table: featureTable, } FeatureActions = Column{ - name: projection.FeatureActionsCol, + name: projection.FeatureActionsCol, + table: featureTable, } ) -func (q *Queries) FeatureByID(ctx context.Context, orgID string) (*Feature, error) { - query, scan := prepareFeatureQuery() +func (q *Queries) FeaturesByOrgID(ctx context.Context, orgID string) (*Features, error) { + query, scan := prepareFeaturesQuery() stmt, args, err := query.Where( sq.Or{ sq.Eq{ @@ -146,11 +168,11 @@ func (q *Queries) FeatureByID(ctx context.Context, orgID string) (*Feature, erro return scan(row) } -func (q *Queries) DefaultFeature(ctx context.Context) (*Feature, error) { - query, scan := prepareFeatureQuery() +func (q *Queries) DefaultFeatures(ctx context.Context) (*Features, error) { + query, scan := prepareFeaturesQuery() stmt, args, err := query.Where(sq.Eq{ FeatureColumnAggregateID.identifier(): domain.IAMID, - }).OrderBy(FeatureColumnIsDefault.identifier()).ToSql() + }).ToSql() if err != nil { return nil, errors.ThrowInternal(err, "QUERY-1Ndlg", "Errors.Query.SQLStatement") } @@ -159,10 +181,9 @@ func (q *Queries) DefaultFeature(ctx context.Context) (*Feature, error) { return scan(row) } -func prepareFeatureQuery() (sq.SelectBuilder, func(*sql.Row) (*Feature, error)) { +func prepareFeaturesQuery() (sq.SelectBuilder, func(*sql.Row) (*Features, error)) { return sq.Select( FeatureColumnAggregateID.identifier(), - FeatureColumnCreationDate.identifier(), FeatureColumnChangeDate.identifier(), FeatureColumnSequence.identifier(), FeatureColumnIsDefault.identifier(), @@ -187,25 +208,26 @@ func prepareFeatureQuery() (sq.SelectBuilder, func(*sql.Row) (*Feature, error)) FeatureCustomTextLogin.identifier(), FeatureLockoutPolicy.identifier(), FeatureActions.identifier(), - ).From(loginPolicyTable.identifier()).PlaceholderFormat(sq.Dollar), - func(row *sql.Row) (*Feature, error) { - p := new(Feature) + ).From(featureTable.identifier()).PlaceholderFormat(sq.Dollar), + func(row *sql.Row) (*Features, error) { + p := new(Features) + tierName := sql.NullString{} + tierDescription := sql.NullString{} + stateDescription := sql.NullString{} err := row.Scan( &p.AggregateID, - &p.CreationDate, &p.ChangeDate, &p.Sequence, &p.IsDefault, - &p.TierName, - &p.TierDescription, + &tierName, + &tierDescription, &p.State, - &p.StateDescription, + &stateDescription, &p.AuditLogRetention, &p.LoginPolicyFactors, &p.LoginPolicyIDP, &p.LoginPolicyPasswordless, &p.LoginPolicyRegistration, - &p.LoginPolicyRegistration, &p.LoginPolicyUsernameLogin, &p.LoginPolicyPasswordReset, &p.PasswordComplexityPolicy, @@ -221,10 +243,66 @@ func prepareFeatureQuery() (sq.SelectBuilder, func(*sql.Row) (*Feature, error)) ) if err != nil { if errs.Is(err, sql.ErrNoRows) { - return nil, errors.ThrowNotFound(err, "QUERY-M9fse", "Errors.Feature.NotFound") + return nil, errors.ThrowNotFound(err, "QUERY-M9fse", "Errors.Features.NotFound") } return nil, errors.ThrowInternal(err, "QUERY-3o9gd", "Errors.Internal") } + p.TierName = tierName.String + p.TierDescription = tierDescription.String + p.StateDescription = stateDescription.String return p, nil } } + +func (f *Features) EnabledFeatureTypes() []string { + list := make([]string, 0) + if f.LoginPolicyFactors { + list = append(list, domain.FeatureLoginPolicyFactors) + } + if f.LoginPolicyIDP { + list = append(list, domain.FeatureLoginPolicyIDP) + } + if f.LoginPolicyPasswordless { + list = append(list, domain.FeatureLoginPolicyPasswordless) + } + if f.LoginPolicyRegistration { + list = append(list, domain.FeatureLoginPolicyRegistration) + } + if f.LoginPolicyUsernameLogin { + list = append(list, domain.FeatureLoginPolicyUsernameLogin) + } + if f.LoginPolicyPasswordReset { + list = append(list, domain.FeatureLoginPolicyPasswordReset) + } + if f.PasswordComplexityPolicy { + list = append(list, domain.FeaturePasswordComplexityPolicy) + } + if f.LabelPolicyPrivateLabel { + list = append(list, domain.FeatureLabelPolicyPrivateLabel) + } + if f.LabelPolicyWatermark { + list = append(list, domain.FeatureLabelPolicyWatermark) + } + if f.CustomDomain { + list = append(list, domain.FeatureCustomDomain) + } + if f.PrivacyPolicy { + list = append(list, domain.FeaturePrivacyPolicy) + } + if f.MetadataUser { + list = append(list, domain.FeatureMetadataUser) + } + if f.CustomTextMessage { + list = append(list, domain.FeatureCustomTextMessage) + } + if f.CustomTextLogin { + list = append(list, domain.FeatureCustomTextLogin) + } + if f.LockoutPolicy { + list = append(list, domain.FeatureLockoutPolicy) + } + if f.Actions { + list = append(list, domain.FeatureActions) + } + return list +} diff --git a/internal/query/features_test.go b/internal/query/features_test.go new file mode 100644 index 0000000000..c3ad3248cb --- /dev/null +++ b/internal/query/features_test.go @@ -0,0 +1,348 @@ +package query + +import ( + "database/sql" + "database/sql/driver" + "errors" + "fmt" + "regexp" + "testing" + "time" + + "github.com/caos/zitadel/internal/domain" + errs "github.com/caos/zitadel/internal/errors" +) + +func Test_FeaturesPrepares(t *testing.T) { + type want struct { + sqlExpectations sqlExpectation + err checkErr + } + tests := []struct { + name string + prepare interface{} + want want + object interface{} + }{ + { + name: "prepareFeaturesQuery no result", + prepare: prepareFeaturesQuery, + want: want{ + sqlExpectations: mockQuery( + regexp.QuoteMeta(`SELECT zitadel.projections.features.aggregate_id,`+ + ` zitadel.projections.features.change_date,`+ + ` zitadel.projections.features.sequence,`+ + ` zitadel.projections.features.is_default,`+ + ` zitadel.projections.features.tier_name,`+ + ` zitadel.projections.features.tier_description,`+ + ` zitadel.projections.features.state,`+ + ` zitadel.projections.features.state_description,`+ + ` zitadel.projections.features.audit_log_retention,`+ + ` zitadel.projections.features.login_policy_factors,`+ + ` zitadel.projections.features.login_policy_idp,`+ + ` zitadel.projections.features.login_policy_passwordless,`+ + ` zitadel.projections.features.login_policy_registration,`+ + ` zitadel.projections.features.login_policy_username_login,`+ + ` zitadel.projections.features.login_policy_password_reset,`+ + ` zitadel.projections.features.password_complexity_policy,`+ + ` zitadel.projections.features.label_policy_private_label,`+ + ` zitadel.projections.features.label_policy_watermark,`+ + ` zitadel.projections.features.custom_domain,`+ + ` zitadel.projections.features.privacy_policy,`+ + ` zitadel.projections.features.metadata_user,`+ + ` zitadel.projections.features.custom_text_message,`+ + ` zitadel.projections.features.custom_text_login,`+ + ` zitadel.projections.features.lockout_policy,`+ + ` zitadel.projections.features.actions`+ + ` FROM zitadel.projections.features`), + 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: (*Features)(nil), + }, + { + name: "prepareFeaturesQuery found", + prepare: prepareFeaturesQuery, + want: want{ + sqlExpectations: mockQuery( + regexp.QuoteMeta(`SELECT zitadel.projections.features.aggregate_id,`+ + ` zitadel.projections.features.change_date,`+ + ` zitadel.projections.features.sequence,`+ + ` zitadel.projections.features.is_default,`+ + ` zitadel.projections.features.tier_name,`+ + ` zitadel.projections.features.tier_description,`+ + ` zitadel.projections.features.state,`+ + ` zitadel.projections.features.state_description,`+ + ` zitadel.projections.features.audit_log_retention,`+ + ` zitadel.projections.features.login_policy_factors,`+ + ` zitadel.projections.features.login_policy_idp,`+ + ` zitadel.projections.features.login_policy_passwordless,`+ + ` zitadel.projections.features.login_policy_registration,`+ + ` zitadel.projections.features.login_policy_username_login,`+ + ` zitadel.projections.features.login_policy_password_reset,`+ + ` zitadel.projections.features.password_complexity_policy,`+ + ` zitadel.projections.features.label_policy_private_label,`+ + ` zitadel.projections.features.label_policy_watermark,`+ + ` zitadel.projections.features.custom_domain,`+ + ` zitadel.projections.features.privacy_policy,`+ + ` zitadel.projections.features.metadata_user,`+ + ` zitadel.projections.features.custom_text_message,`+ + ` zitadel.projections.features.custom_text_login,`+ + ` zitadel.projections.features.lockout_policy,`+ + ` zitadel.projections.features.actions`+ + ` FROM zitadel.projections.features`), + []string{ + "aggregate_id", + "change_date", + "sequence", + "is_default", + "tier_name", + "tier_description", + "state", + "state_description", + "audit_log_retention", + "login_policy_factors", + "login_policy_idp", + "login_policy_passwordless", + "login_policy_registration", + "login_policy_username_login", + "login_policy_password_reset", + "password_complexity_policy", + "label_policy_private_label", + "label_policy_watermark", + "custom_domain", + "privacy_policy", + "metadata_user", + "custom_text_message", + "custom_text_login", + "lockout_policy", + "actions", + }, + []driver.Value{ + "aggregate-id", + testNow, + uint64(20211115), + true, + "tier-name", + "tier-description", + 1, + "state-description", + uint(604800000000000), // 7days in nanoseconds + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + }, + ), + }, + object: &Features{ + AggregateID: "aggregate-id", + ChangeDate: testNow, + Sequence: 20211115, + IsDefault: true, + TierName: "tier-name", + TierDescription: "tier-description", + State: domain.FeaturesStateActive, + StateDescription: "state-description", + AuditLogRetention: 7 * 24 * time.Hour, + LoginPolicyFactors: true, + LoginPolicyIDP: true, + LoginPolicyPasswordless: true, + LoginPolicyRegistration: true, + LoginPolicyUsernameLogin: true, + LoginPolicyPasswordReset: true, + PasswordComplexityPolicy: true, + LabelPolicyPrivateLabel: true, + LabelPolicyWatermark: true, + CustomDomain: true, + PrivacyPolicy: true, + MetadataUser: true, + CustomTextMessage: true, + CustomTextLogin: true, + LockoutPolicy: true, + Actions: true, + }, + }, + { + name: "prepareFeaturesQuery found with empty", + prepare: prepareFeaturesQuery, + want: want{ + sqlExpectations: mockQuery( + regexp.QuoteMeta(`SELECT zitadel.projections.features.aggregate_id,`+ + ` zitadel.projections.features.change_date,`+ + ` zitadel.projections.features.sequence,`+ + ` zitadel.projections.features.is_default,`+ + ` zitadel.projections.features.tier_name,`+ + ` zitadel.projections.features.tier_description,`+ + ` zitadel.projections.features.state,`+ + ` zitadel.projections.features.state_description,`+ + ` zitadel.projections.features.audit_log_retention,`+ + ` zitadel.projections.features.login_policy_factors,`+ + ` zitadel.projections.features.login_policy_idp,`+ + ` zitadel.projections.features.login_policy_passwordless,`+ + ` zitadel.projections.features.login_policy_registration,`+ + ` zitadel.projections.features.login_policy_username_login,`+ + ` zitadel.projections.features.login_policy_password_reset,`+ + ` zitadel.projections.features.password_complexity_policy,`+ + ` zitadel.projections.features.label_policy_private_label,`+ + ` zitadel.projections.features.label_policy_watermark,`+ + ` zitadel.projections.features.custom_domain,`+ + ` zitadel.projections.features.privacy_policy,`+ + ` zitadel.projections.features.metadata_user,`+ + ` zitadel.projections.features.custom_text_message,`+ + ` zitadel.projections.features.custom_text_login,`+ + ` zitadel.projections.features.lockout_policy,`+ + ` zitadel.projections.features.actions`+ + ` FROM zitadel.projections.features`), + []string{ + "aggregate_id", + "change_date", + "sequence", + "is_default", + "tier_name", + "tier_description", + "state", + "state_description", + "audit_log_retention", + "login_policy_factors", + "login_policy_idp", + "login_policy_passwordless", + "login_policy_registration", + "login_policy_username_login", + "login_policy_password_reset", + "password_complexity_policy", + "label_policy_private_label", + "label_policy_watermark", + "custom_domain", + "privacy_policy", + "metadata_user", + "custom_text_message", + "custom_text_login", + "lockout_policy", + "actions", + }, + []driver.Value{ + "aggregate-id", + testNow, + uint64(20211115), + true, + nil, + nil, + 1, + nil, + uint(604800000000000), // 7days in nanoseconds + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + }, + ), + }, + object: &Features{ + AggregateID: "aggregate-id", + ChangeDate: testNow, + Sequence: 20211115, + IsDefault: true, + TierName: "", + TierDescription: "", + State: domain.FeaturesStateActive, + StateDescription: "", + AuditLogRetention: 7 * 24 * time.Hour, + LoginPolicyFactors: true, + LoginPolicyIDP: true, + LoginPolicyPasswordless: true, + LoginPolicyRegistration: true, + LoginPolicyUsernameLogin: true, + LoginPolicyPasswordReset: true, + PasswordComplexityPolicy: true, + LabelPolicyPrivateLabel: true, + LabelPolicyWatermark: true, + CustomDomain: true, + PrivacyPolicy: true, + MetadataUser: true, + CustomTextMessage: true, + CustomTextLogin: true, + LockoutPolicy: true, + Actions: true, + }, + }, + { + name: "prepareFeaturesQuery sql err", + prepare: prepareFeaturesQuery, + want: want{ + sqlExpectations: mockQueryErr( + regexp.QuoteMeta(`SELECT zitadel.projections.features.aggregate_id,`+ + ` zitadel.projections.features.change_date,`+ + ` zitadel.projections.features.sequence,`+ + ` zitadel.projections.features.is_default,`+ + ` zitadel.projections.features.tier_name,`+ + ` zitadel.projections.features.tier_description,`+ + ` zitadel.projections.features.state,`+ + ` zitadel.projections.features.state_description,`+ + ` zitadel.projections.features.audit_log_retention,`+ + ` zitadel.projections.features.login_policy_factors,`+ + ` zitadel.projections.features.login_policy_idp,`+ + ` zitadel.projections.features.login_policy_passwordless,`+ + ` zitadel.projections.features.login_policy_registration,`+ + ` zitadel.projections.features.login_policy_username_login,`+ + ` zitadel.projections.features.login_policy_password_reset,`+ + ` zitadel.projections.features.password_complexity_policy,`+ + ` zitadel.projections.features.label_policy_private_label,`+ + ` zitadel.projections.features.label_policy_watermark,`+ + ` zitadel.projections.features.custom_domain,`+ + ` zitadel.projections.features.privacy_policy,`+ + ` zitadel.projections.features.metadata_user,`+ + ` zitadel.projections.features.custom_text_message,`+ + ` zitadel.projections.features.custom_text_login,`+ + ` zitadel.projections.features.lockout_policy,`+ + ` zitadel.projections.features.actions`+ + ` FROM zitadel.projections.features`), + 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/feature.go b/internal/query/projection/feature.go index 63f93d0ef4..d544d68bb0 100644 --- a/internal/query/projection/feature.go +++ b/internal/query/projection/feature.go @@ -4,12 +4,12 @@ import ( "context" "github.com/caos/logging" - "github.com/caos/zitadel/internal/repository/features" "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/eventstore" "github.com/caos/zitadel/internal/eventstore/handler" "github.com/caos/zitadel/internal/eventstore/handler/crdb" + "github.com/caos/zitadel/internal/repository/features" "github.com/caos/zitadel/internal/repository/iam" "github.com/caos/zitadel/internal/repository/org" ) @@ -59,7 +59,6 @@ func (p *FeatureProjection) reducers() []handler.AggregateReducer { const ( FeatureAggregateIDCol = "aggregate_id" - FeatureCreationDateCol = "creation_date" FeatureChangeDateCol = "change_date" FeatureSequenceCol = "sequence" FeatureIsDefaultCol = "is_default" @@ -103,7 +102,6 @@ func (p *FeatureProjection) reduceFeatureSet(event eventstore.EventReader) (*han cols := []handler.Column{ handler.NewCol(FeatureAggregateIDCol, featureEvent.Aggregate().ID), - handler.NewCol(FeatureCreationDateCol, featureEvent.CreationDate()), handler.NewCol(FeatureChangeDateCol, featureEvent.CreationDate()), handler.NewCol(FeatureSequenceCol, featureEvent.Sequence()), handler.NewCol(FeatureIsDefaultCol, isDefault), diff --git a/internal/query/projection/feature_test.go b/internal/query/projection/feature_test.go index ed71f8f306..06e2092452 100644 --- a/internal/query/projection/feature_test.go +++ b/internal/query/projection/feature_test.go @@ -63,11 +63,10 @@ func TestFeatureProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPSERT INTO zitadel.projections.features (aggregate_id, creation_date, change_date, sequence, is_default, tier_name, tier_description, state, state_description, audit_log_retention, login_policy_factors, login_policy_idp, login_policy_passwordless, login_policy_registration, login_policy_username_login, login_policy_password_reset, password_complexity_policy, label_policy_private_label, label_policy_watermark, custom_domain, privacy_policy, metadata_user, custom_text_message, custom_text_login, lockout_policy, actions) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26)", + expectedStmt: "UPSERT INTO zitadel.projections.features (aggregate_id, change_date, sequence, is_default, tier_name, tier_description, state, state_description, audit_log_retention, login_policy_factors, login_policy_idp, login_policy_passwordless, login_policy_registration, login_policy_username_login, login_policy_password_reset, password_complexity_policy, label_policy_private_label, label_policy_watermark, custom_domain, privacy_policy, metadata_user, custom_text_message, custom_text_login, lockout_policy, actions) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25)", expectedArgs: []interface{}{ "agg-id", anyArg{}, - anyArg{}, uint64(15), false, "TierName", @@ -97,6 +96,36 @@ func TestFeatureProjection_reduces(t *testing.T) { }, }, }, + { + name: "org.reduceFeatureSet required values only", + args: args{ + event: getEvent(testEvent( + repository.EventType(org.FeaturesSetEventType), + org.AggregateType, + []byte(`{}`), + ), org.FeaturesSetEventMapper), + }, + reduce: (&FeatureProjection{}).reduceFeatureSet, + want: wantReduce{ + aggregateType: eventstore.AggregateType("org"), + sequence: 15, + previousSequence: 10, + projection: FeatureTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPSERT INTO zitadel.projections.features (aggregate_id, change_date, sequence, is_default) VALUES ($1, $2, $3, $4)", + expectedArgs: []interface{}{ + "agg-id", + anyArg{}, + uint64(15), + false, + }, + }, + }, + }, + }, + }, { name: "org.reduceFeatureRemoved", reduce: (&FeatureProjection{}).reduceFeatureRemoved, @@ -164,11 +193,10 @@ func TestFeatureProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPSERT INTO zitadel.projections.features (aggregate_id, creation_date, change_date, sequence, is_default, tier_name, tier_description, state, state_description, audit_log_retention, login_policy_factors, login_policy_idp, login_policy_passwordless, login_policy_registration, login_policy_username_login, login_policy_password_reset, password_complexity_policy, label_policy_private_label, label_policy_watermark, custom_domain, privacy_policy, metadata_user, custom_text_message, custom_text_login, lockout_policy, actions) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26)", + expectedStmt: "UPSERT INTO zitadel.projections.features (aggregate_id, change_date, sequence, is_default, tier_name, tier_description, state, state_description, audit_log_retention, login_policy_factors, login_policy_idp, login_policy_passwordless, login_policy_registration, login_policy_username_login, login_policy_password_reset, password_complexity_policy, label_policy_private_label, label_policy_watermark, custom_domain, privacy_policy, metadata_user, custom_text_message, custom_text_login, lockout_policy, actions) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25)", expectedArgs: []interface{}{ "agg-id", anyArg{}, - anyArg{}, uint64(15), true, "TierName", @@ -203,7 +231,7 @@ func TestFeatureProjection_reduces(t *testing.T) { t.Run(tt.name, func(t *testing.T) { event := baseEvent(t) got, err := tt.reduce(event) - if _, ok := err.(errors.InvalidArgument); !ok { + if !errors.IsErrorInvalidArgument(err) { t.Errorf("no wrong event mapping: %v, got: %v", err, got) } diff --git a/migrations/cockroach/V1.92__features_not_null.sql b/migrations/cockroach/V1.92__features_not_null.sql new file mode 100644 index 0000000000..06eeaccb34 --- /dev/null +++ b/migrations/cockroach/V1.92__features_not_null.sql @@ -0,0 +1,64 @@ +alter table zitadel.projections.features + drop column creation_date, + alter column is_default set default false, + alter column state set default 0, + alter column audit_log_retention set default 0, + alter column login_policy_factors set default false, + alter column login_policy_idp set default false, + alter column login_policy_passwordless set default false, + alter column login_policy_registration set default false, + alter column login_policy_username_login set default false, + alter column login_policy_password_reset set default false, + alter column password_complexity_policy set default false, + alter column label_policy_private_label set default false, + alter column label_policy_watermark set default false, + alter column custom_domain set default false, + alter column privacy_policy set default false, + alter column metadata_user set default false, + alter column custom_text_message set default false, + alter column custom_text_login set default false, + alter column lockout_policy set default false, + alter column actions set default false; + +update zitadel.projections.features set is_default = default where is_default is null; +update zitadel.projections.features set state = 0 where state is null; +update zitadel.projections.features set audit_log_retention = 0 where audit_log_retention is null; +update zitadel.projections.features set login_policy_factors = default where login_policy_factors is null; +update zitadel.projections.features set login_policy_idp = default where login_policy_idp is null; +update zitadel.projections.features set login_policy_passwordless = default where login_policy_passwordless is null; +update zitadel.projections.features set login_policy_registration = default where login_policy_registration is null; +update zitadel.projections.features set login_policy_username_login = default where login_policy_username_login is null; +update zitadel.projections.features set login_policy_password_reset = default where login_policy_password_reset is null; +update zitadel.projections.features set password_complexity_policy = default where password_complexity_policy is null; +update zitadel.projections.features set label_policy_private_label = default where label_policy_private_label is null; +update zitadel.projections.features set label_policy_watermark = default where label_policy_watermark is null; +update zitadel.projections.features set custom_domain = default where custom_domain is null; +update zitadel.projections.features set privacy_policy = default where privacy_policy is null; +update zitadel.projections.features set metadata_user = default where metadata_user is null; +update zitadel.projections.features set custom_text_message = default where custom_text_message is null; +update zitadel.projections.features set custom_text_login = default where custom_text_login is null; +update zitadel.projections.features set lockout_policy = default where lockout_policy is null; +update zitadel.projections.features set actions = default where actions is null; + +alter table zitadel.projections.features + alter column change_date set not null, + alter column sequence set not null, + alter column is_default set not null, + alter column state set not null, + alter column audit_log_retention set not null, + alter column login_policy_factors set not null, + alter column login_policy_idp set not null, + alter column login_policy_passwordless set not null, + alter column login_policy_registration set not null, + alter column login_policy_username_login set not null, + alter column login_policy_password_reset set not null, + alter column password_complexity_policy set not null, + alter column label_policy_private_label set not null, + alter column label_policy_watermark set not null, + alter column custom_domain set not null, + alter column privacy_policy set not null, + alter column metadata_user set not null, + alter column custom_text_message set not null, + alter column custom_text_login set not null, + alter column lockout_policy set not null, + alter column actions set not null;