fix: features query (#2610)

This commit is contained in:
Fabi 2021-11-21 20:22:25 +01:00 committed by GitHub
parent 56e10ecf30
commit 76346cb070
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 604 additions and 1619 deletions

View File

@ -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)
}

View File

@ -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)
}

View File

@ -52,8 +52,6 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es
handler{view, bulkLimit, configs.cycleDuration("MailTemplate"), errorCount, es}), handler{view, bulkLimit, configs.cycleDuration("MailTemplate"), errorCount, es}),
newMessageText( newMessageText(
handler{view, bulkLimit, configs.cycleDuration("MessageText"), errorCount, es}), handler{view, bulkLimit, configs.cycleDuration("MessageText"), errorCount, es}),
newFeatures(
handler{view, bulkLimit, configs.cycleDuration("Features"), errorCount, es}),
newCustomText( newCustomText(
handler{view, bulkLimit, configs.cycleDuration("CustomTexts"), errorCount, es}), handler{view, bulkLimit, configs.cycleDuration("CustomTexts"), errorCount, es}),
} }

View File

@ -30,7 +30,6 @@ type EsRepository struct {
spooler *es_spol.Spooler spooler *es_spol.Spooler
eventstore.IAMRepository eventstore.IAMRepository
eventstore.AdministratorRepo eventstore.AdministratorRepo
eventstore.FeaturesRepo
eventstore.UserRepo eventstore.UserRepo
} }
@ -74,12 +73,6 @@ func Start(ctx context.Context, conf Config, systemDefaults sd.SystemDefaults, c
AdministratorRepo: eventstore.AdministratorRepo{ AdministratorRepo: eventstore.AdministratorRepo{
View: view, View: view,
}, },
FeaturesRepo: eventstore.FeaturesRepo{
Eventstore: es,
View: view,
SearchLimit: conf.SearchLimit,
SystemDefaults: systemDefaults,
},
UserRepo: eventstore.UserRepo{ UserRepo: eventstore.UserRepo{
Eventstore: es, Eventstore: es,
View: view, View: view,

View File

@ -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)
}

View File

@ -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)
}

View File

@ -6,6 +6,5 @@ type Repository interface {
Health(ctx context.Context) error Health(ctx context.Context) error
IAMRepository IAMRepository
AdministratorRepository AdministratorRepository
FeaturesRepository
UserRepository UserRepository
} }

View File

@ -10,12 +10,12 @@ import (
) )
func (s *Server) GetDefaultFeatures(ctx context.Context, _ *admin_pb.GetDefaultFeaturesRequest) (*admin_pb.GetDefaultFeaturesResponse, error) { 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 { if err != nil {
return nil, err return nil, err
} }
return &admin_pb.GetDefaultFeaturesResponse{ return &admin_pb.GetDefaultFeaturesResponse{
Features: features_grpc.FeaturesFromModel(features), Features: features_grpc.ModelFeaturesToPb(features),
}, nil }, 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) { 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 { if err != nil {
return nil, err return nil, err
} }
return &admin_pb.GetOrgFeaturesResponse{ return &admin_pb.GetOrgFeaturesResponse{
Features: features_grpc.FeaturesFromModel(features), Features: features_grpc.ModelFeaturesToPb(features),
}, nil }, nil
} }

View File

@ -25,7 +25,6 @@ type Server struct {
iam repository.IAMRepository iam repository.IAMRepository
administrator repository.AdministratorRepository administrator repository.AdministratorRepository
repo repository.Repository repo repository.Repository
features repository.FeaturesRepository
users repository.UserRepository users repository.UserRepository
iamDomain string iamDomain string
} }
@ -41,7 +40,6 @@ func CreateServer(command *command.Commands, query *query.Queries, repo reposito
iam: repo, iam: repo,
administrator: repo, administrator: repo,
repo: repo, repo: repo,
features: repo,
users: repo, users: repo,
iamDomain: iamDomain, iamDomain: iamDomain,
} }

View File

