feat(admin-api): list events (#4989)

* docs: update cockroachdb version to 22.2
* feat(adminAPI): ListEventTypes returns the list of event types ZITADEL implements
* feat(adminAPI): ListAggregateTypes returns the list of aggregate types ZITADEL implements
* feat(adminAPI): ListEvents allows `IAM_OWNERS` to search for events
This commit is contained in:
Silvan
2023-01-16 12:30:03 +01:00
committed by GitHub
parent 74c1c39207
commit 1bf1f335dc
30 changed files with 888 additions and 360 deletions

View File

@@ -5,6 +5,7 @@ import (
"database/sql"
"encoding/json"
"reflect"
"sort"
"sync"
"time"
@@ -19,6 +20,8 @@ type Eventstore struct {
repo repository.Repository
interceptorMutex sync.Mutex
eventInterceptors map[EventType]eventTypeInterceptors
eventTypes []string
aggregateTypes []string
PushTimeout time.Duration
}
@@ -73,6 +76,14 @@ func (es *Eventstore) NewInstance(ctx context.Context, instanceID string) error
return es.repo.CreateInstance(ctx, instanceID)
}
func (es *Eventstore) EventTypes() []string {
return es.eventTypes
}
func (es *Eventstore) AggregateTypes() []string {
return es.aggregateTypes
}
func commandsToRepository(instanceID string, cmds []Command) (events []*repository.Event, constraints []*repository.UniqueConstraint, err error) {
events = make([]*repository.Event, len(cmds))
for i, cmd := range cmds {
@@ -224,13 +235,16 @@ func (es *Eventstore) FilterToQueryReducer(ctx context.Context, r QueryReducer)
}
// RegisterFilterEventMapper registers a function for mapping an eventstore event to an event
func (es *Eventstore) RegisterFilterEventMapper(eventType EventType, mapper func(*repository.Event) (Event, error)) *Eventstore {
func (es *Eventstore) RegisterFilterEventMapper(aggregateType AggregateType, eventType EventType, mapper func(*repository.Event) (Event, error)) *Eventstore {
if mapper == nil || eventType == "" {
return es
}
es.interceptorMutex.Lock()
defer es.interceptorMutex.Unlock()
es.appendEventType(eventType)
es.appendAggregateType(aggregateType)
interceptor := es.eventInterceptors[eventType]
interceptor.eventMapper = mapper
es.eventInterceptors[eventType] = interceptor
@@ -238,6 +252,22 @@ func (es *Eventstore) RegisterFilterEventMapper(eventType EventType, mapper func
return es
}
func (es *Eventstore) appendEventType(typ EventType) {
i := sort.SearchStrings(es.eventTypes, string(typ))
if i > 0 && es.eventTypes[i-1] == string(typ) {
return
}
es.eventTypes = append(es.eventTypes[:i], append([]string{string(typ)}, es.eventTypes[i:]...)...)
}
func (es *Eventstore) appendAggregateType(typ AggregateType) {
i := sort.SearchStrings(es.aggregateTypes, string(typ))
if len(es.aggregateTypes) > i && es.aggregateTypes[i] == string(typ) {
return
}
es.aggregateTypes = append(es.aggregateTypes[:i], append([]string{string(typ)}, es.aggregateTypes[i:]...)...)
}
func EventData(event Command) ([]byte, error) {
switch data := event.Data().(type) {
case nil:

View File

@@ -156,7 +156,7 @@ func Test_eventstore_RegisterFilterEventMapper(t *testing.T) {
es := &Eventstore{
eventInterceptors: tt.fields.eventMapper,
}
es = es.RegisterFilterEventMapper(tt.args.eventType, tt.args.mapper)
es = es.RegisterFilterEventMapper("test", tt.args.eventType, tt.args.mapper)
if len(es.eventInterceptors) != tt.res.mapperCount {
t.Errorf("unexpected mapper count: want %d, got %d", tt.res.mapperCount, len(es.eventInterceptors))
return
@@ -990,7 +990,7 @@ func TestEventstore_Push(t *testing.T) {
eventInterceptors: map[EventType]eventTypeInterceptors{},
}
for eventType, mapper := range tt.fields.eventMapper {
es = es.RegisterFilterEventMapper(eventType, mapper)
es = es.RegisterFilterEventMapper("test", eventType, mapper)
}
if len(es.eventInterceptors) != len(tt.fields.eventMapper) {
t.Errorf("register event mapper failed expected mapper amount: %d, got: %d", len(tt.fields.eventMapper), len(es.eventInterceptors))
@@ -1130,7 +1130,7 @@ func TestEventstore_FilterEvents(t *testing.T) {
}
for eventType, mapper := range tt.fields.eventMapper {
es = es.RegisterFilterEventMapper(eventType, mapper)
es = es.RegisterFilterEventMapper("test", eventType, mapper)
}
if len(es.eventInterceptors) != len(tt.fields.eventMapper) {
t.Errorf("register event mapper failed expected mapper amount: %d, got: %d", len(tt.fields.eventMapper), len(es.eventInterceptors))
@@ -1448,7 +1448,7 @@ func TestEventstore_FilterToReducer(t *testing.T) {
eventInterceptors: map[EventType]eventTypeInterceptors{},
}
for eventType, mapper := range tt.fields.eventMapper {
es = es.RegisterFilterEventMapper(eventType, mapper)
es = es.RegisterFilterEventMapper("test", eventType, mapper)
}
if len(es.eventInterceptors) != len(tt.fields.eventMapper) {
t.Errorf("register event mapper failed expected mapper amount: %d, got: %d", len(tt.fields.eventMapper), len(es.eventInterceptors))
@@ -1589,7 +1589,7 @@ func TestEventstore_mapEvents(t *testing.T) {
eventInterceptors: map[EventType]eventTypeInterceptors{},
}
for eventType, mapper := range tt.fields.eventMapper {
es = es.RegisterFilterEventMapper(eventType, mapper)
es = es.RegisterFilterEventMapper("test", eventType, mapper)
}
if len(es.eventInterceptors) != len(tt.fields.eventMapper) {
t.Errorf("register event mapper failed expected mapper amount: %d, got: %d", len(tt.fields.eventMapper), len(es.eventInterceptors))

View File

@@ -43,8 +43,8 @@ func NewUserAddedEvent(id string, firstName string) *UserAddedEvent {
}
}
func UserAddedEventMapper() (eventstore.EventType, func(*repository.Event) (eventstore.Event, error)) {
return "user.added", func(event *repository.Event) (eventstore.Event, error) {
func UserAddedEventMapper() (eventstore.AggregateType, eventstore.EventType, func(*repository.Event) (eventstore.Event, error)) {
return "user", "user.added", func(event *repository.Event) (eventstore.Event, error) {
e := &UserAddedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(event),
}
@@ -88,8 +88,8 @@ func NewUserFirstNameChangedEvent(id, firstName string) *UserFirstNameChangedEve
}
}
func UserFirstNameChangedMapper() (eventstore.EventType, func(*repository.Event) (eventstore.Event, error)) {
return "user.firstName.changed", func(event *repository.Event) (eventstore.Event, error) {
func UserFirstNameChangedMapper() (eventstore.AggregateType, eventstore.EventType, func(*repository.Event) (eventstore.Event, error)) {
return "user", "user.firstName.changed", func(event *repository.Event) (eventstore.Event, error) {
e := &UserFirstNameChangedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(event),
}
@@ -130,8 +130,8 @@ func NewUserPasswordCheckedEvent(id string) *UserPasswordCheckedEvent {
}
}
func UserPasswordCheckedMapper() (eventstore.EventType, func(*repository.Event) (eventstore.Event, error)) {
return "user.password.checked", func(event *repository.Event) (eventstore.Event, error) {
func UserPasswordCheckedMapper() (eventstore.AggregateType, eventstore.EventType, func(*repository.Event) (eventstore.Event, error)) {
return "user", "user.password.checked", func(event *repository.Event) (eventstore.Event, error) {
return &UserPasswordCheckedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(event),
}, nil
@@ -167,8 +167,8 @@ func NewUserDeletedEvent(id string) *UserDeletedEvent {
}
}
func UserDeletedMapper() (eventstore.EventType, func(*repository.Event) (eventstore.Event, error)) {
return "user.deleted", func(event *repository.Event) (eventstore.Event, error) {
func UserDeletedMapper() (eventstore.AggregateType, eventstore.EventType, func(*repository.Event) (eventstore.Event, error)) {
return "user", "user.deleted", func(event *repository.Event) (eventstore.Event, error) {
return &UserDeletedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(event),
}, nil

View File

@@ -17,6 +17,7 @@ type SearchQueryBuilder struct {
desc bool
resourceOwner string
instanceID string
editorUser string
queries []*SearchQuery
tx *sql.Tx
}
@@ -124,6 +125,11 @@ func (builder *SearchQueryBuilder) SetTx(tx *sql.Tx) *SearchQueryBuilder {
return builder
}
func (builder *SearchQueryBuilder) EditorUser(id string) *SearchQueryBuilder {
builder.editorUser = id
return builder
}
// AddQuery creates a new sub query.
// All fields in the sub query are AND-connected in the storage request.
// Multiple sub queries are OR-connected in the storage request.
@@ -245,6 +251,7 @@ func (builder *SearchQueryBuilder) build(instanceID string) (*repository.SearchQ
query.creationDateAfterFilter,
query.builder.resourceOwnerFilter,
query.builder.instanceIDFilter,
query.builder.editorUserFilter,
} {
if filter := f(); filter != nil {
if err := filter.Validate(); err != nil {
@@ -353,6 +360,13 @@ func (builder *SearchQueryBuilder) instanceIDFilter() *repository.Filter {
return repository.NewFilter(repository.FieldInstanceID, builder.instanceID, repository.OperationEquals)
}
func (builder *SearchQueryBuilder) editorUserFilter() *repository.Filter {
if builder.editorUser == "" {
return nil
}
return repository.NewFilter(repository.FieldEditorUser, builder.editorUser, repository.OperationEquals)
}
func (query *SearchQuery) creationDateAfterFilter() *repository.Filter {
if query.creationDateAfter.IsZero() {
return nil