From 191690d90587456992373dd695694ea13c2ee9ca Mon Sep 17 00:00:00 2001 From: Silvan Date: Tue, 7 Apr 2020 18:36:37 +0200 Subject: [PATCH] feat(eventstore): sdk (#39) * sdk * fix(sdk): return correct error type * AppendEventError instead of Aggregater error * fix(tests): tests * fix(tests): wantErr to is error func --- internal/errors/already_exists.go | 4 +- internal/errors/caos_error.go | 4 +- internal/errors/deadline_exceeded.go | 2 +- internal/errors/generate/error.go.tmpl | 2 +- internal/errors/generate/error_test.go.tmpl | 2 +- internal/errors/internal.go | 2 +- internal/errors/invalid_argument.go | 2 +- internal/errors/not_found.go | 2 +- internal/errors/permission_denied.go | 2 +- internal/errors/precondition_failed.go | 2 +- internal/errors/unauthenticated.go | 2 +- internal/errors/unavailable.go | 2 +- internal/errors/unimplemented.go | 2 +- internal/errors/unknown.go | 2 +- internal/eventstore/eventstore.go | 8 +- internal/eventstore/models/aggregate.go | 8 - internal/eventstore/sdk/append_event_error.go | 36 ++++ .../eventstore/sdk/append_event_error_test.go | 31 +++ internal/eventstore/sdk/sdk.go | 72 +++++++ internal/eventstore/sdk/sdk_test.go | 193 ++++++++++++++++++ .../repository/eventsourcing/project.go | 25 +-- .../repository/eventsourcing/eventstore.go | 68 +++--- ...tstore_mock.go => eventstore_mock_test.go} | 0 .../eventsourcing/{events.go => model.go} | 45 ++++ .../{events_test.go => model_test.go} | 47 ++++- .../repository/eventsourcing/project.go | 138 +++++-------- .../repository/eventsourcing/project_test.go | 55 +---- 27 files changed, 524 insertions(+), 234 deletions(-) create mode 100644 internal/eventstore/sdk/append_event_error.go create mode 100644 internal/eventstore/sdk/append_event_error_test.go create mode 100644 internal/eventstore/sdk/sdk.go create mode 100644 internal/eventstore/sdk/sdk_test.go rename internal/project/repository/eventsourcing/{eventstore_mock.go => eventstore_mock_test.go} (100%) rename internal/project/repository/eventsourcing/{events.go => model.go} (56%) rename internal/project/repository/eventsourcing/{events_test.go => model_test.go} (84%) diff --git a/internal/errors/already_exists.go b/internal/errors/already_exists.go index 17720298dc..57e1c3dea7 100644 --- a/internal/errors/already_exists.go +++ b/internal/errors/already_exists.go @@ -17,11 +17,11 @@ type AlreadyExistsError struct { } func ThrowAlreadyExists(parent error, id, message string) error { - return &AlreadyExistsError{createCaosError(parent, id, message)} + return &AlreadyExistsError{CreateCaosError(parent, id, message)} } func ThrowAlreadyExistsf(parent error, id, format string, a ...interface{}) error { - return &AlreadyExistsError{createCaosError(parent, id, fmt.Sprintf(format, a...))} + return &AlreadyExistsError{CreateCaosError(parent, id, fmt.Sprintf(format, a...))} } func (err *AlreadyExistsError) IsAlreadyExists() {} diff --git a/internal/errors/caos_error.go b/internal/errors/caos_error.go index 4788b501ee..94e82b7a19 100644 --- a/internal/errors/caos_error.go +++ b/internal/errors/caos_error.go @@ -13,10 +13,10 @@ type CaosError struct { } func ThrowError(parent error, id, message string) error { - return createCaosError(parent, id, message) + return CreateCaosError(parent, id, message) } -func createCaosError(parent error, id, message string) *CaosError { +func CreateCaosError(parent error, id, message string) *CaosError { return &CaosError{ Parent: parent, ID: id, diff --git a/internal/errors/deadline_exceeded.go b/internal/errors/deadline_exceeded.go index b2a9a31b8e..715e19e3f2 100644 --- a/internal/errors/deadline_exceeded.go +++ b/internal/errors/deadline_exceeded.go @@ -19,7 +19,7 @@ type DeadlineExceededError struct { } func ThrowDeadlineExceeded(parent error, id, message string) error { - return &DeadlineExceededError{createCaosError(parent, id, message)} + return &DeadlineExceededError{CreateCaosError(parent, id, message)} } func ThrowDeadlineExceededf(parent error, id, format string, a ...interface{}) error { diff --git a/internal/errors/generate/error.go.tmpl b/internal/errors/generate/error.go.tmpl index f41e99ff9c..7513451c0e 100644 --- a/internal/errors/generate/error.go.tmpl +++ b/internal/errors/generate/error.go.tmpl @@ -19,7 +19,7 @@ type {{.ErrorName}}Error struct { } func Throw{{.ErrorName}}(parent error, id, message string) error { - return &{{.ErrorName}}Error{createCaosError(parent, id, message)} + return &{{.ErrorName}}Error{CreateCaosError(parent, id, message)} } func Throw{{.ErrorName}}f(parent error, id, format string, a ...interface{}) error { diff --git a/internal/errors/generate/error_test.go.tmpl b/internal/errors/generate/error_test.go.tmpl index 3b83a3a5ac..0de94982fc 100644 --- a/internal/errors/generate/error_test.go.tmpl +++ b/internal/errors/generate/error_test.go.tmpl @@ -12,7 +12,7 @@ import ( func Test{{.ErrorName}}Error(t *testing.T) { var err interface{} err = new(caos_errs.{{.ErrorName}}Error) - _, ok := err.(caos_errs.{{.ErrorName}}) + _, ok := err.(*caos_errs.{{.ErrorName}}) assert.True(t, ok) } diff --git a/internal/errors/internal.go b/internal/errors/internal.go index 3f5a2c91d5..eea3fb616e 100644 --- a/internal/errors/internal.go +++ b/internal/errors/internal.go @@ -19,7 +19,7 @@ type InternalError struct { } func ThrowInternal(parent error, id, message string) error { - return &InternalError{createCaosError(parent, id, message)} + return &InternalError{CreateCaosError(parent, id, message)} } func ThrowInternalf(parent error, id, format string, a ...interface{}) error { diff --git a/internal/errors/invalid_argument.go b/internal/errors/invalid_argument.go index ab3eee5ae0..0f9194248b 100644 --- a/internal/errors/invalid_argument.go +++ b/internal/errors/invalid_argument.go @@ -17,7 +17,7 @@ type InvalidArgumentError struct { } func ThrowInvalidArgument(parent error, id, message string) error { - return &InvalidArgumentError{createCaosError(parent, id, message)} + return &InvalidArgumentError{CreateCaosError(parent, id, message)} } func ThrowInvalidArgumentf(parent error, id, format string, a ...interface{}) error { diff --git a/internal/errors/not_found.go b/internal/errors/not_found.go index fa988d0b69..f0859b7b94 100644 --- a/internal/errors/not_found.go +++ b/internal/errors/not_found.go @@ -12,7 +12,7 @@ type NotFoundError struct { } func ThrowNotFound(parent error, id, message string) error { - return &NotFoundError{createCaosError(parent, id, message)} + return &NotFoundError{CreateCaosError(parent, id, message)} } func ThrowNotFoundf(parent error, id, format string, a ...interface{}) error { diff --git a/internal/errors/permission_denied.go b/internal/errors/permission_denied.go index b6900ee097..18ac62a7b6 100644 --- a/internal/errors/permission_denied.go +++ b/internal/errors/permission_denied.go @@ -19,7 +19,7 @@ type PermissionDeniedError struct { } func ThrowPermissionDenied(parent error, id, message string) error { - return &PermissionDeniedError{createCaosError(parent, id, message)} + return &PermissionDeniedError{CreateCaosError(parent, id, message)} } func ThrowPermissionDeniedf(parent error, id, format string, a ...interface{}) error { diff --git a/internal/errors/precondition_failed.go b/internal/errors/precondition_failed.go index a789de9968..89e0c0ee7c 100644 --- a/internal/errors/precondition_failed.go +++ b/internal/errors/precondition_failed.go @@ -19,7 +19,7 @@ type PreconditionFailedError struct { } func ThrowPreconditionFailed(parent error, id, message string) error { - return &PreconditionFailedError{createCaosError(parent, id, message)} + return &PreconditionFailedError{CreateCaosError(parent, id, message)} } func ThrowPreconditionFailedf(parent error, id, format string, a ...interface{}) error { diff --git a/internal/errors/unauthenticated.go b/internal/errors/unauthenticated.go index fa1984ec5d..3f6ea204b0 100644 --- a/internal/errors/unauthenticated.go +++ b/internal/errors/unauthenticated.go @@ -19,7 +19,7 @@ type UnauthenticatedError struct { } func ThrowUnauthenticated(parent error, id, message string) error { - return &UnauthenticatedError{createCaosError(parent, id, message)} + return &UnauthenticatedError{CreateCaosError(parent, id, message)} } func ThrowUnauthenticatedf(parent error, id, format string, a ...interface{}) error { diff --git a/internal/errors/unavailable.go b/internal/errors/unavailable.go index e7e593a4c2..0c0a09e46f 100644 --- a/internal/errors/unavailable.go +++ b/internal/errors/unavailable.go @@ -19,7 +19,7 @@ type UnavailableError struct { } func ThrowUnavailable(parent error, id, message string) error { - return &UnavailableError{createCaosError(parent, id, message)} + return &UnavailableError{CreateCaosError(parent, id, message)} } func ThrowUnavailablef(parent error, id, format string, a ...interface{}) error { diff --git a/internal/errors/unimplemented.go b/internal/errors/unimplemented.go index 1eea555b2a..e5fde78f88 100644 --- a/internal/errors/unimplemented.go +++ b/internal/errors/unimplemented.go @@ -19,7 +19,7 @@ type UnimplementedError struct { } func ThrowUnimplemented(parent error, id, message string) error { - return &UnimplementedError{createCaosError(parent, id, message)} + return &UnimplementedError{CreateCaosError(parent, id, message)} } func ThrowUnimplementedf(parent error, id, format string, a ...interface{}) error { diff --git a/internal/errors/unknown.go b/internal/errors/unknown.go index 27d254adc4..fc1c62aa72 100644 --- a/internal/errors/unknown.go +++ b/internal/errors/unknown.go @@ -19,7 +19,7 @@ type UnknownError struct { } func ThrowUnknown(parent error, id, message string) error { - return &UnknownError{createCaosError(parent, id, message)} + return &UnknownError{CreateCaosError(parent, id, message)} } func ThrowUnknownf(parent error, id, format string, a ...interface{}) error { diff --git a/internal/eventstore/eventstore.go b/internal/eventstore/eventstore.go index 724541419b..63e1e542e7 100644 --- a/internal/eventstore/eventstore.go +++ b/internal/eventstore/eventstore.go @@ -33,7 +33,7 @@ func (es *eventstore) PushAggregates(ctx context.Context, aggregates ...*models. } for _, event := range aggregate.Events { if err = event.Validate(); err != nil { - return err + return errors.ThrowInvalidArgument(err, "EVENT-tzIhl", "validate event failed") } } } @@ -42,12 +42,6 @@ func (es *eventstore) PushAggregates(ctx context.Context, aggregates ...*models. return err } - for _, aggregate := range aggregates { - if aggregate.Appender != nil { - aggregate.Appender(aggregate.Events...) - } - } - return nil } diff --git a/internal/eventstore/models/aggregate.go b/internal/eventstore/models/aggregate.go index 0c8b42de7f..e0bb4d2c0d 100644 --- a/internal/eventstore/models/aggregate.go +++ b/internal/eventstore/models/aggregate.go @@ -24,11 +24,8 @@ type Aggregate struct { editorUser string resourceOwner string Events []*Event - Appender appender } -type appender func(...*Event) - func (a *Aggregate) AppendEvent(typ EventType, payload interface{}) (*Aggregate, error) { if string(typ) == "" { return a, errors.ThrowInvalidArgument(nil, "MODEL-TGoCb", "no event type") @@ -81,8 +78,3 @@ func (a *Aggregate) Validate() error { return nil } - -func (a *Aggregate) SetAppender(appendFn appender) *Aggregate { - a.Appender = appendFn - return a -} diff --git a/internal/eventstore/sdk/append_event_error.go b/internal/eventstore/sdk/append_event_error.go new file mode 100644 index 0000000000..bb1bee3a3a --- /dev/null +++ b/internal/eventstore/sdk/append_event_error.go @@ -0,0 +1,36 @@ +package sdk + +import ( + "fmt" + + "github.com/caos/zitadel/internal/errors" +) + +var ( + _ AppendEventError = (*appendEventError)(nil) + _ errors.Error = (*appendEventError)(nil) +) + +type AppendEventError interface { + error + IsAppendEventError() +} + +type appendEventError struct { + *errors.CaosError +} + +func ThrowAppendEventError(parent error, id, message string) error { + return &appendEventError{errors.CreateCaosError(parent, id, message)} +} + +func ThrowAggregaterf(parent error, id, format string, a ...interface{}) error { + return ThrowAppendEventError(parent, id, fmt.Sprintf(format, a...)) +} + +func (err *appendEventError) IsAppendEventError() {} + +func IsAppendEventError(err error) bool { + _, ok := err.(AppendEventError) + return ok +} diff --git a/internal/eventstore/sdk/append_event_error_test.go b/internal/eventstore/sdk/append_event_error_test.go new file mode 100644 index 0000000000..cb1c67cd2c --- /dev/null +++ b/internal/eventstore/sdk/append_event_error_test.go @@ -0,0 +1,31 @@ +package sdk + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestAppendEventError(t *testing.T) { + var err interface{} + err = new(appendEventError) + _, ok := err.(*appendEventError) + assert.True(t, ok) +} + +func TestThrowAppendEventErrorf(t *testing.T) { + err := ThrowAggregaterf(nil, "id", "msg") + _, ok := err.(*appendEventError) + assert.True(t, ok) +} + +func TestIsAppendEventError(t *testing.T) { + err := ThrowAppendEventError(nil, "id", "msg") + ok := IsAppendEventError(err) + assert.True(t, ok) + + err = errors.New("i am found") + ok = IsAppendEventError(err) + assert.False(t, ok) +} diff --git a/internal/eventstore/sdk/sdk.go b/internal/eventstore/sdk/sdk.go new file mode 100644 index 0000000000..c00b8b79a8 --- /dev/null +++ b/internal/eventstore/sdk/sdk.go @@ -0,0 +1,72 @@ +package sdk + +import ( + "context" + + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore/models" + es_models "github.com/caos/zitadel/internal/eventstore/models" +) + +type filterFunc func(context.Context, *es_models.SearchQuery) ([]*es_models.Event, error) +type appendFunc func(...*es_models.Event) error +type aggregateFunc func(context.Context) (*es_models.Aggregate, error) +type pushFunc func(context.Context, ...*es_models.Aggregate) error + +func Filter(ctx context.Context, filter filterFunc, appender appendFunc, query *es_models.SearchQuery) error { + events, err := filter(ctx, query) + if err != nil { + return err + } + if len(events) == 0 { + return errors.ThrowNotFound(nil, "EVENT-8due3", "no events found") + } + err = appender(events...) + if err != nil{ + return ThrowAppendEventError(err, "SDK-awiWK", "appender failed") + } + return nil +} + +// Push creates the aggregates from aggregater +// and pushes the aggregates to the given pushFunc +// the given events are appended by the appender +func Push(ctx context.Context, push pushFunc, appender appendFunc, aggregaters ...aggregateFunc) (err error) { + if len(aggregaters) < 1 { + return errors.ThrowPreconditionFailed(nil, "SDK-q9wjp", "no aggregaters passed") + } + + aggregates, err := makeAggregates(ctx, aggregaters) + if err != nil { + return err + } + + err = push(ctx, aggregates...) + if err != nil { + return err + } + + + return appendAggregates(appender, aggregates) +} + +func appendAggregates(appender appendFunc, aggregates []*models.Aggregate) error { + for _, aggregate := range aggregates { + err := appender(aggregate.Events...) + if err != nil { + return ThrowAppendEventError(err, "SDK-o6kzK", "aggregator failed") + } + } + return nil +} + +func makeAggregates(ctx context.Context, aggregaters []aggregateFunc) (aggregates []*models.Aggregate, err error) { + aggregates = make([]*models.Aggregate, len(aggregaters)) + for i, aggregater := range aggregaters { + aggregates[i], err = aggregater(ctx) + if err != nil { + return nil, err + } + } + return aggregates, nil +} \ No newline at end of file diff --git a/internal/eventstore/sdk/sdk_test.go b/internal/eventstore/sdk/sdk_test.go new file mode 100644 index 0000000000..63021321b4 --- /dev/null +++ b/internal/eventstore/sdk/sdk_test.go @@ -0,0 +1,193 @@ +package sdk + +import ( + "context" + "testing" + + "github.com/caos/zitadel/internal/errors" + es_models "github.com/caos/zitadel/internal/eventstore/models" +) + +func TestFilter(t *testing.T) { + type args struct { + filter filterFunc + appender appendFunc + } + tests := []struct { + name string + args args + wantErr func(error) bool + }{ + { + name: "filter error", + args: args{ + filter: func(context.Context, *es_models.SearchQuery) ([]*es_models.Event, error) { + return nil, errors.ThrowInternal(nil, "test-46VX2", "test error") + }, + appender: nil, + }, + wantErr: errors.IsInternal, + }, + { + name: "no events found", + args: args{ + filter: func(context.Context, *es_models.SearchQuery) ([]*es_models.Event, error) { + return []*es_models.Event{}, nil + }, + appender: nil, + }, + wantErr: errors.IsNotFound, + }, + { + name: "append fails", + args: args{ + filter: func(context.Context, *es_models.SearchQuery) ([]*es_models.Event, error) { + return []*es_models.Event{&es_models.Event{}}, nil + }, + appender: func(...*es_models.Event) error { + return errors.ThrowInvalidArgument(nil, "SDK-DhBzl", "test error") + }, + }, + wantErr: IsAppendEventError, + }, + { + name: "filter correct", + args: args{ + filter: func(context.Context, *es_models.SearchQuery) ([]*es_models.Event, error) { + return []*es_models.Event{&es_models.Event{}}, nil + }, + appender: func(...*es_models.Event) error { + return nil + }, + }, + wantErr: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := Filter(context.Background(), tt.args.filter, tt.args.appender, nil) + if tt.wantErr == nil && err != nil { + t.Errorf("no error expected %v", err) + } + if tt.wantErr != nil && !tt.wantErr(err) { + t.Errorf("no error has wrong type %v", err) + } + }) + } +} + +func TestPush(t *testing.T) { + type args struct { + push pushFunc + appender appendFunc + aggregaters []aggregateFunc + } + tests := []struct { + name string + args args + wantErr func(error) bool + }{ + { + name: "no aggregates", + args: args{ + push: nil, + appender: nil, + aggregaters: nil, + }, + wantErr: errors.IsPreconditionFailed, + }, + { + name: "aggregater fails", + args: args{ + push: nil, + appender: nil, + aggregaters: []aggregateFunc{ + func(context.Context) (*es_models.Aggregate, error) { + return nil, errors.ThrowInternal(nil, "SDK-Ec5x2", "test err") + }, + }, + }, + wantErr: errors.IsInternal, + }, + { + name: "push fails", + args: args{ + push: func(context.Context, ...*es_models.Aggregate) error { + return errors.ThrowInternal(nil, "SDK-0g4gW", "test error") + }, + appender: nil, + aggregaters: []aggregateFunc{ + func(context.Context) (*es_models.Aggregate, error) { + return &es_models.Aggregate{}, nil + }, + }, + }, + wantErr: errors.IsInternal, + }, + { + name: "append aggregates fails", + args: args{ + push: func(context.Context, ...*es_models.Aggregate) error { + return nil + }, + appender: func(...*es_models.Event) error { + return errors.ThrowInvalidArgument(nil, "SDK-BDhcT", "test err") + }, + aggregaters: []aggregateFunc{ + func(context.Context) (*es_models.Aggregate, error) { + return &es_models.Aggregate{Events: []*es_models.Event{&es_models.Event{}}}, nil + }, + }, + }, + wantErr: IsAppendEventError, + }, + { + name: "correct one aggregate", + args: args{ + push: func(context.Context, ...*es_models.Aggregate) error { + return nil + }, + appender: func(...*es_models.Event) error { + return nil + }, + aggregaters: []aggregateFunc{ + func(context.Context) (*es_models.Aggregate, error) { + return &es_models.Aggregate{Events: []*es_models.Event{&es_models.Event{}}}, nil + }, + }, + }, + wantErr: nil, + }, + { + name: "correct multiple aggregate", + args: args{ + push: func(context.Context, ...*es_models.Aggregate) error { + return nil + }, + appender: func(...*es_models.Event) error { + return nil + }, + aggregaters: []aggregateFunc{ + func(context.Context) (*es_models.Aggregate, error) { + return &es_models.Aggregate{Events: []*es_models.Event{&es_models.Event{}}}, nil + }, + func(context.Context) (*es_models.Aggregate, error) { + return &es_models.Aggregate{Events: []*es_models.Event{&es_models.Event{}}}, nil + }, + }, + }, + wantErr: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := Push(context.Background(), tt.args.push, tt.args.appender, tt.args.aggregaters...) + if tt.wantErr == nil && err != nil { + t.Errorf("no error expected %v", err) + } + if tt.wantErr != nil && !tt.wantErr(err) { + t.Errorf("no error has wrong type %v", err) + } + }) + } +} diff --git a/internal/management/repository/eventsourcing/project.go b/internal/management/repository/eventsourcing/project.go index 5b3f75ab3a..3dbd1e3bbc 100644 --- a/internal/management/repository/eventsourcing/project.go +++ b/internal/management/repository/eventsourcing/project.go @@ -2,6 +2,7 @@ package eventsourcing import ( "context" + proj_model "github.com/caos/zitadel/internal/project/model" proj_event "github.com/caos/zitadel/internal/project/repository/eventsourcing" ) @@ -26,11 +27,7 @@ func (repo *ProjectRepo) ProjectByID(ctx context.Context, id string) (project *p func (repo *ProjectRepo) CreateProject(ctx context.Context, name string) (*proj_model.Project, error) { project := &proj_model.Project{Name: name} - project, err := repo.ProjectEvents.CreateProject(ctx, project) - if err != nil { - return nil, err - } - return project, nil + return repo.ProjectEvents.CreateProject(ctx, project) } func (repo *ProjectRepo) UpdateProject(ctx context.Context, project *proj_model.Project) (*proj_model.Project, error) { @@ -39,11 +36,7 @@ func (repo *ProjectRepo) UpdateProject(ctx context.Context, project *proj_model. return nil, err } - project, err = repo.ProjectEvents.UpdateProject(ctx, existingProject, project) - if err != nil { - return nil, err - } - return project, err + return repo.ProjectEvents.UpdateProject(ctx, existingProject, project) } func (repo *ProjectRepo) DeactivateProject(ctx context.Context, id string) (*proj_model.Project, error) { @@ -52,11 +45,7 @@ func (repo *ProjectRepo) DeactivateProject(ctx context.Context, id string) (*pro return nil, err } - project, err = repo.ProjectEvents.DeactivateProject(ctx, project) - if err != nil { - return nil, err - } - return project, err + return repo.ProjectEvents.DeactivateProject(ctx, project) } func (repo *ProjectRepo) ReactivateProject(ctx context.Context, id string) (*proj_model.Project, error) { @@ -65,9 +54,5 @@ func (repo *ProjectRepo) ReactivateProject(ctx context.Context, id string) (*pro return nil, err } - project, err = repo.ProjectEvents.ReactivateProject(ctx, project) - if err != nil { - return nil, err - } - return project, err + return repo.ProjectEvents.ReactivateProject(ctx, project) } diff --git a/internal/project/repository/eventsourcing/eventstore.go b/internal/project/repository/eventsourcing/eventstore.go index ae395f091a..ea7fb70f82 100644 --- a/internal/project/repository/eventsourcing/eventstore.go +++ b/internal/project/repository/eventsourcing/eventstore.go @@ -2,8 +2,10 @@ package eventsourcing import ( "context" + caos_errs "github.com/caos/zitadel/internal/errors" es_int "github.com/caos/zitadel/internal/eventstore" + es_sdk "github.com/caos/zitadel/internal/eventstore/sdk" proj_model "github.com/caos/zitadel/internal/project/model" ) @@ -20,22 +22,17 @@ func StartProject(conf ProjectConfig) (*ProjectEventstore, error) { } func (es *ProjectEventstore) ProjectByID(ctx context.Context, project *proj_model.Project) (*proj_model.Project, error) { - filter, err := ProjectByIDQuery(project.ID, project.Sequence) + query, err := ProjectByIDQuery(project.ID, project.Sequence) if err != nil { return nil, err } - events, err := es.Eventstore.FilterEvents(ctx, filter) + + p := ProjectFromModel(project) + err = es_sdk.Filter(ctx, es.FilterEvents, p.AppendEvents, query) if err != nil { return nil, err } - if len(events) == 0 { - return nil, caos_errs.ThrowNotFound(nil, "EVENT-8due3", "Could not find project events") - } - foundProject, err := ProjectFromEvents(nil, events...) - if err != nil { - return nil, err - } - return ProjectToModel(foundProject), nil + return ProjectToModel(p), nil } func (es *ProjectEventstore) CreateProject(ctx context.Context, project *proj_model.Project) (*proj_model.Project, error) { @@ -44,34 +41,29 @@ func (es *ProjectEventstore) CreateProject(ctx context.Context, project *proj_mo } project.State = proj_model.Active repoProject := ProjectFromModel(project) - projectAggregate, err := ProjectCreateAggregate(ctx, es.Eventstore.AggregateCreator(), repoProject) - if err != nil { - return nil, err - } - err = es.PushAggregates(ctx, projectAggregate) + + createAggregate := ProjectCreateAggregate(es.AggregateCreator(), repoProject) + err := es_sdk.Push(ctx, es.PushAggregates, repoProject.AppendEvents, createAggregate) if err != nil { return nil, err } - repoProject.AppendEvents(projectAggregate.Events...) return ProjectToModel(repoProject), nil } -func (es *ProjectEventstore) UpdateProject(ctx context.Context, existing *proj_model.Project, new *proj_model.Project) (*proj_model.Project, error) { - if !new.IsValid() { +func (es *ProjectEventstore) UpdateProject(ctx context.Context, existingProject *proj_model.Project, project *proj_model.Project) (*proj_model.Project, error) { + if !project.IsValid() { return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-9dk45", "Name is required") } - repoExisting := ProjectFromModel(existing) - repoNew := ProjectFromModel(new) - projectAggregate, err := ProjectUpdateAggregate(ctx, es.AggregateCreator(), repoExisting, repoNew) + repoExisting := ProjectFromModel(existingProject) + repoNew := ProjectFromModel(project) + + updateAggregate := ProjectUpdateAggregate(es.AggregateCreator(), repoExisting, repoNew) + err := es_sdk.Push(ctx, es.PushAggregates, repoExisting.AppendEvents, updateAggregate) if err != nil { return nil, err } - err = es.PushAggregates(ctx, projectAggregate) - if err != nil { - return nil, err - } - repoExisting.AppendEvents(projectAggregate.Events...) + return ProjectToModel(repoExisting), nil } @@ -79,16 +71,10 @@ func (es *ProjectEventstore) DeactivateProject(ctx context.Context, existing *pr if !existing.IsActive() { return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-die45", "project must be active") } + repoExisting := ProjectFromModel(existing) - projectAggregate, err := ProjectDeactivateAggregate(ctx, es.AggregateCreator(), repoExisting) - if err != nil { - return nil, err - } - err = es.PushAggregates(ctx, projectAggregate) - if err != nil { - return nil, err - } - repoExisting.AppendEvents(projectAggregate.Events...) + aggregate := ProjectDeactivateAggregate(es.AggregateCreator(), repoExisting) + es_sdk.Push(ctx, es.PushAggregates, repoExisting.AppendEvents, aggregate) return ProjectToModel(repoExisting), nil } @@ -96,15 +82,9 @@ func (es *ProjectEventstore) ReactivateProject(ctx context.Context, existing *pr if existing.IsActive() { return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-die45", "project must be inactive") } + repoExisting := ProjectFromModel(existing) - projectAggregate, err := ProjectReactivateAggregate(ctx, es.AggregateCreator(), repoExisting) - if err != nil { - return nil, err - } - err = es.PushAggregates(ctx, projectAggregate) - if err != nil { - return nil, err - } - repoExisting.AppendEvents(projectAggregate.Events...) + aggregate := ProjectReactivateAggregate(es.AggregateCreator(), repoExisting) + es_sdk.Push(ctx, es.PushAggregates, repoExisting.AppendEvents, aggregate) return ProjectToModel(repoExisting), nil } diff --git a/internal/project/repository/eventsourcing/eventstore_mock.go b/internal/project/repository/eventsourcing/eventstore_mock_test.go similarity index 100% rename from internal/project/repository/eventsourcing/eventstore_mock.go rename to internal/project/repository/eventsourcing/eventstore_mock_test.go diff --git a/internal/project/repository/eventsourcing/events.go b/internal/project/repository/eventsourcing/model.go similarity index 56% rename from internal/project/repository/eventsourcing/events.go rename to internal/project/repository/eventsourcing/model.go index 8afd4d842d..e8214d5c3e 100644 --- a/internal/project/repository/eventsourcing/events.go +++ b/internal/project/repository/eventsourcing/model.go @@ -2,11 +2,48 @@ package eventsourcing import ( "encoding/json" + "github.com/caos/logging" es_models "github.com/caos/zitadel/internal/eventstore/models" "github.com/caos/zitadel/internal/project/model" ) +const ( + projectVersion = "v1" +) + +type Project struct { + es_models.ObjectRoot + Name string `json:"name,omitempty"` + State int32 `json:"-"` +} + +func ProjectFromModel(project *model.Project) *Project { + return &Project{ + ObjectRoot: es_models.ObjectRoot{ + ID: project.ObjectRoot.ID, + Sequence: project.Sequence, + ChangeDate: project.ChangeDate, + CreationDate: project.CreationDate, + }, + Name: project.Name, + State: model.ProjectStateToInt(project.State), + } +} + +func ProjectToModel(project *Project) *model.Project { + return &model.Project{ + ObjectRoot: es_models.ObjectRoot{ + ID: project.ID, + ChangeDate: project.ChangeDate, + CreationDate: project.CreationDate, + Sequence: project.Sequence, + }, + Name: project.Name, + State: model.ProjectStateFromInt(project.State), + } +} + func ProjectFromEvents(project *Project, events ...*es_models.Event) (*Project, error) { if project == nil { project = &Project{} @@ -15,6 +52,14 @@ func ProjectFromEvents(project *Project, events ...*es_models.Event) (*Project, return project, project.AppendEvents(events...) } +func (p *Project) Changes(changed *Project) map[string]interface{} { + changes := make(map[string]interface{}, 1) + if changed.Name != "" && p.Name != changed.Name { + changes["name"] = changed.Name + } + return changes +} + func (p *Project) AppendEvents(events ...*es_models.Event) error { for _, event := range events { if err := p.AppendEvent(event); err != nil { diff --git a/internal/project/repository/eventsourcing/events_test.go b/internal/project/repository/eventsourcing/model_test.go similarity index 84% rename from internal/project/repository/eventsourcing/events_test.go rename to internal/project/repository/eventsourcing/model_test.go index f95a11b69a..cb15326875 100644 --- a/internal/project/repository/eventsourcing/events_test.go +++ b/internal/project/repository/eventsourcing/model_test.go @@ -2,9 +2,10 @@ package eventsourcing import ( "encoding/json" + "testing" + es_models "github.com/caos/zitadel/internal/eventstore/models" "github.com/caos/zitadel/internal/project/model" - "testing" ) func TestProjectFromEvents(t *testing.T) { @@ -167,3 +168,47 @@ func TestAppendReactivatedEvent(t *testing.T) { }) } } + +func TestChanges(t *testing.T) { + type args struct { + existing *Project + new *Project + } + type res struct { + changesLen int + } + tests := []struct { + name string + args args + res res + }{ + { + name: "project name changes", + args: args{ + existing: &Project{Name: "Name"}, + new: &Project{Name: "NameChanged"}, + }, + res: res{ + changesLen: 1, + }, + }, + { + name: "no changes", + args: args{ + existing: &Project{Name: "Name"}, + new: &Project{Name: "Name"}, + }, + res: res{ + changesLen: 0, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + changes := tt.args.existing.Changes(tt.args.new) + if len(changes) != tt.res.changesLen { + t.Errorf("got wrong changes len: expected: %v, actual: %v ", tt.res.changesLen, len(changes)) + } + }) + } +} diff --git a/internal/project/repository/eventsourcing/project.go b/internal/project/repository/eventsourcing/project.go index a4325b9340..5a73dce919 100644 --- a/internal/project/repository/eventsourcing/project.go +++ b/internal/project/repository/eventsourcing/project.go @@ -5,6 +5,7 @@ import ( "strconv" "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore/models" es_models "github.com/caos/zitadel/internal/eventstore/models" "github.com/caos/zitadel/internal/project/model" "github.com/sony/sonyflake" @@ -12,50 +13,6 @@ import ( var idGenerator = sonyflake.NewSonyflake(sonyflake.Settings{}) -const ( - projectVersion = "v1" -) - -type Project struct { - es_models.ObjectRoot - Name string `json:"name,omitempty"` - State int32 `json:"-"` -} - -func (p *Project) Changes(changed *Project) map[string]interface{} { - changes := make(map[string]interface{}, 1) - if changed.Name != "" && p.Name != changed.Name { - changes["name"] = changed.Name - } - return changes -} - -func ProjectFromModel(project *model.Project) *Project { - return &Project{ - ObjectRoot: es_models.ObjectRoot{ - ID: project.ObjectRoot.ID, - Sequence: project.Sequence, - ChangeDate: project.ChangeDate, - CreationDate: project.CreationDate, - }, - Name: project.Name, - State: model.ProjectStateToInt(project.State), - } -} - -func ProjectToModel(project *Project) *model.Project { - return &model.Project{ - ObjectRoot: es_models.ObjectRoot{ - ID: project.ID, - ChangeDate: project.ChangeDate, - CreationDate: project.CreationDate, - Sequence: project.Sequence, - }, - Name: project.Name, - State: model.ProjectStateFromInt(project.State), - } -} - func ProjectByIDQuery(id string, latestSequence uint64) (*es_models.SearchQuery, error) { if id == "" { return nil, errors.ThrowPreconditionFailed(nil, "EVENT-dke74", "id should be filled") @@ -74,58 +31,61 @@ func ProjectAggregate(ctx context.Context, aggCreator *es_models.AggregateCreato return aggCreator.NewAggregate(ctx, id, model.ProjectAggregate, projectVersion, sequence) } -func ProjectCreateAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, project *Project) (*es_models.Aggregate, error) { - if project == nil { - return nil, errors.ThrowPreconditionFailed(nil, "EVENT-kdie6", "project should not be nil") - } - var err error - id, err := idGenerator.NextID() - if err != nil { - return nil, err - } - project.ID = strconv.FormatUint(id, 10) +func ProjectCreateAggregate(aggCreator *es_models.AggregateCreator, project *Project) func(ctx context.Context) (*es_models.Aggregate, error) { + return func(ctx context.Context) (*es_models.Aggregate, error) { + if project == nil { + return nil, errors.ThrowPreconditionFailed(nil, "EVENT-kdie6", "project should not be nil") + } + var err error + id, err := idGenerator.NextID() + if err != nil { + return nil, err + } + project.ID = strconv.FormatUint(id, 10) - agg, err := ProjectAggregate(ctx, aggCreator, project.ID, project.Sequence) - if err != nil { - return nil, err - } + agg, err := ProjectAggregate(ctx, aggCreator, project.ID, project.Sequence) + if err != nil { + return nil, err + } - return agg.AppendEvent(model.ProjectAdded, project) + return agg.AppendEvent(model.ProjectAdded, project) + } } -func ProjectUpdateAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, existing *Project, new *Project) (*es_models.Aggregate, error) { - if existing == nil { - return nil, errors.ThrowPreconditionFailed(nil, "EVENT-dk93d", "existing project should not be nil") +func ProjectUpdateAggregate(aggCreator *es_models.AggregateCreator, existing *Project, new *Project) func(ctx context.Context) (*es_models.Aggregate, error) { + return func(ctx context.Context) (*es_models.Aggregate, error) { + if existing == nil { + return nil, errors.ThrowPreconditionFailed(nil, "EVENT-dk93d", "existing project should not be nil") + } + if new == nil { + return nil, errors.ThrowPreconditionFailed(nil, "EVENT-dhr74", "new project should not be nil") + } + agg, err := ProjectAggregate(ctx, aggCreator, existing.ID, existing.Sequence) + if err != nil { + return nil, err + } + changes := existing.Changes(new) + return agg.AppendEvent(model.ProjectChanged, changes) } - if new == nil { - return nil, errors.ThrowPreconditionFailed(nil, "EVENT-dhr74", "new project should not be nil") - } - agg, err := ProjectAggregate(ctx, aggCreator, existing.ID, existing.Sequence) - if err != nil { - return nil, err - } - changes := existing.Changes(new) - return agg.AppendEvent(model.ProjectChanged, changes) } -func ProjectDeactivateAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, existing *Project) (*es_models.Aggregate, error) { - if existing == nil { - return nil, errors.ThrowPreconditionFailed(nil, "EVENT-ueh45", "existing project should not be nil") - } - agg, err := ProjectAggregate(ctx, aggCreator, existing.ID, existing.Sequence) - if err != nil { - return nil, err - } - return agg.AppendEvent(model.ProjectDeactivated, nil) +func ProjectDeactivateAggregate(aggCreator *es_models.AggregateCreator, project *Project) func(ctx context.Context) (*es_models.Aggregate, error) { + return projectStateAggregate(aggCreator, project, model.ProjectDeactivated) } -func ProjectReactivateAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, existing *Project) (*es_models.Aggregate, error) { - if existing == nil { - return nil, errors.ThrowPreconditionFailed(nil, "EVENT-37dur", "existing project should not be nil") - } - agg, err := ProjectAggregate(ctx, aggCreator, existing.ID, existing.Sequence) - if err != nil { - return nil, err - } - return agg.AppendEvent(model.ProjectReactivated, nil) +func ProjectReactivateAggregate(aggCreator *es_models.AggregateCreator, project *Project) func(ctx context.Context) (*es_models.Aggregate, error) { + return projectStateAggregate(aggCreator, project, model.ProjectReactivated) +} + +func projectStateAggregate(aggCreator *es_models.AggregateCreator, project *Project, state models.EventType) func(ctx context.Context) (*es_models.Aggregate, error) { + return func(ctx context.Context) (*es_models.Aggregate, error) { + if project == nil { + return nil, errors.ThrowPreconditionFailed(nil, "EVENT-37dur", "existing project should not be nil") + } + agg, err := ProjectAggregate(ctx, aggCreator, project.ID, project.Sequence) + if err != nil { + return nil, err + } + return agg.AppendEvent(state, nil) + } } diff --git a/internal/project/repository/eventsourcing/project_test.go b/internal/project/repository/eventsourcing/project_test.go index f63ad67217..7ae064129c 100644 --- a/internal/project/repository/eventsourcing/project_test.go +++ b/internal/project/repository/eventsourcing/project_test.go @@ -2,57 +2,14 @@ package eventsourcing import ( "context" + "testing" + "github.com/caos/zitadel/internal/api/auth" caos_errs "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/eventstore/models" "github.com/caos/zitadel/internal/project/model" - "testing" ) -func TestChanges(t *testing.T) { - type args struct { - existing *Project - new *Project - } - type res struct { - changesLen int - } - tests := []struct { - name string - args args - res res - }{ - { - name: "project name changes", - args: args{ - existing: &Project{Name: "Name"}, - new: &Project{Name: "NameChanged"}, - }, - res: res{ - changesLen: 1, - }, - }, - { - name: "no changes", - args: args{ - existing: &Project{Name: "Name"}, - new: &Project{Name: "Name"}, - }, - res: res{ - changesLen: 0, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - changes := tt.args.existing.Changes(tt.args.new) - if len(changes) != tt.res.changesLen { - t.Errorf("got wrong changes len: expected: %v, actual: %v ", tt.res.changesLen, len(changes)) - } - }) - } -} - func TestProjectByIDQuery(t *testing.T) { type args struct { id string @@ -231,7 +188,7 @@ func TestProjectCreateAggregate(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - agg, err := ProjectCreateAggregate(tt.args.ctx, tt.args.aggCreator, tt.args.new) + agg, err := ProjectCreateAggregate(tt.args.aggCreator, tt.args.new)(tt.args.ctx) if !tt.res.wantErr && len(agg.Events) != tt.res.eventLen { t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events)) @@ -312,7 +269,7 @@ func TestProjectUpdateAggregate(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - agg, err := ProjectUpdateAggregate(tt.args.ctx, tt.args.aggCreator, tt.args.existing, tt.args.new) + agg, err := ProjectUpdateAggregate(tt.args.aggCreator, tt.args.existing, tt.args.new)(tt.args.ctx) if !tt.res.wantErr && len(agg.Events) != tt.res.eventLen { t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events)) @@ -376,7 +333,7 @@ func TestProjectDeactivateAggregate(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - agg, err := ProjectDeactivateAggregate(tt.args.ctx, tt.args.aggCreator, tt.args.existing) + agg, err := ProjectDeactivateAggregate(tt.args.aggCreator, tt.args.existing)(tt.args.ctx) if !tt.res.wantErr && len(agg.Events) != tt.res.eventLen { t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events)) @@ -437,7 +394,7 @@ func TestProjectReactivateAggregate(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - agg, err := ProjectReactivateAggregate(tt.args.ctx, tt.args.aggCreator, tt.args.existing) + agg, err := ProjectReactivateAggregate(tt.args.aggCreator, tt.args.existing)(tt.args.ctx) if !tt.res.wantErr && len(agg.Events) != tt.res.eventLen { t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events))