@ -8,11 +8,11 @@ import (
) )
func (s *Server) ListMyZitadelFeatures(ctx context.Context, _ *auth_pb.ListMyZitadelFeaturesRequest) (*auth_pb.ListMyZitadelFeaturesResponse, error) { 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 { if err != nil {
return nil, err return nil, err
} }
return &auth_pb.ListMyZitadelFeaturesResponse{ return &auth_pb.ListMyZitadelFeaturesResponse{
Result: features.FeatureList(), Result: features.EnabledFeatureTypes(),
}, nil }, nil
} }

View File

@ -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) { func (s *Server) ListMyUserChanges(ctx context.Context, req *auth_pb.ListMyUserChangesRequest) (*auth_pb.ListMyUserChangesResponse, error) {
sequence, limit, asc := change.ChangeQueryToModel(req.Query) 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 { if err != nil {
return nil, err return nil, err
} }

View File

@ -5,16 +5,14 @@ import (
object_grpc "github.com/caos/zitadel/internal/api/grpc/object" object_grpc "github.com/caos/zitadel/internal/api/grpc/object"
"github.com/caos/zitadel/internal/domain" "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" 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{ return &features_pb.Features{
Details: object_grpc.ToViewDetailsPb(features.Sequence, features.CreationDate, features.ChangeDate, features.AggregateID), IsDefault: features.IsDefault,
Tier: FeatureTierToPb(features.TierName, features.TierDescription, features.State, features.StateDescription), Tier: FeatureTierToPb(features.TierName, features.TierDescription, features.State, features.StateDescription),
IsDefault: features.Default,
AuditLogRetention: durationpb.New(features.AuditLogRetention), AuditLogRetention: durationpb.New(features.AuditLogRetention),
LoginPolicyFactors: features.LoginPolicyFactors, LoginPolicyFactors: features.LoginPolicyFactors,
LoginPolicyIdp: features.LoginPolicyIDP, LoginPolicyIdp: features.LoginPolicyIDP,
@ -34,6 +32,11 @@ func FeaturesFromModel(features *features_model.FeaturesView) *features_pb.Featu
MetadataUser: features.MetadataUser, MetadataUser: features.MetadataUser,
LockoutPolicy: features.LockoutPolicy, LockoutPolicy: features.LockoutPolicy,
Actions: features.Actions, Actions: features.Actions,
Details: object_grpc.ChangeToDetailsPb(
features.Sequence,
features.ChangeDate,
features.AggregateID,
),
} }
} }

View File

@ -9,11 +9,11 @@ import (
) )
func (s *Server) GetFeatures(ctx context.Context, req *mgmt_pb.GetFeaturesRequest) (*mgmt_pb.GetFeaturesResponse, error) { 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 { if err != nil {
return nil, err return nil, err
} }
return &mgmt_pb.GetFeaturesResponse{ return &mgmt_pb.GetFeaturesResponse{
Features: features_grpc.FeaturesFromModel(features), Features: features_grpc.ModelFeaturesToPb(features),
}, nil }, nil
} }

View File

@ -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) { func (s *Server) ListOrgChanges(ctx context.Context, req *mgmt_pb.ListOrgChangesRequest) (*mgmt_pb.ListOrgChangesResponse, error) {
sequence, limit, asc := change_grpc.ChangeQueryToModel(req.Query) 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 { if err != nil {
return nil, err return nil, err
} }

View File

@ -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) { func (s *Server) ListProjectChanges(ctx context.Context, req *mgmt_pb.ListProjectChangesRequest) (*mgmt_pb.ListProjectChangesResponse, error) {
sequence, limit, asc := change_grpc.ChangeQueryToModel(req.Query) 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 { if err != nil {
return nil, err return nil, err
} }

View File

@ -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) { func (s *Server) ListAppChanges(ctx context.Context, req *mgmt_pb.ListAppChangesRequest) (*mgmt_pb.ListAppChangesResponse, error) {
sequence, limit, asc := change_grpc.ChangeQueryToModel(req.Query) 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 { if err != nil {
return nil, err return nil, err
} }

View File

@ -28,7 +28,6 @@ type Server struct {
user repository.UserRepository user repository.UserRepository
usergrant repository.UserGrantRepository usergrant repository.UserGrantRepository
iam repository.IamRepository iam repository.IamRepository
features repository.FeaturesRepository
authZ authz.Config authZ authz.Config
systemDefaults systemdefaults.SystemDefaults systemDefaults systemdefaults.SystemDefaults
} }
@ -46,7 +45,6 @@ func CreateServer(command *command.Commands, query *query.Queries, repo reposito
user: repo, user: repo,
usergrant: repo, usergrant: repo,
iam: repo, iam: repo,
features: repo,
systemDefaults: sd, systemDefaults: sd,
} }
} }

