mirror of
https://github.com/zitadel/zitadel.git
synced 2026-11-03 12:47:09 -06:00
<!-- CURSOR_AGENT_PR_BODY_BEGIN --> # Which Problems Are Solved Deleting an application loads the SAML config only to learn the entity id, then deletes the `entity_ids` unique constraint by that field. The app name constraint is deleted by `name:projectID`. Neither row is tagged with the app id, so the delete cannot use the owner index. # How the Problems Are Solved `appname` and `entity_ids` rows are tagged `app:<appID>`, in addition to the existing org and project tags. - Live writes stamp the tag on application add/rename and on SAML entity-id add/change. - Setup 79 appends `app:<appID>` on the next version run, including rows that already have `org` and `project` owners. - When the owners backfill is finalized, `RemoveApplication` emits `RemoveByOwner(app, appID)` and does not read the SAML config. - Before that backfill, removal still deletes by the app name and, for SAML apps, by the entity id. Renames stay field deletes. Project names and roles stay tagged with org and project only. # Additional Changes - `UniqueConstraintOwnerApp` is added next to the other owner kinds. # Additional Context - Base branch: `v4.x` - Follow-up for the unique-constraint owners work (setup 78/79) Verified locally: - `go test ./cmd/setup -run TestBackfillUniqueConstraintOwnersStmts` - `go test ./internal/command -run 'TestCommandSide_RemoveApplication|TestCommandSide_RemoveDynamicOIDCClient'` - `go test ./internal/eventstore ./internal/v2/eventstore` <!-- CURSOR_AGENT_PR_BODY_END --> <div><a href="https://cursor.com/agents/bc-31bfb5c4-5888-43f0-85cf-af1af097ca30?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a> <a href="https://cursor.com/background-agent?bcId=bc-31bfb5c4-5888-43f0-85cf-af1af097ca30&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a> </div> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Silvan <adlerhurst@users.noreply.github.com>
283 lines
7.0 KiB
Go
283 lines
7.0 KiB
Go
package project
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/zitadel/zitadel/internal/eventstore"
|
|
"github.com/zitadel/zitadel/internal/zerrors"
|
|
)
|
|
|
|
const (
|
|
UniqueAppNameType = "appname"
|
|
applicationEventTypePrefix = projectEventTypePrefix + "application."
|
|
ApplicationAddedType = applicationEventTypePrefix + "added"
|
|
ApplicationChangedType = applicationEventTypePrefix + "changed"
|
|
ApplicationDeactivatedType = applicationEventTypePrefix + "deactivated"
|
|
ApplicationReactivatedType = applicationEventTypePrefix + "reactivated"
|
|
ApplicationRemovedType = applicationEventTypePrefix + "removed"
|
|
)
|
|
|
|
func NewAddApplicationUniqueConstraint(name, projectID string) *eventstore.UniqueConstraint {
|
|
return eventstore.NewAddEventUniqueConstraint(
|
|
UniqueAppNameType,
|
|
fmt.Sprintf("%s:%s", name, projectID),
|
|
"Errors.Project.App.AlreadyExists")
|
|
}
|
|
|
|
func NewRemoveApplicationUniqueConstraint(name, projectID string) *eventstore.UniqueConstraint {
|
|
return eventstore.NewRemoveUniqueConstraint(
|
|
UniqueAppNameType,
|
|
fmt.Sprintf("%s:%s", name, projectID))
|
|
}
|
|
|
|
type ApplicationAddedEvent struct {
|
|
eventstore.BaseEvent `json:"-"`
|
|
|
|
AppID string `json:"appId,omitempty"`
|
|
Name string `json:"name,omitempty"`
|
|
}
|
|
|
|
func (e *ApplicationAddedEvent) Payload() interface{} {
|
|
return e
|
|
}
|
|
|
|
func (e *ApplicationAddedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
|
|
return []*eventstore.UniqueConstraint{NewAddApplicationUniqueConstraint(e.Name, e.Aggregate().ID).WithOwners(appOwnerTags(e.Aggregate(), e.AppID)...)}
|
|
}
|
|
|
|
func NewApplicationAddedEvent(
|
|
ctx context.Context,
|
|
aggregate *eventstore.Aggregate,
|
|
appID,
|
|
name string,
|
|
) *ApplicationAddedEvent {
|
|
return &ApplicationAddedEvent{
|
|
BaseEvent: *eventstore.NewBaseEventForPush(
|
|
ctx,
|
|
aggregate,
|
|
ApplicationAddedType,
|
|
),
|
|
AppID: appID,
|
|
Name: name,
|
|
}
|
|
}
|
|
|
|
func ApplicationAddedEventMapper(event eventstore.Event) (eventstore.Event, error) {
|
|
e := &ApplicationAddedEvent{
|
|
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
|
}
|
|
|
|
err := event.Unmarshal(e)
|
|
if err != nil {
|
|
return nil, zerrors.ThrowInternal(err, "APPLICATION-Nffg2", "unable to unmarshal application")
|
|
}
|
|
|
|
return e, nil
|
|
}
|
|
|
|
type ApplicationChangedEvent struct {
|
|
eventstore.BaseEvent `json:"-"`
|
|
|
|
AppID string `json:"appId,omitempty"`
|
|
Name string `json:"name,omitempty"`
|
|
oldName string
|
|
}
|
|
|
|
func (e *ApplicationChangedEvent) Payload() interface{} {
|
|
return e
|
|
}
|
|
|
|
func (e *ApplicationChangedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
|
|
return []*eventstore.UniqueConstraint{
|
|
NewRemoveApplicationUniqueConstraint(e.oldName, e.Aggregate().ID),
|
|
NewAddApplicationUniqueConstraint(e.Name, e.Aggregate().ID).WithOwners(appOwnerTags(e.Aggregate(), e.AppID)...),
|
|
}
|
|
}
|
|
|
|
func NewApplicationChangedEvent(
|
|
ctx context.Context,
|
|
aggregate *eventstore.Aggregate,
|
|
appID,
|
|
oldName,
|
|
newName string,
|
|
) *ApplicationChangedEvent {
|
|
return &ApplicationChangedEvent{
|
|
BaseEvent: *eventstore.NewBaseEventForPush(
|
|
ctx,
|
|
aggregate,
|
|
ApplicationChangedType,
|
|
),
|
|
AppID: appID,
|
|
Name: newName,
|
|
oldName: oldName,
|
|
}
|
|
}
|
|
|
|
func ApplicationChangedEventMapper(event eventstore.Event) (eventstore.Event, error) {
|
|
e := &ApplicationChangedEvent{
|
|
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
|
}
|
|
|
|
err := event.Unmarshal(e)
|
|
if err != nil {
|
|
return nil, zerrors.ThrowInternal(err, "APPLICATION-9l0cs", "unable to unmarshal application")
|
|
}
|
|
|
|
return e, nil
|
|
}
|
|
|
|
type ApplicationDeactivatedEvent struct {
|
|
eventstore.BaseEvent `json:"-"`
|
|
|
|
AppID string `json:"appId,omitempty"`
|
|
}
|
|
|
|
func (e *ApplicationDeactivatedEvent) Payload() interface{} {
|
|
return e
|
|
}
|
|
|
|
func (e *ApplicationDeactivatedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
|
|
return nil
|
|
}
|
|
|
|
func NewApplicationDeactivatedEvent(
|
|
ctx context.Context,
|
|
aggregate *eventstore.Aggregate,
|
|
appID string,
|
|
) *ApplicationDeactivatedEvent {
|
|
return &ApplicationDeactivatedEvent{
|
|
BaseEvent: *eventstore.NewBaseEventForPush(
|
|
ctx,
|
|
aggregate,
|
|
ApplicationDeactivatedType,
|
|
),
|
|
AppID: appID,
|
|
}
|
|
}
|
|
|
|
func ApplicationDeactivatedEventMapper(event eventstore.Event) (eventstore.Event, error) {
|
|
e := &ApplicationDeactivatedEvent{
|
|
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
|
}
|
|
|
|
err := event.Unmarshal(e)
|
|
if err != nil {
|
|
return nil, zerrors.ThrowInternal(err, "APPLICATION-0p9fB", "unable to unmarshal application")
|
|
}
|
|
|
|
return e, nil
|
|
}
|
|
|
|
type ApplicationReactivatedEvent struct {
|
|
eventstore.BaseEvent `json:"-"`
|
|
|
|
AppID string `json:"appId,omitempty"`
|
|
}
|
|
|
|
func (e *ApplicationReactivatedEvent) Payload() interface{} {
|
|
return e
|
|
}
|
|
|
|
func (e *ApplicationReactivatedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
|
|
return nil
|
|
}
|
|
|
|
func NewApplicationReactivatedEvent(
|
|
ctx context.Context,
|
|
aggregate *eventstore.Aggregate,
|
|
appID string,
|
|
) *ApplicationReactivatedEvent {
|
|
return &ApplicationReactivatedEvent{
|
|
BaseEvent: *eventstore.NewBaseEventForPush(
|
|
ctx,
|
|
aggregate,
|
|
ApplicationReactivatedType,
|
|
),
|
|
AppID: appID,
|
|
}
|
|
}
|
|
|
|
func ApplicationReactivatedEventMapper(event eventstore.Event) (eventstore.Event, error) {
|
|
e := &ApplicationReactivatedEvent{
|
|
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
|
}
|
|
|
|
err := event.Unmarshal(e)
|
|
if err != nil {
|
|
return nil, zerrors.ThrowInternal(err, "APPLICATION-1m9e3", "unable to unmarshal application")
|
|
}
|
|
|
|
return e, nil
|
|
}
|
|
|
|
type ApplicationRemovedEvent struct {
|
|
eventstore.BaseEvent `json:"-"`
|
|
|
|
AppID string `json:"appId,omitempty"`
|
|
name string
|
|
entityID string
|
|
// removeByOwner deletes appname and entity_ids by app id after setup 79 has stamped that tag.
|
|
removeByOwner bool
|
|
}
|
|
|
|
func (e *ApplicationRemovedEvent) Payload() interface{} {
|
|
return e
|
|
}
|
|
|
|
func (e *ApplicationRemovedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
|
|
if e.removeByOwner {
|
|
return []*eventstore.UniqueConstraint{
|
|
eventstore.NewRemoveUniqueConstraintsByOwner(eventstore.UniqueConstraintOwnerApp, e.AppID),
|
|
}
|
|
}
|
|
remove := []*eventstore.UniqueConstraint{NewRemoveApplicationUniqueConstraint(e.name, e.Aggregate().ID)}
|
|
if e.entityID != "" {
|
|
remove = append(remove, NewRemoveSAMLConfigEntityIDUniqueConstraint(e.entityID))
|
|
}
|
|
return remove
|
|
}
|
|
|
|
func NewApplicationRemovedEvent(
|
|
ctx context.Context,
|
|
aggregate *eventstore.Aggregate,
|
|
appID,
|
|
name string,
|
|
entityID string,
|
|
) *ApplicationRemovedEvent {
|
|
return &ApplicationRemovedEvent{
|
|
BaseEvent: *eventstore.NewBaseEventForPush(
|
|
ctx,
|
|
aggregate,
|
|
ApplicationRemovedType,
|
|
),
|
|
AppID: appID,
|
|
name: name,
|
|
entityID: entityID,
|
|
}
|
|
}
|
|
|
|
// NewApplicationRemovedByOwnerEvent removes every unique constraint tagged with the app id.
|
|
func NewApplicationRemovedByOwnerEvent(
|
|
ctx context.Context,
|
|
aggregate *eventstore.Aggregate,
|
|
appID string,
|
|
) *ApplicationRemovedEvent {
|
|
removed := NewApplicationRemovedEvent(ctx, aggregate, appID, "", "")
|
|
removed.removeByOwner = true
|
|
return removed
|
|
}
|
|
|
|
func ApplicationRemovedEventMapper(event eventstore.Event) (eventstore.Event, error) {
|
|
e := &ApplicationRemovedEvent{
|
|
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
|
}
|
|
|
|
err := event.Unmarshal(e)
|
|
if err != nil {
|
|
return nil, zerrors.ThrowInternal(err, "APPLICATION-1m9e3", "unable to unmarshal application")
|
|
}
|
|
|
|
return e, nil
|
|
}
|