diff --git a/backend/domain/instance.go b/backend/domain/instance.go index 44ab2f089e..8036274e60 100644 --- a/backend/domain/instance.go +++ b/backend/domain/instance.go @@ -4,7 +4,6 @@ import ( "context" "github.com/zitadel/zitadel/backend/repository" - "github.com/zitadel/zitadel/backend/repository/orchestrate" "github.com/zitadel/zitadel/backend/storage/database" "github.com/zitadel/zitadel/backend/telemetry/logging" "github.com/zitadel/zitadel/backend/telemetry/tracing" @@ -13,11 +12,11 @@ import ( type Instance struct { db database.Pool - instance instanceOrchestrator - user userOrchestrator + instance instanceRepository + user userRepository } -type instanceOrchestrator interface { +type instanceRepository interface { ByID(ctx context.Context, querier database.Querier, id string) (*repository.Instance, error) ByDomain(ctx context.Context, querier database.Querier, domain string) (*repository.Instance, error) Create(ctx context.Context, tx database.Transaction, instance *repository.Instance) (*repository.Instance, error) @@ -25,9 +24,15 @@ type instanceOrchestrator interface { func NewInstance(db database.Pool, tracer *tracing.Tracer, logger *logging.Logger) *Instance { b := &Instance{ - db: db, - instance: orchestrate.Instance(), - user: orchestrate.User(), + db: db, + instance: repository.NewInstance( + repository.WithLogger[repository.InstanceOptions](logger), + repository.WithTracer[repository.InstanceOptions](tracer), + ), + user: repository.NewUser( + repository.WithLogger[repository.UserOptions](logger), + repository.WithTracer[repository.UserOptions](tracer), + ), } return b @@ -59,5 +64,6 @@ func (b *Instance) SetUp(ctx context.Context, request *SetUpInstance) (err error return err } _, err = b.user.Create(ctx, tx, request.User) + b.authorizations.authorizeusers return err } diff --git a/backend/domain/user.go b/backend/domain/user.go index 971e6d6583..8df253c132 100644 --- a/backend/domain/user.go +++ b/backend/domain/user.go @@ -7,7 +7,7 @@ import ( "github.com/zitadel/zitadel/backend/storage/database" ) -type userOrchestrator interface { +type userRepository interface { Create(ctx context.Context, tx database.Transaction, user *repository.User) (*repository.User, error) ByID(ctx context.Context, querier database.Querier, id string) (*repository.User, error) } diff --git a/backend/repository/cached/instance.go b/backend/repository/cached/instance.go deleted file mode 100644 index 4859e06b65..0000000000 --- a/backend/repository/cached/instance.go +++ /dev/null @@ -1,34 +0,0 @@ -package cached - -import ( - "context" - "log" - - "github.com/zitadel/zitadel/backend/repository" - "github.com/zitadel/zitadel/backend/storage/cache" -) - -type Instance struct { - cache.Cache[repository.InstanceIndex, string, *repository.Instance] -} - -func NewInstance(c cache.Cache[repository.InstanceIndex, string, *repository.Instance]) *Instance { - return &Instance{c} -} - -func (i *Instance) ByID(ctx context.Context, id string) *repository.Instance { - log.Println("cached.instance.byID") - instance, _ := i.Cache.Get(ctx, repository.InstanceByID, id) - return instance -} - -func (i *Instance) ByDomain(ctx context.Context, domain string) *repository.Instance { - log.Println("cached.instance.byDomain") - instance, _ := i.Cache.Get(ctx, repository.InstanceByDomain, domain) - return instance -} - -func (i *Instance) Set(ctx context.Context, instance *repository.Instance) { - log.Println("cached.instance.set") - i.Cache.Set(ctx, instance) -} diff --git a/backend/repository/cached/user.go b/backend/repository/cached/user.go deleted file mode 100644 index 9bb5c11a18..0000000000 --- a/backend/repository/cached/user.go +++ /dev/null @@ -1,28 +0,0 @@ -package cached - -import ( - "context" - "log" - - "github.com/zitadel/zitadel/backend/repository" - "github.com/zitadel/zitadel/backend/storage/cache" -) - -type User struct { - cache.Cache[repository.UserIndex, string, *repository.User] -} - -func NewUser(c cache.Cache[repository.UserIndex, string, *repository.User]) *User { - return &User{c} -} - -func (i *User) ByID(ctx context.Context, id string) *repository.User { - log.Println("cached.user.byid") - user, _ := i.Cache.Get(ctx, repository.UserByIDIndex, id) - return user -} - -func (i *User) Set(ctx context.Context, user *repository.User) { - log.Println("cached.user.set") - i.Cache.Set(ctx, user) -} diff --git a/backend/repository/event/instance.go b/backend/repository/event/instance.go deleted file mode 100644 index 19fcce6b44..0000000000 --- a/backend/repository/event/instance.go +++ /dev/null @@ -1,17 +0,0 @@ -package event - -import ( - "context" - "log" - - "github.com/zitadel/zitadel/backend/repository" -) - -func (s *store) CreateInstance(ctx context.Context, instance *repository.Instance) (*repository.Instance, error) { - log.Println("event.instance.create") - err := s.es.Push(ctx, instance) - if err != nil { - return nil, err - } - return instance, nil -} diff --git a/backend/repository/event/store.go b/backend/repository/event/store.go deleted file mode 100644 index eff0636a35..0000000000 --- a/backend/repository/event/store.go +++ /dev/null @@ -1,16 +0,0 @@ -package event - -import ( - "github.com/zitadel/zitadel/backend/storage/database" - "github.com/zitadel/zitadel/backend/storage/eventstore" -) - -type store struct { - es *eventstore.Eventstore -} - -func Store(client database.Executor) *store { - return &store{ - es: eventstore.New(client), - } -} diff --git a/backend/repository/event/user.go b/backend/repository/event/user.go deleted file mode 100644 index 71087043c0..0000000000 --- a/backend/repository/event/user.go +++ /dev/null @@ -1,17 +0,0 @@ -package event - -import ( - "context" - "log" - - "github.com/zitadel/zitadel/backend/repository" -) - -func (s *store) CreateUser(ctx context.Context, user *repository.User) (*repository.User, error) { - log.Println("event.user.create") - err := s.es.Push(ctx, user) - if err != nil { - return nil, err - } - return user, nil -} diff --git a/backend/repository/instance.go b/backend/repository/instance.go index 16ba2bea96..7ee923d641 100644 --- a/backend/repository/instance.go +++ b/backend/repository/instance.go @@ -1,37 +1,115 @@ package repository -import "github.com/zitadel/zitadel/backend/storage/cache" +import ( + "context" + + "github.com/zitadel/zitadel/backend/handler" + "github.com/zitadel/zitadel/backend/storage/database" + "github.com/zitadel/zitadel/backend/telemetry/logging" + "github.com/zitadel/zitadel/backend/telemetry/tracing" +) type Instance struct { ID string Name string } -type InstanceIndex uint8 - -var InstanceIndices = []InstanceIndex{ - InstanceByID, - InstanceByDomain, +type InstanceOptions struct { + cache *InstanceCache } -const ( - InstanceByID InstanceIndex = iota - InstanceByDomain -) +type instance struct { + options[InstanceOptions] + *InstanceOptions +} -var _ cache.Entry[InstanceIndex, string] = (*Instance)(nil) +func NewInstance(opts ...Option[InstanceOptions]) *instance { + i := new(instance) + i.InstanceOptions = &i.options.custom -// Keys implements [cache.Entry]. -func (i *Instance) Keys(index InstanceIndex) (key []string) { - switch index { - case InstanceByID: - return []string{i.ID} - case InstanceByDomain: - return []string{i.Name} + for _, opt := range opts { + opt.apply(&i.options) } - return nil + return i +} + +func WithInstanceCache(c *InstanceCache) Option[InstanceOptions] { + return func(opts *options[InstanceOptions]) { + opts.custom.cache = c + } +} + +func (i *instance) Create(ctx context.Context, tx database.Transaction, instance *Instance) (*Instance, error) { + return tracing.Wrap(i.tracer, "instance.SetUp", + handler.Chains( + handler.Decorates( + execute(tx).CreateInstance, + tracing.Decorate[*Instance, *Instance](i.tracer, tracing.WithSpanName("instance.sql.SetUp")), + logging.Decorate[*Instance, *Instance](i.logger, "instance.sql.SetUp"), + ), + handler.Decorates( + events(tx).CreateInstance, + tracing.Decorate[*Instance, *Instance](i.tracer, tracing.WithSpanName("instance.event.SetUp")), + logging.Decorate[*Instance, *Instance](i.logger, "instance.event.SetUp"), + ), + handler.SkipReturnPreviousHandler(i.cache, + handler.Decorates( + handler.NoReturnToHandle(i.cache.Set), + tracing.Decorate[*Instance, *Instance](i.tracer, tracing.WithSpanName("instance.cache.SetUp")), + logging.Decorate[*Instance, *Instance](i.logger, "instance.cache.SetUp"), + ), + ), + ), + )(ctx, instance) +} + +func (i *instance) ByID(ctx context.Context, querier database.Querier, id string) (*Instance, error) { + return tracing.Wrap(i.tracer, "instance.byID", + handler.SkipNext( + handler.SkipNilHandler(i.cache, + handler.ResFuncToHandle(i.cache.ByID), + ), + handler.Chain( + handler.Decorates( + query(querier).InstanceByID, + tracing.Decorate[string, *Instance](i.tracer, tracing.WithSpanName("instance.sql.ByID")), + logging.Decorate[string, *Instance](i.logger, "instance.sql.ByID"), + ), + handler.SkipNilHandler(i.cache, handler.NoReturnToHandle(i.cache.Set)), + ), + ), + )(ctx, id) +} + +func (i *instance) ByDomain(ctx context.Context, querier database.Querier, domain string) (*Instance, error) { + return tracing.Wrap(i.tracer, "instance.byDomain", + handler.SkipNext( + handler.SkipNilHandler(i.cache, + handler.ResFuncToHandle(i.cache.ByDomain), + ), + handler.Chain( + handler.Decorate( + query(querier).InstanceByDomain, + tracing.Decorate[string, *Instance](i.tracer, tracing.WithSpanName("instance.sql.ByDomain")), + ), + handler.SkipNilHandler(i.cache, handler.NoReturnToHandle(i.cache.Set)), + ), + ), + )(ctx, domain) } type ListRequest struct { Limit uint16 } + +func (i *instance) List(ctx context.Context, querier database.Querier, request *ListRequest) ([]*Instance, error) { + return tracing.Wrap(i.tracer, "instance.list", + handler.Chains( + handler.Decorates( + query(querier).ListInstances, + tracing.Decorate[*ListRequest, []*Instance](i.tracer, tracing.WithSpanName("instance.sql.List")), + logging.Decorate[*ListRequest, []*Instance](i.logger, "instance.sql.List"), + ), + ), + )(ctx, request) +} diff --git a/backend/repository/orchestrate/handler/handle.go b/backend/repository/orchestrate/handler/handle.go deleted file mode 100644 index c3dd46c17e..0000000000 --- a/backend/repository/orchestrate/handler/handle.go +++ /dev/null @@ -1,110 +0,0 @@ -package handler - -import ( - "context" -) - -// Handler is a function that handles the in. -type Handler[Out, In any] func(ctx context.Context, in Out) (out In, err error) - -// Decorator is a function that decorates the handle function. -type Decorator[In, Out any] func(ctx context.Context, in In, handle Handler[In, Out]) (out Out, err error) - -// Chain chains the handle function with the next handler. -// The next handler is called after the handle function. -func Chain[In, Out any](handle Handler[In, Out], next Handler[Out, Out]) Handler[In, Out] { - return func(ctx context.Context, in In) (out Out, err error) { - out, err = handle(ctx, in) - if err != nil { - return out, err - } - return next(ctx, out) - } -} - -func Chains[In, Out any](handle Handler[In, Out], nexts ...Handler[Out, Out]) Handler[In, Out] { - return func(ctx context.Context, in In) (out Out, err error) { - for _, next := range nexts { - handle = Chain(handle, next) - } - return handle(ctx, in) - } -} - -// Decorate decorates the handle function with the decorate function. -// The decorate function is called before the handle function. -func Decorate[In, Out any](handle Handler[In, Out], decorate Decorator[In, Out]) Handler[In, Out] { - return func(ctx context.Context, in In) (out Out, err error) { - return decorate(ctx, in, handle) - } -} - -// Decorates decorates the handle function with the decorate functions. -// The decorates function is called before the handle function. -func Decorates[In, Out any](handle Handler[In, Out], decorates ...Decorator[In, Out]) Handler[In, Out] { - return func(ctx context.Context, in In) (out Out, err error) { - for i := len(decorates) - 1; i >= 0; i-- { - handle = Decorate(handle, decorates[i]) - } - return handle(ctx, in) - } -} - -// SkipNext skips the next handler if the handle function returns a non-nil response. -func SkipNext[In, Out any](handle Handler[In, Out], next Handler[In, Out]) Handler[In, Out] { - return func(ctx context.Context, in In) (out Out, err error) { - var empty Out - out, err = handle(ctx, in) - // TODO: does this work? - if any(out) != any(empty) || err != nil { - return out, err - } - return next(ctx, in) - } -} - -// SkipNilHandler skips the handle function if the handler is nil. -// If handle is nil, an empty output is returned. -// The function is safe to call with nil handler. -func SkipNilHandler[O, In, Out any](handler *O, handle Handler[In, Out]) Handler[In, Out] { - return func(ctx context.Context, in In) (out Out, err error) { - if handler == nil { - return out, nil - } - return handle(ctx, in) - } -} - -// SkipReturnPreviousHandler skips the handle function if the handler is nil and returns the input. -// The function is safe to call with nil handler. -func SkipReturnPreviousHandler[O, In any](handler *O, handle Handler[In, In]) Handler[In, In] { - return func(ctx context.Context, in In) (out In, err error) { - if handler == nil { - return in, nil - } - return handle(ctx, in) - } -} - -func ResFuncToHandle[In any, Out any](fn func(context.Context, In) Out) Handler[In, Out] { - return func(ctx context.Context, in In) (out Out, err error) { - return fn(ctx, in), nil - } -} - -func ErrFuncToHandle[In any](fn func(context.Context, In) error) Handler[In, In] { - return func(ctx context.Context, in In) (out In, err error) { - err = fn(ctx, in) - if err != nil { - return out, err - } - return in, nil - } -} - -func NoReturnToHandle[In any](fn func(context.Context, In)) Handler[In, In] { - return func(ctx context.Context, in In) (out In, err error) { - fn(ctx, in) - return in, nil - } -} diff --git a/backend/repository/orchestrate/instance.go b/backend/repository/orchestrate/instance.go deleted file mode 100644 index 3378c0163b..0000000000 --- a/backend/repository/orchestrate/instance.go +++ /dev/null @@ -1,100 +0,0 @@ -package orchestrate - -import ( - "context" - - "github.com/zitadel/zitadel/backend/repository" - "github.com/zitadel/zitadel/backend/repository/cached" - "github.com/zitadel/zitadel/backend/repository/event" - "github.com/zitadel/zitadel/backend/repository/orchestrate/handler" - "github.com/zitadel/zitadel/backend/repository/sql" - "github.com/zitadel/zitadel/backend/repository/telemetry/logged" - "github.com/zitadel/zitadel/backend/repository/telemetry/traced" - "github.com/zitadel/zitadel/backend/storage/cache" - "github.com/zitadel/zitadel/backend/storage/database" - "github.com/zitadel/zitadel/backend/telemetry/tracing" -) - -type InstanceOptions struct { - cache *cached.Instance -} - -type instance struct { - options[InstanceOptions] - *InstanceOptions -} - -func Instance(opts ...Option[InstanceOptions]) *instance { - i := new(instance) - i.InstanceOptions = &i.options.custom - - for _, opt := range opts { - opt.apply(&i.options) - } - return i -} - -func WithInstanceCache(c cache.Cache[repository.InstanceIndex, string, *repository.Instance]) Option[InstanceOptions] { - return func(opts *options[InstanceOptions]) { - opts.custom.cache = cached.NewInstance(c) - } -} - -func (i *instance) Create(ctx context.Context, tx database.Transaction, instance *repository.Instance) (*repository.Instance, error) { - return traced.Wrap(i.tracer, "instance.SetUp", - handler.Chains( - handler.Decorates( - sql.Execute(tx).CreateInstance, - traced.Decorate[*repository.Instance, *repository.Instance](i.tracer, tracing.WithSpanName("instance.sql.SetUp")), - logged.Decorate[*repository.Instance, *repository.Instance](i.logger, "instance.sql.SetUp"), - ), - handler.Decorates( - event.Store(tx).CreateInstance, - traced.Decorate[*repository.Instance, *repository.Instance](i.tracer, tracing.WithSpanName("instance.event.SetUp")), - logged.Decorate[*repository.Instance, *repository.Instance](i.logger, "instance.event.SetUp"), - ), - handler.SkipReturnPreviousHandler(i.cache, - handler.Decorates( - handler.NoReturnToHandle(i.cache.Set), - traced.Decorate[*repository.Instance, *repository.Instance](i.tracer, tracing.WithSpanName("instance.cache.SetUp")), - logged.Decorate[*repository.Instance, *repository.Instance](i.logger, "instance.cache.SetUp"), - ), - ), - ), - )(ctx, instance) -} - -func (i *instance) ByID(ctx context.Context, querier database.Querier, id string) (*repository.Instance, error) { - return traced.Wrap(i.tracer, "instance.byID", - handler.SkipNext( - handler.SkipNilHandler(i.cache, - handler.ResFuncToHandle(i.cache.ByID), - ), - handler.Chain( - handler.Decorates( - sql.Query(querier).InstanceByID, - traced.Decorate[string, *repository.Instance](i.tracer, tracing.WithSpanName("instance.sql.ByID")), - logged.Decorate[string, *repository.Instance](i.logger, "instance.sql.ByID"), - ), - handler.SkipNilHandler(i.cache, handler.NoReturnToHandle(i.cache.Set)), - ), - ), - )(ctx, id) -} - -func (i *instance) ByDomain(ctx context.Context, querier database.Querier, domain string) (*repository.Instance, error) { - return traced.Wrap(i.tracer, "instance.byDomain", - handler.SkipNext( - handler.SkipNilHandler(i.cache, - handler.ResFuncToHandle(i.cache.ByDomain), - ), - handler.Chain( - handler.Decorate( - sql.Query(querier).InstanceByDomain, - traced.Decorate[string, *repository.Instance](i.tracer, tracing.WithSpanName("instance.sql.ByDomain")), - ), - handler.SkipNilHandler(i.cache, handler.NoReturnToHandle(i.cache.Set)), - ), - ), - )(ctx, domain) -} diff --git a/backend/repository/orchestrate/instance_test.go b/backend/repository/orchestrate/instance_test.go deleted file mode 100644 index 1b2ba7f55f..0000000000 --- a/backend/repository/orchestrate/instance_test.go +++ /dev/null @@ -1,244 +0,0 @@ -package orchestrate_test - -import ( - "context" - "fmt" - "log/slog" - "os" - "reflect" - "testing" - - "github.com/zitadel/zitadel/backend/repository" - "github.com/zitadel/zitadel/backend/repository/orchestrate" - "github.com/zitadel/zitadel/backend/repository/sql" - "github.com/zitadel/zitadel/backend/storage/cache" - "github.com/zitadel/zitadel/backend/storage/cache/connector/gomap" - "github.com/zitadel/zitadel/backend/storage/database" - "github.com/zitadel/zitadel/backend/storage/database/mock" - "github.com/zitadel/zitadel/backend/telemetry/logging" - "github.com/zitadel/zitadel/backend/telemetry/tracing" -) - -func Test_instance_Create(t *testing.T) { - type args struct { - ctx context.Context - tx database.Transaction - instance *repository.Instance - } - tests := []struct { - name string - opts []orchestrate.Option[orchestrate.InstanceOptions] - args args - want *repository.Instance - wantErr bool - }{ - { - name: "simple", - opts: []orchestrate.Option[orchestrate.InstanceOptions]{ - orchestrate.WithTracer[orchestrate.InstanceOptions](tracing.NewTracer("test")), - orchestrate.WithLogger[orchestrate.InstanceOptions](logging.New(slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug})))), - orchestrate.WithInstanceCache( - gomap.NewCache[repository.InstanceIndex, string, *repository.Instance](context.Background(), repository.InstanceIndices, cache.Config{}), - ), - }, - args: args{ - ctx: context.Background(), - tx: mock.NewTransaction(t, mock.ExpectExec(sql.InstanceCreateStmt, "ID", "Name")), - instance: &repository.Instance{ - ID: "ID", - Name: "Name", - }, - }, - want: &repository.Instance{ - ID: "ID", - Name: "Name", - }, - wantErr: false, - }, - { - name: "without cache", - opts: []orchestrate.Option[orchestrate.InstanceOptions]{ - orchestrate.WithTracer[orchestrate.InstanceOptions](tracing.NewTracer("test")), - orchestrate.WithLogger[orchestrate.InstanceOptions](logging.New(slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug})))), - }, - args: args{ - ctx: context.Background(), - tx: mock.NewTransaction(t, mock.ExpectExec(sql.InstanceCreateStmt, "ID", "Name")), - instance: &repository.Instance{ - ID: "ID", - Name: "Name", - }, - }, - want: &repository.Instance{ - ID: "ID", - Name: "Name", - }, - wantErr: false, - }, - { - name: "without cache, tracer", - opts: []orchestrate.Option[orchestrate.InstanceOptions]{ - orchestrate.WithLogger[orchestrate.InstanceOptions](logging.New(slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug})))), - }, - args: args{ - ctx: context.Background(), - tx: mock.NewTransaction(t, mock.ExpectExec(sql.InstanceCreateStmt, "ID", "Name")), - instance: &repository.Instance{ - ID: "ID", - Name: "Name", - }, - }, - want: &repository.Instance{ - ID: "ID", - Name: "Name", - }, - wantErr: false, - }, - { - name: "without cache, tracer, logger", - args: args{ - ctx: context.Background(), - tx: mock.NewTransaction(t, mock.ExpectExec(sql.InstanceCreateStmt, "ID", "Name")), - instance: &repository.Instance{ - ID: "ID", - Name: "Name", - }, - }, - want: &repository.Instance{ - ID: "ID", - Name: "Name", - }, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - fmt.Printf("------------------------ %s ------------------------\n", tt.name) - i := orchestrate.Instance(tt.opts...) - got, err := i.Create(tt.args.ctx, tt.args.tx, tt.args.instance) - if (err != nil) != tt.wantErr { - t.Errorf("instance.Create() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("instance.Create() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_instance_ByID(t *testing.T) { - type args struct { - ctx context.Context - tx database.Transaction - id string - } - tests := []struct { - name string - opts []orchestrate.Option[orchestrate.InstanceOptions] - args args - want *repository.Instance - wantErr bool - }{ - { - name: "simple, not cached", - opts: []orchestrate.Option[orchestrate.InstanceOptions]{ - orchestrate.WithTracer[orchestrate.InstanceOptions](tracing.NewTracer("test")), - orchestrate.WithLogger[orchestrate.InstanceOptions](logging.New(slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug})))), - orchestrate.WithInstanceCache( - gomap.NewCache[repository.InstanceIndex, string, *repository.Instance](context.Background(), repository.InstanceIndices, cache.Config{}), - ), - }, - args: args{ - ctx: context.Background(), - tx: mock.NewTransaction(t, - mock.ExpectQueryRow(mock.NewRow(t, "id", "Name"), sql.InstanceByIDStmt, "id"), - ), - id: "id", - }, - want: &repository.Instance{ - ID: "id", - Name: "Name", - }, - wantErr: false, - }, - { - name: "simple, cached", - opts: []orchestrate.Option[orchestrate.InstanceOptions]{ - orchestrate.WithTracer[orchestrate.InstanceOptions](tracing.NewTracer("test")), - orchestrate.WithLogger[orchestrate.InstanceOptions](logging.New(slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug})))), - orchestrate.WithInstanceCache( - func() cache.Cache[repository.InstanceIndex, string, *repository.Instance] { - c := gomap.NewCache[repository.InstanceIndex, string, *repository.Instance](context.Background(), repository.InstanceIndices, cache.Config{}) - c.Set(context.Background(), &repository.Instance{ - ID: "id", - Name: "Name", - }) - return c - }(), - ), - }, - args: args{ - ctx: context.Background(), - tx: mock.NewTransaction(t, - mock.ExpectQueryRow(mock.NewRow(t, "id", "Name"), sql.InstanceByIDStmt, "id"), - ), - id: "id", - }, - want: &repository.Instance{ - ID: "id", - Name: "Name", - }, - wantErr: false, - }, - // { - // name: "without cache, tracer", - // opts: []orchestrate.Option[orchestrate.InstanceOptions]{ - // orchestrate.WithLogger[orchestrate.InstanceOptions](logging.New(slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug})))), - // }, - // args: args{ - // ctx: context.Background(), - // tx: mock.NewTransaction(), - // id: &repository.Instance{ - // ID: "ID", - // Name: "Name", - // }, - // }, - // want: &repository.Instance{ - // ID: "ID", - // Name: "Name", - // }, - // wantErr: false, - // }, - // { - // name: "without cache, tracer, logger", - // args: args{ - // ctx: context.Background(), - // tx: mock.NewTransaction(), - // id: &repository.Instance{ - // ID: "ID", - // Name: "Name", - // }, - // }, - // want: &repository.Instance{ - // ID: "ID", - // Name: "Name", - // }, - // wantErr: false, - // }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - fmt.Printf("------------------------ %s ------------------------\n", tt.name) - i := orchestrate.Instance(tt.opts...) - got, err := i.ByID(tt.args.ctx, tt.args.tx, tt.args.id) - if (err != nil) != tt.wantErr { - t.Errorf("instance.Create() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("instance.Create() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/backend/repository/orchestrate/option.go b/backend/repository/orchestrate/option.go deleted file mode 100644 index efad690a47..0000000000 --- a/backend/repository/orchestrate/option.go +++ /dev/null @@ -1,35 +0,0 @@ -package orchestrate - -import ( - "github.com/zitadel/zitadel/backend/telemetry/logging" - "github.com/zitadel/zitadel/backend/telemetry/tracing" -) - -// options are the default options for orchestrators. -type options[T any] struct { - custom T - defaultOptions -} - -type defaultOptions struct { - tracer *tracing.Tracer - logger *logging.Logger -} - -type Option[T any] func(*options[T]) - -func WithTracer[T any](tracer *tracing.Tracer) Option[T] { - return func(o *options[T]) { - o.tracer = tracer - } -} - -func WithLogger[T any](logger *logging.Logger) Option[T] { - return func(o *options[T]) { - o.logger = logger - } -} - -func (o Option[T]) apply(opts *options[T]) { - o(opts) -} diff --git a/backend/repository/orchestrate/user.go b/backend/repository/orchestrate/user.go deleted file mode 100644 index ff3de5bb10..0000000000 --- a/backend/repository/orchestrate/user.go +++ /dev/null @@ -1,70 +0,0 @@ -package orchestrate - -import ( - "context" - - "github.com/zitadel/zitadel/backend/repository" - "github.com/zitadel/zitadel/backend/repository/cached" - "github.com/zitadel/zitadel/backend/repository/event" - "github.com/zitadel/zitadel/backend/repository/orchestrate/handler" - "github.com/zitadel/zitadel/backend/repository/sql" - "github.com/zitadel/zitadel/backend/repository/telemetry/traced" - "github.com/zitadel/zitadel/backend/storage/cache" - "github.com/zitadel/zitadel/backend/storage/database" - "github.com/zitadel/zitadel/backend/telemetry/tracing" -) - -type UserOptions struct { - cache *cached.User -} - -type user struct { - options[UserOptions] - *UserOptions -} - -func User(opts ...Option[UserOptions]) *user { - i := new(user) - i.UserOptions = &i.options.custom - - for _, opt := range opts { - opt(&i.options) - } - return i -} - -func WithUserCache(cache cache.Cache[repository.UserIndex, string, *repository.User]) Option[UserOptions] { - return func(i *options[UserOptions]) { - i.custom.cache = cached.NewUser(cache) - } -} - -func (i *user) Create(ctx context.Context, tx database.Transaction, user *repository.User) (*repository.User, error) { - return traced.Wrap(i.tracer, "user.Create", - handler.Chain( - handler.Decorate( - sql.Execute(tx).CreateUser, - traced.Decorate[*repository.User, *repository.User](i.tracer, tracing.WithSpanName("user.sql.Create")), - ), - handler.Decorate( - event.Store(tx).CreateUser, - traced.Decorate[*repository.User, *repository.User](i.tracer, tracing.WithSpanName("user.event.Create")), - ), - ), - )(ctx, user) -} - -func (i *user) ByID(ctx context.Context, querier database.Querier, id string) (*repository.User, error) { - return handler.SkipNext( - handler.SkipNilHandler(i.cache, - handler.ResFuncToHandle(i.cache.ByID), - ), - handler.Chain( - handler.Decorate( - sql.Query(querier).UserByID, - traced.Decorate[string, *repository.User](i.tracer, tracing.WithSpanName("user.sql.ByID")), - ), - handler.SkipNilHandler(i.custom.cache, handler.NoReturnToHandle(i.cache.Set)), - ), - )(ctx, id) -} diff --git a/backend/repository/sql/client.go b/backend/repository/sql/client.go deleted file mode 100644 index 860397c0a6..0000000000 --- a/backend/repository/sql/client.go +++ /dev/null @@ -1,21 +0,0 @@ -package sql - -import ( - "github.com/zitadel/zitadel/backend/storage/database" -) - -type executor[C database.Executor] struct { - client C -} - -func Execute[C database.Executor](client C) *executor[C] { - return &executor[C]{client: client} -} - -type querier[C database.Querier] struct { - client C -} - -func Query[C database.Querier](client C) *querier[C] { - return &querier[C]{client: client} -} diff --git a/backend/repository/sql/instance.go b/backend/repository/sql/instance.go deleted file mode 100644 index 459912be3e..0000000000 --- a/backend/repository/sql/instance.go +++ /dev/null @@ -1,63 +0,0 @@ -package sql - -import ( - "context" - "log" - - "github.com/zitadel/zitadel/backend/repository" -) - -const InstanceByIDStmt = `SELECT id, name FROM instances WHERE id = $1` - -func (q *querier[C]) InstanceByID(ctx context.Context, id string) (*repository.Instance, error) { - log.Println("sql.instance.byID") - row := q.client.QueryRow(ctx, InstanceByIDStmt, id) - var instance repository.Instance - if err := row.Scan(&instance.ID, &instance.Name); err != nil { - return nil, err - } - return &instance, nil -} - -const instanceByDomainQuery = `SELECT i.id, i.name FROM instances i JOIN instance_domains id ON i.id = id.instance_id WHERE id.domain = $1` - -func (q *querier[C]) InstanceByDomain(ctx context.Context, domain string) (*repository.Instance, error) { - log.Println("sql.instance.byDomain") - row := q.client.QueryRow(ctx, instanceByDomainQuery, domain) - var instance repository.Instance - if err := row.Scan(&instance.ID, &instance.Name); err != nil { - return nil, err - } - return &instance, nil -} - -func (q *querier[C]) ListInstances(ctx context.Context, request *repository.ListRequest) (res []*repository.Instance, err error) { - log.Println("sql.instance.list") - rows, err := q.client.Query(ctx, "SELECT id, name FROM instances") - if err != nil { - return nil, err - } - defer rows.Close() - for rows.Next() { - var instance repository.Instance - if err = rows.Scan(&instance.ID, &instance.Name); err != nil { - return nil, err - } - res = append(res, &instance) - } - if err = rows.Err(); err != nil { - return nil, err - } - return res, nil -} - -const InstanceCreateStmt = `INSERT INTO instances (id, name) VALUES ($1, $2)` - -func (e *executor[C]) CreateInstance(ctx context.Context, instance *repository.Instance) (*repository.Instance, error) { - log.Println("sql.instance.create") - err := e.client.Exec(ctx, InstanceCreateStmt, instance.ID, instance.Name) - if err != nil { - return nil, err - } - return instance, nil -} diff --git a/backend/repository/sql/user.go b/backend/repository/sql/user.go deleted file mode 100644 index d43bae1772..0000000000 --- a/backend/repository/sql/user.go +++ /dev/null @@ -1,29 +0,0 @@ -package sql - -import ( - "context" - "log" - - "github.com/zitadel/zitadel/backend/repository" -) - -const userByIDQuery = `SELECT id, username FROM users WHERE id = $1` - -func (q *querier[C]) UserByID(ctx context.Context, id string) (res *repository.User, err error) { - log.Println("sql.user.byID") - row := q.client.QueryRow(ctx, userByIDQuery, id) - var user repository.User - if err := row.Scan(&user.ID, &user.Username); err != nil { - return nil, err - } - return &user, nil -} - -func (e *executor[C]) CreateUser(ctx context.Context, user *repository.User) (res *repository.User, err error) { - log.Println("sql.user.create") - err = e.client.Exec(ctx, "INSERT INTO users (id, username) VALUES ($1, $2)", user.ID, user.Username) - if err != nil { - return nil, err - } - return user, nil -} diff --git a/backend/repository/telemetry/logged/global.go b/backend/repository/telemetry/logged/global.go deleted file mode 100644 index 733cdee118..0000000000 --- a/backend/repository/telemetry/logged/global.go +++ /dev/null @@ -1,44 +0,0 @@ -package logged - -import ( - "context" - "log" - "log/slog" - - "github.com/zitadel/zitadel/backend/repository/orchestrate/handler" - "github.com/zitadel/zitadel/backend/telemetry/logging" -) - -// Wrap decorates the given handle function with logging. -// The function is safe to call with nil logger. -func Wrap[Req, Res any](logger *logging.Logger, name string, handle handler.Handler[Req, Res]) handler.Handler[Req, Res] { - if logger == nil { - return handle - } - return func(ctx context.Context, r Req) (_ Res, err error) { - logger.Debug("execute", slog.String("handler", name)) - defer logger.Debug("done", slog.String("handler", name)) - log.Println("log.wrap", name) - return handle(ctx, r) - } -} - -// Decorate decorates the given handle function with logging. -// The function is safe to call with nil logger. -func Decorate[Req, Res any](logger *logging.Logger, name string) handler.Decorator[Req, Res] { - return func(ctx context.Context, request Req, handle handler.Handler[Req, Res]) (res Res, err error) { - if logger == nil { - return handle(ctx, request) - } - logger = logger.With("handler", name) - logger.DebugContext(ctx, "execute") - log.Println("logged.decorate", name) - defer func() { - if err != nil { - logger.ErrorContext(ctx, "failed", slog.String("cause", err.Error())) - } - logger.DebugContext(ctx, "done") - }() - return handle(ctx, request) - } -} diff --git a/backend/repository/telemetry/traced/global.go b/backend/repository/telemetry/traced/global.go deleted file mode 100644 index 91ecdb88b8..0000000000 --- a/backend/repository/telemetry/traced/global.go +++ /dev/null @@ -1,50 +0,0 @@ -package traced - -import ( - "context" - "log" - - "github.com/zitadel/zitadel/backend/repository/orchestrate/handler" - "github.com/zitadel/zitadel/backend/telemetry/tracing" -) - -// Wrap decorates the given handle function with tracing. -// The function is safe to call with nil tracer. -func Wrap[Req, Res any](tracer *tracing.Tracer, name string, handle handler.Handler[Req, Res]) handler.Handler[Req, Res] { - if tracer == nil { - return handle - } - return func(ctx context.Context, r Req) (_ Res, err error) { - ctx, span := tracer.Start( - ctx, - name, - ) - log.Println("trace.wrap", name) - defer func() { - if err != nil { - span.RecordError(err) - } - span.End() - }() - return handle(ctx, r) - } -} - -// Decorate decorates the given handle function with tracing. -// The function is safe to call with nil tracer. -func Decorate[Req, Res any](tracer *tracing.Tracer, opts ...tracing.DecorateOption) handler.Decorator[Req, Res] { - return func(ctx context.Context, r Req, handle handler.Handler[Req, Res]) (_ Res, err error) { - if tracer == nil { - return handle(ctx, r) - } - o := new(tracing.DecorateOptions) - for _, opt := range opts { - opt(o) - } - log.Println("traced.decorate") - - ctx, end := o.Start(ctx, tracer) - defer end(err) - return handle(ctx, r) - } -} diff --git a/backend/repository/user.go b/backend/repository/user.go index 1a5dc80279..a44cfbd30e 100644 --- a/backend/repository/user.go +++ b/backend/repository/user.go @@ -1,33 +1,97 @@ package repository -import "github.com/zitadel/zitadel/backend/storage/cache" +import ( + "context" + + "github.com/zitadel/zitadel/backend/handler" + "github.com/zitadel/zitadel/backend/storage/database" + "github.com/zitadel/zitadel/backend/telemetry/tracing" + "github.com/zitadel/zitadel/internal/crypto" +) type User struct { ID string Username string } -type UserIndex uint8 - -var UserIndices = []UserIndex{ - UserByIDIndex, - UserByUsernameIndex, +type UserOptions struct { + cache *UserCache } -const ( - UserByIDIndex UserIndex = iota - UserByUsernameIndex -) +type user struct { + options[UserOptions] + *UserOptions +} -var _ cache.Entry[UserIndex, string] = (*User)(nil) +func NewUser(opts ...Option[UserOptions]) *user { + i := new(user) + i.UserOptions = &i.options.custom -// Keys implements [cache.Entry]. -func (u *User) Keys(index UserIndex) (key []string) { - switch index { - case UserByIDIndex: - return []string{u.ID} - case UserByUsernameIndex: - return []string{u.Username} + for _, opt := range opts { + opt(&i.options) } - return nil + return i } + +func WithUserCache(c *UserCache) Option[UserOptions] { + return func(i *options[UserOptions]) { + i.custom.cache = c + } +} + +func (u *user) Create(ctx context.Context, tx database.Transaction, user *User) (*User, error) { + return tracing.Wrap(u.tracer, "user.Create", + handler.Chain( + handler.Decorate( + execute(tx).CreateUser, + tracing.Decorate[*User, *User](u.tracer, tracing.WithSpanName("user.sql.Create")), + ), + handler.Decorate( + events(tx).CreateUser, + tracing.Decorate[*User, *User](u.tracer, tracing.WithSpanName("user.event.Create")), + ), + ), + )(ctx, user) +} + +func (u *user) ByID(ctx context.Context, client database.Querier, id string) (*User, error) { + return handler.SkipNext( + handler.SkipNilHandler(u.cache, + handler.ResFuncToHandle(u.cache.ByID), + ), + handler.Chain( + handler.Decorate( + query(client).UserByID, + tracing.Decorate[string, *User](u.tracer, tracing.WithSpanName("user.sql.ByID")), + ), + handler.SkipNilHandler(u.custom.cache, handler.NoReturnToHandle(u.cache.Set)), + ), + )(ctx, id) +} + +type ChangeEmail struct { + UserID string + Email string + Opt *ChangeEmailOption +} + +type ChangeEmailOption struct { + returnCode bool + isVerified bool + sendCode bool +} + +type ChangeEmailVerifiedOption struct { + isVerified bool +} + +type ChangeEmailReturnCodeOption struct { + alg crypto.EncryptionAlgorithm +} + +type ChangeEmailSendCodeOption struct { + alg crypto.EncryptionAlgorithm + urlTemplate string +} + +func (u *user) ChangeEmail(ctx context.Context, client database.Executor, change *ChangeEmail) diff --git a/backend/storage/database/database.go b/backend/storage/database/database.go index 2108f36423..7a4dc932ae 100644 --- a/backend/storage/database/database.go +++ b/backend/storage/database/database.go @@ -72,6 +72,14 @@ type Querier interface { QueryRow(ctx context.Context, stmt string, args ...any) Row } +func Query[Out any](q Querier, fn func(q Querier) ([]Out, error)) ([]Out, error) { + return fn(q) +} + +func QueryRow[Out any](q Querier, fn func(q Querier) (Out, error)) (Out, error) { + return fn(q) +} + type Executor interface { Exec(ctx context.Context, stmt string, args ...any) error } diff --git a/backend/telemetry/logging/logger.go b/backend/telemetry/logging/logger.go index c452d1021f..5a29d8aa01 100644 --- a/backend/telemetry/logging/logger.go +++ b/backend/telemetry/logging/logger.go @@ -1,6 +1,12 @@ package logging -import "log/slog" +import ( + "context" + "log" + "log/slog" + + "github.com/zitadel/zitadel/backend/handler" +) type Logger struct { *slog.Logger @@ -13,3 +19,37 @@ func New(l *slog.Logger) *Logger { func (l *Logger) With(args ...any) *Logger { return &Logger{l.Logger.With(args...)} } + +// Wrap decorates the given handle function with +// The function is safe to call with nil logger. +func Wrap[Req, Res any](logger *Logger, name string, handle handler.Handle[Req, Res]) handler.Handle[Req, Res] { + if logger == nil { + return handle + } + return func(ctx context.Context, r Req) (_ Res, err error) { + logger.Debug("execute", slog.String("handler", name)) + defer logger.Debug("done", slog.String("handler", name)) + log.Println("log.wrap", name) + return handle(ctx, r) + } +} + +// Decorate decorates the given handle function with logging. +// The function is safe to call with nil logger. +func Decorate[Req, Res any](logger *Logger, name string) handler.Middleware[Req, Res] { + return func(ctx context.Context, request Req, handle handler.Handle[Req, Res]) (res Res, err error) { + if logger == nil { + return handle(ctx, request) + } + logger = logger.With("handler", name) + logger.DebugContext(ctx, "execute") + log.Println("logged.decorate", name) + defer func() { + if err != nil { + logger.ErrorContext(ctx, "failed", slog.String("cause", err.Error())) + } + logger.DebugContext(ctx, "done") + }() + return handle(ctx, request) + } +} diff --git a/backend/telemetry/tracing/tracer.go b/backend/telemetry/tracing/tracer.go index 6c0801fb2c..d3634075e1 100644 --- a/backend/telemetry/tracing/tracer.go +++ b/backend/telemetry/tracing/tracer.go @@ -2,10 +2,13 @@ package tracing import ( "context" + "log" "runtime" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/trace" + + "github.com/zitadel/zitadel/backend/handler" ) type Tracer struct{ trace.Tracer } @@ -43,6 +46,47 @@ func WithSpanEndOptions(opts ...trace.SpanEndOption) DecorateOption { } } +// Wrap decorates the given handle function with tracing. +// The function is safe to call with nil tracer. +func Wrap[Req, Res any](tracer *Tracer, name string, handle handler.Handle[Req, Res]) handler.Handle[Req, Res] { + if tracer == nil { + return handle + } + return func(ctx context.Context, r Req) (_ Res, err error) { + ctx, span := tracer.Start( + ctx, + name, + ) + log.Println("trace.wrap", name) + defer func() { + if err != nil { + span.RecordError(err) + } + span.End() + }() + return handle(ctx, r) + } +} + +// Decorate decorates the given handle function with +// The function is safe to call with nil tracer. +func Decorate[Req, Res any](tracer *Tracer, opts ...DecorateOption) handler.Middleware[Req, Res] { + return func(ctx context.Context, r Req, handle handler.Handle[Req, Res]) (_ Res, err error) { + if tracer == nil { + return handle(ctx, r) + } + o := new(DecorateOptions) + for _, opt := range opts { + opt(o) + } + log.Println("traced.decorate") + + ctx, end := o.Start(ctx, tracer) + defer end(err) + return handle(ctx, r) + } +} + func (o *DecorateOptions) Start(ctx context.Context, tracer *Tracer) (context.Context, func(error)) { if o.spanName == "" { o.spanName = functionName() @@ -56,23 +100,6 @@ func (o *DecorateOptions) end(err error) { o.span.End(o.endOpts...) } -// func (t Tracer) Decorate(ctx context.Context, fn func(ctx context.Context) error, opts ...DecorateOption) { -// o := new(DecorateOptions) -// for _, opt := range opts { -// opt(o) -// } - -// if o.spanName == "" { -// o.spanName = functionName() -// } - -// ctx, span := t.Tracer.Start(ctx, o.spanName, o.startOpts...) -// defer span.End(o.endOpts...) - -// err := fn(ctx) -// span.RecordError(err) -// } - func functionName() string { counter, _, _, success := runtime.Caller(2) diff --git a/docs/static/img/zitadel_cluster_architecture.png b/docs/static/img/zitadel_cluster_architecture.png deleted file mode 100644 index a968610b70..0000000000 Binary files a/docs/static/img/zitadel_cluster_architecture.png and /dev/null differ diff --git a/docs/static/img/zitadel_multicluster_architecture.png b/docs/static/img/zitadel_multicluster_architecture.png deleted file mode 100644 index 3efccde609..0000000000 Binary files a/docs/static/img/zitadel_multicluster_architecture.png and /dev/null differ