View File

@ -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) { func (s *Server) ListUserChanges(ctx context.Context, req *mgmt_pb.ListUserChangesRequest) (*mgmt_pb.ListUserChangesResponse, error) {
sequence, limit, asc := change_grpc.ChangeQueryToModel(req.Query) 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 { if err != nil {
return nil, err return nil, err
} }

View File

@ -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)
}

View File

@ -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)
}

View File

@ -59,7 +59,6 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es
handler{view, bulkLimit, configs.cycleDuration("ExternalIDP"), errorCount, es}, handler{view, bulkLimit, configs.cycleDuration("ExternalIDP"), errorCount, es},
systemDefaults), systemDefaults),
newLabelPolicy(handler{view, bulkLimit, configs.cycleDuration("LabelPolicy"), errorCount, es}), 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}), newRefreshToken(handler{view, bulkLimit, configs.cycleDuration("RefreshToken"), errorCount, es}),
newCustomText(handler{view, bulkLimit, configs.cycleDuration("CustomTexts"), errorCount, es}), newCustomText(handler{view, bulkLimit, configs.cycleDuration("CustomTexts"), errorCount, es}),
newMetadata(handler{view, bulkLimit, configs.cycleDuration("Metadata"), errorCount, es}), newMetadata(handler{view, bulkLimit, configs.cycleDuration("Metadata"), errorCount, es}),

View File

@ -47,7 +47,6 @@ type EsRepository struct {
eventstore.UserGrantRepo eventstore.UserGrantRepo
eventstore.OrgRepository eventstore.OrgRepository
eventstore.IAMRepository 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) { 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, LoginDir: statikLoginFS,
IAMV2QuerySide: queries, IAMV2QuerySide: queries,
}, },
eventstore.FeaturesRepo{
Eventstore: es,
View: view,
},
}, nil }, nil
} }

View File

@ -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)
}

View File

@ -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)
}

View File

@ -15,6 +15,5 @@ type Repository interface {
UserGrantRepository UserGrantRepository
OrgRepository OrgRepository
IAMRepository IAMRepository
FeaturesRepository
RefreshTokenRepository RefreshTokenRepository
} }

View File

