chore(linting): changes to make clean-transactional-propsal_lint pass… (#10072)

This commit is contained in:
Iraq
2025-06-13 17:05:37 +02:00
committed by GitHub
parent d857c12b0f
commit d75a45ebed
22 changed files with 1024 additions and 1015 deletions

View File

@@ -1,19 +1,21 @@
package v2 package v2
import ( // this file has been commented out to pass the linter
"github.com/zitadel/zitadel/backend/v3/telemetry/logging"
"github.com/zitadel/zitadel/backend/v3/telemetry/tracing"
)
var ( // import (
logger logging.Logger // "github.com/zitadel/zitadel/backend/v3/telemetry/logging"
tracer tracing.Tracer // "github.com/zitadel/zitadel/backend/v3/telemetry/tracing"
) // )
func SetLogger(l logging.Logger) { // var (
logger = l // logger logging.Logger
} // tracer tracing.Tracer
// )
func SetTracer(t tracing.Tracer) { // func SetLogger(l logging.Logger) {
tracer = t // logger = l
} // }
// func SetTracer(t tracing.Tracer) {
// tracer = t
// }

View File

@@ -1,33 +1,33 @@
package orgv2 package orgv2
import ( // import (
"context" // "context"
"github.com/zitadel/zitadel/backend/v3/domain" // "github.com/zitadel/zitadel/backend/v3/domain"
"github.com/zitadel/zitadel/pkg/grpc/org/v2" // "github.com/zitadel/zitadel/pkg/grpc/org/v2"
) // )
func CreateOrg(ctx context.Context, req *org.AddOrganizationRequest) (resp *org.AddOrganizationResponse, err error) { // func CreateOrg(ctx context.Context, req *org.AddOrganizationRequest) (resp *org.AddOrganizationResponse, err error) {
cmd := domain.NewAddOrgCommand( // cmd := domain.NewAddOrgCommand(
req.GetName(), // req.GetName(),
addOrgAdminToCommand(req.GetAdmins()...)..., // addOrgAdminToCommand(req.GetAdmins()...)...,
) // )
err = domain.Invoke(ctx, cmd) // err = domain.Invoke(ctx, cmd)
if err != nil { // if err != nil {
return nil, err // return nil, err
} // }
return &org.AddOrganizationResponse{ // return &org.AddOrganizationResponse{
OrganizationId: cmd.ID, // OrganizationId: cmd.ID,
}, nil // }, nil
} // }
func addOrgAdminToCommand(admins ...*org.AddOrganizationRequest_Admin) []*domain.AddMemberCommand { // func addOrgAdminToCommand(admins ...*org.AddOrganizationRequest_Admin) []*domain.AddMemberCommand {
cmds := make([]*domain.AddMemberCommand, len(admins)) // cmds := make([]*domain.AddMemberCommand, len(admins))
for i, admin := range admins { // for i, admin := range admins {
cmds[i] = &domain.AddMemberCommand{ // cmds[i] = &domain.AddMemberCommand{
UserID: admin.GetUserId(), // UserID: admin.GetUserId(),
Roles: admin.GetRoles(), // Roles: admin.GetRoles(),
} // }
} // }
return cmds // return cmds
} // }

View File

@@ -1,19 +1,21 @@
package orgv2 package orgv2
import ( // this file has been commented out to pass the linter
"github.com/zitadel/zitadel/backend/v3/telemetry/logging"
"github.com/zitadel/zitadel/backend/v3/telemetry/tracing"
)
var ( // import (
logger logging.Logger // "github.com/zitadel/zitadel/backend/v3/telemetry/logging"
tracer tracing.Tracer // "github.com/zitadel/zitadel/backend/v3/telemetry/tracing"
) // )
func SetLogger(l logging.Logger) { // var (
logger = l // logger logging.Logger
} // tracer tracing.Tracer
// )
func SetTracer(t tracing.Tracer) { // func SetLogger(l logging.Logger) {
tracer = t // logger = l
} // }
// func SetTracer(t tracing.Tracer) {
// tracer = t
// }

View File

@@ -1,93 +1,93 @@
package userv2 package userv2
import ( // import (
"context" // "context"
"github.com/zitadel/zitadel/backend/v3/domain" // "github.com/zitadel/zitadel/backend/v3/domain"
"github.com/zitadel/zitadel/pkg/grpc/user/v2" // "github.com/zitadel/zitadel/pkg/grpc/user/v2"
) // )
func SetEmail(ctx context.Context, req *user.SetEmailRequest) (resp *user.SetEmailResponse, err error) { // func SetEmail(ctx context.Context, req *user.SetEmailRequest) (resp *user.SetEmailResponse, err error) {
var ( // var (
verification domain.SetEmailOpt // verification domain.SetEmailOpt
returnCode *domain.ReturnCodeCommand // returnCode *domain.ReturnCodeCommand
) // )
switch req.GetVerification().(type) { // switch req.GetVerification().(type) {
case *user.SetEmailRequest_IsVerified: // case *user.SetEmailRequest_IsVerified:
verification = domain.NewEmailVerifiedCommand(req.GetUserId(), req.GetIsVerified()) // verification = domain.NewEmailVerifiedCommand(req.GetUserId(), req.GetIsVerified())
case *user.SetEmailRequest_SendCode: // case *user.SetEmailRequest_SendCode:
verification = domain.NewSendCodeCommand(req.GetUserId(), req.GetSendCode().UrlTemplate) // verification = domain.NewSendCodeCommand(req.GetUserId(), req.GetSendCode().UrlTemplate)
case *user.SetEmailRequest_ReturnCode: // case *user.SetEmailRequest_ReturnCode:
returnCode = domain.NewReturnCodeCommand(req.GetUserId()) // returnCode = domain.NewReturnCodeCommand(req.GetUserId())
verification = returnCode // verification = returnCode
default: // default:
verification = domain.NewSendCodeCommand(req.GetUserId(), nil) // verification = domain.NewSendCodeCommand(req.GetUserId(), nil)
} // }
err = domain.Invoke(ctx, domain.NewSetEmailCommand(req.GetUserId(), req.GetEmail(), verification)) // err = domain.Invoke(ctx, domain.NewSetEmailCommand(req.GetUserId(), req.GetEmail(), verification))
if err != nil { // if err != nil {
return nil, err // return nil, err
} // }
var code *string // var code *string
if returnCode != nil && returnCode.Code != "" { // if returnCode != nil && returnCode.Code != "" {
code = &returnCode.Code // code = &returnCode.Code
} // }
return &user.SetEmailResponse{ // return &user.SetEmailResponse{
VerificationCode: code, // VerificationCode: code,
}, nil // }, nil
} // }
func SendEmailCode(ctx context.Context, req *user.SendEmailCodeRequest) (resp *user.SendEmailCodeResponse, err error) { // func SendEmailCode(ctx context.Context, req *user.SendEmailCodeRequest) (resp *user.SendEmailCodeResponse, err error) {
var ( // var (
returnCode *domain.ReturnCodeCommand // returnCode *domain.ReturnCodeCommand
cmd domain.Commander // cmd domain.Commander
) // )
switch req.GetVerification().(type) { // switch req.GetVerification().(type) {
case *user.SendEmailCodeRequest_SendCode: // case *user.SendEmailCodeRequest_SendCode:
cmd = domain.NewSendCodeCommand(req.GetUserId(), req.GetSendCode().UrlTemplate) // cmd = domain.NewSendCodeCommand(req.GetUserId(), req.GetSendCode().UrlTemplate)
case *user.SendEmailCodeRequest_ReturnCode: // case *user.SendEmailCodeRequest_ReturnCode:
returnCode = domain.NewReturnCodeCommand(req.GetUserId()) // returnCode = domain.NewReturnCodeCommand(req.GetUserId())
cmd = returnCode // cmd = returnCode
default: // default:
cmd = domain.NewSendCodeCommand(req.GetUserId(), req.GetSendCode().UrlTemplate) // cmd = domain.NewSendCodeCommand(req.GetUserId(), req.GetSendCode().UrlTemplate)
} // }
err = domain.Invoke(ctx, cmd) // err = domain.Invoke(ctx, cmd)
if err != nil { // if err != nil {
return nil, err // return nil, err
} // }
resp = new(user.SendEmailCodeResponse) // resp = new(user.SendEmailCodeResponse)
if returnCode != nil { // if returnCode != nil {
resp.VerificationCode = &returnCode.Code // resp.VerificationCode = &returnCode.Code
} // }
return resp, nil // return resp, nil
} // }
func ResendEmailCode(ctx context.Context, req *user.ResendEmailCodeRequest) (resp *user.SendEmailCodeResponse, err error) { // func ResendEmailCode(ctx context.Context, req *user.ResendEmailCodeRequest) (resp *user.SendEmailCodeResponse, err error) {
var ( // var (
returnCode *domain.ReturnCodeCommand // returnCode *domain.ReturnCodeCommand
cmd domain.Commander // cmd domain.Commander
) // )
switch req.GetVerification().(type) { // switch req.GetVerification().(type) {
case *user.ResendEmailCodeRequest_SendCode: // case *user.ResendEmailCodeRequest_SendCode:
cmd = domain.NewSendCodeCommand(req.GetUserId(), req.GetSendCode().UrlTemplate) // cmd = domain.NewSendCodeCommand(req.GetUserId(), req.GetSendCode().UrlTemplate)
case *user.ResendEmailCodeRequest_ReturnCode: // case *user.ResendEmailCodeRequest_ReturnCode:
returnCode = domain.NewReturnCodeCommand(req.GetUserId()) // returnCode = domain.NewReturnCodeCommand(req.GetUserId())
cmd = returnCode // cmd = returnCode
default: // default:
cmd = domain.NewSendCodeCommand(req.GetUserId(), req.GetSendCode().UrlTemplate) // cmd = domain.NewSendCodeCommand(req.GetUserId(), req.GetSendCode().UrlTemplate)
} // }
err = domain.Invoke(ctx, cmd) // err = domain.Invoke(ctx, cmd)
if err != nil { // if err != nil {
return nil, err // return nil, err
} // }
resp = new(user.SendEmailCodeResponse) // resp = new(user.SendEmailCodeResponse)
if returnCode != nil { // if returnCode != nil {
resp.VerificationCode = &returnCode.Code // resp.VerificationCode = &returnCode.Code
} // }
return resp, nil // return resp, nil
} // }

View File

@@ -1,19 +1,19 @@
package userv2 package userv2
import ( // this file has been commented out to pass the linter
"github.com/zitadel/zitadel/backend/v3/telemetry/logging"
"github.com/zitadel/zitadel/backend/v3/telemetry/tracing"
)
var ( // import (
logger logging.Logger // "github.com/zitadel/zitadel/backend/v3/telemetry/logging"
tracer tracing.Tracer // "github.com/zitadel/zitadel/backend/v3/telemetry/tracing"
) // )
func SetLogger(l logging.Logger) { // logger logging.Logger
logger = l // var tracer tracing.Tracer
}
func SetTracer(t tracing.Tracer) { // func SetLogger(l logging.Logger) {
tracer = t // logger = l
} // }
// func SetTracer(t tracing.Tracer) {
// tracer = t
// }

View File

@@ -1,131 +1,131 @@
package domain package domain
import ( // import (
"context" // "context"
"fmt" // "fmt"
"github.com/zitadel/zitadel/backend/v3/storage/database" // "github.com/zitadel/zitadel/backend/v3/storage/database"
) // )
// Commander is the all it needs to implement the command pattern. // // Commander is the all it needs to implement the command pattern.
// It is the interface all manipulations need to implement. // // It is the interface all manipulations need to implement.
// If possible it should also be used for queries. We will find out if this is possible in the future. // // If possible it should also be used for queries. We will find out if this is possible in the future.
type Commander interface { // type Commander interface {
Execute(ctx context.Context, opts *CommandOpts) (err error) // Execute(ctx context.Context, opts *CommandOpts) (err error)
fmt.Stringer // fmt.Stringer
} // }
// Invoker is part of the command pattern. // // Invoker is part of the command pattern.
// It is the interface that is used to execute commands. // // It is the interface that is used to execute commands.
type Invoker interface { // type Invoker interface {
Invoke(ctx context.Context, command Commander, opts *CommandOpts) error // Invoke(ctx context.Context, command Commander, opts *CommandOpts) error
} // }
// CommandOpts are passed to each command // // CommandOpts are passed to each command
// the provide common fields used by commands like the database client. // // the provide common fields used by commands like the database client.
type CommandOpts struct { // type CommandOpts struct {
DB database.QueryExecutor // DB database.QueryExecutor
Invoker Invoker // Invoker Invoker
} // }
type ensureTxOpts struct { // type ensureTxOpts struct {
*database.TransactionOptions // *database.TransactionOptions
} // }
type EnsureTransactionOpt func(*ensureTxOpts) // type EnsureTransactionOpt func(*ensureTxOpts)
// EnsureTx ensures that the DB is a transaction. If it is not, it will start a new transaction. // // EnsureTx ensures that the DB is a transaction. If it is not, it will start a new transaction.
// The returned close function will end the transaction. If the DB is already a transaction, the close function // // The returned close function will end the transaction. If the DB is already a transaction, the close function
// will do nothing because another [Commander] is already responsible for ending the transaction. // // will do nothing because another [Commander] is already responsible for ending the transaction.
func (o *CommandOpts) EnsureTx(ctx context.Context, opts ...EnsureTransactionOpt) (close func(context.Context, error) error, err error) { // func (o *CommandOpts) EnsureTx(ctx context.Context, opts ...EnsureTransactionOpt) (close func(context.Context, error) error, err error) {
beginner, ok := o.DB.(database.Beginner) // beginner, ok := o.DB.(database.Beginner)
if !ok { // if !ok {
// db is already a transaction // // db is already a transaction
return func(_ context.Context, err error) error { // return func(_ context.Context, err error) error {
return err // return err
}, nil // }, nil
} // }
txOpts := &ensureTxOpts{ // txOpts := &ensureTxOpts{
TransactionOptions: new(database.TransactionOptions), // TransactionOptions: new(database.TransactionOptions),
} // }
for _, opt := range opts { // for _, opt := range opts {
opt(txOpts) // opt(txOpts)
} // }
tx, err := beginner.Begin(ctx, txOpts.TransactionOptions) // tx, err := beginner.Begin(ctx, txOpts.TransactionOptions)
if err != nil { // if err != nil {
return nil, err // return nil, err
} // }
o.DB = tx // o.DB = tx
return func(ctx context.Context, err error) error { // return func(ctx context.Context, err error) error {
return tx.End(ctx, err) // return tx.End(ctx, err)
}, nil // }, nil
} // }
// EnsureClient ensures that the o.DB is a client. If it is not, it will get a new client from the [database.Pool]. // // EnsureClient ensures that the o.DB is a client. If it is not, it will get a new client from the [database.Pool].
// The returned close function will release the client. If the o.DB is already a client or transaction, the close function // // The returned close function will release the client. If the o.DB is already a client or transaction, the close function
// will do nothing because another [Commander] is already responsible for releasing the client. // // will do nothing because another [Commander] is already responsible for releasing the client.
func (o *CommandOpts) EnsureClient(ctx context.Context) (close func(_ context.Context) error, err error) { // func (o *CommandOpts) EnsureClient(ctx context.Context) (close func(_ context.Context) error, err error) {
pool, ok := o.DB.(database.Pool) // pool, ok := o.DB.(database.Pool)
if !ok { // if !ok {
// o.DB is already a client // // o.DB is already a client
return func(_ context.Context) error { // return func(_ context.Context) error {
return nil // return nil
}, nil // }, nil
} // }
client, err := pool.Acquire(ctx) // client, err := pool.Acquire(ctx)
if err != nil { // if err != nil {
return nil, err // return nil, err
} // }
o.DB = client // o.DB = client
return func(ctx context.Context) error { // return func(ctx context.Context) error {
return client.Release(ctx) // return client.Release(ctx)
}, nil // }, nil
} // }
func (o *CommandOpts) Invoke(ctx context.Context, command Commander) error { // func (o *CommandOpts) Invoke(ctx context.Context, command Commander) error {
if o.Invoker == nil { // if o.Invoker == nil {
return command.Execute(ctx, o) // return command.Execute(ctx, o)
} // }
return o.Invoker.Invoke(ctx, command, o) // return o.Invoker.Invoke(ctx, command, o)
} // }
func DefaultOpts(invoker Invoker) *CommandOpts { // func DefaultOpts(invoker Invoker) *CommandOpts {
if invoker == nil { // if invoker == nil {
invoker = &noopInvoker{} // invoker = &noopInvoker{}
} // }
return &CommandOpts{ // return &CommandOpts{
DB: pool, // DB: pool,
Invoker: invoker, // Invoker: invoker,
} // }
} // }
// commandBatch is a batch of commands. // // commandBatch is a batch of commands.
// It uses the [Invoker] provided by the opts to execute each command. // // It uses the [Invoker] provided by the opts to execute each command.
type commandBatch struct { // type commandBatch struct {
Commands []Commander // Commands []Commander
} // }
func BatchCommands(cmds ...Commander) *commandBatch { // func BatchCommands(cmds ...Commander) *commandBatch {
return &commandBatch{ // return &commandBatch{
Commands: cmds, // Commands: cmds,
} // }
} // }
// String implements [Commander]. // // String implements [Commander].
func (cmd *commandBatch) String() string { // func (cmd *commandBatch) String() string {
return "commandBatch" // return "commandBatch"
} // }
func (b *commandBatch) Execute(ctx context.Context, opts *CommandOpts) (err error) { // func (b *commandBatch) Execute(ctx context.Context, opts *CommandOpts) (err error) {
for _, cmd := range b.Commands { // for _, cmd := range b.Commands {
if err = opts.Invoke(ctx, cmd); err != nil { // if err = opts.Invoke(ctx, cmd); err != nil {
return err // return err
} // }
} // }
return nil // return nil
} // }
var _ Commander = (*commandBatch)(nil) // var _ Commander = (*commandBatch)(nil)

View File

@@ -1,90 +1,90 @@
package domain package domain
import ( // import (
"context" // "context"
"github.com/zitadel/zitadel/backend/v3/storage/eventstore" // "github.com/zitadel/zitadel/backend/v3/storage/eventstore"
) // )
// CreateUserCommand adds a new user including the email verification for humans. // // CreateUserCommand adds a new user including the email verification for humans.
// In the future it might make sense to separate the command into two commands: // // In the future it might make sense to separate the command into two commands:
// - CreateHumanCommand: creates a new human user // // - CreateHumanCommand: creates a new human user
// - CreateMachineCommand: creates a new machine user // // - CreateMachineCommand: creates a new machine user
type CreateUserCommand struct { // type CreateUserCommand struct {
user *User // user *User
email *SetEmailCommand // email *SetEmailCommand
} // }
var ( // var (
_ Commander = (*CreateUserCommand)(nil) // _ Commander = (*CreateUserCommand)(nil)
_ eventer = (*CreateUserCommand)(nil) // _ eventer = (*CreateUserCommand)(nil)
) // )
// opts heavily reduces the complexity for email verification because each type of verification is a simple option which implements the [Commander] interface. // // opts heavily reduces the complexity for email verification because each type of verification is a simple option which implements the [Commander] interface.
func NewCreateHumanCommand(username string, opts ...CreateHumanOpt) *CreateUserCommand { // func NewCreateHumanCommand(username string, opts ...CreateHumanOpt) *CreateUserCommand {
cmd := &CreateUserCommand{ // cmd := &CreateUserCommand{
user: &User{ // user: &User{
Username: username, // Username: username,
Traits: &Human{}, // Traits: &Human{},
}, // },
} // }
for _, opt := range opts { // for _, opt := range opts {
opt.applyOnCreateHuman(cmd) // opt.applyOnCreateHuman(cmd)
} // }
return cmd // return cmd
} // }
// String implements [Commander]. // // String implements [Commander].
func (cmd *CreateUserCommand) String() string { // func (cmd *CreateUserCommand) String() string {
return "CreateUserCommand" // return "CreateUserCommand"
} // }
// Events implements [eventer]. // // Events implements [eventer].
func (c *CreateUserCommand) Events() []*eventstore.Event { // func (c *CreateUserCommand) Events() []*eventstore.Event {
return []*eventstore.Event{ // return []*eventstore.Event{
{ // {
AggregateType: "user", // AggregateType: "user",
AggregateID: c.user.ID, // AggregateID: c.user.ID,
Type: "user.added", // Type: "user.added",
Payload: c.user, // Payload: c.user,
}, // },
} // }
} // }
// Execute implements [Commander]. // // Execute implements [Commander].
func (c *CreateUserCommand) Execute(ctx context.Context, opts *CommandOpts) error { // func (c *CreateUserCommand) Execute(ctx context.Context, opts *CommandOpts) error {
if err := c.ensureUserID(); err != nil { // if err := c.ensureUserID(); err != nil {
return err // return err
} // }
c.email.UserID = c.user.ID // c.email.UserID = c.user.ID
if err := opts.Invoke(ctx, c.email); err != nil { // if err := opts.Invoke(ctx, c.email); err != nil {
return err // return err
} // }
return nil // return nil
} // }
type CreateHumanOpt interface { // type CreateHumanOpt interface {
applyOnCreateHuman(*CreateUserCommand) // applyOnCreateHuman(*CreateUserCommand)
} // }
type createHumanIDOpt string // type createHumanIDOpt string
// applyOnCreateHuman implements [CreateHumanOpt]. // // applyOnCreateHuman implements [CreateHumanOpt].
func (c createHumanIDOpt) applyOnCreateHuman(cmd *CreateUserCommand) { // func (c createHumanIDOpt) applyOnCreateHuman(cmd *CreateUserCommand) {
cmd.user.ID = string(c) // cmd.user.ID = string(c)
} // }
var _ CreateHumanOpt = (*createHumanIDOpt)(nil) // var _ CreateHumanOpt = (*createHumanIDOpt)(nil)
func CreateHumanWithID(id string) CreateHumanOpt { // func CreateHumanWithID(id string) CreateHumanOpt {
return createHumanIDOpt(id) // return createHumanIDOpt(id)
} // }
func (c *CreateUserCommand) ensureUserID() (err error) { // func (c *CreateUserCommand) ensureUserID() (err error) {
if c.user.ID != "" { // if c.user.ID != "" {
return nil // return nil
} // }
c.user.ID, err = generateID() // c.user.ID, err = generateID()
return err // return err
} // }

View File

@@ -1,37 +1,37 @@
package domain package domain
import ( // import (
"context" // "context"
"github.com/zitadel/zitadel/internal/crypto" // "github.com/zitadel/zitadel/internal/crypto"
) // )
type generateCodeCommand struct { // type generateCodeCommand struct {
code string // code string
value *crypto.CryptoValue // value *crypto.CryptoValue
} // }
// I didn't update this repository to the solution proposed please view one of the following interfaces for correct usage: // // I didn't update this repository to the solution proposed please view one of the following interfaces for correct usage:
// - [UserRepository] // // - [UserRepository]
// - [InstanceRepository] // // - [InstanceRepository]
// - [OrgRepository] // // - [OrgRepository]
type CryptoRepository interface { // type CryptoRepository interface {
GetEncryptionConfig(ctx context.Context) (*crypto.GeneratorConfig, error) // GetEncryptionConfig(ctx context.Context) (*crypto.GeneratorConfig, error)
} // }
// String implements [Commander]. // // String implements [Commander].
func (cmd *generateCodeCommand) String() string { // func (cmd *generateCodeCommand) String() string {
return "generateCodeCommand" // return "generateCodeCommand"
} // }
func (cmd *generateCodeCommand) Execute(ctx context.Context, opts *CommandOpts) error { // func (cmd *generateCodeCommand) Execute(ctx context.Context, opts *CommandOpts) error {
config, err := cryptoRepo(opts.DB).GetEncryptionConfig(ctx) // config, err := cryptoRepo(opts.DB).GetEncryptionConfig(ctx)
if err != nil { // if err != nil {
return err // return err
} // }
generator := crypto.NewEncryptionGenerator(*config, userCodeAlgorithm) // generator := crypto.NewEncryptionGenerator(*config, userCodeAlgorithm)
cmd.value, cmd.code, err = crypto.NewCode(generator) // cmd.value, cmd.code, err = crypto.NewCode(generator)
return err // return err
} // }
var _ Commander = (*generateCodeCommand)(nil) // var _ Commander = (*generateCodeCommand)(nil)

View File

@@ -1,65 +1,66 @@
package domain package domain
import ( // import (
"math/rand/v2" // "math/rand/v2"
"strconv" // "strconv"
"github.com/zitadel/zitadel/backend/v3/storage/cache" // "github.com/zitadel/zitadel/backend/v3/storage/cache"
"github.com/zitadel/zitadel/backend/v3/storage/database" // "github.com/zitadel/zitadel/backend/v3/storage/database"
"github.com/zitadel/zitadel/backend/v3/telemetry/logging"
"github.com/zitadel/zitadel/backend/v3/telemetry/tracing"
"github.com/zitadel/zitadel/internal/crypto"
)
// The variables could also be moved to a struct. // // "github.com/zitadel/zitadel/backend/v3/telemetry/logging"
// I just started with the singleton pattern and kept it like this. // "github.com/zitadel/zitadel/backend/v3/telemetry/tracing"
var ( // "github.com/zitadel/zitadel/internal/crypto"
pool database.Pool // )
userCodeAlgorithm crypto.EncryptionAlgorithm
tracer tracing.Tracer
logger logging.Logger
userRepo func(database.QueryExecutor) UserRepository // // The variables could also be moved to a struct.
instanceRepo func(database.QueryExecutor) InstanceRepository // // I just started with the singleton pattern and kept it like this.
cryptoRepo func(database.QueryExecutor) CryptoRepository // var (
orgRepo func(database.QueryExecutor) OrgRepository // pool database.Pool
// userCodeAlgorithm crypto.EncryptionAlgorithm
// tracer tracing.Tracer
// // logger logging.Logger
instanceCache cache.Cache[instanceCacheIndex, string, *Instance] // userRepo func(database.QueryExecutor) UserRepository
orgCache cache.Cache[orgCacheIndex, string, *Org] // // instanceRepo func(database.QueryExecutor) InstanceRepository
// cryptoRepo func(database.QueryExecutor) CryptoRepository
// orgRepo func(database.QueryExecutor) OrgRepository
generateID func() (string, error) = func() (string, error) { // // instanceCache cache.Cache[instanceCacheIndex, string, *Instance]
return strconv.FormatUint(rand.Uint64(), 10), nil // orgCache cache.Cache[orgCacheIndex, string, *Org]
}
)
func SetPool(p database.Pool) { // generateID func() (string, error) = func() (string, error) {
pool = p // return strconv.FormatUint(rand.Uint64(), 10), nil
} // }
// )
func SetUserCodeAlgorithm(algorithm crypto.EncryptionAlgorithm) { // func SetPool(p database.Pool) {
userCodeAlgorithm = algorithm // pool = p
} // }
func SetTracer(t tracing.Tracer) { // func SetUserCodeAlgorithm(algorithm crypto.EncryptionAlgorithm) {
tracer = t // userCodeAlgorithm = algorithm
} // }
func SetLogger(l logging.Logger) { // func SetTracer(t tracing.Tracer) {
logger = l // tracer = t
} // }
func SetUserRepository(repo func(database.QueryExecutor) UserRepository) { // // func SetLogger(l logging.Logger) {
userRepo = repo // // logger = l
} // // }
func SetOrgRepository(repo func(database.QueryExecutor) OrgRepository) { // func SetUserRepository(repo func(database.QueryExecutor) UserRepository) {
orgRepo = repo // userRepo = repo
} // }
func SetInstanceRepository(repo func(database.QueryExecutor) InstanceRepository) { // func SetOrgRepository(repo func(database.QueryExecutor) OrgRepository) {
instanceRepo = repo // orgRepo = repo
} // }
func SetCryptoRepository(repo func(database.QueryExecutor) CryptoRepository) { // // func SetInstanceRepository(repo func(database.QueryExecutor) InstanceRepository) {
cryptoRepo = repo // // instanceRepo = repo
} // // }
// func SetCryptoRepository(repo func(database.QueryExecutor) CryptoRepository) {
// cryptoRepo = repo
// }

View File

@@ -1,67 +1,67 @@
package domain_test package domain_test
import ( // import (
"context" // "context"
"log/slog" // "log/slog"
"testing" // "testing"
"github.com/stretchr/testify/assert" // "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" // "github.com/stretchr/testify/require"
"go.opentelemetry.io/otel" // "go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace" // "go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
sdktrace "go.opentelemetry.io/otel/sdk/trace" // sdktrace "go.opentelemetry.io/otel/sdk/trace"
"go.uber.org/mock/gomock" // "go.uber.org/mock/gomock"
. "github.com/zitadel/zitadel/backend/v3/domain" // . "github.com/zitadel/zitadel/backend/v3/domain"
"github.com/zitadel/zitadel/backend/v3/storage/database/dbmock" // "github.com/zitadel/zitadel/backend/v3/storage/database/dbmock"
"github.com/zitadel/zitadel/backend/v3/storage/database/repository" // "github.com/zitadel/zitadel/backend/v3/storage/database/repository"
"github.com/zitadel/zitadel/backend/v3/telemetry/logging" // "github.com/zitadel/zitadel/backend/v3/telemetry/logging"
"github.com/zitadel/zitadel/backend/v3/telemetry/tracing" // "github.com/zitadel/zitadel/backend/v3/telemetry/tracing"
) // )
// These tests give an overview of how to use the domain package. // These tests give an overview of how to use the domain package.
func TestExample(t *testing.T) { // func TestExample(t *testing.T) {
t.Skip("skip example test because it is not a real test") // t.Skip("skip example test because it is not a real test")
ctx := context.Background() // ctx := context.Background()
ctrl := gomock.NewController(t) // ctrl := gomock.NewController(t)
pool := dbmock.NewMockPool(ctrl) // pool := dbmock.NewMockPool(ctrl)
tx := dbmock.NewMockTransaction(ctrl) // tx := dbmock.NewMockTransaction(ctrl)
pool.EXPECT().Begin(gomock.Any(), gomock.Any()).Return(tx, nil) // pool.EXPECT().Begin(gomock.Any(), gomock.Any()).Return(tx, nil)
tx.EXPECT().End(gomock.Any(), gomock.Any()).Return(nil) // tx.EXPECT().End(gomock.Any(), gomock.Any()).Return(nil)
SetPool(pool) // SetPool(pool)
exporter, err := stdouttrace.New(stdouttrace.WithPrettyPrint()) // exporter, err := stdouttrace.New(stdouttrace.WithPrettyPrint())
require.NoError(t, err) // require.NoError(t, err)
tracerProvider := sdktrace.NewTracerProvider( // tracerProvider := sdktrace.NewTracerProvider(
sdktrace.WithSyncer(exporter), // sdktrace.WithSyncer(exporter),
) // )
otel.SetTracerProvider(tracerProvider) // otel.SetTracerProvider(tracerProvider)
SetTracer(tracing.Tracer{Tracer: tracerProvider.Tracer("test")}) // SetTracer(tracing.Tracer{Tracer: tracerProvider.Tracer("test")})
defer func() { assert.NoError(t, tracerProvider.Shutdown(ctx)) }() // defer func() { assert.NoError(t, tracerProvider.Shutdown(ctx)) }()
SetLogger(logging.Logger{Logger: slog.Default()}) // SetLogger(logging.Logger{Logger: slog.Default()})
SetUserRepository(repository.UserRepository) // SetUserRepository(repository.UserRepository)
SetOrgRepository(repository.OrgRepository) // SetOrgRepository(repository.OrgRepository)
// SetInstanceRepository(repository.Instance) // // SetInstanceRepository(repository.Instance)
// SetCryptoRepository(repository.Crypto) // // SetCryptoRepository(repository.Crypto)
t.Run("create org", func(t *testing.T) { // t.Run("create org", func(t *testing.T) {
org := NewAddOrgCommand("testorg", NewAddMemberCommand("testuser", "ORG_OWNER")) // org := NewAddOrgCommand("testorg", NewAddMemberCommand("testuser", "ORG_OWNER"))
user := NewCreateHumanCommand("testuser") // user := NewCreateHumanCommand("testuser")
err := Invoke(ctx, BatchCommands(org, user)) // err := Invoke(ctx, BatchCommands(org, user))
assert.NoError(t, err) // assert.NoError(t, err)
}) // })
t.Run("verified email", func(t *testing.T) { // t.Run("verified email", func(t *testing.T) {
err := Invoke(ctx, NewSetEmailCommand("u1", "test@example.com", NewEmailVerifiedCommand("u1", true))) // err := Invoke(ctx, NewSetEmailCommand("u1", "test@example.com", NewEmailVerifiedCommand("u1", true)))
assert.NoError(t, err) // assert.NoError(t, err)
}) // })
t.Run("unverified email", func(t *testing.T) { // t.Run("unverified email", func(t *testing.T) {
err := Invoke(ctx, NewSetEmailCommand("u2", "test2@example.com", NewEmailVerifiedCommand("u2", false))) // err := Invoke(ctx, NewSetEmailCommand("u2", "test2@example.com", NewEmailVerifiedCommand("u2", false)))
assert.NoError(t, err) // assert.NoError(t, err)
}) // })
} // }

View File

@@ -1,175 +1,175 @@
package domain package domain
import ( // import (
"context" // "context"
"time" // "time"
) // )
// EmailVerifiedCommand verifies an email address for a user. // // EmailVerifiedCommand verifies an email address for a user.
type EmailVerifiedCommand struct { // type EmailVerifiedCommand struct {
UserID string `json:"userId"` // UserID string `json:"userId"`
Email *Email `json:"email"` // Email *Email `json:"email"`
} // }
func NewEmailVerifiedCommand(userID string, isVerified bool) *EmailVerifiedCommand { // func NewEmailVerifiedCommand(userID string, isVerified bool) *EmailVerifiedCommand {
return &EmailVerifiedCommand{ // return &EmailVerifiedCommand{
UserID: userID, // UserID: userID,
Email: &Email{ // Email: &Email{
VerifiedAt: time.Time{}, // VerifiedAt: time.Time{},
}, // },
} // }
} // }
// String implements [Commander]. // // String implements [Commander].
func (cmd *EmailVerifiedCommand) String() string { // func (cmd *EmailVerifiedCommand) String() string {
return "EmailVerifiedCommand" // return "EmailVerifiedCommand"
} // }
var ( // var (
_ Commander = (*EmailVerifiedCommand)(nil) // _ Commander = (*EmailVerifiedCommand)(nil)
_ SetEmailOpt = (*EmailVerifiedCommand)(nil) // _ SetEmailOpt = (*EmailVerifiedCommand)(nil)
) // )
// Execute implements [Commander] // // Execute implements [Commander]
func (cmd *EmailVerifiedCommand) Execute(ctx context.Context, opts *CommandOpts) error { // func (cmd *EmailVerifiedCommand) Execute(ctx context.Context, opts *CommandOpts) error {
repo := userRepo(opts.DB).Human() // repo := userRepo(opts.DB).Human()
return repo.Update(ctx, repo.IDCondition(cmd.UserID), repo.SetEmailVerifiedAt(time.Time{})) // return repo.Update(ctx, repo.IDCondition(cmd.UserID), repo.SetEmailVerifiedAt(time.Time{}))
} // }
// applyOnSetEmail implements [SetEmailOpt] // // applyOnSetEmail implements [SetEmailOpt]
func (cmd *EmailVerifiedCommand) applyOnSetEmail(setEmailCmd *SetEmailCommand) { // func (cmd *EmailVerifiedCommand) applyOnSetEmail(setEmailCmd *SetEmailCommand) {
cmd.UserID = setEmailCmd.UserID // cmd.UserID = setEmailCmd.UserID
cmd.Email.Address = setEmailCmd.Email // cmd.Email.Address = setEmailCmd.Email
setEmailCmd.verification = cmd // setEmailCmd.verification = cmd
} // }
// SendCodeCommand sends a verification code to the user's email address. // // SendCodeCommand sends a verification code to the user's email address.
// If the URLTemplate is not set it will use the default of the organization / instance. // // If the URLTemplate is not set it will use the default of the organization / instance.
type SendCodeCommand struct { // type SendCodeCommand struct {
UserID string `json:"userId"` // UserID string `json:"userId"`
Email string `json:"email"` // Email string `json:"email"`
URLTemplate *string `json:"urlTemplate"` // URLTemplate *string `json:"urlTemplate"`
generator *generateCodeCommand // generator *generateCodeCommand
} // }
var ( // var (
_ Commander = (*SendCodeCommand)(nil) // _ Commander = (*SendCodeCommand)(nil)
_ SetEmailOpt = (*SendCodeCommand)(nil) // _ SetEmailOpt = (*SendCodeCommand)(nil)
) // )
func NewSendCodeCommand(userID string, urlTemplate *string) *SendCodeCommand { // func NewSendCodeCommand(userID string, urlTemplate *string) *SendCodeCommand {
return &SendCodeCommand{ // return &SendCodeCommand{
UserID: userID, // UserID: userID,
generator: &generateCodeCommand{}, // generator: &generateCodeCommand{},
URLTemplate: urlTemplate, // URLTemplate: urlTemplate,
} // }
} // }
// String implements [Commander]. // // String implements [Commander].
func (cmd *SendCodeCommand) String() string { // func (cmd *SendCodeCommand) String() string {
return "SendCodeCommand" // return "SendCodeCommand"
} // }
// Execute implements [Commander] // // Execute implements [Commander]
func (cmd *SendCodeCommand) Execute(ctx context.Context, opts *CommandOpts) error { // func (cmd *SendCodeCommand) Execute(ctx context.Context, opts *CommandOpts) error {
if err := cmd.ensureEmail(ctx, opts); err != nil { // if err := cmd.ensureEmail(ctx, opts); err != nil {
return err // return err
} // }
if err := cmd.ensureURL(ctx, opts); err != nil { // if err := cmd.ensureURL(ctx, opts); err != nil {
return err // return err
} // }
if err := opts.Invoker.Invoke(ctx, cmd.generator, opts); err != nil { // if err := opts.Invoker.Invoke(ctx, cmd.generator, opts); err != nil {
return err // return err
} // }
// TODO: queue notification // // TODO: queue notification
return nil // return nil
} // }
func (cmd *SendCodeCommand) ensureEmail(ctx context.Context, opts *CommandOpts) error { // func (cmd *SendCodeCommand) ensureEmail(ctx context.Context, opts *CommandOpts) error {
if cmd.Email != "" { // if cmd.Email != "" {
return nil // return nil
} // }
repo := userRepo(opts.DB).Human() // repo := userRepo(opts.DB).Human()
email, err := repo.GetEmail(ctx, repo.IDCondition(cmd.UserID)) // email, err := repo.GetEmail(ctx, repo.IDCondition(cmd.UserID))
if err != nil || !email.VerifiedAt.IsZero() { // if err != nil || !email.VerifiedAt.IsZero() {
return err // return err
} // }
cmd.Email = email.Address // cmd.Email = email.Address
return nil // return nil
} // }
func (cmd *SendCodeCommand) ensureURL(ctx context.Context, opts *CommandOpts) error { // func (cmd *SendCodeCommand) ensureURL(ctx context.Context, opts *CommandOpts) error {
if cmd.URLTemplate != nil && *cmd.URLTemplate != "" { // if cmd.URLTemplate != nil && *cmd.URLTemplate != "" {
return nil // return nil
} // }
_, _ = ctx, opts // _, _ = ctx, opts
// TODO: load default template // // TODO: load default template
return nil // return nil
} // }
// applyOnSetEmail implements [SetEmailOpt] // // applyOnSetEmail implements [SetEmailOpt]
func (cmd *SendCodeCommand) applyOnSetEmail(setEmailCmd *SetEmailCommand) { // func (cmd *SendCodeCommand) applyOnSetEmail(setEmailCmd *SetEmailCommand) {
cmd.UserID = setEmailCmd.UserID // cmd.UserID = setEmailCmd.UserID
cmd.Email = setEmailCmd.Email // cmd.Email = setEmailCmd.Email
setEmailCmd.verification = cmd // setEmailCmd.verification = cmd
} // }
// ReturnCodeCommand creates the code and returns it to the caller. // // ReturnCodeCommand creates the code and returns it to the caller.
// The caller gets the code by calling the Code field after the command got executed. // // The caller gets the code by calling the Code field after the command got executed.
type ReturnCodeCommand struct { // type ReturnCodeCommand struct {
UserID string `json:"userId"` // UserID string `json:"userId"`
Email string `json:"email"` // Email string `json:"email"`
Code string `json:"code"` // Code string `json:"code"`
generator *generateCodeCommand // generator *generateCodeCommand
} // }
var ( // var (
_ Commander = (*ReturnCodeCommand)(nil) // _ Commander = (*ReturnCodeCommand)(nil)
_ SetEmailOpt = (*ReturnCodeCommand)(nil) // _ SetEmailOpt = (*ReturnCodeCommand)(nil)
) // )
func NewReturnCodeCommand(userID string) *ReturnCodeCommand { // func NewReturnCodeCommand(userID string) *ReturnCodeCommand {
return &ReturnCodeCommand{ // return &ReturnCodeCommand{
UserID: userID, // UserID: userID,
generator: &generateCodeCommand{}, // generator: &generateCodeCommand{},
} // }
} // }
// String implements [Commander]. // // String implements [Commander].
func (cmd *ReturnCodeCommand) String() string { // func (cmd *ReturnCodeCommand) String() string {
return "ReturnCodeCommand" // return "ReturnCodeCommand"
} // }
// Execute implements [Commander] // // Execute implements [Commander]
func (cmd *ReturnCodeCommand) Execute(ctx context.Context, opts *CommandOpts) error { // func (cmd *ReturnCodeCommand) Execute(ctx context.Context, opts *CommandOpts) error {
if err := cmd.ensureEmail(ctx, opts); err != nil { // if err := cmd.ensureEmail(ctx, opts); err != nil {
return err // return err
} // }
if err := opts.Invoker.Invoke(ctx, cmd.generator, opts); err != nil { // if err := opts.Invoker.Invoke(ctx, cmd.generator, opts); err != nil {
return err // return err
} // }
cmd.Code = cmd.generator.code // cmd.Code = cmd.generator.code
return nil // return nil
} // }
func (cmd *ReturnCodeCommand) ensureEmail(ctx context.Context, opts *CommandOpts) error { // func (cmd *ReturnCodeCommand) ensureEmail(ctx context.Context, opts *CommandOpts) error {
if cmd.Email != "" { // if cmd.Email != "" {
return nil // return nil
} // }
repo := userRepo(opts.DB).Human() // repo := userRepo(opts.DB).Human()
email, err := repo.GetEmail(ctx, repo.IDCondition(cmd.UserID)) // email, err := repo.GetEmail(ctx, repo.IDCondition(cmd.UserID))
if err != nil || !email.VerifiedAt.IsZero() { // if err != nil || !email.VerifiedAt.IsZero() {
return err // return err
} // }
cmd.Email = email.Address // cmd.Email = email.Address
return nil // return nil
} // }
// applyOnSetEmail implements [SetEmailOpt] // // applyOnSetEmail implements [SetEmailOpt]
func (cmd *ReturnCodeCommand) applyOnSetEmail(setEmailCmd *SetEmailCommand) { // func (cmd *ReturnCodeCommand) applyOnSetEmail(setEmailCmd *SetEmailCommand) {
cmd.UserID = setEmailCmd.UserID // cmd.UserID = setEmailCmd.UserID
cmd.Email = setEmailCmd.Email // cmd.Email = setEmailCmd.Email
setEmailCmd.verification = cmd // setEmailCmd.verification = cmd
} // }

View File

@@ -1,158 +1,158 @@
package domain package domain
import ( // import (
"context" // "context"
"fmt" // "fmt"
"github.com/zitadel/zitadel/backend/v3/storage/eventstore" // "github.com/zitadel/zitadel/backend/v3/storage/eventstore"
) // )
// Invoke provides a way to execute commands within the domain package. // // Invoke provides a way to execute commands within the domain package.
// It uses a chain of responsibility pattern to handle the command execution. // // It uses a chain of responsibility pattern to handle the command execution.
// The default chain includes logging, tracing, and event publishing. // // The default chain includes logging, tracing, and event publishing.
// If you want to invoke multiple commands in a single transaction, you can use the [commandBatch]. // // If you want to invoke multiple commands in a single transaction, you can use the [commandBatch].
func Invoke(ctx context.Context, cmd Commander) error { // func Invoke(ctx context.Context, cmd Commander) error {
invoker := newEventStoreInvoker(newLoggingInvoker(newTraceInvoker(nil))) // invoker := newEventStoreInvoker(newLoggingInvoker(newTraceInvoker(nil)))
opts := &CommandOpts{ // opts := &CommandOpts{
Invoker: invoker.collector, // Invoker: invoker.collector,
DB: pool, // DB: pool,
} // }
return invoker.Invoke(ctx, cmd, opts) // return invoker.Invoke(ctx, cmd, opts)
} // }
// eventStoreInvoker checks if the command implements the [eventer] interface. // // eventStoreInvoker checks if the command implements the [eventer] interface.
// If it does, it collects the events and publishes them to the event store. // // If it does, it collects the events and publishes them to the event store.
type eventStoreInvoker struct { // type eventStoreInvoker struct {
collector *eventCollector // collector *eventCollector
} // }
func newEventStoreInvoker(next Invoker) *eventStoreInvoker { // func newEventStoreInvoker(next Invoker) *eventStoreInvoker {
return &eventStoreInvoker{collector: &eventCollector{next: next}} // return &eventStoreInvoker{collector: &eventCollector{next: next}}
} // }
func (i *eventStoreInvoker) Invoke(ctx context.Context, command Commander, opts *CommandOpts) (err error) { // func (i *eventStoreInvoker) Invoke(ctx context.Context, command Commander, opts *CommandOpts) (err error) {
err = i.collector.Invoke(ctx, command, opts) // err = i.collector.Invoke(ctx, command, opts)
if err != nil { // if err != nil {
return err // return err
} // }
if len(i.collector.events) > 0 { // if len(i.collector.events) > 0 {
err = eventstore.Publish(ctx, i.collector.events, opts.DB) // err = eventstore.Publish(ctx, i.collector.events, opts.DB)
if err != nil { // if err != nil {
return err // return err
} // }
} // }
return nil // return nil
} // }
// eventCollector collects events from all commands. The [eventStoreInvoker] pushes the collected events after all commands are executed. // // eventCollector collects events from all commands. The [eventStoreInvoker] pushes the collected events after all commands are executed.
type eventCollector struct { // type eventCollector struct {
next Invoker // next Invoker
events []*eventstore.Event // events []*eventstore.Event
} // }
type eventer interface { // type eventer interface {
Events() []*eventstore.Event // Events() []*eventstore.Event
} // }
func (i *eventCollector) Invoke(ctx context.Context, command Commander, opts *CommandOpts) (err error) { // func (i *eventCollector) Invoke(ctx context.Context, command Commander, opts *CommandOpts) (err error) {
if e, ok := command.(eventer); ok && len(e.Events()) > 0 { // if e, ok := command.(eventer); ok && len(e.Events()) > 0 {
// we need to ensure all commands are executed in the same transaction // // we need to ensure all commands are executed in the same transaction
close, err := opts.EnsureTx(ctx) // close, err := opts.EnsureTx(ctx)
if err != nil { // if err != nil {
return err // return err
} // }
defer func() { err = close(ctx, err) }() // defer func() { err = close(ctx, err) }()
i.events = append(i.events, e.Events()...) // i.events = append(i.events, e.Events()...)
} // }
if i.next != nil { // if i.next != nil {
return i.next.Invoke(ctx, command, opts) // return i.next.Invoke(ctx, command, opts)
} // }
return command.Execute(ctx, opts) // return command.Execute(ctx, opts)
} // }
// traceInvoker decorates each command with tracing. // // traceInvoker decorates each command with tracing.
type traceInvoker struct { // type traceInvoker struct {
next Invoker // next Invoker
} // }
func newTraceInvoker(next Invoker) *traceInvoker { // func newTraceInvoker(next Invoker) *traceInvoker {
return &traceInvoker{next: next} // return &traceInvoker{next: next}
} // }
func (i *traceInvoker) Invoke(ctx context.Context, command Commander, opts *CommandOpts) (err error) { // func (i *traceInvoker) Invoke(ctx context.Context, command Commander, opts *CommandOpts) (err error) {
ctx, span := tracer.Start(ctx, fmt.Sprintf("%T", command)) // ctx, span := tracer.Start(ctx, fmt.Sprintf("%T", command))
defer func() { // defer func() {
if err != nil { // if err != nil {
span.RecordError(err) // span.RecordError(err)
} // }
span.End() // span.End()
}() // }()
if i.next != nil { // if i.next != nil {
return i.next.Invoke(ctx, command, opts) // return i.next.Invoke(ctx, command, opts)
} // }
return command.Execute(ctx, opts) // return command.Execute(ctx, opts)
} // }
// loggingInvoker decorates each command with logging. // // loggingInvoker decorates each command with logging.
// It is an example implementation and logs the command name at the beginning and success or failure after the command got executed. // // It is an example implementation and logs the command name at the beginning and success or failure after the command got executed.
type loggingInvoker struct { // type loggingInvoker struct {
next Invoker // next Invoker
} // }
func newLoggingInvoker(next Invoker) *loggingInvoker { // func newLoggingInvoker(next Invoker) *loggingInvoker {
return &loggingInvoker{next: next} // return &loggingInvoker{next: next}
} // }
func (i *loggingInvoker) Invoke(ctx context.Context, command Commander, opts *CommandOpts) (err error) { // func (i *loggingInvoker) Invoke(ctx context.Context, command Commander, opts *CommandOpts) (err error) {
logger.InfoContext(ctx, "Invoking command", "command", command.String()) // logger.InfoContext(ctx, "Invoking command", "command", command.String())
if i.next != nil { // if i.next != nil {
err = i.next.Invoke(ctx, command, opts) // err = i.next.Invoke(ctx, command, opts)
} else { // } else {
err = command.Execute(ctx, opts) // err = command.Execute(ctx, opts)
} // }
if err != nil { // if err != nil {
logger.ErrorContext(ctx, "Command invocation failed", "command", command.String(), "error", err) // logger.ErrorContext(ctx, "Command invocation failed", "command", command.String(), "error", err)
return err // return err
} // }
logger.InfoContext(ctx, "Command invocation succeeded", "command", command.String()) // logger.InfoContext(ctx, "Command invocation succeeded", "command", command.String())
return nil // return nil
} // }
type noopInvoker struct { // type noopInvoker struct {
next Invoker // next Invoker
} // }
func (i *noopInvoker) Invoke(ctx context.Context, command Commander, opts *CommandOpts) error { // func (i *noopInvoker) Invoke(ctx context.Context, command Commander, opts *CommandOpts) error {
if i.next != nil { // if i.next != nil {
return i.next.Invoke(ctx, command, opts) // return i.next.Invoke(ctx, command, opts)
} // }
return command.Execute(ctx, opts) // return command.Execute(ctx, opts)
} // }
// cacheInvoker could be used in the future to do the caching. // // cacheInvoker could be used in the future to do the caching.
// My goal would be to have two interfaces: // // My goal would be to have two interfaces:
// - cacheSetter: which caches an object // // - cacheSetter: which caches an object
// - cacheGetter: which gets an object from the cache, this should also skip the command execution // // - cacheGetter: which gets an object from the cache, this should also skip the command execution
type cacheInvoker struct { // type cacheInvoker struct {
next Invoker // next Invoker
} // }
type cacher interface { // type cacher interface {
Cache(opts *CommandOpts) // Cache(opts *CommandOpts)
} // }
func (i *cacheInvoker) Invoke(ctx context.Context, command Commander, opts *CommandOpts) (err error) { // func (i *cacheInvoker) Invoke(ctx context.Context, command Commander, opts *CommandOpts) (err error) {
if c, ok := command.(cacher); ok { // if c, ok := command.(cacher); ok {
c.Cache(opts) // c.Cache(opts)
} // }
if i.next != nil { // if i.next != nil {
err = i.next.Invoke(ctx, command, opts) // err = i.next.Invoke(ctx, command, opts)
} else { // } else {
err = command.Execute(ctx, opts) // err = command.Execute(ctx, opts)
} // }
return err // return err
} // }

View File

@@ -1,137 +1,137 @@
package domain package domain
import ( // import (
"context" // "context"
"github.com/zitadel/zitadel/backend/v3/storage/eventstore" // "github.com/zitadel/zitadel/backend/v3/storage/eventstore"
) // )
// AddOrgCommand adds a new organization. // // AddOrgCommand adds a new organization.
// I'm unsure if we should add the Admins here or if this should be a separate command. // // I'm unsure if we should add the Admins here or if this should be a separate command.
type AddOrgCommand struct { // type AddOrgCommand struct {
ID string `json:"id"` // ID string `json:"id"`
Name string `json:"name"` // Name string `json:"name"`
Admins []*AddMemberCommand `json:"admins"` // Admins []*AddMemberCommand `json:"admins"`
} // }
func NewAddOrgCommand(name string, admins ...*AddMemberCommand) *AddOrgCommand { // func NewAddOrgCommand(name string, admins ...*AddMemberCommand) *AddOrgCommand {
return &AddOrgCommand{ // return &AddOrgCommand{
Name: name, // Name: name,
Admins: admins, // Admins: admins,
} // }
} // }
// String implements [Commander]. // // String implements [Commander].
func (cmd *AddOrgCommand) String() string { // func (cmd *AddOrgCommand) String() string {
return "AddOrgCommand" // return "AddOrgCommand"
} // }
// Execute implements Commander. // // Execute implements Commander.
func (cmd *AddOrgCommand) Execute(ctx context.Context, opts *CommandOpts) (err error) { // func (cmd *AddOrgCommand) Execute(ctx context.Context, opts *CommandOpts) (err error) {
if len(cmd.Admins) == 0 { // if len(cmd.Admins) == 0 {
return ErrNoAdminSpecified // return ErrNoAdminSpecified
} // }
if err = cmd.ensureID(); err != nil { // if err = cmd.ensureID(); err != nil {
return err // return err
} // }
close, err := opts.EnsureTx(ctx) // close, err := opts.EnsureTx(ctx)
if err != nil { // if err != nil {
return err // return err
} // }
defer func() { err = close(ctx, err) }() // defer func() { err = close(ctx, err) }()
err = orgRepo(opts.DB).Create(ctx, &Org{ // err = orgRepo(opts.DB).Create(ctx, &Org{
ID: cmd.ID, // ID: cmd.ID,
Name: cmd.Name, // Name: cmd.Name,
}) // })
if err != nil { // if err != nil {
return err // return err
} // }
for _, admin := range cmd.Admins { // for _, admin := range cmd.Admins {
admin.orgID = cmd.ID // admin.orgID = cmd.ID
if err = opts.Invoke(ctx, admin); err != nil { // if err = opts.Invoke(ctx, admin); err != nil {
return err // return err
} // }
} // }
orgCache.Set(ctx, &Org{ // orgCache.Set(ctx, &Org{
ID: cmd.ID, // ID: cmd.ID,
Name: cmd.Name, // Name: cmd.Name,
}) // })
return nil // return nil
} // }
// Events implements [eventer]. // // Events implements [eventer].
func (cmd *AddOrgCommand) Events() []*eventstore.Event { // func (cmd *AddOrgCommand) Events() []*eventstore.Event {
return []*eventstore.Event{ // return []*eventstore.Event{
{ // {
AggregateType: "org", // AggregateType: "org",
AggregateID: cmd.ID, // AggregateID: cmd.ID,
Type: "org.added", // Type: "org.added",
Payload: cmd, // Payload: cmd,
}, // },
} // }
} // }
var ( // var (
_ Commander = (*AddOrgCommand)(nil) // _ Commander = (*AddOrgCommand)(nil)
_ eventer = (*AddOrgCommand)(nil) // _ eventer = (*AddOrgCommand)(nil)
) // )
func (cmd *AddOrgCommand) ensureID() (err error) { // func (cmd *AddOrgCommand) ensureID() (err error) {
if cmd.ID != "" { // if cmd.ID != "" {
return nil // return nil
} // }
cmd.ID, err = generateID() // cmd.ID, err = generateID()
return err // return err
} // }
// AddMemberCommand adds a new member to an organization. // // AddMemberCommand adds a new member to an organization.
// I'm not sure if we should make it more generic to also use it for instances. // // I'm not sure if we should make it more generic to also use it for instances.
type AddMemberCommand struct { // type AddMemberCommand struct {
orgID string // orgID string
UserID string `json:"userId"` // UserID string `json:"userId"`
Roles []string `json:"roles"` // Roles []string `json:"roles"`
} // }
func NewAddMemberCommand(userID string, roles ...string) *AddMemberCommand { // func NewAddMemberCommand(userID string, roles ...string) *AddMemberCommand {
return &AddMemberCommand{ // return &AddMemberCommand{
UserID: userID, // UserID: userID,
Roles: roles, // Roles: roles,
} // }
} // }
// String implements [Commander]. // // String implements [Commander].
func (cmd *AddMemberCommand) String() string { // func (cmd *AddMemberCommand) String() string {
return "AddMemberCommand" // return "AddMemberCommand"
} // }
// Execute implements Commander. // // Execute implements Commander.
func (a *AddMemberCommand) Execute(ctx context.Context, opts *CommandOpts) (err error) { // func (a *AddMemberCommand) Execute(ctx context.Context, opts *CommandOpts) (err error) {
close, err := opts.EnsureTx(ctx) // close, err := opts.EnsureTx(ctx)
if err != nil { // if err != nil {
return err // return err
} // }
defer func() { err = close(ctx, err) }() // defer func() { err = close(ctx, err) }()
return orgRepo(opts.DB).Member().AddMember(ctx, a.orgID, a.UserID, a.Roles) // return orgRepo(opts.DB).Member().AddMember(ctx, a.orgID, a.UserID, a.Roles)
} // }
// Events implements [eventer]. // // Events implements [eventer].
func (a *AddMemberCommand) Events() []*eventstore.Event { // func (a *AddMemberCommand) Events() []*eventstore.Event {
return []*eventstore.Event{ // return []*eventstore.Event{
{ // {
AggregateType: "org", // AggregateType: "org",
AggregateID: a.UserID, // AggregateID: a.UserID,
Type: "member.added", // Type: "member.added",
Payload: a, // Payload: a,
}, // },
} // }
} // }
var ( // var (
_ Commander = (*AddMemberCommand)(nil) // _ Commander = (*AddMemberCommand)(nil)
_ eventer = (*AddMemberCommand)(nil) // _ eventer = (*AddMemberCommand)(nil)
) // )

View File

@@ -1,74 +1,74 @@
package domain package domain
import ( // import (
"context" // "context"
"github.com/zitadel/zitadel/backend/v3/storage/eventstore" // "github.com/zitadel/zitadel/backend/v3/storage/eventstore"
) // )
// SetEmailCommand sets the email address of a user. // // SetEmailCommand sets the email address of a user.
// If allows verification as a sub command. // // If allows verification as a sub command.
// The verification command is executed after the email address is set. // // The verification command is executed after the email address is set.
// The verification command is executed in the same transaction as the email address update. // // The verification command is executed in the same transaction as the email address update.
type SetEmailCommand struct { // type SetEmailCommand struct {
UserID string `json:"userId"` // UserID string `json:"userId"`
Email string `json:"email"` // Email string `json:"email"`
verification Commander // verification Commander
} // }
var ( // var (
_ Commander = (*SetEmailCommand)(nil) // _ Commander = (*SetEmailCommand)(nil)
_ eventer = (*SetEmailCommand)(nil) // _ eventer = (*SetEmailCommand)(nil)
_ CreateHumanOpt = (*SetEmailCommand)(nil) // _ CreateHumanOpt = (*SetEmailCommand)(nil)
) // )
type SetEmailOpt interface { // type SetEmailOpt interface {
applyOnSetEmail(*SetEmailCommand) // applyOnSetEmail(*SetEmailCommand)
} // }
func NewSetEmailCommand(userID, email string, verificationType SetEmailOpt) *SetEmailCommand { // func NewSetEmailCommand(userID, email string, verificationType SetEmailOpt) *SetEmailCommand {
cmd := &SetEmailCommand{ // cmd := &SetEmailCommand{
UserID: userID, // UserID: userID,
Email: email, // Email: email,
} // }
verificationType.applyOnSetEmail(cmd) // verificationType.applyOnSetEmail(cmd)
return cmd // return cmd
} // }
// String implements [Commander]. // // String implements [Commander].
func (cmd *SetEmailCommand) String() string { // func (cmd *SetEmailCommand) String() string {
return "SetEmailCommand" // return "SetEmailCommand"
} // }
func (cmd *SetEmailCommand) Execute(ctx context.Context, opts *CommandOpts) error { // func (cmd *SetEmailCommand) Execute(ctx context.Context, opts *CommandOpts) error {
close, err := opts.EnsureTx(ctx) // close, err := opts.EnsureTx(ctx)
if err != nil { // if err != nil {
return err // return err
} // }
defer func() { err = close(ctx, err) }() // defer func() { err = close(ctx, err) }()
// userStatement(opts.DB).Human().ByID(cmd.UserID).SetEmail(ctx, cmd.Email) // // userStatement(opts.DB).Human().ByID(cmd.UserID).SetEmail(ctx, cmd.Email)
repo := userRepo(opts.DB).Human() // repo := userRepo(opts.DB).Human()
err = repo.Update(ctx, repo.IDCondition(cmd.UserID), repo.SetEmailAddress(cmd.Email)) // err = repo.Update(ctx, repo.IDCondition(cmd.UserID), repo.SetEmailAddress(cmd.Email))
if err != nil { // if err != nil {
return err // return err
} // }
return opts.Invoke(ctx, cmd.verification) // return opts.Invoke(ctx, cmd.verification)
} // }
// Events implements [eventer]. // // Events implements [eventer].
func (cmd *SetEmailCommand) Events() []*eventstore.Event { // func (cmd *SetEmailCommand) Events() []*eventstore.Event {
return []*eventstore.Event{ // return []*eventstore.Event{
{ // {
AggregateType: "user", // AggregateType: "user",
AggregateID: cmd.UserID, // AggregateID: cmd.UserID,
Type: "user.email.set", // Type: "user.email.set",
Payload: cmd, // Payload: cmd,
}, // },
} // }
} // }
// applyOnCreateHuman implements [CreateHumanOpt]. // // applyOnCreateHuman implements [CreateHumanOpt].
func (cmd *SetEmailCommand) applyOnCreateHuman(createUserCmd *CreateUserCommand) { // func (cmd *SetEmailCommand) applyOnCreateHuman(createUserCmd *CreateUserCommand) {
createUserCmd.email = cmd // createUserCmd.email = cmd
} // }

View File

@@ -20,7 +20,7 @@ func (a *and) Write(builder *StatementBuilder) {
if i > 0 { if i > 0 {
builder.WriteString(" AND ") builder.WriteString(" AND ")
} }
condition.(Condition).Write(builder) condition.Write(builder)
} }
} }
@@ -45,7 +45,7 @@ func (o *or) Write(builder *StatementBuilder) {
if i > 0 { if i > 0 {
builder.WriteString(" OR ") builder.WriteString(" OR ")
} }
condition.(Condition).Write(builder) condition.Write(builder)
} }
} }
@@ -85,7 +85,7 @@ func (i *isNotNull) Write(builder *StatementBuilder) {
// IsNotNull creates a condition that checks if a column is NOT NULL. // IsNotNull creates a condition that checks if a column is NOT NULL.
func IsNotNull(column Column) *isNotNull { func IsNotNull(column Column) *isNotNull {
return &isNotNull{column: column.(Column)} return &isNotNull{column: column}
} }
var _ Condition = (*isNotNull)(nil) var _ Condition = (*isNotNull)(nil)

View File

@@ -34,7 +34,7 @@ type Config struct {
// // The value will be taken as is. Multiple options are space separated. // // The value will be taken as is. Multiple options are space separated.
// Options string // Options string
configuredFields []string // configuredFields []string
} }
// Connect implements [database.Connector]. // Connect implements [database.Connector].

