diff --git a/cmd/setup/60.go b/cmd/setup/60.go new file mode 100644 index 0000000000..74df75453a --- /dev/null +++ b/cmd/setup/60.go @@ -0,0 +1,96 @@ +package setup + +import ( + "context" + + "github.com/zitadel/zitadel/internal/database" + "github.com/zitadel/zitadel/internal/eventstore" + eventstoreV3 "github.com/zitadel/zitadel/internal/eventstore/v3" + "github.com/zitadel/zitadel/internal/repository/org" +) + +type AddIDUniqueConstraintsForOrgs struct { + dbClient *database.DB + eventstore *eventstore.Eventstore +} + +type orgsReadModel struct { + eventstore.ReadModel +} + +func (orm *orgsReadModel) AppendEvents(events ...eventstore.Event) { + orm.ReadModel.AppendEvents(events...) +} + +func (orm *orgsReadModel) Reduce() error { + return nil +} + +type OrgAddEventUpdateIDUniqueConstraint struct { + *org.OrgAddedEvent +} + +func (e *OrgAddEventUpdateIDUniqueConstraint) UniqueConstraints() []*eventstore.UniqueConstraint { + return []*eventstore.UniqueConstraint{org.NewAddOrgIDUniqueConstraint(e.Aggregate().ID)} +} + +type OrgRemoveEventUpdateIDUniqueConstraint struct { + *org.OrgRemovedEvent +} + +func (e *OrgRemoveEventUpdateIDUniqueConstraint) UniqueConstraints() []*eventstore.UniqueConstraint { + return []*eventstore.UniqueConstraint{org.NewRemoveOrgIDUniqueConstraint(e.Aggregate().ID)} +} + +func (mig *AddIDUniqueConstraintsForOrgs) Execute(ctx context.Context, _ eventstore.Event) error { + orm := orgsReadModel{} + sqb := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent). + AddQuery(). + EventTypes( + org.OrgAddedEventType, + org.OrgRemovedEventType, + ). + Builder() + + err := mig.eventstore.FilterToReducer(context.Background(), sqb, &orm) + if err != nil { + } + + tx, err := mig.dbClient.BeginTx(ctx, nil) + if err != nil { + return err + } + + defer func() { + if err == nil { + tx.Commit() + } + }() + + for _, event := range orm.Events { + switch event := event.(type) { + case *org.OrgAddedEvent: + orgAddUpdateUniqueIDConstraint := OrgAddEventUpdateIDUniqueConstraint{event} + err = eventstoreV3.HandleUniqueConstraints(ctx, tx, []eventstore.Command{&orgAddUpdateUniqueIDConstraint}) + if err != nil { + return err + } + case *org.OrgRemovedEvent: + orgRemoveUpdateUniqueIDConstraint := OrgRemoveEventUpdateIDUniqueConstraint{event} + err = eventstoreV3.HandleUniqueConstraints(ctx, tx, []eventstore.Command{&orgRemoveUpdateUniqueIDConstraint}) + if err != nil { + return err + } + } + } + + return nil +} + +func (*AddIDUniqueConstraintsForOrgs) String() string { + return "60_add_id_unique_constraints_for_orgs" +} + +func (f *AddIDUniqueConstraintsForOrgs) Check(lastRun map[string]interface{}) bool { + return true +} diff --git a/cmd/setup/setup.go b/cmd/setup/setup.go index dd23c320c7..ed766765fb 100644 --- a/cmd/setup/setup.go +++ b/cmd/setup/setup.go @@ -217,6 +217,7 @@ func Setup(ctx context.Context, config *Config, steps *Steps, masterKey string) steps.s56IDPTemplate6SAMLFederatedLogout = &IDPTemplate6SAMLFederatedLogout{dbClient: dbClient} steps.s57CreateResourceCounts = &CreateResourceCounts{dbClient: dbClient} steps.s58ReplaceLoginNames3View = &ReplaceLoginNames3View{dbClient: dbClient} + steps.s60AddUIDniqueConstraintsForOrgs = &AddIDUniqueConstraintsForOrgs{eventstore: eventstoreClient, dbClient: dbClient} err = projection.Create(ctx, dbClient, eventstoreClient, config.Projections, nil, nil, nil) logging.OnError(err).Fatal("unable to start projections") @@ -264,6 +265,7 @@ func Setup(ctx context.Context, config *Config, steps *Steps, masterKey string) steps.s56IDPTemplate6SAMLFederatedLogout, steps.s57CreateResourceCounts, steps.s58ReplaceLoginNames3View, + steps.s60AddUIDniqueConstraintsForOrgs, } { setupErr = executeMigration(ctx, eventstoreClient, step, "migration failed") if setupErr != nil { diff --git a/internal/eventstore/v3/push.go b/internal/eventstore/v3/push.go index 6497b96ed8..7018518a6b 100644 --- a/internal/eventstore/v3/push.go +++ b/internal/eventstore/v3/push.go @@ -67,7 +67,7 @@ func (es *Eventstore) writeCommands(ctx context.Context, client database.Context return nil, err } - if err = handleUniqueConstraints(ctx, tx, commands); err != nil { + if err = HandleUniqueConstraints(ctx, tx, commands); err != nil { return nil, err } diff --git a/internal/eventstore/v3/push_without_func.go b/internal/eventstore/v3/push_without_func.go index b94a9e8f54..f45ee2fdd2 100644 --- a/internal/eventstore/v3/push_without_func.go +++ b/internal/eventstore/v3/push_without_func.go @@ -57,7 +57,7 @@ func (es *Eventstore) pushWithoutFunc(ctx context.Context, client database.Conte return nil, err } - if err = handleUniqueConstraints(ctx, tx, commands); err != nil { + if err = HandleUniqueConstraints(ctx, tx, commands); err != nil { return nil, err } diff --git a/internal/eventstore/v3/unique_constraints.go b/internal/eventstore/v3/unique_constraints.go index a491ae4f5c..4ac359fd55 100644 --- a/internal/eventstore/v3/unique_constraints.go +++ b/internal/eventstore/v3/unique_constraints.go @@ -25,7 +25,7 @@ var ( addConstraintStmt string ) -func handleUniqueConstraints(ctx context.Context, tx database.Tx, commands []eventstore.Command) (err error) { +func HandleUniqueConstraints(ctx context.Context, tx database.Tx, commands []eventstore.Command) (err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() diff --git a/internal/repository/org/org.go b/internal/repository/org/org.go index 5c9d3005dc..64ce8ac4ea 100644 --- a/internal/repository/org/org.go +++ b/internal/repository/org/org.go @@ -12,6 +12,7 @@ import ( const ( uniqueOrgname = "org_name" + uniqueOrgID = "org_id" OrgAddedEventType = orgEventTypePrefix + "added" OrgChangedEventType = orgEventTypePrefix + "changed" OrgDeactivatedEventType = orgEventTypePrefix + "deactivated" @@ -25,14 +26,14 @@ const ( func NewAddOrgIDUniqueConstraint(orgID string) *eventstore.UniqueConstraint { return eventstore.NewAddEventUniqueConstraint( - uniqueOrgname, + uniqueOrgID, orgID, "Errors.Org.AlreadyExists") } func NewRemoveOrgIDUniqueConstraint(orgID string) *eventstore.UniqueConstraint { return eventstore.NewRemoveUniqueConstraint( - uniqueOrgname, + uniqueOrgID, orgID) }