@ -15,10 +15,10 @@ import (
v1 "github.com/caos/zitadel/internal/eventstore/v1" v1 "github.com/caos/zitadel/internal/eventstore/v1"
"github.com/caos/zitadel/internal/eventstore/v1/models" "github.com/caos/zitadel/internal/eventstore/v1/models"
es_sdk "github.com/caos/zitadel/internal/eventstore/v1/sdk" 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_model "github.com/caos/zitadel/internal/iam/model"
iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model" iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
iam_view "github.com/caos/zitadel/internal/iam/repository/view" iam_view "github.com/caos/zitadel/internal/iam/repository/view"
"github.com/caos/zitadel/internal/query"
"github.com/caos/zitadel/internal/telemetry/tracing" "github.com/caos/zitadel/internal/telemetry/tracing"
usr_model "github.com/caos/zitadel/internal/user/model" usr_model "github.com/caos/zitadel/internal/user/model"
usr_view "github.com/caos/zitadel/internal/user/repository/view" usr_view "github.com/caos/zitadel/internal/user/repository/view"
@ -30,6 +30,7 @@ type TokenVerifierRepo struct {
IAMID string IAMID string
Eventstore v1.Eventstore Eventstore v1.Eventstore
View *view.View View *view.View
Query *query.Queries
} }
func (repo *TokenVerifierRepo) TokenByID(ctx context.Context, tokenID, userID string) (*usr_model.TokenView, error) { 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 { func (repo *TokenVerifierRepo) CheckOrgFeatures(ctx context.Context, orgID string, requiredFeatures ...string) error {
features, err := repo.View.FeaturesByAggregateID(orgID) features, err := repo.Query.FeaturesByOrgID(ctx, orgID)
if caos_errs.IsNotFound(err) {
return repo.checkDefaultFeatures(ctx, requiredFeatures...)
}
if err != nil { if err != nil {
return err return err
} }
return checkFeatures(features, requiredFeatures...) 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 { for _, requiredFeature := range requiredFeatures {
if strings.HasPrefix(requiredFeature, domain.FeatureLoginPolicy) { if strings.HasPrefix(requiredFeature, domain.FeatureLoginPolicy) {
if err := checkLoginPolicyFeatures(features, requiredFeature); err != nil { if err := checkLoginPolicyFeatures(features, requiredFeature); err != nil {
@ -187,7 +185,7 @@ func checkFeatures(features *features_view_model.FeaturesView, requiredFeatures
return nil return nil
} }
func checkLoginPolicyFeatures(features *features_view_model.FeaturesView, requiredFeature string) error { func checkLoginPolicyFeatures(features *query.Features, requiredFeature string) error {
switch requiredFeature { switch requiredFeature {
case domain.FeatureLoginPolicyFactors: case domain.FeatureLoginPolicyFactors:
if !features.LoginPolicyFactors { if !features.LoginPolicyFactors {
@ -221,7 +219,7 @@ func checkLoginPolicyFeatures(features *features_view_model.FeaturesView, requir
return nil return nil
} }
func checkLabelPolicyFeatures(features *features_view_model.FeaturesView, requiredFeature string) error { func checkLabelPolicyFeatures(features *query.Features, requiredFeature string) error {
switch requiredFeature { switch requiredFeature {
case domain.FeatureLabelPolicyPrivateLabel: case domain.FeatureLabelPolicyPrivateLabel:
if !features.LabelPolicyPrivateLabel { 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 { func (repo *TokenVerifierRepo) checkDefaultFeatures(ctx context.Context, requiredFeatures ...string) error {
features, viewErr := repo.View.FeaturesByAggregateID(domain.IAMID) features, err := repo.Query.DefaultFeatures(ctx)
if viewErr != nil && !caos_errs.IsNotFound(viewErr) { if err != nil {
return viewErr return err
} }
if caos_errs.IsNotFound(viewErr) { return checkFeatures(features, requiredFeatures...)
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...)
} }
func (repo *TokenVerifierRepo) getIAMEvents(ctx context.Context, sequence uint64) ([]*models.Event, error) { func (repo *TokenVerifierRepo) getIAMEvents(ctx context.Context, sequence uint64) ([]*models.Event, error) {

View File

@ -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)
}

View File

@ -38,8 +38,6 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es
handler{view, bulkLimit, configs.cycleDuration("UserMemberships"), errorCount, es}), handler{view, bulkLimit, configs.cycleDuration("UserMemberships"), errorCount, es}),
newApplication( newApplication(
handler{view, bulkLimit, configs.cycleDuration("Application"), errorCount, es}), handler{view, bulkLimit, configs.cycleDuration("Application"), errorCount, es}),
newFeatures(
handler{view, bulkLimit, configs.cycleDuration("Features"), errorCount, es}),
} }
} }

View File

@ -72,6 +72,7 @@ func Start(conf Config, authZ authz.Config, systemDefaults sd.SystemDefaults, qu
Eventstore: es, Eventstore: es,
IAMID: systemDefaults.IamID, IAMID: systemDefaults.IamID,
View: view, View: view,
Query: queries,
}, },
}, nil }, nil
} }

View File

@ -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)
}

View File

@ -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
}

View File

@ -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...)
}

View File

@ -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
}

View File

@ -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 ""
}
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -60,8 +60,6 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es
handler{view, bulkLimit, configs.cycleDuration("MailTemplate"), errorCount, es}), handler{view, bulkLimit, configs.cycleDuration("MailTemplate"), errorCount, es}),
newMessageText( newMessageText(
handler{view, bulkLimit, configs.cycleDuration("MessageText"), errorCount, es}), handler{view, bulkLimit, configs.cycleDuration("MessageText"), errorCount, es}),
newFeatures(
handler{view, bulkLimit, configs.cycleDuration("Features"), errorCount, es}),
newCustomText( newCustomText(
handler{view, bulkLimit, configs.cycleDuration("CustomText"), errorCount, es}), handler{view, bulkLimit, configs.cycleDuration("CustomText"), errorCount, es}),
newMetadata( newMetadata(

View File

@ -31,7 +31,6 @@ type EsRepository struct {
eventstore.UserRepo eventstore.UserRepo
eventstore.UserGrantRepo eventstore.UserGrantRepo
eventstore.IAMRepository eventstore.IAMRepository
eventstore.FeaturesRepo
view *mgmt_view.View 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}, UserRepo: eventstore.UserRepo{es, conf.SearchLimit, view, systemDefaults, assetsAPI},
UserGrantRepo: eventstore.UserGrantRepo{conf.SearchLimit, view, assetsAPI}, UserGrantRepo: eventstore.UserGrantRepo{conf.SearchLimit, view, assetsAPI},
IAMRepository: eventstore.IAMRepository{IAMV2Query: queries}, IAMRepository: eventstore.IAMRepository{IAMV2Query: queries},
FeaturesRepo: eventstore.FeaturesRepo{es, view, conf.SearchLimit, systemDefaults},
view: view, view: view,
}, nil }, nil
} }