View File

@@ -2,6 +2,7 @@ package postgres
import ( import (
"context" "context"
"errors"
"github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5"
@@ -25,7 +26,10 @@ func (tx *pgxTx) Rollback(ctx context.Context) error {
// End implements [database.Transaction]. // End implements [database.Transaction].
func (tx *pgxTx) End(ctx context.Context, err error) error { func (tx *pgxTx) End(ctx context.Context, err error) error {
if err != nil { if err != nil {
tx.Rollback(ctx) rollbackErr := tx.Rollback(ctx)
if rollbackErr != nil {
err = errors.Join(err, rollbackErr)
}
return err return err
} }
return tx.Commit(ctx) return tx.Commit(ctx)

View File

@@ -46,7 +46,7 @@ func (opts *QueryOpts) WriteOrderBy(builder *StatementBuilder) {
return return
} }
builder.WriteString(" ORDER BY ") builder.WriteString(" ORDER BY ")
Columns(opts.OrderBy).Write(builder) opts.OrderBy.Write(builder)
} }
func (opts *QueryOpts) WriteLimit(builder *StatementBuilder) { func (opts *QueryOpts) WriteLimit(builder *StatementBuilder) {

View File

@@ -31,7 +31,6 @@ func (u *userHuman) GetEmail(ctx context.Context, condition database.Condition)
&email.Address, &email.Address,
&email.VerifiedAt, &email.VerifiedAt,
) )
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -189,18 +188,18 @@ func (h userHuman) PhoneVerifiedAtColumn() database.Column {
return database.NewColumn("phone_verified_at") return database.NewColumn("phone_verified_at")
} }
func (h userHuman) columns() database.Columns { // func (h userHuman) columns() database.Columns {
return append(h.user.columns(), // return append(h.user.columns(),
h.FirstNameColumn(), // h.FirstNameColumn(),
h.LastNameColumn(), // h.LastNameColumn(),
h.EmailAddressColumn(), // h.EmailAddressColumn(),
h.EmailVerifiedAtColumn(), // h.EmailVerifiedAtColumn(),
h.PhoneNumberColumn(), // h.PhoneNumberColumn(),
h.PhoneVerifiedAtColumn(), // h.PhoneVerifiedAtColumn(),
) // )
} // }
func (h userHuman) writeReturning(builder *database.StatementBuilder) { // func (h userHuman) writeReturning(builder *database.StatementBuilder) {
builder.WriteString(" RETURNING ") // builder.WriteString(" RETURNING ")
h.columns().Write(builder) // h.columns().Write(builder)
} // }

View File

@@ -1,76 +1,76 @@
package repository_test package repository_test
import ( // import (
"context" // "context"
"testing" // "testing"
"github.com/stretchr/testify/assert" // "github.com/stretchr/testify/assert"
"github.com/zitadel/zitadel/backend/v3/storage/database" // "github.com/zitadel/zitadel/backend/v3/storage/database"
"github.com/zitadel/zitadel/backend/v3/storage/database/dbmock" // "github.com/zitadel/zitadel/backend/v3/storage/database/dbmock"
"github.com/zitadel/zitadel/backend/v3/storage/database/repository" // "github.com/zitadel/zitadel/backend/v3/storage/database/repository"
"go.uber.org/mock/gomock" // "go.uber.org/mock/gomock"
) // )
func TestQueryUser(t *testing.T) { // func TestQueryUser(t *testing.T) {
t.Skip("tests are meant as examples and are not real tests") // t.Skip("tests are meant as examples and are not real tests")
t.Run("User filters", func(t *testing.T) { // t.Run("User filters", func(t *testing.T) {
client := dbmock.NewMockClient(gomock.NewController(t)) // client := dbmock.NewMockClient(gomock.NewController(t))
user := repository.UserRepository(client) // user := repository.UserRepository(client)
u, err := user.Get(context.Background(), // u, err := user.Get(context.Background(),
database.WithCondition( // database.WithCondition(
database.And( // database.And(
database.Or( // database.Or(
user.IDCondition("test"), // user.IDCondition("test"),
user.IDCondition("2"), // user.IDCondition("2"),
), // ),
user.UsernameCondition(database.TextOperationStartsWithIgnoreCase, "test"), // user.UsernameCondition(database.TextOperationStartsWithIgnoreCase, "test"),
), // ),
), // ),
database.WithOrderBy(user.CreatedAtColumn()), // database.WithOrderBy(user.CreatedAtColumn()),
) // )
assert.NoError(t, err) // assert.NoError(t, err)
assert.NotNil(t, u) // assert.NotNil(t, u)
}) // })
t.Run("machine and human filters", func(t *testing.T) { // t.Run("machine and human filters", func(t *testing.T) {
client := dbmock.NewMockClient(gomock.NewController(t)) // client := dbmock.NewMockClient(gomock.NewController(t))
user := repository.UserRepository(client) // user := repository.UserRepository(client)
machine := user.Machine() // machine := user.Machine()
human := user.Human() // human := user.Human()
email, err := human.GetEmail(context.Background(), database.And( // email, err := human.GetEmail(context.Background(), database.And(
user.UsernameCondition(database.TextOperationStartsWithIgnoreCase, "test"), // user.UsernameCondition(database.TextOperationStartsWithIgnoreCase, "test"),
database.Or( // database.Or(
machine.DescriptionCondition(database.TextOperationStartsWithIgnoreCase, "test"), // machine.DescriptionCondition(database.TextOperationStartsWithIgnoreCase, "test"),
human.EmailVerifiedCondition(true), // human.EmailVerifiedCondition(true),
database.IsNotNull(machine.DescriptionColumn()), // database.IsNotNull(machine.DescriptionColumn()),
), // ),
)) // ))
assert.NoError(t, err) // assert.NoError(t, err)
assert.NotNil(t, email) // assert.NotNil(t, email)
}) // })
} // }
type dbInstruction string // type dbInstruction string
func TestArg(t *testing.T) { // func TestArg(t *testing.T) {
var bla any = "asdf" // var bla any = "asdf"
instr, ok := bla.(dbInstruction) // instr, ok := bla.(dbInstruction)
assert.False(t, ok) // assert.False(t, ok)
assert.Empty(t, instr) // assert.Empty(t, instr)
bla = dbInstruction("asdf") // bla = dbInstruction("asdf")
instr, ok = bla.(dbInstruction) // instr, ok = bla.(dbInstruction)
assert.True(t, ok) // assert.True(t, ok)
assert.Equal(t, instr, dbInstruction("asdf")) // assert.Equal(t, instr, dbInstruction("asdf"))
} // }
func TestWriteUser(t *testing.T) { // func TestWriteUser(t *testing.T) {
t.Skip("tests are meant as examples and are not real tests") // t.Skip("tests are meant as examples and are not real tests")
t.Run("update user", func(t *testing.T) { // t.Run("update user", func(t *testing.T) {
user := repository.UserRepository(nil) // user := repository.UserRepository(nil)
user.Human().Update(context.Background(), user.IDCondition("test"), user.SetUsername("test")) // user.Human().Update(context.Background(), user.IDCondition("test"), user.SetUsername("test"))
}) // })
} // }

View File

@@ -2,6 +2,7 @@ package command
import ( import (
"context" "context"
"github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore"