diff --git a/internal/query/projection/custom_text.go b/internal/query/projection/custom_text.go new file mode 100644 index 0000000000..69337901d1 --- /dev/null +++ b/internal/query/projection/custom_text.go @@ -0,0 +1,149 @@ +package projection + +import ( + "context" + + "github.com/caos/logging" + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/eventstore/handler" + "github.com/caos/zitadel/internal/eventstore/handler/crdb" + "github.com/caos/zitadel/internal/repository/iam" + "github.com/caos/zitadel/internal/repository/org" + "github.com/caos/zitadel/internal/repository/policy" +) + +type CustomTextProjection struct { + crdb.StatementHandler +} + +const ( + CustomTextTable = "zitadel.projections.custom_texts" + + CustomTextAggregateIDCol = "aggregate_id" + CustomTextCreationDateCol = "creation_date" + CustomTextChangeDateCol = "change_date" + CustomTextSequenceCol = "sequence" + CustomTextIsDefaultCol = "is_default" + CustomTextTemplateCol = "template" + CustomTextLanguageCol = "language" + CustomTextKeyCol = "key" + CustomTextTextCol = "text" +) + +func NewCustomTextProjection(ctx context.Context, config crdb.StatementHandlerConfig) *CustomTextProjection { + p := &CustomTextProjection{} + config.ProjectionName = CustomTextTable + config.Reducers = p.reducers() + p.StatementHandler = crdb.NewStatementHandler(ctx, config) + return p +} + +func (p *CustomTextProjection) reducers() []handler.AggregateReducer { + return []handler.AggregateReducer{ + { + Aggregate: org.AggregateType, + EventRedusers: []handler.EventReducer{ + { + Event: org.CustomTextSetEventType, + Reduce: p.reduceSet, + }, + { + Event: org.CustomTextRemovedEventType, + Reduce: p.reduceRemoved, + }, + { + Event: org.CustomTextTemplateRemovedEventType, + Reduce: p.reduceTemplateRemoved, + }, + }, + }, + { + Aggregate: iam.AggregateType, + EventRedusers: []handler.EventReducer{ + { + Event: iam.CustomTextSetEventType, + Reduce: p.reduceSet, + }, + { + Event: iam.CustomTextRemovedEventType, + Reduce: p.reduceRemoved, + }, + { + Event: iam.CustomTextTemplateRemovedEventType, + Reduce: p.reduceTemplateRemoved, + }, + }, + }, + } +} + +func (p *CustomTextProjection) reduceSet(event eventstore.EventReader) (*handler.Statement, error) { + var customTextEvent policy.CustomTextSetEvent + var isDefault bool + switch e := event.(type) { + case *org.CustomTextSetEvent: + customTextEvent = e.CustomTextSetEvent + isDefault = false + case *iam.CustomTextSetEvent: + customTextEvent = e.CustomTextSetEvent + isDefault = true + default: + logging.LogWithFields("PROJE-g0Jfs", "seq", event.Sequence(), "expectedTypes", []eventstore.EventType{org.CustomTextSetEventType, iam.CustomTextSetEventType}).Error("wrong event type") + return nil, errors.ThrowInvalidArgument(nil, "PROJE-KKfw4", "reduce.wrong.event.type") + } + return crdb.NewCreateStatement( + &customTextEvent, + []handler.Column{ + handler.NewCol(CustomTextAggregateIDCol, customTextEvent.Aggregate().ID), + handler.NewCol(CustomTextCreationDateCol, customTextEvent.CreationDate()), + handler.NewCol(CustomTextChangeDateCol, customTextEvent.CreationDate()), + handler.NewCol(CustomTextSequenceCol, customTextEvent.Sequence()), + handler.NewCol(CustomTextIsDefaultCol, isDefault), + handler.NewCol(CustomTextTemplateCol, customTextEvent.Template), + handler.NewCol(CustomTextLanguageCol, customTextEvent.Language.String()), + handler.NewCol(CustomTextKeyCol, customTextEvent.Key), + handler.NewCol(CustomTextTextCol, customTextEvent.Text), + }), nil +} + +func (p *CustomTextProjection) reduceRemoved(event eventstore.EventReader) (*handler.Statement, error) { + var customTextEvent policy.CustomTextRemovedEvent + switch e := event.(type) { + case *org.CustomTextRemovedEvent: + customTextEvent = e.CustomTextRemovedEvent + case *iam.CustomTextRemovedEvent: + customTextEvent = e.CustomTextRemovedEvent + default: + logging.LogWithFields("PROJE-2Nigw", "seq", event.Sequence(), "expectedTypes", []eventstore.EventType{org.CustomTextRemovedEventType, iam.CustomTextRemovedEventType}).Error("wrong event type") + return nil, errors.ThrowInvalidArgument(nil, "PROJE-n9wJg", "reduce.wrong.event.type") + } + return crdb.NewDeleteStatement( + &customTextEvent, + []handler.Condition{ + handler.NewCond(CustomTextAggregateIDCol, customTextEvent.Aggregate().ID), + handler.NewCond(CustomTextTemplateCol, customTextEvent.Template), + handler.NewCond(CustomTextKeyCol, customTextEvent.Key), + handler.NewCond(CustomTextLanguageCol, customTextEvent.Language), + }), nil +} + +func (p *CustomTextProjection) reduceTemplateRemoved(event eventstore.EventReader) (*handler.Statement, error) { + var customTextEvent policy.CustomTextTemplateRemovedEvent + switch e := event.(type) { + case *org.CustomTextTemplateRemovedEvent: + customTextEvent = e.CustomTextTemplateRemovedEvent + case *iam.CustomTextTemplateRemovedEvent: + customTextEvent = e.CustomTextTemplateRemovedEvent + default: + logging.LogWithFields("PROJE-J9wfg", "seq", event.Sequence(), "expectedTypes", []eventstore.EventType{org.CustomTextTemplateRemovedEventType, iam.CustomTextTemplateRemovedEventType}).Error("wrong event type") + return nil, errors.ThrowInvalidArgument(nil, "PROJE-29iPf", "reduce.wrong.event.type") + } + return crdb.NewDeleteStatement( + &customTextEvent, + []handler.Condition{ + handler.NewCond(CustomTextAggregateIDCol, customTextEvent.Aggregate().ID), + handler.NewCond(CustomTextTemplateCol, customTextEvent.Template), + handler.NewCond(CustomTextLanguageCol, customTextEvent.Language), + }), nil +} diff --git a/internal/query/projection/custom_text_test.go b/internal/query/projection/custom_text_test.go new file mode 100644 index 0000000000..733728f073 --- /dev/null +++ b/internal/query/projection/custom_text_test.go @@ -0,0 +1,253 @@ +package projection + +import ( + "testing" + + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/eventstore/handler" + "github.com/caos/zitadel/internal/eventstore/repository" + "github.com/caos/zitadel/internal/repository/iam" + "github.com/caos/zitadel/internal/repository/org" + "golang.org/x/text/language" +) + +func TestCustomTextProjection_reduces(t *testing.T) { + type args struct { + event func(t *testing.T) eventstore.EventReader + } + tests := []struct { + name string + args args + reduce func(event eventstore.EventReader) (*handler.Statement, error) + want wantReduce + }{ + { + name: "org.reduceSet", + args: args{ + event: getEvent(testEvent( + repository.EventType(org.CustomTextSetEventType), + org.AggregateType, + []byte(`{ + "key": "Text", + "language": "en", + "template": "InitCode", + "text": "Test" + }`), + ), org.CustomTextSetEventMapper), + }, + reduce: (&CustomTextProjection{}).reduceSet, + want: wantReduce{ + aggregateType: eventstore.AggregateType("org"), + sequence: 15, + previousSequence: 10, + projection: CustomTextTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "INSERT INTO zitadel.projections.custom_texts (aggregate_id, creation_date, change_date, sequence, is_default, template, language, key, text) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", + expectedArgs: []interface{}{ + "agg-id", + anyArg{}, + anyArg{}, + uint64(15), + false, + "InitCode", + "en", + "Text", + "Test", + }, + }, + }, + }, + }, + }, + { + name: "org.reduceRemoved", + reduce: (&CustomTextProjection{}).reduceRemoved, + args: args{ + event: getEvent(testEvent( + repository.EventType(org.CustomTextRemovedEventType), + org.AggregateType, + []byte(`{ + "key": "Text", + "language": "en", + "template": "InitCode" + }`), + ), org.CustomTextRemovedEventMapper), + }, + want: wantReduce{ + aggregateType: eventstore.AggregateType("org"), + sequence: 15, + previousSequence: 10, + projection: CustomTextTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "DELETE FROM zitadel.projections.custom_texts WHERE (aggregate_id = $1) AND (template = $2) AND (key = $3) AND (language = $4)", + expectedArgs: []interface{}{ + "agg-id", + "InitCode", + "Text", + language.English, + }, + }, + }, + }, + }, + }, + { + name: "org.reduceTemplateRemoved", + reduce: (&CustomTextProjection{}).reduceTemplateRemoved, + args: args{ + event: getEvent(testEvent( + repository.EventType(org.CustomTextTemplateRemovedEventType), + org.AggregateType, + []byte(`{ + "key": "Text", + "language": "en", + "template": "InitCode" + }`), + ), org.CustomTextTemplateRemovedEventMapper), + }, + want: wantReduce{ + aggregateType: eventstore.AggregateType("org"), + sequence: 15, + previousSequence: 10, + projection: CustomTextTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "DELETE FROM zitadel.projections.custom_texts WHERE (aggregate_id = $1) AND (template = $2) AND (language = $3)", + expectedArgs: []interface{}{ + "agg-id", + "InitCode", + language.English, + }, + }, + }, + }, + }, + }, + { + name: "iam.reduceAdded", + reduce: (&CustomTextProjection{}).reduceSet, + args: args{ + event: getEvent(testEvent( + repository.EventType(iam.CustomTextSetEventType), + iam.AggregateType, + []byte(`{ + "key": "Text", + "language": "en", + "template": "InitCode", + "text": "Test" + }`), + ), iam.CustomTextSetEventMapper), + }, + want: wantReduce{ + aggregateType: eventstore.AggregateType("iam"), + sequence: 15, + previousSequence: 10, + projection: CustomTextTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "INSERT INTO zitadel.projections.custom_texts (aggregate_id, creation_date, change_date, sequence, is_default, template, language, key, text) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", + expectedArgs: []interface{}{ + "agg-id", + anyArg{}, + anyArg{}, + uint64(15), + true, + "InitCode", + "en", + "Text", + "Test", + }, + }, + }, + }, + }, + }, + { + name: "iam.reduceRemoved", + reduce: (&CustomTextProjection{}).reduceRemoved, + args: args{ + event: getEvent(testEvent( + repository.EventType(iam.CustomTextTemplateRemovedEventType), + iam.AggregateType, + []byte(`{ + "key": "Text", + "language": "en", + "template": "InitCode" + }`), + ), iam.CustomTextRemovedEventMapper), + }, + want: wantReduce{ + aggregateType: eventstore.AggregateType("iam"), + sequence: 15, + previousSequence: 10, + projection: CustomTextTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "DELETE FROM zitadel.projections.custom_texts WHERE (aggregate_id = $1) AND (template = $2) AND (key = $3) AND (language = $4)", + expectedArgs: []interface{}{ + "agg-id", + "InitCode", + "Text", + language.English, + }, + }, + }, + }, + }, + }, + { + name: "iam.reduceTemplateRemoved", + reduce: (&CustomTextProjection{}).reduceTemplateRemoved, + args: args{ + event: getEvent(testEvent( + repository.EventType(iam.CustomTextTemplateRemovedEventType), + iam.AggregateType, + []byte(`{ + "key": "Text", + "language": "en", + "template": "InitCode" + }`), + ), iam.CustomTextTemplateRemovedEventMapper), + }, + want: wantReduce{ + aggregateType: eventstore.AggregateType("iam"), + sequence: 15, + previousSequence: 10, + projection: CustomTextTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "DELETE FROM zitadel.projections.custom_texts WHERE (aggregate_id = $1) AND (template = $2) AND (language = $3)", + expectedArgs: []interface{}{ + "agg-id", + "InitCode", + language.English, + }, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + event := baseEvent(t) + got, err := tt.reduce(event) + if _, ok := err.(errors.InvalidArgument); !ok { + t.Errorf("no wrong event mapping: %v, got: %v", err, got) + } + + event = tt.args.event(t) + got, err = tt.reduce(event) + assertReduce(t, got, err, tt.want) + }) + } +} diff --git a/internal/query/projection/mail_template.go b/internal/query/projection/mail_template.go new file mode 100644 index 0000000000..c3be64e6a9 --- /dev/null +++ b/internal/query/projection/mail_template.go @@ -0,0 +1,140 @@ +package projection + +import ( + "context" + + "github.com/caos/logging" + "github.com/caos/zitadel/internal/domain" + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/eventstore/handler" + "github.com/caos/zitadel/internal/eventstore/handler/crdb" + "github.com/caos/zitadel/internal/repository/iam" + "github.com/caos/zitadel/internal/repository/org" + "github.com/caos/zitadel/internal/repository/policy" +) + +type MailTemplateProjection struct { + crdb.StatementHandler +} + +const ( + MailTemplateTable = "zitadel.projections.mail_templates" + + MailTemplateAggregateIDCol = "aggregate_id" + MailTemplateCreationDateCol = "creation_date" + MailTemplateChangeDateCol = "change_date" + MailTemplateSequenceCol = "sequence" + MailTemplateStateCol = "state" + MailTemplateTemplateCol = "template" + MailTemplateIsDefaultCol = "is_default" +) + +func NewMailTemplateProjection(ctx context.Context, config crdb.StatementHandlerConfig) *MailTemplateProjection { + p := &MailTemplateProjection{} + config.ProjectionName = MailTemplateTable + config.Reducers = p.reducers() + p.StatementHandler = crdb.NewStatementHandler(ctx, config) + return p +} + +func (p *MailTemplateProjection) reducers() []handler.AggregateReducer { + return []handler.AggregateReducer{ + { + Aggregate: org.AggregateType, + EventRedusers: []handler.EventReducer{ + { + Event: org.MailTemplateAddedEventType, + Reduce: p.reduceAdded, + }, + { + Event: org.MailTemplateChangedEventType, + Reduce: p.reduceChanged, + }, + { + Event: org.MailTemplateRemovedEventType, + Reduce: p.reduceRemoved, + }, + }, + }, + { + Aggregate: iam.AggregateType, + EventRedusers: []handler.EventReducer{ + { + Event: iam.MailTemplateAddedEventType, + Reduce: p.reduceAdded, + }, + { + Event: iam.MailTemplateChangedEventType, + Reduce: p.reduceChanged, + }, + }, + }, + } +} + +func (p *MailTemplateProjection) reduceAdded(event eventstore.EventReader) (*handler.Statement, error) { + var templateEvent policy.MailTemplateAddedEvent + var isDefault bool + switch e := event.(type) { + case *org.MailTemplateAddedEvent: + templateEvent = e.MailTemplateAddedEvent + isDefault = false + case *iam.MailTemplateAddedEvent: + templateEvent = e.MailTemplateAddedEvent + isDefault = true + default: + logging.LogWithFields("PROJE-94jfG", "seq", event.Sequence(), "expectedTypes", []eventstore.EventType{org.MailTemplateAddedEventType, iam.MailTemplateAddedEventType}).Error("wrong event type") + return nil, errors.ThrowInvalidArgument(nil, "PROJE-0pJ3f", "reduce.wrong.event.type") + } + return crdb.NewCreateStatement( + &templateEvent, + []handler.Column{ + handler.NewCol(MailTemplateAggregateIDCol, templateEvent.Aggregate().ID), + handler.NewCol(MailTemplateCreationDateCol, templateEvent.CreationDate()), + handler.NewCol(MailTemplateChangeDateCol, templateEvent.CreationDate()), + handler.NewCol(MailTemplateSequenceCol, templateEvent.Sequence()), + handler.NewCol(MailTemplateStateCol, domain.PolicyStateActive), + handler.NewCol(MailTemplateIsDefaultCol, isDefault), + handler.NewCol(MailTemplateTemplateCol, templateEvent.Template), + }), nil +} + +func (p *MailTemplateProjection) reduceChanged(event eventstore.EventReader) (*handler.Statement, error) { + var policyEvent policy.MailTemplateChangedEvent + switch e := event.(type) { + case *org.MailTemplateChangedEvent: + policyEvent = e.MailTemplateChangedEvent + case *iam.MailTemplateChangedEvent: + policyEvent = e.MailTemplateChangedEvent + default: + logging.LogWithFields("PROJE-02J9f", "seq", event.Sequence(), "expectedTypes", []eventstore.EventType{org.MailTemplateChangedEventType, iam.MailTemplateChangedEventType}).Error("wrong event type") + return nil, errors.ThrowInvalidArgument(nil, "PROJE-gJ03f", "reduce.wrong.event.type") + } + cols := []handler.Column{ + handler.NewCol(MailTemplateChangeDateCol, policyEvent.CreationDate()), + handler.NewCol(MailTemplateSequenceCol, policyEvent.Sequence()), + } + if policyEvent.Template != nil { + cols = append(cols, handler.NewCol(MailTemplateTemplateCol, *policyEvent.Template)) + } + return crdb.NewUpdateStatement( + &policyEvent, + cols, + []handler.Condition{ + handler.NewCond(MailTemplateAggregateIDCol, policyEvent.Aggregate().ID), + }), nil +} + +func (p *MailTemplateProjection) reduceRemoved(event eventstore.EventReader) (*handler.Statement, error) { + policyEvent, ok := event.(*org.MailTemplateRemovedEvent) + if !ok { + logging.LogWithFields("PROJE-2m0fp", "seq", event.Sequence(), "expectedType", org.MailTemplateRemovedEventType).Error("wrong event type") + return nil, errors.ThrowInvalidArgument(nil, "PROJE-3jJGs", "reduce.wrong.event.type") + } + return crdb.NewDeleteStatement( + policyEvent, + []handler.Condition{ + handler.NewCond(MailTemplateAggregateIDCol, policyEvent.Aggregate().ID), + }), nil +} diff --git a/internal/query/projection/mail_template_test.go b/internal/query/projection/mail_template_test.go new file mode 100644 index 0000000000..12a0a53afb --- /dev/null +++ b/internal/query/projection/mail_template_test.go @@ -0,0 +1,200 @@ +package projection + +import ( + "testing" + + "github.com/caos/zitadel/internal/domain" + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/eventstore/handler" + "github.com/caos/zitadel/internal/eventstore/repository" + "github.com/caos/zitadel/internal/repository/iam" + "github.com/caos/zitadel/internal/repository/org" +) + +func TestMailTemplateProjection_reduces(t *testing.T) { + type args struct { + event func(t *testing.T) eventstore.EventReader + } + tests := []struct { + name string + args args + reduce func(event eventstore.EventReader) (*handler.Statement, error) + want wantReduce + }{ + { + name: "org.reduceAdded", + args: args{ + event: getEvent(testEvent( + repository.EventType(org.MailTemplateAddedEventType), + org.AggregateType, + []byte(`{ + "template": "PHRhYmxlPjwvdGFibGU+" + }`), + ), org.MailTemplateAddedEventMapper), + }, + reduce: (&MailTemplateProjection{}).reduceAdded, + want: wantReduce{ + aggregateType: eventstore.AggregateType("org"), + sequence: 15, + previousSequence: 10, + projection: MailTemplateTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "INSERT INTO zitadel.projections.mail_templates (aggregate_id, creation_date, change_date, sequence, state, is_default, template) VALUES ($1, $2, $3, $4, $5, $6, $7)", + expectedArgs: []interface{}{ + "agg-id", + anyArg{}, + anyArg{}, + uint64(15), + domain.PolicyStateActive, + false, + []byte("
"), + }, + }, + }, + }, + }, + }, + { + name: "org.reduceChanged", + reduce: (&MailTemplateProjection{}).reduceChanged, + args: args{ + event: getEvent(testEvent( + repository.EventType(org.MailTemplateChangedEventType), + org.AggregateType, + []byte(`{ + "template": "PHRhYmxlPjwvdGFibGU+" + }`), + ), org.MailTemplateChangedEventMapper), + }, + want: wantReduce{ + aggregateType: eventstore.AggregateType("org"), + sequence: 15, + previousSequence: 10, + projection: MailTemplateTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPDATE zitadel.projections.mail_templates SET (change_date, sequence, template) = ($1, $2, $3) WHERE (aggregate_id = $4)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + []byte("
"), + "agg-id", + }, + }, + }, + }, + }, + }, + { + name: "org.reduceRemoved", + reduce: (&MailTemplateProjection{}).reduceRemoved, + args: args{ + event: getEvent(testEvent( + repository.EventType(org.MailTemplateRemovedEventType), + org.AggregateType, + nil, + ), org.MailTemplateRemovedEventMapper), + }, + want: wantReduce{ + aggregateType: eventstore.AggregateType("org"), + sequence: 15, + previousSequence: 10, + projection: MailTemplateTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "DELETE FROM zitadel.projections.mail_templates WHERE (aggregate_id = $1)", + expectedArgs: []interface{}{ + "agg-id", + }, + }, + }, + }, + }, + }, + { + name: "iam.reduceAdded", + reduce: (&MailTemplateProjection{}).reduceAdded, + args: args{ + event: getEvent(testEvent( + repository.EventType(iam.MailTemplateAddedEventType), + iam.AggregateType, + []byte(`{ + "template": "PHRhYmxlPjwvdGFibGU+" + }`), + ), iam.MailTemplateAddedEventMapper), + }, + want: wantReduce{ + aggregateType: eventstore.AggregateType("iam"), + sequence: 15, + previousSequence: 10, + projection: MailTemplateTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "INSERT INTO zitadel.projections.mail_templates (aggregate_id, creation_date, change_date, sequence, state, is_default, template) VALUES ($1, $2, $3, $4, $5, $6, $7)", + expectedArgs: []interface{}{ + "agg-id", + anyArg{}, + anyArg{}, + uint64(15), + domain.PolicyStateActive, + true, + []byte("
"), + }, + }, + }, + }, + }, + }, + { + name: "iam.reduceChanged", + reduce: (&MailTemplateProjection{}).reduceChanged, + args: args{ + event: getEvent(testEvent( + repository.EventType(iam.MailTemplateChangedEventType), + iam.AggregateType, + []byte(`{ + "template": "PHRhYmxlPjwvdGFibGU+" + }`), + ), iam.MailTemplateChangedEventMapper), + }, + want: wantReduce{ + aggregateType: eventstore.AggregateType("iam"), + sequence: 15, + previousSequence: 10, + projection: MailTemplateTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPDATE zitadel.projections.mail_templates SET (change_date, sequence, template) = ($1, $2, $3) WHERE (aggregate_id = $4)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + []byte("
"), + "agg-id", + }, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + event := baseEvent(t) + got, err := tt.reduce(event) + if _, ok := err.(errors.InvalidArgument); !ok { + t.Errorf("no wrong event mapping: %v, got: %v", err, got) + } + + event = tt.args.event(t) + got, err = tt.reduce(event) + assertReduce(t, got, err, tt.want) + }) + } +} diff --git a/internal/query/projection/message_text_test.go b/internal/query/projection/message_text_test.go new file mode 100644 index 0000000000..0f7f3059d5 --- /dev/null +++ b/internal/query/projection/message_text_test.go @@ -0,0 +1,673 @@ +package projection + +import ( + "testing" + + "github.com/caos/zitadel/internal/domain" + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/eventstore/handler" + "github.com/caos/zitadel/internal/eventstore/repository" + "github.com/caos/zitadel/internal/repository/iam" + "github.com/caos/zitadel/internal/repository/org" + "golang.org/x/text/language" +) + +func TestMessageTextProjection_reduces(t *testing.T) { + type args struct { + event func(t *testing.T) eventstore.EventReader + } + tests := []struct { + name string + args args + reduce func(event eventstore.EventReader) (*handler.Statement, error) + want wantReduce + }{ + { + name: "org.reduceAdded.Title", + args: args{ + event: getEvent(testEvent( + repository.EventType(org.CustomTextSetEventType), + org.AggregateType, + []byte(`{ + "key": "Title", + "language": "en", + "template": "InitCode", + "text": "Test" + }`), + ), org.CustomTextSetEventMapper), + }, + reduce: (&MessageTextProjection{}).reduceAdded, + want: wantReduce{ + aggregateType: eventstore.AggregateType("org"), + sequence: 15, + previousSequence: 10, + projection: MessageTextTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPSERT INTO zitadel.projections.message_texts (aggregate_id, creation_date, change_date, sequence, state, type, language, title) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)", + expectedArgs: []interface{}{ + "agg-id", + anyArg{}, + anyArg{}, + uint64(15), + domain.PolicyStateActive, + "InitCode", + "en", + "Test", + }, + }, + }, + }, + }, + }, + { + name: "org.reduceAdded.PreHeader", + args: args{ + event: getEvent(testEvent( + repository.EventType(org.CustomTextSetEventType), + org.AggregateType, + []byte(`{ + "key": "PreHeader", + "language": "en", + "template": "InitCode", + "text": "Test" + }`), + ), org.CustomTextSetEventMapper), + }, + reduce: (&MessageTextProjection{}).reduceAdded, + want: wantReduce{ + aggregateType: eventstore.AggregateType("org"), + sequence: 15, + previousSequence: 10, + projection: MessageTextTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPSERT INTO zitadel.projections.message_texts (aggregate_id, creation_date, change_date, sequence, state, type, language, pre_header) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)", + expectedArgs: []interface{}{ + "agg-id", + anyArg{}, + anyArg{}, + uint64(15), + domain.PolicyStateActive, + "InitCode", + "en", + "Test", + }, + }, + }, + }, + }, + }, + { + name: "org.reduceAdded.Subject", + args: args{ + event: getEvent(testEvent( + repository.EventType(org.CustomTextSetEventType), + org.AggregateType, + []byte(`{ + "key": "Subject", + "language": "en", + "template": "InitCode", + "text": "Test" + }`), + ), org.CustomTextSetEventMapper), + }, + reduce: (&MessageTextProjection{}).reduceAdded, + want: wantReduce{ + aggregateType: eventstore.AggregateType("org"), + sequence: 15, + previousSequence: 10, + projection: MessageTextTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPSERT INTO zitadel.projections.message_texts (aggregate_id, creation_date, change_date, sequence, state, type, language, subject) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)", + expectedArgs: []interface{}{ + "agg-id", + anyArg{}, + anyArg{}, + uint64(15), + domain.PolicyStateActive, + "InitCode", + "en", + "Test", + }, + }, + }, + }, + }, + }, + { + name: "org.reduceAdded.Greeting", + args: args{ + event: getEvent(testEvent( + repository.EventType(org.CustomTextSetEventType), + org.AggregateType, + []byte(`{ + "key": "Greeting", + "language": "en", + "template": "InitCode", + "text": "Test" + }`), + ), org.CustomTextSetEventMapper), + }, + reduce: (&MessageTextProjection{}).reduceAdded, + want: wantReduce{ + aggregateType: eventstore.AggregateType("org"), + sequence: 15, + previousSequence: 10, + projection: MessageTextTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPSERT INTO zitadel.projections.message_texts (aggregate_id, creation_date, change_date, sequence, state, type, language, greeting) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)", + expectedArgs: []interface{}{ + "agg-id", + anyArg{}, + anyArg{}, + uint64(15), + domain.PolicyStateActive, + "InitCode", + "en", + "Test", + }, + }, + }, + }, + }, + }, + { + name: "org.reduceAdded.Text", + args: args{ + event: getEvent(testEvent( + repository.EventType(org.CustomTextSetEventType), + org.AggregateType, + []byte(`{ + "key": "Text", + "language": "en", + "template": "InitCode", + "text": "Test" + }`), + ), org.CustomTextSetEventMapper), + }, + reduce: (&MessageTextProjection{}).reduceAdded, + want: wantReduce{ + aggregateType: eventstore.AggregateType("org"), + sequence: 15, + previousSequence: 10, + projection: MessageTextTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPSERT INTO zitadel.projections.message_texts (aggregate_id, creation_date, change_date, sequence, state, type, language, text) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)", + expectedArgs: []interface{}{ + "agg-id", + anyArg{}, + anyArg{}, + uint64(15), + domain.PolicyStateActive, + "InitCode", + "en", + "Test", + }, + }, + }, + }, + }, + }, + { + name: "org.reduceAdded.ButtonText", + args: args{ + event: getEvent(testEvent( + repository.EventType(org.CustomTextSetEventType), + org.AggregateType, + []byte(`{ + "key": "ButtonText", + "language": "en", + "template": "InitCode", + "text": "Test" + }`), + ), org.CustomTextSetEventMapper), + }, + reduce: (&MessageTextProjection{}).reduceAdded, + want: wantReduce{ + aggregateType: eventstore.AggregateType("org"), + sequence: 15, + previousSequence: 10, + projection: MessageTextTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPSERT INTO zitadel.projections.message_texts (aggregate_id, creation_date, change_date, sequence, state, type, language, button_text) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)", + expectedArgs: []interface{}{ + "agg-id", + anyArg{}, + anyArg{}, + uint64(15), + domain.PolicyStateActive, + "InitCode", + "en", + "Test", + }, + }, + }, + }, + }, + }, + { + name: "org.reduceAdded.Footer", + args: args{ + event: getEvent(testEvent( + repository.EventType(org.CustomTextSetEventType), + org.AggregateType, + []byte(`{ + "key": "Footer", + "language": "en", + "template": "InitCode", + "text": "Test" + }`), + ), org.CustomTextSetEventMapper), + }, + reduce: (&MessageTextProjection{}).reduceAdded, + want: wantReduce{ + aggregateType: eventstore.AggregateType("org"), + sequence: 15, + previousSequence: 10, + projection: MessageTextTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPSERT INTO zitadel.projections.message_texts (aggregate_id, creation_date, change_date, sequence, state, type, language, footer_text) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)", + expectedArgs: []interface{}{ + "agg-id", + anyArg{}, + anyArg{}, + uint64(15), + domain.PolicyStateActive, + "InitCode", + "en", + "Test", + }, + }, + }, + }, + }, + }, + { + name: "org.reduceRemoved.Title", + args: args{ + event: getEvent(testEvent( + repository.EventType(org.CustomTextRemovedEventType), + org.AggregateType, + []byte(`{ + "key": "Title", + "language": "en", + "template": "InitCode" + }`), + ), org.CustomTextRemovedEventMapper), + }, + reduce: (&MessageTextProjection{}).reduceRemoved, + want: wantReduce{ + aggregateType: eventstore.AggregateType("org"), + sequence: 15, + previousSequence: 10, + projection: MessageTextTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPDATE zitadel.projections.message_texts SET (change_date, sequence, title) = ($1, $2, $3) WHERE (aggregate_id = $4) AND (type = $5) AND (language = $6)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + "", + "agg-id", + "InitCode", + "en", + }, + }, + }, + }, + }, + }, + { + name: "org.reduceRemoved.PreHeader", + args: args{ + event: getEvent(testEvent( + repository.EventType(org.CustomTextRemovedEventType), + org.AggregateType, + []byte(`{ + "key": "PreHeader", + "language": "en", + "template": "InitCode" + }`), + ), org.CustomTextRemovedEventMapper), + }, + reduce: (&MessageTextProjection{}).reduceRemoved, + want: wantReduce{ + aggregateType: eventstore.AggregateType("org"), + sequence: 15, + previousSequence: 10, + projection: MessageTextTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPDATE zitadel.projections.message_texts SET (change_date, sequence, pre_header) = ($1, $2, $3) WHERE (aggregate_id = $4) AND (type = $5) AND (language = $6)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + "", + "agg-id", + "InitCode", + "en", + }, + }, + }, + }, + }, + }, + { + name: "org.reduceRemoved.Subject", + args: args{ + event: getEvent(testEvent( + repository.EventType(org.CustomTextRemovedEventType), + org.AggregateType, + []byte(`{ + "key": "Subject", + "language": "en", + "template": "InitCode" + }`), + ), org.CustomTextRemovedEventMapper), + }, + reduce: (&MessageTextProjection{}).reduceRemoved, + want: wantReduce{ + aggregateType: eventstore.AggregateType("org"), + sequence: 15, + previousSequence: 10, + projection: MessageTextTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPDATE zitadel.projections.message_texts SET (change_date, sequence, subject) = ($1, $2, $3) WHERE (aggregate_id = $4) AND (type = $5) AND (language = $6)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + "", + "agg-id", + "InitCode", + "en", + }, + }, + }, + }, + }, + }, + { + name: "org.reduceRemoved.Greeting", + args: args{ + event: getEvent(testEvent( + repository.EventType(org.CustomTextRemovedEventType), + org.AggregateType, + []byte(`{ + "key": "Greeting", + "language": "en", + "template": "InitCode" + }`), + ), org.CustomTextRemovedEventMapper), + }, + reduce: (&MessageTextProjection{}).reduceRemoved, + want: wantReduce{ + aggregateType: eventstore.AggregateType("org"), + sequence: 15, + previousSequence: 10, + projection: MessageTextTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPDATE zitadel.projections.message_texts SET (change_date, sequence, greeting) = ($1, $2, $3) WHERE (aggregate_id = $4) AND (type = $5) AND (language = $6)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + "", + "agg-id", + "InitCode", + "en", + }, + }, + }, + }, + }, + }, + { + name: "org.reduceRemoved.Text", + args: args{ + event: getEvent(testEvent( + repository.EventType(org.CustomTextRemovedEventType), + org.AggregateType, + []byte(`{ + "key": "Text", + "language": "en", + "template": "InitCode" + }`), + ), org.CustomTextRemovedEventMapper), + }, + reduce: (&MessageTextProjection{}).reduceRemoved, + want: wantReduce{ + aggregateType: eventstore.AggregateType("org"), + sequence: 15, + previousSequence: 10, + projection: MessageTextTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPDATE zitadel.projections.message_texts SET (change_date, sequence, text) = ($1, $2, $3) WHERE (aggregate_id = $4) AND (type = $5) AND (language = $6)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + "", + "agg-id", + "InitCode", + "en", + }, + }, + }, + }, + }, + }, + { + name: "org.reduceRemoved.ButtonText", + args: args{ + event: getEvent(testEvent( + repository.EventType(org.CustomTextRemovedEventType), + org.AggregateType, + []byte(`{ + "key": "ButtonText", + "language": "en", + "template": "InitCode" + }`), + ), org.CustomTextRemovedEventMapper), + }, + reduce: (&MessageTextProjection{}).reduceRemoved, + want: wantReduce{ + aggregateType: eventstore.AggregateType("org"), + sequence: 15, + previousSequence: 10, + projection: MessageTextTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPDATE zitadel.projections.message_texts SET (change_date, sequence, button_text) = ($1, $2, $3) WHERE (aggregate_id = $4) AND (type = $5) AND (language = $6)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + "", + "agg-id", + "InitCode", + "en", + }, + }, + }, + }, + }, + }, + { + name: "org.reduceRemoved.Footer", + args: args{ + event: getEvent(testEvent( + repository.EventType(org.CustomTextRemovedEventType), + org.AggregateType, + []byte(`{ + "key": "Footer", + "language": "en", + "template": "InitCode" + }`), + ), org.CustomTextRemovedEventMapper), + }, + reduce: (&MessageTextProjection{}).reduceRemoved, + want: wantReduce{ + aggregateType: eventstore.AggregateType("org"), + sequence: 15, + previousSequence: 10, + projection: MessageTextTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPDATE zitadel.projections.message_texts SET (change_date, sequence, footer_text) = ($1, $2, $3) WHERE (aggregate_id = $4) AND (type = $5) AND (language = $6)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + "", + "agg-id", + "InitCode", + "en", + }, + }, + }, + }, + }, + }, + { + name: "org.reduceRemoved", + reduce: (&MessageTextProjection{}).reduceTemplateRemoved, + args: args{ + event: getEvent(testEvent( + repository.EventType(org.CustomTextTemplateRemovedEventType), + org.AggregateType, + []byte(`{ + "key": "Title", + "language": "en", + "template": "InitCode" + }`), + ), org.CustomTextTemplateRemovedEventMapper), + }, + want: wantReduce{ + aggregateType: eventstore.AggregateType("org"), + sequence: 15, + previousSequence: 10, + projection: MessageTextTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "DELETE FROM zitadel.projections.message_texts WHERE (aggregate_id = $1) AND (type = $2) AND (language = $3)", + expectedArgs: []interface{}{ + "agg-id", + "InitCode", + language.English, + }, + }, + }, + }, + }, + }, + { + name: "iam.reduceAdded", + reduce: (&MessageTextProjection{}).reduceAdded, + args: args{ + event: getEvent(testEvent( + repository.EventType(iam.CustomTextSetEventType), + iam.AggregateType, + []byte(`{ + "key": "Title", + "language": "en", + "template": "InitCode", + "text": "Test" + }`), + ), iam.CustomTextSetEventMapper), + }, + want: wantReduce{ + aggregateType: eventstore.AggregateType("iam"), + sequence: 15, + previousSequence: 10, + projection: MessageTextTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPSERT INTO zitadel.projections.message_texts (aggregate_id, creation_date, change_date, sequence, state, type, language, title) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)", + expectedArgs: []interface{}{ + "agg-id", + anyArg{}, + anyArg{}, + uint64(15), + domain.PolicyStateActive, + "InitCode", + "en", + "Test", + }, + }, + }, + }, + }, + }, + { + name: "iam.reduceRemoved.Title", + args: args{ + event: getEvent(testEvent( + repository.EventType(iam.CustomTextRemovedEventType), + iam.AggregateType, + []byte(`{ + "key": "Title", + "language": "en", + "template": "InitCode" + }`), + ), iam.CustomTextRemovedEventMapper), + }, + reduce: (&MessageTextProjection{}).reduceRemoved, + want: wantReduce{ + aggregateType: eventstore.AggregateType("iam"), + sequence: 15, + previousSequence: 10, + projection: MessageTextTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPDATE zitadel.projections.message_texts SET (change_date, sequence, title) = ($1, $2, $3) WHERE (aggregate_id = $4) AND (type = $5) AND (language = $6)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + "", + "agg-id", + "InitCode", + "en", + }, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + event := baseEvent(t) + got, err := tt.reduce(event) + if _, ok := err.(errors.InvalidArgument); !ok { + t.Errorf("no wrong event mapping: %v, got: %v", err, got) + } + + event = tt.args.event(t) + got, err = tt.reduce(event) + assertReduce(t, got, err, tt.want) + }) + } +} diff --git a/internal/query/projection/message_texts.go b/internal/query/projection/message_texts.go new file mode 100644 index 0000000000..6ab339e30e --- /dev/null +++ b/internal/query/projection/message_texts.go @@ -0,0 +1,234 @@ +package projection + +import ( + "context" + + "github.com/caos/logging" + "github.com/caos/zitadel/internal/domain" + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/eventstore/handler" + "github.com/caos/zitadel/internal/eventstore/handler/crdb" + "github.com/caos/zitadel/internal/repository/iam" + "github.com/caos/zitadel/internal/repository/org" + "github.com/caos/zitadel/internal/repository/policy" +) + +type MessageTextProjection struct { + crdb.StatementHandler +} + +const ( + MessageTextTable = "zitadel.projections.message_texts" + + MessageTextAggregateIDCol = "aggregate_id" + MessageTextCreationDateCol = "creation_date" + MessageTextChangeDateCol = "change_date" + MessageTextSequenceCol = "sequence" + MessageTextStateCol = "state" + MessageTextTypeCol = "type" + MessageTextLanguageCol = "language" + MessageTextTitleCol = "title" + MessageTextPreHeaderCol = "pre_header" + MessageTextSubjectCol = "subject" + MessageTextGreetingCol = "greeting" + MessageTextTextCol = "text" + MessageTextButtonTextCol = "button_text" + MessageTextFooterCol = "footer_text" +) + +func NewMessageTextProjection(ctx context.Context, config crdb.StatementHandlerConfig) *MessageTextProjection { + p := &MessageTextProjection{} + config.ProjectionName = MessageTextTable + config.Reducers = p.reducers() + p.StatementHandler = crdb.NewStatementHandler(ctx, config) + return p +} + +func (p *MessageTextProjection) reducers() []handler.AggregateReducer { + return []handler.AggregateReducer{ + { + Aggregate: org.AggregateType, + EventRedusers: []handler.EventReducer{ + { + Event: org.CustomTextSetEventType, + Reduce: p.reduceAdded, + }, + { + Event: org.CustomTextRemovedEventType, + Reduce: p.reduceRemoved, + }, + { + Event: org.CustomTextTemplateRemovedEventType, + Reduce: p.reduceTemplateRemoved, + }, + }, + }, + { + Aggregate: iam.AggregateType, + EventRedusers: []handler.EventReducer{ + { + Event: iam.CustomTextSetEventType, + Reduce: p.reduceAdded, + }, + { + Event: iam.CustomTextRemovedEventType, + Reduce: p.reduceRemoved, + }, + { + Event: iam.CustomTextTemplateRemovedEventType, + Reduce: p.reduceTemplateRemoved, + }, + }, + }, + } +} + +func (p *MessageTextProjection) reduceAdded(event eventstore.EventReader) (*handler.Statement, error) { + var templateEvent policy.CustomTextSetEvent + switch e := event.(type) { + case *org.CustomTextSetEvent: + templateEvent = e.CustomTextSetEvent + case *iam.CustomTextSetEvent: + templateEvent = e.CustomTextSetEvent + default: + logging.LogWithFields("PROJE-2N9fg", "seq", event.Sequence(), "expectedTypes", []eventstore.EventType{org.CustomTextSetEventType, iam.CustomTextSetEventType}).Error("wrong event type") + return nil, errors.ThrowInvalidArgument(nil, "PROJE-2n90r", "reduce.wrong.event.type") + } + if !isMessageTemplate(templateEvent.Template) { + return nil, nil + } + + cols := []handler.Column{ + handler.NewCol(MessageTextAggregateIDCol, templateEvent.Aggregate().ID), + handler.NewCol(MessageTextCreationDateCol, templateEvent.CreationDate()), + handler.NewCol(MessageTextChangeDateCol, templateEvent.CreationDate()), + handler.NewCol(MessageTextSequenceCol, templateEvent.Sequence()), + handler.NewCol(MessageTextStateCol, domain.PolicyStateActive), + handler.NewCol(MessageTextTypeCol, templateEvent.Template), + handler.NewCol(MessageTextLanguageCol, templateEvent.Language.String()), + } + if isTitle(templateEvent.Key) { + cols = append(cols, handler.NewCol(MessageTextTitleCol, templateEvent.Text)) + } + if isPreHeader(templateEvent.Key) { + cols = append(cols, handler.NewCol(MessageTextPreHeaderCol, templateEvent.Text)) + } + if isSubject(templateEvent.Key) { + cols = append(cols, handler.NewCol(MessageTextSubjectCol, templateEvent.Text)) + } + if isGreeting(templateEvent.Key) { + cols = append(cols, handler.NewCol(MessageTextGreetingCol, templateEvent.Text)) + } + if isText(templateEvent.Key) { + cols = append(cols, handler.NewCol(MessageTextTextCol, templateEvent.Text)) + } + if isButtonText(templateEvent.Key) { + cols = append(cols, handler.NewCol(MessageTextButtonTextCol, templateEvent.Text)) + } + if isFooterText(templateEvent.Key) { + cols = append(cols, handler.NewCol(MessageTextFooterCol, templateEvent.Text)) + } + return crdb.NewUpsertStatement( + &templateEvent, + cols), nil +} + +func (p *MessageTextProjection) reduceRemoved(event eventstore.EventReader) (*handler.Statement, error) { + var templateEvent policy.CustomTextRemovedEvent + switch e := event.(type) { + case *org.CustomTextRemovedEvent: + templateEvent = e.CustomTextRemovedEvent + case *iam.CustomTextRemovedEvent: + templateEvent = e.CustomTextRemovedEvent + default: + logging.LogWithFields("PROJE-3m022", "seq", event.Sequence(), "expectedTypes", []eventstore.EventType{org.CustomTextRemovedEventType, iam.CustomTextRemovedEventType}).Error("wrong event type") + return nil, errors.ThrowInvalidArgument(nil, "PROJE-fm0ge", "reduce.wrong.event.type") + } + if !isMessageTemplate(templateEvent.Template) { + return nil, nil + } + cols := []handler.Column{ + handler.NewCol(MessageTextChangeDateCol, templateEvent.CreationDate()), + handler.NewCol(MessageTextSequenceCol, templateEvent.Sequence()), + } + if isTitle(templateEvent.Key) { + cols = append(cols, handler.NewCol(MessageTextTitleCol, "")) + } + if isPreHeader(templateEvent.Key) { + cols = append(cols, handler.NewCol(MessageTextPreHeaderCol, "")) + } + if isSubject(templateEvent.Key) { + cols = append(cols, handler.NewCol(MessageTextSubjectCol, "")) + } + if isGreeting(templateEvent.Key) { + cols = append(cols, handler.NewCol(MessageTextGreetingCol, "")) + } + if isText(templateEvent.Key) { + cols = append(cols, handler.NewCol(MessageTextTextCol, "")) + } + if isButtonText(templateEvent.Key) { + cols = append(cols, handler.NewCol(MessageTextButtonTextCol, "")) + } + if isFooterText(templateEvent.Key) { + cols = append(cols, handler.NewCol(MessageTextFooterCol, "")) + } + return crdb.NewUpdateStatement( + &templateEvent, + cols, + []handler.Condition{ + handler.NewCond(MessageTextAggregateIDCol, templateEvent.Aggregate().ID), + handler.NewCond(MessageTextTypeCol, templateEvent.Template), + handler.NewCond(MessageTextLanguageCol, templateEvent.Language.String()), + }, + ), nil +} + +func (p *MessageTextProjection) reduceTemplateRemoved(event eventstore.EventReader) (*handler.Statement, error) { + templateEvent, ok := event.(*org.CustomTextTemplateRemovedEvent) + if !ok { + logging.LogWithFields("PROJE-m03ng", "seq", event.Sequence(), "expectedType", org.CustomTextTemplateRemovedEventType).Error("wrong event type") + return nil, errors.ThrowInvalidArgument(nil, "PROJE-2n9rs", "reduce.wrong.event.type") + } + if !isMessageTemplate(templateEvent.Template) { + return nil, nil + } + return crdb.NewDeleteStatement( + templateEvent, + []handler.Condition{ + handler.NewCond(MessageTextAggregateIDCol, templateEvent.Aggregate().ID), + handler.NewCond(MessageTextTypeCol, templateEvent.Template), + handler.NewCond(MessageTextLanguageCol, templateEvent.Language), + }, + ), nil +} + +func isMessageTemplate(template string) bool { + return template == domain.InitCodeMessageType || + template == domain.PasswordResetMessageType || + template == domain.VerifyEmailMessageType || + template == domain.VerifyPhoneMessageType || + template == domain.DomainClaimedMessageType || + template == domain.PasswordlessRegistrationMessageType +} +func isTitle(key string) bool { + return key == domain.MessageTitle +} +func isPreHeader(key string) bool { + return key == domain.MessagePreHeader +} +func isSubject(key string) bool { + return key == domain.MessageSubject +} +func isGreeting(key string) bool { + return key == domain.MessageGreeting +} +func isText(key string) bool { + return key == domain.MessageText +} +func isButtonText(key string) bool { + return key == domain.MessageButtonText +} +func isFooterText(key string) bool { + return key == domain.MessageFooterText +} diff --git a/internal/query/projection/projection.go b/internal/query/projection/projection.go index e8deea60f0..26b015908a 100644 --- a/internal/query/projection/projection.go +++ b/internal/query/projection/projection.go @@ -48,6 +48,9 @@ func Start(ctx context.Context, sqlClient *sql.DB, es *eventstore.Eventstore, co NewOrgDomainProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["org_domains"])) NewLoginPolicyProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["login_policies"])) NewIDPProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["idps"])) + NewMailTemplateProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["mail_templates"])) + NewMessageTextProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["message_texts"])) + NewCustomTextProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["custom_texts"])) return nil } diff --git a/migrations/cockroach/V1.85__messages.sql b/migrations/cockroach/V1.85__messages.sql new file mode 100644 index 0000000000..cd448faa98 --- /dev/null +++ b/migrations/cockroach/V1.85__messages.sql @@ -0,0 +1,50 @@ +CREATE TABLE zitadel.projections.mail_templates ( + aggregate_id TEXT NOT NULL, + + creation_date TIMESTAMPTZ, + change_date TIMESTAMPTZ, + state SMALLINT, + sequence BIGINT, + is_default BOOLEAN, + + template BYTES, + + PRIMARY KEY (aggregate_id) +); + +CREATE TABLE zitadel.projections.message_texts ( + aggregate_id TEXT, + + creation_date TIMESTAMPTZ, + change_date TIMESTAMPTZ, + state SMALLINT, + sequence BIGINT, + + type TEXT, + language TEXT, + title TEXT, + pre_header TEXT, + subject TEXT, + greeting TEXT, + text TEXT, + button_text TEXT, + footer_text TEXT, + + PRIMARY KEY (aggregate_id, type, language) +); + +CREATE TABLE zitadel.projections.custom_texts ( + aggregate_id TEXT, + + creation_date TIMESTAMPTZ, + change_date TIMESTAMPTZ, + sequence BIGINT, + is_default BOOLEAN, + + template TEXT, + language TEXT, + key TEXT, + text TEXT, + + PRIMARY KEY (aggregate_id, template, key, language) +); \ No newline at end of file