View File

@ -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)
}

View File

@ -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)
}

View File

@ -7,5 +7,4 @@ type Repository interface {
UserRepository UserRepository
UserGrantRepository UserGrantRepository
IamRepository IamRepository
FeaturesRepository
} }

View File

@ -7,14 +7,14 @@ import (
"time" "time"
sq "github.com/Masterminds/squirrel" sq "github.com/Masterminds/squirrel"
"github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/query/projection" "github.com/caos/zitadel/internal/query/projection"
) )
type Feature struct { type Features struct {
AggregateID string AggregateID string
CreationDate time.Time
ChangeDate time.Time ChangeDate time.Time
Sequence uint64 Sequence uint64
IsDefault bool IsDefault bool
@ -42,91 +42,113 @@ type Feature struct {
} }
var ( var (
feautureTable = table{ featureTable = table{
name: projection.FeatureTable, name: projection.FeatureTable,
} }
FeatureColumnAggregateID = Column{ FeatureColumnAggregateID = Column{
name: projection.FeatureAggregateIDCol, name: projection.FeatureAggregateIDCol,
} table: featureTable,
FeatureColumnCreationDate = Column{
name: projection.FeatureCreationDateCol,
} }
FeatureColumnChangeDate = Column{ FeatureColumnChangeDate = Column{
name: projection.FeatureChangeDateCol, name: projection.FeatureChangeDateCol,
table: featureTable,
} }
FeatureColumnSequence = Column{ FeatureColumnSequence = Column{
name: projection.FeatureSequenceCol, name: projection.FeatureSequenceCol,
table: featureTable,
} }
FeatureColumnIsDefault = Column{ FeatureColumnIsDefault = Column{
name: projection.FeatureIsDefaultCol, name: projection.FeatureIsDefaultCol,
table: featureTable,
} }
FeatureTierName = Column{ FeatureTierName = Column{
name: projection.FeatureTierNameCol, name: projection.FeatureTierNameCol,
table: featureTable,
} }
FeatureTierDescription = Column{ FeatureTierDescription = Column{
name: projection.FeatureTierDescriptionCol, name: projection.FeatureTierDescriptionCol,
table: featureTable,
} }
FeatureState = Column{ FeatureState = Column{
name: projection.FeatureStateCol, name: projection.FeatureStateCol,
table: featureTable,
} }
FeatureStateDescription = Column{ FeatureStateDescription = Column{
name: projection.FeatureStateDescriptionCol, name: projection.FeatureStateDescriptionCol,
table: featureTable,
} }
FeatureAuditLogRetention = Column{ FeatureAuditLogRetention = Column{
name: projection.FeatureAuditLogRetentionCol, name: projection.FeatureAuditLogRetentionCol,
table: featureTable,
} }
FeatureLoginPolicyFactors = Column{ FeatureLoginPolicyFactors = Column{
name: projection.FeatureLoginPolicyFactorsCol, name: projection.FeatureLoginPolicyFactorsCol,
table: featureTable,
} }
FeatureLoginPolicyIDP = Column{ FeatureLoginPolicyIDP = Column{
name: projection.FeatureLoginPolicyIDPCol, name: projection.FeatureLoginPolicyIDPCol,
table: featureTable,
} }
FeatureLoginPolicyPasswordless = Column{ FeatureLoginPolicyPasswordless = Column{
name: projection.FeatureLoginPolicyPasswordlessCol, name: projection.FeatureLoginPolicyPasswordlessCol,
table: featureTable,
} }
FeatureLoginPolicyRegistration = Column{ FeatureLoginPolicyRegistration = Column{
name: projection.FeatureLoginPolicyRegistrationCol, name: projection.FeatureLoginPolicyRegistrationCol,
table: featureTable,
} }
FeatureLoginPolicyUsernameLogin = Column{ FeatureLoginPolicyUsernameLogin = Column{
name: projection.FeatureLoginPolicyUsernameLoginCol, name: projection.FeatureLoginPolicyUsernameLoginCol,
table: featureTable,
} }
FeatureLoginPolicyPasswordReset = Column{ FeatureLoginPolicyPasswordReset = Column{
name: projection.FeatureLoginPolicyPasswordResetCol, name: projection.FeatureLoginPolicyPasswordResetCol,
table: featureTable,
} }
FeaturePasswordComplexityPolicy = Column{ FeaturePasswordComplexityPolicy = Column{
name: projection.FeaturePasswordComplexityPolicyCol, name: projection.FeaturePasswordComplexityPolicyCol,
table: featureTable,
} }
FeatureLabelPolicyPrivateLabel = Column{ FeatureLabelPolicyPrivateLabel = Column{
name: projection.FeatureLabelPolicyPrivateLabelCol, name: projection.FeatureLabelPolicyPrivateLabelCol,
table: featureTable,
} }
FeatureLabelPolicyWatermark = Column{ FeatureLabelPolicyWatermark = Column{
name: projection.FeatureLabelPolicyWatermarkCol, name: projection.FeatureLabelPolicyWatermarkCol,
table: featureTable,
} }
FeatureCustomDomain = Column{ FeatureCustomDomain = Column{
name: projection.FeatureCustomDomainCol, name: projection.FeatureCustomDomainCol,
table: featureTable,
} }
FeaturePrivacyPolicy = Column{ FeaturePrivacyPolicy = Column{
name: projection.FeaturePrivacyPolicyCol, name: projection.FeaturePrivacyPolicyCol,
table: featureTable,
} }
FeatureMetadataUser = Column{ FeatureMetadataUser = Column{
name: projection.FeatureMetadataUserCol, name: projection.FeatureMetadataUserCol,
table: featureTable,
} }
FeatureCustomTextMessage = Column{ FeatureCustomTextMessage = Column{
name: projection.FeatureCustomTextMessageCol, name: projection.FeatureCustomTextMessageCol,
table: featureTable,
} }
FeatureCustomTextLogin = Column{ FeatureCustomTextLogin = Column{
name: projection.FeatureCustomTextLoginCol, name: projection.FeatureCustomTextLoginCol,
table: featureTable,
} }
FeatureLockoutPolicy = Column{ FeatureLockoutPolicy = Column{
name: projection.FeatureLockoutPolicyCol, name: projection.FeatureLockoutPolicyCol,
table: featureTable,
} }
FeatureActions = Column{ FeatureActions = Column{
name: projection.FeatureActionsCol, name: projection.FeatureActionsCol,
table: featureTable,
} }
) )
func (q *Queries) FeatureByID(ctx context.Context, orgID string) (*Feature, error) { func (q *Queries) FeaturesByOrgID(ctx context.Context, orgID string) (*Features, error) {
query, scan := prepareFeatureQuery() query, scan := prepareFeaturesQuery()
stmt, args, err := query.Where( stmt, args, err := query.Where(
sq.Or{ sq.Or{
sq.Eq{ sq.Eq{
@ -146,11 +168,11 @@ func (q *Queries) FeatureByID(ctx context.Context, orgID string) (*Feature, erro
return scan(row) return scan(row)
} }
func (q *Queries) DefaultFeature(ctx context.Context) (*Feature, error) { func (q *Queries) DefaultFeatures(ctx context.Context) (*Features, error) {
query, scan := prepareFeatureQuery() query, scan := prepareFeaturesQuery()
stmt, args, err := query.Where(sq.Eq{ stmt, args, err := query.Where(sq.Eq{
FeatureColumnAggregateID.identifier(): domain.IAMID, FeatureColumnAggregateID.identifier(): domain.IAMID,
}).OrderBy(FeatureColumnIsDefault.identifier()).ToSql() }).ToSql()
if err != nil { if err != nil {
return nil, errors.ThrowInternal(err, "QUERY-1Ndlg", "Errors.Query.SQLStatement") 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) return scan(row)
} }
func prepareFeatureQuery() (sq.SelectBuilder, func(*sql.Row) (*Feature, error)) { func prepareFeaturesQuery() (sq.SelectBuilder, func(*sql.Row) (*Features, error)) {
return sq.Select( return sq.Select(
FeatureColumnAggregateID.identifier(), FeatureColumnAggregateID.identifier(),
FeatureColumnCreationDate.identifier(),
FeatureColumnChangeDate.identifier(), FeatureColumnChangeDate.identifier(),
FeatureColumnSequence.identifier(), FeatureColumnSequence.identifier(),
FeatureColumnIsDefault.identifier(), FeatureColumnIsDefault.identifier(),
@ -187,25 +208,26 @@ func prepareFeatureQuery() (sq.SelectBuilder, func(*sql.Row) (*Feature, error))
FeatureCustomTextLogin.identifier(), FeatureCustomTextLogin.identifier(),
FeatureLockoutPolicy.identifier(), FeatureLockoutPolicy.identifier(),
FeatureActions.identifier(), FeatureActions.identifier(),
).From(loginPolicyTable.identifier()).PlaceholderFormat(sq.Dollar), ).From(featureTable.identifier()).PlaceholderFormat(sq.Dollar),
func(row *sql.Row) (*Feature, error) { func(row *sql.Row) (*Features, error) {
p := new(Feature) p := new(Features)
tierName := sql.NullString{}
tierDescription := sql.NullString{}
stateDescription := sql.NullString{}
err := row.Scan( err := row.Scan(
&p.AggregateID, &p.AggregateID,
&p.CreationDate,
&p.ChangeDate, &p.ChangeDate,
&p.Sequence, &p.Sequence,
&p.IsDefault, &p.IsDefault,
&p.TierName, &tierName,
&p.TierDescription, &tierDescription,
&p.State, &p.State,
&p.StateDescription, &stateDescription,
&p.AuditLogRetention, &p.AuditLogRetention,
&p.LoginPolicyFactors, &p.LoginPolicyFactors,
&p.LoginPolicyIDP, &p.LoginPolicyIDP,
&p.LoginPolicyPasswordless, &p.LoginPolicyPasswordless,
&p.LoginPolicyRegistration, &p.LoginPolicyRegistration,
&p.LoginPolicyRegistration,
&p.LoginPolicyUsernameLogin, &p.LoginPolicyUsernameLogin,
&p.LoginPolicyPasswordReset, &p.LoginPolicyPasswordReset,
&p.PasswordComplexityPolicy, &p.PasswordComplexityPolicy,
@ -221,10 +243,66 @@ func prepareFeatureQuery() (sq.SelectBuilder, func(*sql.Row) (*Feature, error))
) )
if err != nil { if err != nil {
if errs.Is(err, sql.ErrNoRows) { 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") return nil, errors.ThrowInternal(err, "QUERY-3o9gd", "Errors.Internal")
} }
p.TierName = tierName.String
p.TierDescription = tierDescription.String
p.StateDescription = stateDescription.String
return p, nil 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
}

View File

@ -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)
})
}
}

View File

@ -4,12 +4,12 @@ import (
"context" "context"
"github.com/caos/logging" "github.com/caos/logging"
"github.com/caos/zitadel/internal/repository/features"
"github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore" "github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/handler" "github.com/caos/zitadel/internal/eventstore/handler"
"github.com/caos/zitadel/internal/eventstore/handler/crdb" "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/iam"
"github.com/caos/zitadel/internal/repository/org" "github.com/caos/zitadel/internal/repository/org"
) )
@ -59,7 +59,6 @@ func (p *FeatureProjection) reducers() []handler.AggregateReducer {
const ( const (
FeatureAggregateIDCol = "aggregate_id" FeatureAggregateIDCol = "aggregate_id"
FeatureCreationDateCol = "creation_date"
FeatureChangeDateCol = "change_date" FeatureChangeDateCol = "change_date"
FeatureSequenceCol = "sequence" FeatureSequenceCol = "sequence"
FeatureIsDefaultCol = "is_default" FeatureIsDefaultCol = "is_default"
@ -103,7 +102,6 @@ func (p *FeatureProjection) reduceFeatureSet(event eventstore.EventReader) (*han
cols := []handler.Column{ cols := []handler.Column{
handler.NewCol(FeatureAggregateIDCol, featureEvent.Aggregate().ID), handler.NewCol(FeatureAggregateIDCol, featureEvent.Aggregate().ID),
handler.NewCol(FeatureCreationDateCol, featureEvent.CreationDate()),
handler.NewCol(FeatureChangeDateCol, featureEvent.CreationDate()), handler.NewCol(FeatureChangeDateCol, featureEvent.CreationDate()),
handler.NewCol(FeatureSequenceCol, featureEvent.Sequence()), handler.NewCol(FeatureSequenceCol, featureEvent.Sequence()),
handler.NewCol(FeatureIsDefaultCol, isDefault), handler.NewCol(FeatureIsDefaultCol, isDefault),

View File

@ -63,11 +63,10 @@ func TestFeatureProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ 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{}{ expectedArgs: []interface{}{
"agg-id", "agg-id",
anyArg{}, anyArg{},
anyArg{},
uint64(15), uint64(15),
false, false,
"TierName", "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", name: "org.reduceFeatureRemoved",
reduce: (&FeatureProjection{}).reduceFeatureRemoved, reduce: (&FeatureProjection{}).reduceFeatureRemoved,
@ -164,11 +193,10 @@ func TestFeatureProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ 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{}{ expectedArgs: []interface{}{
"agg-id", "agg-id",
anyArg{}, anyArg{},
anyArg{},
uint64(15), uint64(15),
true, true,
"TierName", "TierName",
@ -203,7 +231,7 @@ func TestFeatureProjection_reduces(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
event := baseEvent(t) event := baseEvent(t)
got, err := tt.reduce(event) 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) t.Errorf("no wrong event mapping: %v, got: %v", err, got)
} }

View File

@ -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;