From e4d8478b044fb9c045122fa05f0f2d376e35dc14 Mon Sep 17 00:00:00 2001 From: adlerhurst Date: Wed, 30 Sep 2020 10:00:05 +0200 Subject: [PATCH] start sqlite migrations --- .gitignore | 1 + internal/eventstore/v2/aggregate.go | 22 - internal/eventstore/v2/blub_test.go | 283 ++++++++ internal/eventstore/v2/eventstore.go | 203 +++++- internal/eventstore/v2/eventstore_memory.go | 28 - internal/eventstore/v2/eventstore_test.go | 272 +++++++- internal/eventstore/v2/read_model.go | 3 - .../eventstore/v2/repository/aggregate.go | 22 + .../eventstore/v2/{ => repository}/event.go | 10 +- .../eventstore/v2/repository/in_memory.go | 82 +++ .../eventstore/v2/repository/repository.go | 17 + .../{filter.go => repository/search_query.go} | 28 +- .../search_query_test.go} | 2 +- internal/eventstore/v2/repository/version.go | 18 + .../v2/{ => repository}/version_test.go | 2 +- internal/eventstore/v2/search_query.go | 65 +- internal/eventstore/v2/search_query_test.go | 97 +-- internal/eventstore/v2/version.go | 15 +- migrations/sqlite/V1.0__databases.sql | 36 + migrations/sqlite/V1.10__user_machine.sql | 15 + migrations/sqlite/V1.11__usermembership.sql | 18 + migrations/sqlite/V1.12__machine_keys.sql | 14 + .../sqlite/V1.13__machine_keys_public.sql | 5 + migrations/sqlite/V1.14__auth_loginpolicy.sql | 105 +++ migrations/sqlite/V1.15__idp_providers.sql | 3 + migrations/sqlite/V1.1__eventstore.sql | 18 + migrations/sqlite/V1.2__views.sql | 628 ++++++++++++++++++ migrations/sqlite/V1.3__usermembership.sql | 15 + migrations/sqlite/V1.4__compliance.sql | 14 + .../sqlite/V1.5__orgdomain_validationtype.sql | 44 ++ migrations/sqlite/V1.6__origin_allow_list.sql | 15 + migrations/sqlite/V1.7__idps.sql | 105 +++ migrations/sqlite/V1.8__username_change.sql | 7 + migrations/sqlite/V1.9__token.sql | 2 + migrations/sqlite/clean_local.go | 5 + migrations/sqlite/migrate_local.go | 5 + 36 files changed, 2044 insertions(+), 180 deletions(-) delete mode 100644 internal/eventstore/v2/aggregate.go create mode 100644 internal/eventstore/v2/blub_test.go delete mode 100644 internal/eventstore/v2/eventstore_memory.go delete mode 100644 internal/eventstore/v2/read_model.go create mode 100644 internal/eventstore/v2/repository/aggregate.go rename internal/eventstore/v2/{ => repository}/event.go (92%) create mode 100644 internal/eventstore/v2/repository/in_memory.go create mode 100644 internal/eventstore/v2/repository/repository.go rename internal/eventstore/v2/{filter.go => repository/search_query.go} (58%) rename internal/eventstore/v2/{filter_test.go => repository/search_query_test.go} (99%) create mode 100644 internal/eventstore/v2/repository/version.go rename internal/eventstore/v2/{ => repository}/version_test.go (96%) create mode 100644 migrations/sqlite/V1.0__databases.sql create mode 100644 migrations/sqlite/V1.10__user_machine.sql create mode 100644 migrations/sqlite/V1.11__usermembership.sql create mode 100644 migrations/sqlite/V1.12__machine_keys.sql create mode 100644 migrations/sqlite/V1.13__machine_keys_public.sql create mode 100644 migrations/sqlite/V1.14__auth_loginpolicy.sql create mode 100644 migrations/sqlite/V1.15__idp_providers.sql create mode 100644 migrations/sqlite/V1.1__eventstore.sql create mode 100644 migrations/sqlite/V1.2__views.sql create mode 100644 migrations/sqlite/V1.3__usermembership.sql create mode 100644 migrations/sqlite/V1.4__compliance.sql create mode 100644 migrations/sqlite/V1.5__orgdomain_validationtype.sql create mode 100644 migrations/sqlite/V1.6__origin_allow_list.sql create mode 100644 migrations/sqlite/V1.7__idps.sql create mode 100644 migrations/sqlite/V1.8__username_change.sql create mode 100644 migrations/sqlite/V1.9__token.sql create mode 100644 migrations/sqlite/clean_local.go create mode 100644 migrations/sqlite/migrate_local.go diff --git a/.gitignore b/.gitignore index 4b64bce23b..1c5e44c54d 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ key.json .backups cockroach-data/* +.local/* .build/ #binaries diff --git a/internal/eventstore/v2/aggregate.go b/internal/eventstore/v2/aggregate.go deleted file mode 100644 index 08c39b4084..0000000000 --- a/internal/eventstore/v2/aggregate.go +++ /dev/null @@ -1,22 +0,0 @@ -package eventstore - -//AggregateType is the object name -type AggregateType string - -//Aggregate represents an object -type Aggregate struct { - //ID id is the unique identifier of the aggregate - // the client must generate it by it's own - ID string - //Type describes the meaning of this aggregate - // it could an object like user - Type AggregateType - - //ResourceOwner is the organisation which owns this aggregate - // an aggregate can only be managed by one organisation - // use the ID of the org - ResourceOwner string - - //Events describe all the changes made on an aggregate - Events []*Event -} diff --git a/internal/eventstore/v2/blub_test.go b/internal/eventstore/v2/blub_test.go new file mode 100644 index 0000000000..1f1703de97 --- /dev/null +++ b/internal/eventstore/v2/blub_test.go @@ -0,0 +1,283 @@ +package eventstore_test + +import ( + "encoding/json" + "fmt" + "log" +) + +//ReadModel is the minimum representation of a View model. +// it might be saved in a database or in memory +type ReadModel struct { + ProcessedSequence uint64 + ID string + events []Event +} + +//Append adds all the events to the aggregate. +// The function doesn't compute the new state of the read model +func (a *ReadModel) Append(events ...Event) { + a.events = append(a.events, events...) +} + +type ProjectReadModel struct { + ReadModel + Apps []*AppReadModel + Name string +} + +func (p *ProjectReadModel) Append(events ...Event) { + for _, event := range events { + switch event.(type) { + case *AddAppEvent: + app := new(AppReadModel) + app.Append(event) + p.Apps = append(p.Apps, app) + case *UpdateAppEvent: + for _, app := range p.Apps { + app.Append(event) + } + } + } + p.events = append(p.events, events...) +} + +//Reduce calculates the new state of the read model +func (p *ProjectReadModel) Reduce() error { + for i := range p.Apps { + if err := p.Apps[i].Reduce(); err != nil { + return err + } + } + for _, event := range p.events { + switch e := event.(type) { + case *CreateProjectEvent: + p.ID = e.ID + p.Name = e.Name + case *RemoveAppEvent: + for i := len(p.Apps) - 1; i >= 0; i-- { + app := p.Apps[i] + if app.ID == e.GetID() { + p.Apps[i] = p.Apps[len(p.Apps)-1] + p.Apps[len(p.Apps)-1] = nil + p.Apps = p.Apps[:len(p.Apps)-1] + } + } + } + p.ProcessedSequence = event.GetSequence() + } + return nil +} + +type AppReadModel struct { + ReadModel + Name string +} + +//Reduce calculates the new state of the read model +func (a *AppReadModel) Reduce() error { + for _, event := range a.events { + switch e := event.(type) { + case *AddAppEvent: + a.Name = e.Name + a.ID = e.GetID() + case *UpdateAppEvent: + a.Name = e.Name + } + a.ProcessedSequence = event.GetSequence() + } + return nil +} + +//Event is the minimal representation of a event +// which can be processed by the read models +type Event interface { + //GetSequence returns the event sequence + GetSequence() uint64 + //GetID returns the id of the aggregate. It's not the id of the event + GetID() string +} + +//DefaultEvent is the implementation of Event +type DefaultEvent struct { + Sequence uint64 `json:"-"` + ID string `json:"-"` +} + +func (e *DefaultEvent) GetID() string { + return e.ID +} + +func (e *DefaultEvent) GetSequence() uint64 { + return e.Sequence +} + +type CreateProjectEvent struct { + DefaultEvent + Name string `json:"name,omitempty"` +} + +//CreateProjectEventFromEventstore returns the specific type +// of the general EventstoreEvent +func CreateProjectEventFromEventstore(event *EventstoreEvent) (Event, error) { + e := &CreateProjectEvent{ + DefaultEvent: DefaultEvent{Sequence: event.Sequence, ID: event.AggregateID}, + } + err := json.Unmarshal(event.Data, e) + + return e, err +} + +type AddAppEvent struct { + ProjectID string `json:"-"` + AppID string `json:"id"` + Sequence uint64 `json:"-"` + Name string `json:"name,omitempty"` +} + +func (e *AddAppEvent) GetID() string { + return e.AppID +} + +func (e *AddAppEvent) GetSequence() uint64 { + return e.Sequence +} + +func AppAddedEventFromEventstore(event *EventstoreEvent) (Event, error) { + e := &AddAppEvent{ + Sequence: event.Sequence, + ProjectID: event.AggregateID, + } + err := json.Unmarshal(event.Data, e) + + return e, err +} + +type UpdateAppEvent struct { + ProjectID string `json:"-"` + AppID string `json:"id"` + Sequence uint64 `json:"-"` + Name string `json:"name,omitempty"` +} + +func (e *UpdateAppEvent) GetID() string { + return e.AppID +} + +func (e *UpdateAppEvent) GetSequence() uint64 { + return e.Sequence +} + +func AppUpdatedEventFromEventstore(event *EventstoreEvent) (Event, error) { + e := &UpdateAppEvent{ + Sequence: event.Sequence, + ProjectID: event.AggregateID, + } + err := json.Unmarshal(event.Data, e) + + return e, err +} + +type RemoveAppEvent struct { + ProjectID string `json:"-"` + AppID string `json:"id"` + Sequence uint64 `json:"-"` +} + +func (e *RemoveAppEvent) GetID() string { + return e.AppID +} + +func (e *RemoveAppEvent) GetSequence() uint64 { + return e.Sequence +} + +func AppRemovedEventFromEventstore(event *EventstoreEvent) (Event, error) { + e := &RemoveAppEvent{ + Sequence: event.Sequence, + ProjectID: event.AggregateID, + } + err := json.Unmarshal(event.Data, e) + + return e, err +} + +func main() { + eventstore := &Eventstore{ + eventMapper: map[string]func(*EventstoreEvent) (Event, error){ + "project.added": CreateProjectEventFromEventstore, + "app.added": AppAddedEventFromEventstore, + "app.updated": AppUpdatedEventFromEventstore, + "app.removed": AppRemovedEventFromEventstore, + }, + events: []*EventstoreEvent{ + { + AggregateID: "p1", + EventType: "project.added", + Sequence: 1, + Data: []byte(`{"name":"hodor"}`), + }, + { + AggregateID: "123", + EventType: "app.added", + Sequence: 2, + Data: []byte(`{"id":"a1", "name": "ap 1"}`), + }, + { + AggregateID: "123", + EventType: "app.updated", + Sequence: 3, + Data: []byte(`{"id":"a1", "name":"app 1"}`), + }, + { + AggregateID: "123", + EventType: "app.added", + Sequence: 4, + Data: []byte(`{"id":"a2", "name": "app 2"}`), + }, + { + AggregateID: "123", + EventType: "app.removed", + Sequence: 5, + Data: []byte(`{"id":"a1"}`), + }, + }, + } + events, err := eventstore.GetEvents() + if err != nil { + log.Panic(err) + } + + p := &ProjectReadModel{Apps: []*AppReadModel{}} + p.Append(events...) + p.Reduce() + + fmt.Printf("%+v\n", p) + for _, app := range p.Apps { + fmt.Printf("%+v\n", app) + } +} + +//Eventstore is a simple abstraction of the eventstore framework +type Eventstore struct { + eventMapper map[string]func(*EventstoreEvent) (Event, error) + events []*EventstoreEvent +} + +func (es *Eventstore) GetEvents() (events []Event, err error) { + events = make([]Event, len(es.events)) + for i, event := range es.events { + events[i], err = es.eventMapper[event.EventType](event) + if err != nil { + return nil, err + } + } + return events, nil +} + +type EventstoreEvent struct { + AggregateID string + Sequence uint64 + EventType string + Data []byte +} diff --git a/internal/eventstore/v2/eventstore.go b/internal/eventstore/v2/eventstore.go index c0bdb928d4..afa497f365 100644 --- a/internal/eventstore/v2/eventstore.go +++ b/internal/eventstore/v2/eventstore.go @@ -2,11 +2,204 @@ package eventstore import ( "context" + "sync" + + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore/v2/repository" ) -type Eventstore interface { - Health(ctx context.Context) error - PushAggregates(ctx context.Context, aggregates ...*Aggregate) error - FilterEvents(ctx context.Context, searchQuery *SearchQueryFactory) (events []*Event, err error) - LatestSequence(ctx context.Context, searchQuery *SearchQueryFactory) (uint64, error) +type Event interface { + //CheckPrevious ensures the event order if true + // if false the previous sequence is not checked on push + CheckPrevious() bool + + EditorService() string + EditorUser() string + Type() EventType + Data() interface{} + PreviousSequence() uint64 +} + +type eventAppender interface { + //AppendEvents appends the passed events to an internal list of events + AppendEvents(...Event) error +} + +type reducer interface { + //Reduce handles the events of the internal events list + // it only appends the newly added events + Reduce() error +} +type aggregater interface { + eventAppender + reducer + //ID returns the aggreagte id + ID() string + //Type returns the aggregate type + Type() AggregateType + //Events returns the events which will be pushed + Events() []Event + //ResourceOwner returns the organisation id which manages this aggregate + ResourceOwner() string + //Version represents the semantic version of the aggregate + Version() Version +} +type readModeler interface { + eventAppender + reducer +} + +type Eventstore struct { + repo repository.Repository + interceptorMutex sync.Mutex + eventMapper map[EventType]eventTypeInterceptors +} + +type eventTypeInterceptors struct { + pushMapper func(Event) (*repository.Event, error) + filterMapper func(*repository.Event) (Event, error) +} + +//Health checks if the eventstore can properly work +// It checks if the repository can serve load +func (es *Eventstore) Health(ctx context.Context) error { + return es.repo.Health(ctx) +} + +//PushAggregates maps the events of all aggregates to an eventstore event +// based on the pushMapper +func (es *Eventstore) PushAggregates(ctx context.Context, aggregates ...aggregater) ([]Event, error) { + events, err := es.aggregatesToEvents(aggregates) + if err != nil { + return nil, err + } + + err = es.repo.Push(ctx, events...) + if err != nil { + return nil, err + } + + return es.mapEvents(events) +} + +func (es *Eventstore) aggregatesToEvents(aggregates []aggregater) ([]*repository.Event, error) { + events := make([]*repository.Event, 0, len(aggregates)) + for _, aggregate := range aggregates { + var previousEvent *repository.Event + for _, event := range aggregate.Events() { + events = append(events, &repository.Event{ + AggregateID: aggregate.ID(), + AggregateType: repository.AggregateType(aggregate.Type()), + ResourceOwner: aggregate.ResourceOwner(), + EditorService: event.EditorService(), + EditorUser: event.EditorUser(), + Type: repository.EventType(event.Type()), + Version: repository.Version(aggregate.Version()), + PreviousEvent: previousEvent, + Data: event.Data(), + }) + if previousEvent != nil && event.CheckPrevious() { + events[len(events)-1].PreviousSequence = event.PreviousSequence() + } + previousEvent = events[len(events)-1] + } + } + return events, nil +} + +//FilterEvents filters the stored events based on the searchQuery +// and maps the events to the defined event structs +func (es *Eventstore) FilterEvents(ctx context.Context, queryFactory *SearchQueryFactory) ([]Event, error) { + query, err := queryFactory.Build() + if err != nil { + return nil, err + } + events, err := es.repo.Filter(ctx, query) + if err != nil { + return nil, err + } + + return es.mapEvents(events) +} + +func (es *Eventstore) mapEvents(events []*repository.Event) (mappedEvents []Event, err error) { + mappedEvents = make([]Event, len(events)) + + es.interceptorMutex.Lock() + defer es.interceptorMutex.Unlock() + + for i, event := range events { + interceptors, ok := es.eventMapper[EventType(event.Type)] + if !ok || interceptors.filterMapper == nil { + return nil, errors.ThrowPreconditionFailed(nil, "V2-usujB", "event mapper not defined") + } + mappedEvents[i], err = interceptors.filterMapper(event) + if err != nil { + return nil, err + } + } + + return mappedEvents, nil +} + +//FilterToAggregate filters the events based on the searchQuery, appends all events to the aggregate and reduces the aggregate +func (es *Eventstore) FilterToAggregate(ctx context.Context, searchQuery *SearchQueryFactory, aggregate aggregater) (err error) { + events, err := es.FilterEvents(ctx, searchQuery) + if err != nil { + return err + } + if err = aggregate.AppendEvents(events...); err != nil { + return err + } + + return aggregate.Reduce() +} + +//FilterToReadModel filters the events based on the searchQuery, appends all events to the readModel and reduces the readModel +func (es *Eventstore) FilterToReadModel(ctx context.Context, searchQuery *SearchQueryFactory, readModel readModeler) (err error) { + events, err := es.FilterEvents(ctx, searchQuery) + if err != nil { + return err + } + if err = readModel.AppendEvents(events...); err != nil { + return err + } + + return readModel.Reduce() +} + +func (es *Eventstore) LatestSequence(ctx context.Context, searchQuery *SearchQueryFactory) (uint64, error) { + return 0, nil +} + +//RegisterPushEventMapper registers a function for mapping an eventstore event to an event +func (es *Eventstore) RegisterFilterEventMapper(eventType EventType, mapper func(*repository.Event) (Event, error)) error { + if eventType == "" || mapper == nil { + return errors.ThrowInvalidArgument(nil, "V2-IPpUR", "eventType and mapper must be filled") + } + + es.interceptorMutex.Lock() + defer es.interceptorMutex.Unlock() + + interceptor := es.eventMapper[eventType] + interceptor.filterMapper = mapper + es.eventMapper[eventType] = interceptor + + return nil +} + +//RegisterPushEventMapper registers a function for mapping an event to an eventstore event +func (es *Eventstore) RegisterPushEventMapper(eventType EventType, mapper func(Event) (*repository.Event, error)) error { + if eventType == "" || mapper == nil { + return errors.ThrowInvalidArgument(nil, "V2-Kexpp", "eventType and mapper must be filled") + } + + es.interceptorMutex.Lock() + defer es.interceptorMutex.Unlock() + + interceptor := es.eventMapper[eventType] + interceptor.pushMapper = mapper + es.eventMapper[eventType] = interceptor + + return nil } diff --git a/internal/eventstore/v2/eventstore_memory.go b/internal/eventstore/v2/eventstore_memory.go deleted file mode 100644 index 1ca94aa23b..0000000000 --- a/internal/eventstore/v2/eventstore_memory.go +++ /dev/null @@ -1,28 +0,0 @@ -package eventstore - -import "context" - -type inMemoryEventstore struct { - events []*Event -} - -func (es *inMemoryEventstore) Health(ctx context.Context) error { - return nil -} - -func (es *inMemoryEventstore) PushAggregates(ctx context.Context, aggregates ...*Aggregate) error { - return nil -} - -func (es *inMemoryEventstore) FilterEvents(ctx context.Context, searchQuery *SearchQueryFactory) (events []*Event, err error) { - return nil, nil -} - -func (es *inMemoryEventstore) LatestSequence(ctx context.Context, searchQuery *SearchQueryFactory) (uint64, error) { - query, err := searchQuery.Build() - if err != nil { - return 0, err - } -query.Filters[0]. - return 0, nil -} diff --git a/internal/eventstore/v2/eventstore_test.go b/internal/eventstore/v2/eventstore_test.go index 8f4461acd4..b92084aa02 100644 --- a/internal/eventstore/v2/eventstore_test.go +++ b/internal/eventstore/v2/eventstore_test.go @@ -1,24 +1,266 @@ package eventstore -import "testing" +import ( + "reflect" + "testing" + + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore/v2/repository" +) + +type testEvent struct { + description string + shouldCheckPrevious bool +} + +func (e *testEvent) CheckPrevious() bool { + return e.shouldCheckPrevious +} + +func testPushMapper(Event) (*repository.Event, error) { + return &repository.Event{AggregateID: "aggregateID"}, nil +} + +func Test_eventstore_RegisterPushEventMapper(t *testing.T) { + type fields struct { + eventMapper map[EventType]eventTypeInterceptors + } + type args struct { + eventType EventType + mapper func(Event) (*repository.Event, error) + } + type res struct { + event *repository.Event + isErr func(error) bool + } -func TestPushAggregates(t *testing.T) { - type res struct{} - type args struct{} tests := []struct { - name string - args args - res *SearchQueryFactory - }{} + name string + fields fields + args args + res res + }{ + { + name: "no event type", + args: args{ + eventType: "", + mapper: testPushMapper, + }, + fields: fields{ + eventMapper: map[EventType]eventTypeInterceptors{}, + }, + res: res{ + isErr: errors.IsErrorInvalidArgument, + }, + }, + { + name: "no event mapper", + args: args{ + eventType: "event.type", + mapper: nil, + }, + fields: fields{ + eventMapper: map[EventType]eventTypeInterceptors{}, + }, + res: res{ + isErr: errors.IsErrorInvalidArgument, + }, + }, + { + name: "new interceptor", + fields: fields{ + eventMapper: map[EventType]eventTypeInterceptors{}, + }, + args: args{ + eventType: "new.event", + mapper: testPushMapper, + }, + res: res{ + event: &repository.Event{AggregateID: "aggregateID"}, + }, + }, + { + name: "existing interceptor new push mapper", + fields: fields{ + eventMapper: map[EventType]eventTypeInterceptors{ + "existing": {}, + }, + }, + args: args{ + eventType: "new.event", + mapper: testPushMapper, + }, + res: res{ + event: &repository.Event{AggregateID: "aggregateID"}, + }, + }, + { + name: "existing interceptor existing push mapper", + fields: fields{ + eventMapper: map[EventType]eventTypeInterceptors{ + "existing": { + pushMapper: func(Event) (*repository.Event, error) { + return nil, errors.ThrowUnimplemented(nil, "V2-1qPvn", "unimplemented") + }, + }, + }, + }, + args: args{ + eventType: "new.event", + mapper: testPushMapper, + }, + res: res{ + event: &repository.Event{AggregateID: "aggregateID"}, + }, + }, + } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - // factory := NewSearchQueryFactory(tt.args.aggregateTypes...) - // for _, setter := range tt.args.setters { - // factory = setter(factory) - // } - // if !reflect.DeepEqual(factory, tt.res) { - // t.Errorf("NewSearchQueryFactory() = %v, want %v", factory, tt.res) - // } + es := &Eventstore{ + eventMapper: tt.fields.eventMapper, + } + err := es.RegisterPushEventMapper(tt.args.eventType, tt.args.mapper) + if (tt.res.isErr != nil && !tt.res.isErr(err)) || (tt.res.isErr == nil && err != nil) { + t.Errorf("wrong error got: %v", err) + return + } + if tt.res.isErr != nil { + return + } + + mapper := es.eventMapper[tt.args.eventType] + event, err := mapper.pushMapper(nil) + if err != nil { + t.Errorf("unexpected error %v", err) + } + + if !reflect.DeepEqual(tt.res.event, event) { + t.Errorf("events should be deep equal. \ngot %v\nwant %v", event, tt.res.event) + } + }) + } +} + +func testFilterMapper(*repository.Event) (Event, error) { + return &testEvent{description: "hodor"}, nil +} + +func Test_eventstore_RegisterFilterEventMapper(t *testing.T) { + type fields struct { + eventMapper map[EventType]eventTypeInterceptors + } + type args struct { + eventType EventType + mapper func(*repository.Event) (Event, error) + } + type res struct { + event Event + isErr func(error) bool + } + + tests := []struct { + name string + fields fields + args args + res res + }{ + { + name: "no event type", + args: args{ + eventType: "", + mapper: testFilterMapper, + }, + fields: fields{ + eventMapper: map[EventType]eventTypeInterceptors{}, + }, + res: res{ + isErr: errors.IsErrorInvalidArgument, + }, + }, + { + name: "no event mapper", + args: args{ + eventType: "event.type", + mapper: nil, + }, + fields: fields{ + eventMapper: map[EventType]eventTypeInterceptors{}, + }, + res: res{ + isErr: errors.IsErrorInvalidArgument, + }, + }, + { + name: "new interceptor", + fields: fields{ + eventMapper: map[EventType]eventTypeInterceptors{}, + }, + args: args{ + eventType: "event.type", + mapper: testFilterMapper, + }, + res: res{ + event: &testEvent{description: "hodor"}, + }, + }, + { + name: "existing interceptor new filter mapper", + fields: fields{ + eventMapper: map[EventType]eventTypeInterceptors{ + "event.type": {}, + }, + }, + args: args{ + eventType: "new.event", + mapper: testFilterMapper, + }, + res: res{ + event: &testEvent{description: "hodor"}, + }, + }, + { + name: "existing interceptor existing filter mapper", + fields: fields{ + eventMapper: map[EventType]eventTypeInterceptors{ + "event.type": { + filterMapper: func(*repository.Event) (Event, error) { + return nil, errors.ThrowUnimplemented(nil, "V2-1qPvn", "unimplemented") + }, + }, + }, + }, + args: args{ + eventType: "new.event", + mapper: testFilterMapper, + }, + res: res{ + event: &testEvent{description: "hodor"}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + es := &Eventstore{ + eventMapper: tt.fields.eventMapper, + } + err := es.RegisterFilterEventMapper(tt.args.eventType, tt.args.mapper) + if (tt.res.isErr != nil && !tt.res.isErr(err)) || (tt.res.isErr == nil && err != nil) { + t.Errorf("wrong error got: %v", err) + return + } + if tt.res.isErr != nil { + return + } + + mapper := es.eventMapper[tt.args.eventType] + event, err := mapper.filterMapper(nil) + if err != nil { + t.Errorf("unexpected error %v", err) + } + + if !reflect.DeepEqual(tt.res.event, event) { + t.Errorf("events should be deep equal. \ngot %v\nwant %v", event, tt.res.event) + } }) } } diff --git a/internal/eventstore/v2/read_model.go b/internal/eventstore/v2/read_model.go deleted file mode 100644 index 210dc8b243..0000000000 --- a/internal/eventstore/v2/read_model.go +++ /dev/null @@ -1,3 +0,0 @@ -package eventstore - -type ReadModel struct{} diff --git a/internal/eventstore/v2/repository/aggregate.go b/internal/eventstore/v2/repository/aggregate.go new file mode 100644 index 0000000000..edbe899706 --- /dev/null +++ b/internal/eventstore/v2/repository/aggregate.go @@ -0,0 +1,22 @@ +package repository + +//AggregateType is the object name +type AggregateType string + +// //Aggregate represents an object +// type Aggregate struct { +// //ID id is the unique identifier of the aggregate +// // the client must generate it by it's own +// ID string +// //Type describes the meaning of this aggregate +// // it could an object like user +// Type AggregateType + +// //ResourceOwner is the organisation which owns this aggregate +// // an aggregate can only be managed by one organisation +// // use the ID of the org +// ResourceOwner string + +// //Events describe all the changes made on an aggregate +// Events []*Event +// } diff --git a/internal/eventstore/v2/event.go b/internal/eventstore/v2/repository/event.go similarity index 92% rename from internal/eventstore/v2/event.go rename to internal/eventstore/v2/repository/event.go index 8e1cd270f0..4f31d4191e 100644 --- a/internal/eventstore/v2/event.go +++ b/internal/eventstore/v2/repository/event.go @@ -1,6 +1,8 @@ -package eventstore +package repository -import "time" +import ( + "time" +) //Event represents all information about a manipulation of an aggregate type Event struct { @@ -13,6 +15,10 @@ type Event struct { // if it's 0 then it's the first event of this aggregate PreviousSequence uint64 + //PreviousEvent is needed in push to update PreviousSequence + // it implements a linked list + PreviousEvent *Event + //CreationDate is the time the event is created // it's used for human readability. // Don't use it for event ordering, diff --git a/internal/eventstore/v2/repository/in_memory.go b/internal/eventstore/v2/repository/in_memory.go new file mode 100644 index 0000000000..54d1ecafd2 --- /dev/null +++ b/internal/eventstore/v2/repository/in_memory.go @@ -0,0 +1,82 @@ +package repository + +import "context" + +type InMemory struct { + events []*Event +} + +func (repo *InMemory) Health(ctx context.Context) error { return nil } + +// PushEvents adds all events of the given aggregates to the eventstreams of the aggregates. +// This call is transaction save. The transaction will be rolled back if one event fails +func (repo *InMemory) Push(ctx context.Context, events ...*Event) error { + repo.events = append(repo.events, events...) + return nil +} + +// Filter returns all events matching the given search query +func (repo *InMemory) Filter(ctx context.Context, searchQuery *SearchQuery) (events []*Event, err error) { + indexes := repo.filter(searchQuery) + events = make([]*Event, len(indexes)) + for i, index := range indexes { + events[i] = repo.events[index] + } + + return events, nil +} + +func (repo *InMemory) filter(query *SearchQuery) []int { + foundIndex := make([]int, 0, query.Limit) +events: + for i, event := range repo.events { + if query.Limit > 0 && uint64(len(foundIndex)) < query.Limit { + return foundIndex + } + for _, filter := range query.Filters { + var value interface{} + switch filter.field { + case Field_AggregateID: + value = event.AggregateID + case Field_EditorService: + value = event.EditorService + case Field_EventType: + value = event.Type + case Field_AggregateType: + value = event.AggregateType + case Field_EditorUser: + value = event.EditorUser + case Field_ResourceOwner: + value = event.ResourceOwner + case Field_LatestSequence: + value = event.Sequence + } + switch filter.operation { + case Operation_Equals: + if filter.value == value { + foundIndex = append(foundIndex, i) + } + case Operation_Greater: + fallthrough + case Operation_Less: + + return nil + case Operation_In: + values := filter.Value().([]interface{}) + for _, val := range values { + if val == value { + foundIndex = append(foundIndex, i) + continue events + } + } + } + } + } + + return foundIndex +} + +//LatestSequence returns the latests sequence found by the the search query +func (repo *InMemory) LatestSequence(ctx context.Context, queryFactory *SearchQuery) (uint64, error) { + return 0, nil +} diff --git a/internal/eventstore/v2/repository/repository.go b/internal/eventstore/v2/repository/repository.go new file mode 100644 index 0000000000..ed3ec67d68 --- /dev/null +++ b/internal/eventstore/v2/repository/repository.go @@ -0,0 +1,17 @@ +package repository + +import ( + "context" +) + +type Repository interface { + Health(ctx context.Context) error + + // PushEvents adds all events of the given aggregates to the eventstreams of the aggregates. + // This call is transaction save. The transaction will be rolled back if one event fails + Push(ctx context.Context, events ...*Event) error + // Filter returns all events matching the given search query + Filter(ctx context.Context, searchQuery *SearchQuery) (events []*Event, err error) + //LatestSequence returns the latests sequence found by the the search query + LatestSequence(ctx context.Context, queryFactory *SearchQuery) (uint64, error) +} diff --git a/internal/eventstore/v2/filter.go b/internal/eventstore/v2/repository/search_query.go similarity index 58% rename from internal/eventstore/v2/filter.go rename to internal/eventstore/v2/repository/search_query.go index a55cab74e7..931e6234fa 100644 --- a/internal/eventstore/v2/filter.go +++ b/internal/eventstore/v2/repository/search_query.go @@ -1,7 +1,21 @@ -package eventstore +package repository -import ( - "github.com/caos/zitadel/internal/errors" +import "github.com/caos/zitadel/internal/errors" + +type SearchQuery struct { + Columns Columns + Limit uint64 + Desc bool + Filters []*Filter +} + +type Columns int32 + +const ( + Columns_Event = iota + Columns_Max_Sequence + //insert new columns-types above this ColumnsCount because count is needed for validation + ColumnsCount ) type Filter struct { @@ -52,16 +66,16 @@ func (f *Filter) Value() interface{} { func (f *Filter) Validate() error { if f == nil { - return errors.ThrowPreconditionFailed(nil, "MODEL-z6KcG", "filter is nil") + return errors.ThrowPreconditionFailed(nil, "REPO-z6KcG", "filter is nil") } if f.field <= 0 { - return errors.ThrowPreconditionFailed(nil, "MODEL-zw62U", "field not definded") + return errors.ThrowPreconditionFailed(nil, "REPO-zw62U", "field not definded") } if f.value == nil { - return errors.ThrowPreconditionFailed(nil, "MODEL-GJ9ct", "no value definded") + return errors.ThrowPreconditionFailed(nil, "REPO-GJ9ct", "no value definded") } if f.operation <= 0 { - return errors.ThrowPreconditionFailed(nil, "MODEL-RrQTy", "operation not definded") + return errors.ThrowPreconditionFailed(nil, "REPO-RrQTy", "operation not definded") } return nil } diff --git a/internal/eventstore/v2/filter_test.go b/internal/eventstore/v2/repository/search_query_test.go similarity index 99% rename from internal/eventstore/v2/filter_test.go rename to internal/eventstore/v2/repository/search_query_test.go index 7b2fa03982..e35d5d4299 100644 --- a/internal/eventstore/v2/filter_test.go +++ b/internal/eventstore/v2/repository/search_query_test.go @@ -1,4 +1,4 @@ -package eventstore +package repository import ( "reflect" diff --git a/internal/eventstore/v2/repository/version.go b/internal/eventstore/v2/repository/version.go new file mode 100644 index 0000000000..e0b66072ea --- /dev/null +++ b/internal/eventstore/v2/repository/version.go @@ -0,0 +1,18 @@ +package repository + +import ( + "regexp" + + "github.com/caos/zitadel/internal/errors" +) + +var versionRegexp = regexp.MustCompile(`^v[0-9]+(\.[0-9]+){0,2}$`) + +type Version string + +func (v Version) Validate() error { + if !versionRegexp.MatchString(string(v)) { + return errors.ThrowPreconditionFailed(nil, "MODEL-luDuS", "version is not semver") + } + return nil +} diff --git a/internal/eventstore/v2/version_test.go b/internal/eventstore/v2/repository/version_test.go similarity index 96% rename from internal/eventstore/v2/version_test.go rename to internal/eventstore/v2/repository/version_test.go index 5bc92f9d13..c64db50c3c 100644 --- a/internal/eventstore/v2/version_test.go +++ b/internal/eventstore/v2/repository/version_test.go @@ -1,4 +1,4 @@ -package eventstore +package repository import "testing" diff --git a/internal/eventstore/v2/search_query.go b/internal/eventstore/v2/search_query.go index 2b10a7fb93..c4d64b94b0 100644 --- a/internal/eventstore/v2/search_query.go +++ b/internal/eventstore/v2/search_query.go @@ -2,10 +2,11 @@ package eventstore import ( "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore/v2/repository" ) type SearchQueryFactory struct { - columns Columns + columns repository.Columns limit uint64 desc bool aggregateTypes []AggregateType @@ -15,22 +16,16 @@ type SearchQueryFactory struct { resourceOwner string } -type searchQuery struct { - Columns Columns - Limit uint64 - Desc bool - Filters []*Filter -} - -type Columns int32 +type Columns repository.Columns const ( - Columns_Event = iota - Columns_Max_Sequence - //insert new columns-types above this columnsCount because count is needed for validation - columnsCount + Columns_Event Columns = repository.Columns_Event + Columns_Max_Sequence Columns = repository.Columns_Max_Sequence ) +type AggregateType repository.AggregateType +type EventType repository.EventType + func NewSearchQueryFactory(aggregateTypes ...AggregateType) *SearchQueryFactory { return &SearchQueryFactory{ aggregateTypes: aggregateTypes, @@ -38,7 +33,7 @@ func NewSearchQueryFactory(aggregateTypes ...AggregateType) *SearchQueryFactory } func (factory *SearchQueryFactory) Columns(columns Columns) *SearchQueryFactory { - factory.columns = columns + factory.columns = repository.Columns(columns) return factory } @@ -77,17 +72,17 @@ func (factory *SearchQueryFactory) OrderAsc() *SearchQueryFactory { return factory } -func (factory *SearchQueryFactory) Build() (*searchQuery, error) { +func (factory *SearchQueryFactory) Build() (*repository.SearchQuery, error) { if factory == nil || len(factory.aggregateTypes) < 1 || - (factory.columns < 0 || factory.columns >= columnsCount) { + (factory.columns < 0 || factory.columns >= repository.ColumnsCount) { return nil, errors.ThrowPreconditionFailed(nil, "MODEL-tGAD3", "factory invalid") } - filters := []*Filter{ + filters := []*repository.Filter{ factory.aggregateTypeFilter(), } - for _, f := range []func() *Filter{ + for _, f := range []func() *repository.Filter{ factory.aggregateIDFilter, factory.eventSequenceFilter, factory.eventTypeFilter, @@ -98,55 +93,55 @@ func (factory *SearchQueryFactory) Build() (*searchQuery, error) { } } - return &searchQuery{ - Columns: factory.columns, + return &repository.SearchQuery{ + Columns: repository.Columns(factory.columns), Limit: factory.limit, Desc: factory.desc, Filters: filters, }, nil } -func (factory *SearchQueryFactory) aggregateIDFilter() *Filter { +func (factory *SearchQueryFactory) aggregateIDFilter() *repository.Filter { if len(factory.aggregateIDs) < 1 { return nil } if len(factory.aggregateIDs) == 1 { - return NewFilter(Field_AggregateID, factory.aggregateIDs[0], Operation_Equals) + return repository.NewFilter(repository.Field_AggregateID, factory.aggregateIDs[0], repository.Operation_Equals) } - return NewFilter(Field_AggregateID, factory.aggregateIDs, Operation_In) + return repository.NewFilter(repository.Field_AggregateID, factory.aggregateIDs, repository.Operation_In) } -func (factory *SearchQueryFactory) eventTypeFilter() *Filter { +func (factory *SearchQueryFactory) eventTypeFilter() *repository.Filter { if len(factory.eventTypes) < 1 { return nil } if len(factory.eventTypes) == 1 { - return NewFilter(Field_EventType, factory.eventTypes[0], Operation_Equals) + return repository.NewFilter(repository.Field_EventType, factory.eventTypes[0], repository.Operation_Equals) } - return NewFilter(Field_EventType, factory.eventTypes, Operation_In) + return repository.NewFilter(repository.Field_EventType, factory.eventTypes, repository.Operation_In) } -func (factory *SearchQueryFactory) aggregateTypeFilter() *Filter { +func (factory *SearchQueryFactory) aggregateTypeFilter() *repository.Filter { if len(factory.aggregateTypes) == 1 { - return NewFilter(Field_AggregateType, factory.aggregateTypes[0], Operation_Equals) + return repository.NewFilter(repository.Field_AggregateType, factory.aggregateTypes[0], repository.Operation_Equals) } - return NewFilter(Field_AggregateType, factory.aggregateTypes, Operation_In) + return repository.NewFilter(repository.Field_AggregateType, factory.aggregateTypes, repository.Operation_In) } -func (factory *SearchQueryFactory) eventSequenceFilter() *Filter { +func (factory *SearchQueryFactory) eventSequenceFilter() *repository.Filter { if factory.eventSequence == 0 { return nil } - sortOrder := Operation_Greater + sortOrder := repository.Operation_Greater if factory.desc { - sortOrder = Operation_Less + sortOrder = repository.Operation_Less } - return NewFilter(Field_LatestSequence, factory.eventSequence, sortOrder) + return repository.NewFilter(repository.Field_LatestSequence, factory.eventSequence, sortOrder) } -func (factory *SearchQueryFactory) resourceOwnerFilter() *Filter { +func (factory *SearchQueryFactory) resourceOwnerFilter() *repository.Filter { if factory.resourceOwner == "" { return nil } - return NewFilter(Field_ResourceOwner, factory.resourceOwner, Operation_Equals) + return repository.NewFilter(repository.Field_ResourceOwner, factory.resourceOwner, repository.Operation_Equals) } diff --git a/internal/eventstore/v2/search_query_test.go b/internal/eventstore/v2/search_query_test.go index ed0133b770..81598347eb 100644 --- a/internal/eventstore/v2/search_query_test.go +++ b/internal/eventstore/v2/search_query_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore/v2/repository" ) func testSetColumns(columns Columns) func(factory *SearchQueryFactory) *SearchQueryFactory { @@ -82,10 +83,10 @@ func TestSearchQueryFactorySetters(t *testing.T) { { name: "set columns", args: args{ - setters: []func(*SearchQueryFactory) *SearchQueryFactory{testSetColumns(Columns_Max_Sequence)}, + setters: []func(*SearchQueryFactory) *SearchQueryFactory{testSetColumns(repository.Columns_Max_Sequence)}, }, res: &SearchQueryFactory{ - columns: Columns_Max_Sequence, + columns: repository.Columns_Max_Sequence, }, }, { @@ -166,7 +167,7 @@ func TestSearchQueryFactoryBuild(t *testing.T) { } type res struct { isErr func(err error) bool - query *searchQuery + query *repository.SearchQuery } tests := []struct { name string @@ -201,7 +202,7 @@ func TestSearchQueryFactoryBuild(t *testing.T) { args: args{ aggregateTypes: []AggregateType{"user"}, setters: []func(*SearchQueryFactory) *SearchQueryFactory{ - testSetColumns(columnsCount), + testSetColumns(repository.ColumnsCount), }, }, res: res{ @@ -216,12 +217,12 @@ func TestSearchQueryFactoryBuild(t *testing.T) { }, res: res{ isErr: nil, - query: &searchQuery{ + query: &repository.SearchQuery{ Columns: 0, Desc: false, Limit: 0, - Filters: []*Filter{ - NewFilter(Field_AggregateType, AggregateType("user"), Operation_Equals), + Filters: []*repository.Filter{ + repository.NewFilter(repository.Field_AggregateType, AggregateType("user"), repository.Operation_Equals), }, }, }, @@ -234,12 +235,12 @@ func TestSearchQueryFactoryBuild(t *testing.T) { }, res: res{ isErr: nil, - query: &searchQuery{ + query: &repository.SearchQuery{ Columns: 0, Desc: false, Limit: 0, - Filters: []*Filter{ - NewFilter(Field_AggregateType, []AggregateType{"user", "org"}, Operation_In), + Filters: []*repository.Filter{ + repository.NewFilter(repository.Field_AggregateType, []AggregateType{"user", "org"}, repository.Operation_In), }, }, }, @@ -256,13 +257,13 @@ func TestSearchQueryFactoryBuild(t *testing.T) { }, res: res{ isErr: nil, - query: &searchQuery{ + query: &repository.SearchQuery{ Columns: 0, Desc: true, Limit: 5, - Filters: []*Filter{ - NewFilter(Field_AggregateType, AggregateType("user"), Operation_Equals), - NewFilter(Field_LatestSequence, uint64(100), Operation_Less), + Filters: []*repository.Filter{ + repository.NewFilter(repository.Field_AggregateType, AggregateType("user"), repository.Operation_Equals), + repository.NewFilter(repository.Field_LatestSequence, uint64(100), repository.Operation_Less), }, }, }, @@ -279,13 +280,13 @@ func TestSearchQueryFactoryBuild(t *testing.T) { }, res: res{ isErr: nil, - query: &searchQuery{ + query: &repository.SearchQuery{ Columns: 0, Desc: false, Limit: 5, - Filters: []*Filter{ - NewFilter(Field_AggregateType, AggregateType("user"), Operation_Equals), - NewFilter(Field_LatestSequence, uint64(100), Operation_Greater), + Filters: []*repository.Filter{ + repository.NewFilter(repository.Field_AggregateType, AggregateType("user"), repository.Operation_Equals), + repository.NewFilter(repository.Field_LatestSequence, uint64(100), repository.Operation_Greater), }, }, }, @@ -298,18 +299,18 @@ func TestSearchQueryFactoryBuild(t *testing.T) { testSetLimit(5), testSetSortOrder(false), testSetSequence(100), - testSetColumns(Columns_Max_Sequence), + testSetColumns(repository.Columns_Max_Sequence), }, }, res: res{ isErr: nil, - query: &searchQuery{ - Columns: Columns_Max_Sequence, + query: &repository.SearchQuery{ + Columns: repository.Columns_Max_Sequence, Desc: true, Limit: 5, - Filters: []*Filter{ - NewFilter(Field_AggregateType, AggregateType("user"), Operation_Equals), - NewFilter(Field_LatestSequence, uint64(100), Operation_Less), + Filters: []*repository.Filter{ + repository.NewFilter(repository.Field_AggregateType, AggregateType("user"), repository.Operation_Equals), + repository.NewFilter(repository.Field_LatestSequence, uint64(100), repository.Operation_Less), }, }, }, @@ -324,13 +325,13 @@ func TestSearchQueryFactoryBuild(t *testing.T) { }, res: res{ isErr: nil, - query: &searchQuery{ + query: &repository.SearchQuery{ Columns: 0, Desc: false, Limit: 0, - Filters: []*Filter{ - NewFilter(Field_AggregateType, AggregateType("user"), Operation_Equals), - NewFilter(Field_AggregateID, "1234", Operation_Equals), + Filters: []*repository.Filter{ + repository.NewFilter(repository.Field_AggregateType, AggregateType("user"), repository.Operation_Equals), + repository.NewFilter(repository.Field_AggregateID, "1234", repository.Operation_Equals), }, }, }, @@ -345,13 +346,13 @@ func TestSearchQueryFactoryBuild(t *testing.T) { }, res: res{ isErr: nil, - query: &searchQuery{ + query: &repository.SearchQuery{ Columns: 0, Desc: false, Limit: 0, - Filters: []*Filter{ - NewFilter(Field_AggregateType, AggregateType("user"), Operation_Equals), - NewFilter(Field_AggregateID, []string{"1234", "0815"}, Operation_In), + Filters: []*repository.Filter{ + repository.NewFilter(repository.Field_AggregateType, AggregateType("user"), repository.Operation_Equals), + repository.NewFilter(repository.Field_AggregateID, []string{"1234", "0815"}, repository.Operation_In), }, }, }, @@ -366,13 +367,13 @@ func TestSearchQueryFactoryBuild(t *testing.T) { }, res: res{ isErr: nil, - query: &searchQuery{ + query: &repository.SearchQuery{ Columns: 0, Desc: false, Limit: 0, - Filters: []*Filter{ - NewFilter(Field_AggregateType, AggregateType("user"), Operation_Equals), - NewFilter(Field_LatestSequence, uint64(8), Operation_Greater), + Filters: []*repository.Filter{ + repository.NewFilter(repository.Field_AggregateType, AggregateType("user"), repository.Operation_Equals), + repository.NewFilter(repository.Field_LatestSequence, uint64(8), repository.Operation_Greater), }, }, }, @@ -387,13 +388,13 @@ func TestSearchQueryFactoryBuild(t *testing.T) { }, res: res{ isErr: nil, - query: &searchQuery{ + query: &repository.SearchQuery{ Columns: 0, Desc: false, Limit: 0, - Filters: []*Filter{ - NewFilter(Field_AggregateType, AggregateType("user"), Operation_Equals), - NewFilter(Field_EventType, EventType("user.created"), Operation_Equals), + Filters: []*repository.Filter{ + repository.NewFilter(repository.Field_AggregateType, AggregateType("user"), repository.Operation_Equals), + repository.NewFilter(repository.Field_EventType, EventType("user.created"), repository.Operation_Equals), }, }, }, @@ -408,13 +409,13 @@ func TestSearchQueryFactoryBuild(t *testing.T) { }, res: res{ isErr: nil, - query: &searchQuery{ + query: &repository.SearchQuery{ Columns: 0, Desc: false, Limit: 0, - Filters: []*Filter{ - NewFilter(Field_AggregateType, AggregateType("user"), Operation_Equals), - NewFilter(Field_EventType, []EventType{"user.created", "user.changed"}, Operation_In), + Filters: []*repository.Filter{ + repository.NewFilter(repository.Field_AggregateType, AggregateType("user"), repository.Operation_Equals), + repository.NewFilter(repository.Field_EventType, []EventType{"user.created", "user.changed"}, repository.Operation_In), }, }, }, @@ -429,13 +430,13 @@ func TestSearchQueryFactoryBuild(t *testing.T) { }, res: res{ isErr: nil, - query: &searchQuery{ + query: &repository.SearchQuery{ Columns: 0, Desc: false, Limit: 0, - Filters: []*Filter{ - NewFilter(Field_AggregateType, AggregateType("user"), Operation_Equals), - NewFilter(Field_ResourceOwner, "hodor", Operation_Equals), + Filters: []*repository.Filter{ + repository.NewFilter(repository.Field_AggregateType, AggregateType("user"), repository.Operation_Equals), + repository.NewFilter(repository.Field_ResourceOwner, "hodor", repository.Operation_Equals), }, }, }, diff --git a/internal/eventstore/v2/version.go b/internal/eventstore/v2/version.go index 27e0427f2a..0b36850fc9 100644 --- a/internal/eventstore/v2/version.go +++ b/internal/eventstore/v2/version.go @@ -1,18 +1,7 @@ package eventstore import ( - "regexp" - - "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore/v2/repository" ) -var versionRegexp = regexp.MustCompile(`^v[0-9]+(\.[0-9]+){0,2}$`) - -type Version string - -func (v Version) Validate() error { - if !versionRegexp.MatchString(string(v)) { - return errors.ThrowPreconditionFailed(nil, "MODEL-luDuS", "version is not semver") - } - return nil -} +type Version repository.Version diff --git a/migrations/sqlite/V1.0__databases.sql b/migrations/sqlite/V1.0__databases.sql new file mode 100644 index 0000000000..ce7ce6b93e --- /dev/null +++ b/migrations/sqlite/V1.0__databases.sql @@ -0,0 +1,36 @@ +ATTACH DATABASE '${GOPATH}/src/github.com/caos/zitadel/.local/management.db' AS 'management'; +ATTACH DATABASE '${GOPATH}/src/github.com/caos/zitadel/.local/auth.db' AS 'auth'; +ATTACH DATABASE '${GOPATH}/src/github.com/caos/zitadel/.local/notification.db' AS 'notification'; +ATTACH DATABASE '${GOPATH}/src/github.com/caos/zitadel/.local/adminapi.db' AS 'adminapi'; +ATTACH DATABASE '${GOPATH}/src/github.com/caos/zitadel/.local/authz.db' AS 'authz'; +ATTACH DATABASE '${GOPATH}/src/github.com/caos/zitadel/.local/eventstore.db' AS 'eventstore'; + + + +-- CREATE USER eventstore; +-- GRANT SELECT, INSERT ON DATABASE eventstore TO eventstore; + +-- CREATE USER management; +-- GRANT SELECT, INSERT, UPDATE, DELETE ON DATABASE management TO management; +-- GRANT SELECT, INSERT ON DATABASE eventstore TO management; + +-- CREATE USER adminapi; +-- GRANT SELECT, INSERT, UPDATE, DELETE, DROP ON DATABASE adminapi TO adminapi; +-- GRANT SELECT, INSERT ON DATABASE eventstore TO adminapi; +-- GRANT SELECT, INSERT, UPDATE, DROP, DELETE ON DATABASE auth TO adminapi; +-- GRANT SELECT, INSERT, UPDATE, DROP, DELETE ON DATABASE authz TO adminapi; +-- GRANT SELECT, INSERT, UPDATE, DROP, DELETE ON DATABASE management TO adminapi; +-- GRANT SELECT, INSERT, UPDATE, DROP, DELETE ON DATABASE notification TO adminapi; + +-- CREATE USER auth; +-- GRANT SELECT, INSERT, UPDATE, DELETE ON DATABASE auth TO auth; +-- GRANT SELECT, INSERT ON DATABASE eventstore TO auth; + +-- CREATE USER notification; +-- GRANT SELECT, INSERT, UPDATE, DELETE ON DATABASE notification TO notification; +-- GRANT SELECT, INSERT ON DATABASE eventstore TO notification; + +-- CREATE USER authz; +-- GRANT SELECT, INSERT, UPDATE, DELETE ON DATABASE authz TO authz; +-- GRANT SELECT, INSERT ON DATABASE eventstore TO authz; +-- GRANT SELECT, INSERT, UPDATE ON DATABASE auth TO authz; diff --git a/migrations/sqlite/V1.10__user_machine.sql b/migrations/sqlite/V1.10__user_machine.sql new file mode 100644 index 0000000000..534d4a5397 --- /dev/null +++ b/migrations/sqlite/V1.10__user_machine.sql @@ -0,0 +1,15 @@ +ALTER TABLE management.users ADD COLUMN machine_name STRING, ADD COLUMN machine_description STRING, ADD COLUMN user_type STRING; +ALTER TABLE adminapi.users ADD COLUMN machine_name STRING, ADD COLUMN machine_description STRING, ADD COLUMN user_type STRING; +ALTER TABLE auth.users ADD COLUMN machine_name STRING, ADD COLUMN machine_description STRING, ADD COLUMN user_type STRING; + +CREATE TABLE management.machine_keys ( + id TEXT, + user_id TEXT, + + machine_type SMALLINT, + expiration_date TIMESTAMPTZ, + sequence BIGINT, + creation_date TIMESTAMPTZ, + + PRIMARY KEY (id, user_id) +) \ No newline at end of file diff --git a/migrations/sqlite/V1.11__usermembership.sql b/migrations/sqlite/V1.11__usermembership.sql new file mode 100644 index 0000000000..20bba86942 --- /dev/null +++ b/migrations/sqlite/V1.11__usermembership.sql @@ -0,0 +1,18 @@ +CREATE TABLE auth.user_memberships ( + user_id TEXT, + member_type SMALLINT, + aggregate_id TEXT, + object_id TEXT, + + roles TEXT ARRAY, + display_name TEXT, + resource_owner TEXT, + resource_owner_name TEXT, + creation_date TIMESTAMPTZ, + change_date TIMESTAMPTZ, + sequence BIGINT, + + PRIMARY KEY (user_id, member_type, aggregate_id, object_id) +); + +ALTER TABLE management.user_memberships ADD COLUMN resource_owner_name TEXT; \ No newline at end of file diff --git a/migrations/sqlite/V1.12__machine_keys.sql b/migrations/sqlite/V1.12__machine_keys.sql new file mode 100644 index 0000000000..d428bd87c1 --- /dev/null +++ b/migrations/sqlite/V1.12__machine_keys.sql @@ -0,0 +1,14 @@ +CREATE TABLE auth.machine_keys ( + id TEXT, + user_id TEXT, + + machine_type SMALLINT, + expiration_date TIMESTAMPTZ, + sequence BIGINT, + creation_date TIMESTAMPTZ, + public_key JSONB, + + PRIMARY KEY (id, user_id) +); + +ALTER TABLE management.machine_keys ADD COLUMN public_key JSONB; \ No newline at end of file diff --git a/migrations/sqlite/V1.13__machine_keys_public.sql b/migrations/sqlite/V1.13__machine_keys_public.sql new file mode 100644 index 0000000000..faa3917d4c --- /dev/null +++ b/migrations/sqlite/V1.13__machine_keys_public.sql @@ -0,0 +1,5 @@ +ALTER TABLE management.machine_keys DROP COLUMN IF EXISTS public_key; +ALTER TABLE management.machine_keys ADD COLUMN public_key BYTES; + +ALTER TABLE auth.machine_keys DROP COLUMN IF EXISTS public_key; +ALTER TABLE auth.machine_keys ADD COLUMN public_key BYTES; \ No newline at end of file diff --git a/migrations/sqlite/V1.14__auth_loginpolicy.sql b/migrations/sqlite/V1.14__auth_loginpolicy.sql new file mode 100644 index 0000000000..5aeb109a96 --- /dev/null +++ b/migrations/sqlite/V1.14__auth_loginpolicy.sql @@ -0,0 +1,105 @@ +ALTER TABLE adminapi.idp_configs ADD COLUMN oidc_idp_display_name_mapping SMALLINT; +ALTER TABLE adminapi.idp_configs ADD COLUMN oidc_idp_username_mapping SMALLINT; + +ALTER TABLE management.idp_configs ADD COLUMN oidc_idp_display_name_mapping SMALLINT; +ALTER TABLE management.idp_configs ADD COLUMN oidc_idp_username_mapping SMALLINT; + +CREATE TABLE auth.idp_configs ( + idp_config_id TEXT, + + creation_date TIMESTAMPTZ, + change_date TIMESTAMPTZ, + sequence BIGINT, + aggregate_id TEXT, + name TEXT, + logo_src BYTES, + idp_state SMALLINT, + idp_provider_type SMALLINT, + + is_oidc BOOLEAN, + oidc_client_id TEXT, + oidc_client_secret JSONB, + oidc_issuer TEXT, + oidc_scopes TEXT ARRAY, + oidc_idp_display_name_mapping SMALLINT, + oidc_idp_username_mapping SMALLINT, + + PRIMARY KEY (idp_config_id) +); + + +CREATE TABLE auth.login_policies ( + aggregate_id TEXT, + + creation_date TIMESTAMPTZ, + change_date TIMESTAMPTZ, + login_policy_state SMALLINT, + sequence BIGINT, + + allow_register BOOLEAN, + allow_username_password BOOLEAN, + allow_external_idp BOOLEAN, + + PRIMARY KEY (aggregate_id) +); + +CREATE TABLE auth.idp_providers ( + aggregate_id TEXT, + idp_config_id TEXT, + + creation_date TIMESTAMPTZ, + change_date TIMESTAMPTZ, + sequence BIGINT, + + name string, + idp_config_type SMALLINT, + idp_provider_type SMALLINT, + + PRIMARY KEY (aggregate_id, idp_config_id) +); + + +CREATE TABLE auth.user_external_idps ( + external_user_id TEXT, + idp_config_id TEXT, + user_id TEXT, + idp_name TEXT, + user_display_name TEXT, + + creation_date TIMESTAMPTZ, + change_date TIMESTAMPTZ, + sequence BIGINT, + resource_owner TEXT, + + PRIMARY KEY (external_user_id, idp_config_id) +); + +CREATE TABLE management.user_external_idps ( + idp_config_id TEXT, + external_user_id TEXT, + user_id TEXT, + idp_name TEXT, + user_display_name TEXT, + + creation_date TIMESTAMPTZ, + change_date TIMESTAMPTZ, + sequence BIGINT, + resource_owner TEXT, + + PRIMARY KEY (external_user_id, idp_config_id) +); + +CREATE TABLE adminapi.user_external_idps ( + idp_config_id TEXT, + external_user_id TEXT, + user_id TEXT, + idp_name TEXT, + user_display_name TEXT, + + creation_date TIMESTAMPTZ, + change_date TIMESTAMPTZ, + sequence BIGINT, + resource_owner TEXT, + + PRIMARY KEY (external_user_id, idp_config_id) +); \ No newline at end of file diff --git a/migrations/sqlite/V1.15__idp_providers.sql b/migrations/sqlite/V1.15__idp_providers.sql new file mode 100644 index 0000000000..4bdbd8fd56 --- /dev/null +++ b/migrations/sqlite/V1.15__idp_providers.sql @@ -0,0 +1,3 @@ +ALTER TABLE adminapi.idp_providers ADD COLUMN idp_state SMALLINT; +ALTER TABLE management.idp_providers ADD COLUMN idp_state SMALLINT; +ALTER TABLE auth.idp_providers ADD COLUMN idp_state SMALLINT; diff --git a/migrations/sqlite/V1.1__eventstore.sql b/migrations/sqlite/V1.1__eventstore.sql new file mode 100644 index 0000000000..173309ec85 --- /dev/null +++ b/migrations/sqlite/V1.1__eventstore.sql @@ -0,0 +1,18 @@ +CREATE TABLE eventstore.events ( + event_type TEXT, + aggregate_type TEXT NOT NULL, + aggregate_id TEXT NOT NULL, + aggregate_version TEXT NOT NULL, + event_sequence INTEGER, + previous_sequence BIGINT, + creation_date TIMESTAMPT NOT NULL DEFAULT CURRENT_TIMESTAMP, + event_data JSONB, + editor_user TEXT NOT NULL, + editor_service TEXT NOT NULL, + resource_owner TEXT NOT NULL, + + CONSTRAINT event_sequence_pk PRIMARY KEY (event_sequence DESC), + CONSTRAINT previous_sequence_unique UNIQUE (previous_sequence DESC) +); + +CREATE INDEX eventstore.agg_type_agg_id ON events (aggregate_type, aggregate_id); diff --git a/migrations/sqlite/V1.2__views.sql b/migrations/sqlite/V1.2__views.sql new file mode 100644 index 0000000000..447fad8f23 --- /dev/null +++ b/migrations/sqlite/V1.2__views.sql @@ -0,0 +1,628 @@ +CREATE TABLE management.locks ( + locker_id TEXT, + locked_until TIMESTAMP, + view_name TEXT, + + PRIMARY KEY (view_name) +); + +CREATE TABLE management.current_sequences ( + view_name TEXT, + current_sequence BIGINT, + timestamp TIMESTAMP, + + PRIMARY KEY (view_name) +); + +CREATE TABLE management.failed_events ( + view_name TEXT, + failed_sequence BIGINT, + failure_count SMALLINT, + err_msg TEXT, + + PRIMARY KEY (view_name, failed_sequence) +); + +CREATE TABLE management.projects ( + project_id TEXT, + + creation_date TIMESTAMP, + change_date TIMESTAMP, + project_name TEXT, + project_state SMALLINT, + resource_owner TEXT, + sequence BIGINT, + + PRIMARY KEY (project_id) +); + +CREATE TABLE management.project_grants ( + grant_id TEXT, + + creation_date TIMESTAMP, + change_date TIMESTAMP, + project_id TEXT, + project_name TEXT, + org_name TEXT, + project_state SMALLINT, + resource_owner TEXT, + org_id TEXT, + granted_role_keys TEXT Array, + sequence BIGINT, + resource_owner_name TEXT, + + PRIMARY KEY (grant_id) +); + +CREATE TABLE management.project_roles ( + project_id TEXT, + role_key TEXT, + display_name TEXT, + resource_owner TEXT, + org_id TEXT, + group_name TEXT, + + creation_date TIMESTAMP, + sequence BIGINT, + + PRIMARY KEY (org_id, project_id, role_key) +); + +CREATE TABLE management.project_members ( + user_id TEXT, + project_id TEXT, + + creation_date TIMESTAMP, + change_date TIMESTAMP, + + user_name TEXT, + email_address TEXT, + first_name TEXT, + last_name TEXT, + roles TEXT ARRAY, + display_name TEXT, + sequence BIGINT, + + PRIMARY KEY (project_id, user_id) +); + +CREATE TABLE management.project_grant_members ( + user_id TEXT, + grant_id TEXT, + project_id TEXT, + + creation_date TIMESTAMP, + change_date TIMESTAMP, + + user_name TEXT, + email_address TEXT, + first_name TEXT, + last_name TEXT, + roles TEXT ARRAY, + display_name TEXT, + sequence BIGINT, + + PRIMARY KEY (grant_id, user_id) +); + +CREATE TABLE management.applications ( + id TEXT, + + creation_date TIMESTAMP, + change_date TIMESTAMP, + sequence BIGINT, + + app_state SMALLINT, + resource_owner TEXT, + app_name TEXT, + project_id TEXT, + app_type SMALLINT, + is_oidc BOOLEAN, + oidc_client_id TEXT, + oidc_redirect_uris TEXT ARRAY, + oidc_response_types SMALLINT ARRAY, + oidc_grant_types SMALLINT ARRAY, + oidc_application_type SMALLINT, + oidc_auth_method_type SMALLINT, + oidc_post_logout_redirect_uris TEXT ARRAY, + + PRIMARY KEY (id) +); + +CREATE TABLE management.users ( + id TEXT, + + creation_date TIMESTAMP, + change_date TIMESTAMP, + + resource_owner TEXT, + user_state SMALLINT, + last_login TIMESTAMP, + password_change TIMESTAMP, + user_name TEXT, + login_names TEXT ARRAY, + preferred_login_name TEXT, + first_name TEXT, + last_name TEXT, + nick_Name TEXT, + display_name TEXT, + preferred_language TEXT, + gender SMALLINT, + email TEXT, + is_email_verified BOOLEAN, + phone TEXT, + is_phone_verified BOOLEAN, + country TEXT, + locality TEXT, + postal_code TEXT, + region TEXT, + street_address TEXT, + otp_state SMALLINT, + sequence BIGINT, + password_set BOOLEAN, + password_change_required BOOLEAN, + mfa_max_set_up SMALLINT, + mfa_init_skipped TIMESTAMP, + init_required BOOLEAN, + + PRIMARY KEY (id) +); + +CREATE TABLE management.user_grants ( + id TEXT, + resource_owner TEXT, + project_id TEXT, + user_id TEXT, + org_name TEXT, + project_name TEXT, + user_name TEXT, + display_name TEXT, + first_name TEXT, + last_name TEXT, + email TEXT, + role_keys TEXT Array, + grant_id TEXT, + + grant_state SMALLINT, + creation_date TIMESTAMP, + change_date TIMESTAMP, + sequence BIGINT, + + PRIMARY KEY (id) +); + +CREATE TABLE management.org_domains ( + creation_date TIMESTAMP, + change_date TIMESTAMP, + sequence BIGINT, + + domain TEXT, + org_id TEXT, + verified BOOLEAN, + primary_domain BOOLEAN, + + PRIMARY KEY (org_id, domain) +); + +CREATE TABLE auth.locks ( + locker_id TEXT, + locked_until TIMESTAMP, + view_name TEXT, + + PRIMARY KEY (view_name) +); + +CREATE TABLE auth.current_sequences ( + view_name TEXT, + timestamp TIMESTAMP, + + current_sequence BIGINT, + + PRIMARY KEY (view_name) +); + +CREATE TABLE auth.failed_events ( + view_name TEXT, + failed_sequence BIGINT, + failure_count SMALLINT, + err_msg TEXT, + + PRIMARY KEY (view_name, failed_sequence) +); + +CREATE TABLE auth.auth_requests ( + id TEXT, + request JSONB, + code TEXT, + request_type smallint, + + PRIMARY KEY (id) +); + +CREATE TABLE auth.users ( + id TEXT, + + creation_date TIMESTAMP, + change_date TIMESTAMP, + + resource_owner TEXT, + user_state SMALLINT, + password_set BOOLEAN, + password_change_required BOOLEAN, + password_change TIMESTAMP, + last_login TIMESTAMP, + user_name TEXT, + login_names TEXT ARRAY, + preferred_login_name TEXT, + first_name TEXT, + last_name TEXT, + nick_name TEXT, + display_name TEXT, + preferred_language TEXT, + gender SMALLINT, + email TEXT, + is_email_verified BOOLEAN, + phone TEXT, + is_phone_verified BOOLEAN, + country TEXT, + locality TEXT, + postal_code TEXT, + region TEXT, + street_address TEXT, + otp_state SMALLINT, + mfa_max_set_up SMALLINT, + mfa_init_skipped TIMESTAMP, + sequence BIGINT, + init_required BOOLEAN, + + PRIMARY KEY (id) +); + +CREATE TABLE auth.user_sessions ( + creation_date TIMESTAMP, + change_date TIMESTAMP, + + resource_owner TEXT, + state SMALLINT, + user_agent_id TEXT, + user_id TEXT, + user_name TEXT, + password_verification TIMESTAMP, + mfa_software_verification TIMESTAMP, + mfa_hardware_verification TIMESTAMP, + sequence BIGINT, + mfa_software_verification_type SMALLINT, + mfa_hardware_verification_type SMALLINT, + user_display_name TEXT, + login_name TEXT, + + PRIMARY KEY (user_agent_id, user_id) +); + +CREATE TABLE auth.tokens ( + id TEXT, + + creation_date TIMESTAMP, + change_date TIMESTAMP, + + resource_owner TEXT, + application_id TEXT, + user_agent_id TEXT, + user_id TEXT, + expiration TIMESTAMP, + sequence BIGINT, + scopes TEXT ARRAY, + audience TEXT ARRAY, + + PRIMARY KEY (id) +); + + +CREATE TABLE notification.locks ( + locker_id TEXT, + locked_until TIMESTAMP, + view_name TEXT, + + PRIMARY KEY (view_name) +); + +CREATE TABLE notification.current_sequences ( + view_name TEXT, + timestamp TIMESTAMP, + + current_sequence BIGINT, + + PRIMARY KEY (view_name) +); + +CREATE TABLE notification.failed_events ( + view_name TEXT, + failed_sequence BIGINT, + failure_count SMALLINT, + err_msg TEXT, + + PRIMARY KEY (view_name, failed_sequence) +); + +CREATE TABLE notification.notify_users ( + id TEXT, + + creation_date TIMESTAMP, + change_date TIMESTAMP, + + resource_owner TEXT, + user_name TEXT, + first_name TEXT, + last_name TEXT, + nick_Name TEXT, + display_name TEXT, + preferred_language TEXT, + gender SMALLINT, + last_email TEXT, + verified_email TEXT, + last_phone TEXT, + verified_phone TEXT, + sequence BIGINT, + password_set BOOLEAN, + login_names TEXT, + preferred_login_name TEXT, + + PRIMARY KEY (id) +); + + +CREATE TABLE adminapi.orgs ( + id TEXT, + creation_date TIMESTAMP, + change_date TIMESTAMP, + resource_owner TEXT, + org_state SMALLINT, + sequence BIGINT, + + domain TEXT, + name TEXT, + + PRIMARY KEY (id) +); + +CREATE TABLE adminapi.failed_events ( + view_name TEXT, + failed_sequence BIGINT, + failure_count SMALLINT, + err_msg TEXT, + + PRIMARY KEY (view_name, failed_sequence) +); + +CREATE TABLE adminapi.locks ( + locker_id TEXT, + locked_until TIMESTAMP, + view_name TEXT, + + PRIMARY KEY (view_name) +); + +CREATE TABLE adminapi.current_sequences ( + view_name TEXT, + timestamp TIMESTAMP, + + current_sequence BIGINT, + + PRIMARY KEY (view_name) +); + +CREATE TABLE adminapi.iam_members ( + user_id TEXT, + + iam_id TEXT, + creation_date TIMESTAMP, + change_date TIMESTAMP, + + user_name TEXT, + email_address TEXT, + first_name TEXT, + last_name TEXT, + roles TEXT ARRAY, + display_name TEXT, + sequence BIGINT, + + PRIMARY KEY (user_id) +); + +CREATE TABLE management.orgs ( + id TEXT, + creation_date TIMESTAMP, + change_date TIMESTAMP, + resource_owner TEXT, + org_state SMALLINT, + sequence BIGINT, + + domain TEXT, + name TEXT, + + PRIMARY KEY (id) +); + +CREATE TABLE management.org_members ( + user_id TEXT, + org_id TEXT, + + creation_date TIMESTAMP, + change_date TIMESTAMP, + + user_name TEXT, + email_address TEXT, + first_name TEXT, + last_name TEXT, + roles TEXT ARRAY, + display_name TEXT, + sequence BIGINT, + + PRIMARY KEY (org_id, user_id) +); + + +CREATE TABLE auth.keys ( + id TEXT, + + creation_date TIMESTAMP, + change_date TIMESTAMP, + + resource_owner TEXT, + private BOOLEAN, + expiry TIMESTAMP, + algorithm TEXT, + usage SMALLINT, + key JSONB, + sequence BIGINT, + + PRIMARY KEY (id, private) +); + +CREATE TABLE auth.applications ( + id TEXT, + + creation_date TIMESTAMP, + change_date TIMESTAMP, + sequence BIGINT, + + app_state SMALLINT, + resource_owner TEXT, + app_name TEXT, + project_id TEXT, + app_type SMALLINT, + is_oidc BOOLEAN, + oidc_client_id TEXT, + oidc_redirect_uris TEXT ARRAY, + oidc_response_types SMALLINT ARRAY, + oidc_grant_types SMALLINT ARRAY, + oidc_application_type SMALLINT, + oidc_auth_method_type SMALLINT, + oidc_post_logout_redirect_uris TEXT ARRAY, + + PRIMARY KEY (id) +); + +CREATE TABLE auth.user_grants ( + id TEXT, + resource_owner TEXT, + project_id TEXT, + user_id TEXT, + org_name TEXT, + project_name TEXT, + user_name TEXT, + first_name TEXT, + last_name TEXT, + display_name TEXT, + email TEXT, + role_keys TEXT Array, + grant_id TEXT, + + grant_state SMALLINT, + creation_date TIMESTAMP, + change_date TIMESTAMP, + sequence BIGINT, + + PRIMARY KEY (id) +); + +CREATE TABLE auth.orgs ( + id TEXT, + creation_date TIMESTAMP, + change_date TIMESTAMP, + resource_owner TEXT, + org_state SMALLINT, + sequence BIGINT, + + domain TEXT, + name TEXT, + + PRIMARY KEY (id) +); + +CREATE TABLE authz.locks ( + locker_id TEXT, + locked_until TIMESTAMP, + view_name TEXT, + + PRIMARY KEY (view_name) +); + +CREATE TABLE authz.current_sequences ( + view_name TEXT, + timestamp TIMESTAMP, + + current_sequence BIGINT, + + PRIMARY KEY (view_name) +); + +CREATE TABLE authz.failed_events ( + view_name TEXT, + failed_sequence BIGINT, + failure_count SMALLINT, + err_msg TEXT, + + PRIMARY KEY (view_name, failed_sequence) +); + +CREATE TABLE authz.user_grants ( + id TEXT, + resource_owner TEXT, + project_id TEXT, + user_id TEXT, + org_name TEXT, + project_name TEXT, + user_name TEXT, + first_name TEXT, + last_name TEXT, + display_name TEXT, + email TEXT, + role_keys TEXT Array, + grant_id TEXT, + + grant_state SMALLINT, + creation_date TIMESTAMP, + change_date TIMESTAMP, + sequence BIGINT, + + PRIMARY KEY (id) +); + +CREATE TABLE authz.applications ( + id TEXT, + + creation_date TIMESTAMP, + change_date TIMESTAMP, + sequence BIGINT, + + app_state SMALLINT, + resource_owner TEXT, + app_name TEXT, + project_id TEXT, + app_type SMALLINT, + is_oidc BOOLEAN, + oidc_client_id TEXT, + oidc_redirect_uris TEXT ARRAY, + oidc_response_types SMALLINT ARRAY, + oidc_grant_types SMALLINT ARRAY, + oidc_application_type SMALLINT, + oidc_auth_method_type SMALLINT, + oidc_post_logout_redirect_uris TEXT ARRAY, + + PRIMARY KEY (id) +); + +CREATE TABLE authz.orgs ( + id TEXT, + creation_date TIMESTAMP, + change_date TIMESTAMP, + resource_owner TEXT, + org_state SMALLINT, + sequence BIGINT, + + domain TEXT, + name TEXT, + + PRIMARY KEY (id) +); diff --git a/migrations/sqlite/V1.3__usermembership.sql b/migrations/sqlite/V1.3__usermembership.sql new file mode 100644 index 0000000000..a750219095 --- /dev/null +++ b/migrations/sqlite/V1.3__usermembership.sql @@ -0,0 +1,15 @@ +CREATE TABLE management.user_memberships ( + user_id TEXT, + member_type SMALLINT, + aggregate_id TEXT, + object_id TEXT, + + roles TEXT ARRAY, + display_name TEXT, + resource_owner TEXT, + creation_date TIMESTAMPTZ, + change_date TIMESTAMPTZ, + sequence BIGINT, + + PRIMARY KEY (user_id, member_type, aggregate_id, object_id) +); diff --git a/migrations/sqlite/V1.4__compliance.sql b/migrations/sqlite/V1.4__compliance.sql new file mode 100644 index 0000000000..f9d6eb8c84 --- /dev/null +++ b/migrations/sqlite/V1.4__compliance.sql @@ -0,0 +1,14 @@ +ALTER TABLE management.applications ADD COLUMN oidc_version SMALLINT; +ALTER TABLE management.applications ADD COLUMN none_compliant BOOLEAN; +ALTER TABLE management.applications ADD COLUMN compliance_problems TEXT ARRAY; +ALTER TABLE management.applications ADD COLUMN dev_mode BOOLEAN; + +ALTER TABLE auth.applications ADD COLUMN oidc_version SMALLINT; +ALTER TABLE auth.applications ADD COLUMN none_compliant BOOLEAN; +ALTER TABLE auth.applications ADD COLUMN compliance_problems TEXT ARRAY; +ALTER TABLE auth.applications ADD COLUMN dev_mode BOOLEAN; + +ALTER TABLE authz.applications ADD COLUMN oidc_version SMALLINT; +ALTER TABLE authz.applications ADD COLUMN none_compliant BOOLEAN; +ALTER TABLE authz.applications ADD COLUMN compliance_problems TEXT ARRAY; +ALTER TABLE authz.applications ADD COLUMN dev_mode BOOLEAN; \ No newline at end of file diff --git a/migrations/sqlite/V1.5__orgdomain_validationtype.sql b/migrations/sqlite/V1.5__orgdomain_validationtype.sql new file mode 100644 index 0000000000..271451b1d4 --- /dev/null +++ b/migrations/sqlite/V1.5__orgdomain_validationtype.sql @@ -0,0 +1,44 @@ +BEGIN; + +ALTER TABLE management.org_domains ADD COLUMN validation_type SMALLINT; + +CREATE TABLE adminapi.users ( + id TEXT, + + creation_date TIMESTAMPTZ, + change_date TIMESTAMPTZ, + + resource_owner TEXT, + user_state SMALLINT, + last_login TIMESTAMPTZ, + password_change TIMESTAMPTZ, + user_name TEXT, + login_names TEXT ARRAY, + preferred_login_name TEXT, + first_name TEXT, + last_name TEXT, + nick_Name TEXT, + display_name TEXT, + preferred_language TEXT, + gender SMALLINT, + email TEXT, + is_email_verified BOOLEAN, + phone TEXT, + is_phone_verified BOOLEAN, + country TEXT, + locality TEXT, + postal_code TEXT, + region TEXT, + street_address TEXT, + otp_state SMALLINT, + sequence BIGINT, + password_set BOOLEAN, + password_change_required BOOLEAN, + mfa_max_set_up SMALLINT, + mfa_init_skipped TIMESTAMPTZ, + init_required BOOLEAN, + + PRIMARY KEY (id) +); + +COMMIT; \ No newline at end of file diff --git a/migrations/sqlite/V1.6__origin_allow_list.sql b/migrations/sqlite/V1.6__origin_allow_list.sql new file mode 100644 index 0000000000..7589e8cb9f --- /dev/null +++ b/migrations/sqlite/V1.6__origin_allow_list.sql @@ -0,0 +1,15 @@ +BEGIN; + +ALTER TABLE management.applications ADD COLUMN origin_allow_list TEXT ARRAY; +ALTER TABLE auth.applications ADD COLUMN origin_allow_list TEXT ARRAY; +ALTER TABLE authz.applications ADD COLUMN origin_allow_list TEXT ARRAY; + +TRUNCATE TABLE management.applications; +TRUNCATE TABLE auth.applications; +TRUNCATE TABLE authz.applications; + +UPDATE management.current_sequences set current_sequence = 0 where view_name = 'management.applications'; +UPDATE auth.current_sequences set current_sequence = 0 where view_name = 'auth.applications'; +UPDATE authz.current_sequences set current_sequence = 0 where view_name = 'authz.applications'; + +COMMIT; \ No newline at end of file diff --git a/migrations/sqlite/V1.7__idps.sql b/migrations/sqlite/V1.7__idps.sql new file mode 100644 index 0000000000..0650b3d3c7 --- /dev/null +++ b/migrations/sqlite/V1.7__idps.sql @@ -0,0 +1,105 @@ + +CREATE TABLE adminapi.idp_configs ( + idp_config_id TEXT, + + creation_date TIMESTAMPTZ, + change_date TIMESTAMPTZ, + sequence BIGINT, + aggregate_id TEXT, + name TEXT, + logo_src BYTES, + idp_state SMALLINT, + idp_provider_type SMALLINT, + + is_oidc BOOLEAN, + oidc_client_id TEXT, + oidc_client_secret JSONB, + oidc_issuer TEXT, + oidc_scopes TEXT ARRAY, + + PRIMARY KEY (idp_config_id) +); + + +CREATE TABLE management.idp_configs ( + idp_config_id TEXT, + + creation_date TIMESTAMPTZ, + change_date TIMESTAMPTZ, + sequence BIGINT, + aggregate_id TEXT, + name TEXT, + logo_src BYTES, + idp_state SMALLINT, + idp_provider_type SMALLINT, + + is_oidc BOOLEAN, + oidc_client_id TEXT, + oidc_client_secret JSONB, + oidc_issuer TEXT, + oidc_scopes TEXT ARRAY, + + PRIMARY KEY (idp_config_id) +); + + +CREATE TABLE adminapi.login_policies ( + aggregate_id TEXT, + + creation_date TIMESTAMPTZ, + change_date TIMESTAMPTZ, + login_policy_state SMALLINT, + sequence BIGINT, + + allow_register BOOLEAN, + allow_username_password BOOLEAN, + allow_external_idp BOOLEAN, + + PRIMARY KEY (aggregate_id) +); + + +CREATE TABLE management.login_policies ( + aggregate_id TEXT, + + creation_date TIMESTAMPTZ, + change_date TIMESTAMPTZ, + login_policy_state SMALLINT, + sequence BIGINT, + + allow_register BOOLEAN, + allow_username_password BOOLEAN, + allow_external_idp BOOLEAN, + + PRIMARY KEY (aggregate_id) +); + +CREATE TABLE adminapi.idp_providers ( + aggregate_id TEXT, + idp_config_id TEXT, + + creation_date TIMESTAMPTZ, + change_date TIMESTAMPTZ, + sequence BIGINT, + + name string, + idp_config_type SMALLINT, + idp_provider_type SMALLINT, + + PRIMARY KEY (aggregate_id, idp_config_id) +); + +CREATE TABLE management.idp_providers ( + aggregate_id TEXT, + idp_config_id TEXT, + + creation_date TIMESTAMPTZ, + change_date TIMESTAMPTZ, + sequence BIGINT, + + name string, + idp_config_type SMALLINT, + idp_provider_type SMALLINT, + + PRIMARY KEY (aggregate_id, idp_config_id) +); \ No newline at end of file diff --git a/migrations/sqlite/V1.8__username_change.sql b/migrations/sqlite/V1.8__username_change.sql new file mode 100644 index 0000000000..c8440a2092 --- /dev/null +++ b/migrations/sqlite/V1.8__username_change.sql @@ -0,0 +1,7 @@ +BEGIN; + +ALTER TABLE management.users ADD COLUMN username_change_required BOOLEAN; +ALTER TABLE auth.users ADD COLUMN username_change_required BOOLEAN; +ALTER TABLE adminapi.users ADD COLUMN username_change_required BOOLEAN; + +COMMIT; \ No newline at end of file diff --git a/migrations/sqlite/V1.9__token.sql b/migrations/sqlite/V1.9__token.sql new file mode 100644 index 0000000000..33424018e4 --- /dev/null +++ b/migrations/sqlite/V1.9__token.sql @@ -0,0 +1,2 @@ + +ALTER TABLE auth.tokens ADD COLUMN preferred_language TEXT; \ No newline at end of file diff --git a/migrations/sqlite/clean_local.go b/migrations/sqlite/clean_local.go new file mode 100644 index 0000000000..7878f63ca5 --- /dev/null +++ b/migrations/sqlite/clean_local.go @@ -0,0 +1,5 @@ +//+build ignore + +package migrations + +//go:generate flyway -url=jdbc:postgresql://localhost:26257/defaultdb -user=root -password= -locations=filesystem:./ clean diff --git a/migrations/sqlite/migrate_local.go b/migrations/sqlite/migrate_local.go new file mode 100644 index 0000000000..f442c01400 --- /dev/null +++ b/migrations/sqlite/migrate_local.go @@ -0,0 +1,5 @@ +//+build ignore + +package migrations + +//go:generate flyway -url=jdbc:sqlite:/Users/silvanreusser/go/src/github.com/caos/zitadel/.local/zitadel.db -user=admin -password= -schemas=eventstore, -locations=filesystem:./ migrate