multiple tries

This commit is contained in:
adlerhurst
2025-04-29 06:03:47 +02:00
parent 77c4cc8185
commit 986c62b61a
131 changed files with 9805 additions and 47 deletions

View File

@@ -0,0 +1,105 @@
package domain
import (
"context"
"github.com/zitadel/zitadel/backend/v3/storage/database"
)
type Commander interface {
Execute(ctx context.Context, opts *CommandOpts) (err error)
}
type Invoker interface {
Invoke(ctx context.Context, command Commander, opts *CommandOpts) error
}
type CommandOpts struct {
DB database.QueryExecutor
Invoker Invoker
}
type ensureTxOpts struct {
*database.TransactionOptions
}
type EnsureTransactionOpt func(*ensureTxOpts)
// 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
// 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) {
beginner, ok := o.DB.(database.Beginner)
if !ok {
// db is already a transaction
return func(_ context.Context, err error) error {
return err
}, nil
}
txOpts := &ensureTxOpts{
TransactionOptions: new(database.TransactionOptions),
}
for _, opt := range opts {
opt(txOpts)
}
tx, err := beginner.Begin(ctx, txOpts.TransactionOptions)
if err != nil {
return nil, err
}
o.DB = tx
return func(ctx context.Context, err error) error {
return tx.End(ctx, err)
}, nil
}
// 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
// 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) {
pool, ok := o.DB.(database.Pool)
if !ok {
// o.DB is already a client
return func(_ context.Context) error {
return nil
}, nil
}
client, err := pool.Acquire(ctx)
if err != nil {
return nil, err
}
o.DB = client
return func(ctx context.Context) error {
return client.Release(ctx)
}, nil
}
func (o *CommandOpts) Invoke(ctx context.Context, command Commander) error {
if o.Invoker == nil {
return command.Execute(ctx, o)
}
return o.Invoker.Invoke(ctx, command, o)
}
func DefaultOpts(invoker Invoker) *CommandOpts {
if invoker == nil {
invoker = &noopInvoker{}
}
return &CommandOpts{
DB: pool,
Invoker: invoker,
}
}
type noopInvoker struct {
next Invoker
}
func (i *noopInvoker) Invoke(ctx context.Context, command Commander, opts *CommandOpts) error {
if i.next != nil {
return i.next.Invoke(ctx, command, opts)
}
return command.Execute(ctx, opts)
}

View File

@@ -0,0 +1,76 @@
package domain
import (
"context"
v4 "github.com/zitadel/zitadel/backend/v3/storage/database/repository/stmt/v4"
"github.com/zitadel/zitadel/backend/v3/storage/eventstore"
)
type CreateUserCommand struct {
user *User
email *SetEmailCommand
}
var (
_ Commander = (*CreateUserCommand)(nil)
_ eventer = (*CreateUserCommand)(nil)
)
func NewCreateHumanCommand(username string, opts ...CreateHumanOpt) *CreateUserCommand {
cmd := &CreateUserCommand{
user: &User{
User: v4.User{
Username: username,
Traits: &v4.Human{},
},
},
}
for _, opt := range opts {
opt.applyOnCreateHuman(cmd)
}
return cmd
}
// Events implements [eventer].
func (c *CreateUserCommand) Events() []*eventstore.Event {
panic("unimplemented")
}
// Execute implements [Commander].
func (c *CreateUserCommand) Execute(ctx context.Context, opts *CommandOpts) error {
if err := c.ensureUserID(); err != nil {
return err
}
c.email.UserID = c.user.ID
if err := opts.Invoke(ctx, c.email); err != nil {
return err
}
return nil
}
type CreateHumanOpt interface {
applyOnCreateHuman(*CreateUserCommand)
}
type createHumanIDOpt string
// applyOnCreateHuman implements [CreateHumanOpt].
func (c createHumanIDOpt) applyOnCreateHuman(cmd *CreateUserCommand) {
cmd.user.ID = string(c)
}
var _ CreateHumanOpt = (*createHumanIDOpt)(nil)
func CreateHumanWithID(id string) CreateHumanOpt {
return createHumanIDOpt(id)
}
func (c *CreateUserCommand) ensureUserID() (err error) {
if c.user.ID != "" {
return nil
}
c.user.ID, err = generateID()
return err
}

View File

@@ -0,0 +1,26 @@
package domain
import (
"context"
"github.com/zitadel/zitadel/internal/crypto"
)
type generateCodeCommand struct {
code string
value *crypto.CryptoValue
}
type CryptoRepository interface {
GetEncryptionConfig(ctx context.Context) (*crypto.GeneratorConfig, error)
}
func (cmd *generateCodeCommand) Execute(ctx context.Context, opts *CommandOpts) error {
config, err := cryptoRepo(opts.DB).GetEncryptionConfig(ctx)
if err != nil {
return err
}
generator := crypto.NewEncryptionGenerator(*config, userCodeAlgorithm)
cmd.value, cmd.code, err = crypto.NewCode(generator)
return err
}

View File

@@ -0,0 +1,52 @@
package domain
import (
"math/rand/v2"
"strconv"
"github.com/zitadel/zitadel/backend/v3/storage/cache"
"github.com/zitadel/zitadel/backend/v3/storage/database"
"github.com/zitadel/zitadel/backend/v3/telemetry/tracing"
"github.com/zitadel/zitadel/internal/crypto"
)
var (
pool database.Pool
userCodeAlgorithm crypto.EncryptionAlgorithm
tracer tracing.Tracer
// userRepo func(database.QueryExecutor) UserRepository
instanceRepo func(database.QueryExecutor) InstanceRepository
cryptoRepo func(database.QueryExecutor) CryptoRepository
orgRepo func(database.QueryExecutor) OrgRepository
instanceCache cache.Cache[string, string, *Instance]
generateID func() (string, error) = func() (string, error) {
return strconv.FormatUint(rand.Uint64(), 10), nil
}
)
func SetPool(p database.Pool) {
pool = p
}
func SetUserCodeAlgorithm(algorithm crypto.EncryptionAlgorithm) {
userCodeAlgorithm = algorithm
}
func SetTracer(t tracing.Tracer) {
tracer = t
}
// func SetUserRepository(repo func(database.QueryExecutor) UserRepository) {
// userRepo = repo
// }
func SetInstanceRepository(repo func(database.QueryExecutor) InstanceRepository) {
instanceRepo = repo
}
func SetCryptoRepository(repo func(database.QueryExecutor) CryptoRepository) {
cryptoRepo = repo
}

View File

@@ -0,0 +1,45 @@
package domain_test
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
. "github.com/zitadel/zitadel/backend/v3/domain"
"github.com/zitadel/zitadel/backend/v3/storage/database/repository"
"github.com/zitadel/zitadel/backend/v3/telemetry/tracing"
)
func TestExample(t *testing.T) {
ctx := context.Background()
// SetPool(pool)
exporter, err := stdouttrace.New(stdouttrace.WithPrettyPrint())
require.NoError(t, err)
tracerProvider := sdktrace.NewTracerProvider(
sdktrace.WithSyncer(exporter),
)
otel.SetTracerProvider(tracerProvider)
SetTracer(tracing.Tracer{Tracer: tracerProvider.Tracer("test")})
defer func() { assert.NoError(t, tracerProvider.Shutdown(ctx)) }()
SetUserRepository(repository.User)
SetInstanceRepository(repository.Instance)
SetCryptoRepository(repository.Crypto)
t.Run("verified email", func(t *testing.T) {
err := Invoke(ctx, NewSetEmailCommand("u1", "test@example.com", NewEmailVerifiedCommand("u1", true)))
assert.NoError(t, err)
})
t.Run("unverified email", func(t *testing.T) {
err := Invoke(ctx, NewSetEmailCommand("u2", "test2@example.com", NewEmailVerifiedCommand("u2", false)))
assert.NoError(t, err)
})
}

View File

@@ -0,0 +1,155 @@
package domain
import (
"context"
v4 "github.com/zitadel/zitadel/backend/v3/storage/database/repository/stmt/v4"
)
type EmailVerifiedCommand struct {
UserID string `json:"userId"`
Email *Email `json:"email"`
}
func NewEmailVerifiedCommand(userID string, isVerified bool) *EmailVerifiedCommand {
return &EmailVerifiedCommand{
UserID: userID,
Email: &Email{
IsVerified: isVerified,
},
}
}
var (
_ Commander = (*EmailVerifiedCommand)(nil)
_ SetEmailOpt = (*EmailVerifiedCommand)(nil)
)
// Execute implements [Commander]
func (cmd *EmailVerifiedCommand) Execute(ctx context.Context, opts *CommandOpts) error {
return userRepo(opts.DB).Human().ByID(cmd.UserID).Exec().SetEmailVerified(ctx, cmd.Email.Address)
}
// applyOnSetEmail implements [SetEmailOpt]
func (cmd *EmailVerifiedCommand) applyOnSetEmail(setEmailCmd *SetEmailCommand) {
cmd.UserID = setEmailCmd.UserID
cmd.Email.Address = setEmailCmd.Email
setEmailCmd.verification = cmd
}
type SendCodeCommand struct {
UserID string `json:"userId"`
Email string `json:"email"`
URLTemplate *string `json:"urlTemplate"`
generator *generateCodeCommand
}
var (
_ Commander = (*SendCodeCommand)(nil)
_ SetEmailOpt = (*SendCodeCommand)(nil)
)
func NewSendCodeCommand(userID string, urlTemplate *string) *SendCodeCommand {
return &SendCodeCommand{
UserID: userID,
generator: &generateCodeCommand{},
URLTemplate: urlTemplate,
}
}
// Execute implements [Commander]
func (cmd *SendCodeCommand) Execute(ctx context.Context, opts *CommandOpts) error {
if err := cmd.ensureEmail(ctx, opts); err != nil {
return err
}
if err := cmd.ensureURL(ctx, opts); err != nil {
return err
}
if err := opts.Invoker.Invoke(ctx, cmd.generator, opts); err != nil {
return err
}
// TODO: queue notification
return nil
}
func (cmd *SendCodeCommand) ensureEmail(ctx context.Context, opts *CommandOpts) error {
if cmd.Email != "" {
return nil
}
email, err := userRepo(opts.DB).Human().ByID(cmd.UserID).Exec().GetEmail(ctx)
if err != nil || email.IsVerified {
return err
}
cmd.Email = email.Address
return nil
}
func (cmd *SendCodeCommand) ensureURL(ctx context.Context, opts *CommandOpts) error {
if cmd.URLTemplate != nil && *cmd.URLTemplate != "" {
return nil
}
_, _ = ctx, opts
// TODO: load default template
return nil
}
// applyOnSetEmail implements [SetEmailOpt]
func (cmd *SendCodeCommand) applyOnSetEmail(setEmailCmd *SetEmailCommand) {
cmd.UserID = setEmailCmd.UserID
cmd.Email = setEmailCmd.Email
setEmailCmd.verification = cmd
}
type ReturnCodeCommand struct {
UserID string `json:"userId"`
Email string `json:"email"`
Code string `json:"code"`
generator *generateCodeCommand
}
var (
_ Commander = (*ReturnCodeCommand)(nil)
_ SetEmailOpt = (*ReturnCodeCommand)(nil)
)
func NewReturnCodeCommand(userID string) *ReturnCodeCommand {
return &ReturnCodeCommand{
UserID: userID,
generator: &generateCodeCommand{},
}
}
// Execute implements [Commander]
func (cmd *ReturnCodeCommand) Execute(ctx context.Context, opts *CommandOpts) error {
if err := cmd.ensureEmail(ctx, opts); err != nil {
return err
}
if err := opts.Invoker.Invoke(ctx, cmd.generator, opts); err != nil {
return err
}
cmd.Code = cmd.generator.code
return nil
}
func (cmd *ReturnCodeCommand) ensureEmail(ctx context.Context, opts *CommandOpts) error {
if cmd.Email != "" {
return nil
}
user := v4.UserRepository(opts.DB)
user.WithCondition(user.IDCondition(cmd.UserID))
email, err := user.he.GetEmail(ctx)
if err != nil || email.IsVerified {
return err
}
cmd.Email = email.Address
return nil
}
// applyOnSetEmail implements [SetEmailOpt]
func (cmd *ReturnCodeCommand) applyOnSetEmail(setEmailCmd *SetEmailCommand) {
cmd.UserID = setEmailCmd.UserID
cmd.Email = setEmailCmd.Email
setEmailCmd.verification = cmd
}

View File

@@ -0,0 +1,7 @@
package domain
import "errors"
var (
ErrNoAdminSpecified = errors.New("at least one admin must be specified")
)

View File

@@ -0,0 +1,36 @@
package domain
import (
"context"
"time"
)
type Instance struct {
ID string `json:"id"`
Name string `json:"name"`
CreatedAt time.Time `json:"-"`
UpdatedAt time.Time `json:"-"`
DeletedAt time.Time `json:"-"`
}
// Keys implements the [cache.Entry].
func (i *Instance) Keys(index string) (key []string) {
// TODO: Return the correct keys for the instance cache, e.g., i.ID, i.Domain
return []string{}
}
type InstanceRepository interface {
ByID(ctx context.Context, id string) (*Instance, error)
Create(ctx context.Context, instance *Instance) error
On(id string) InstanceOperation
}
type InstanceOperation interface {
AdminRepository
Update(ctx context.Context, instance *Instance) error
Delete(ctx context.Context) error
}
type CreateInstance struct {
Name string `json:"name"`
}

View File

@@ -0,0 +1,94 @@
package domain
import (
"context"
"fmt"
"github.com/zitadel/zitadel/backend/v3/storage/eventstore"
)
var defaultInvoker = newEventStoreInvoker(newTraceInvoker(nil))
func Invoke(ctx context.Context, cmd Commander) error {
invoker := newEventStoreInvoker(newTraceInvoker(nil))
opts := &CommandOpts{
Invoker: invoker.collector,
}
return invoker.Invoke(ctx, cmd, opts)
}
type eventStoreInvoker struct {
collector *eventCollector
}
func newEventStoreInvoker(next Invoker) *eventStoreInvoker {
return &eventStoreInvoker{collector: &eventCollector{next: next}}
}
func (i *eventStoreInvoker) Invoke(ctx context.Context, command Commander, opts *CommandOpts) (err error) {
err = i.collector.Invoke(ctx, command, opts)
if err != nil {
return err
}
if len(i.collector.events) > 0 {
err = eventstore.Publish(ctx, i.collector.events, opts.DB)
if err != nil {
return err
}
}
return nil
}
type eventCollector struct {
next Invoker
events []*eventstore.Event
}
type eventer interface {
Events() []*eventstore.Event
}
func (i *eventCollector) Invoke(ctx context.Context, command Commander, opts *CommandOpts) (err error) {
if e, ok := command.(eventer); ok && len(e.Events()) > 0 {
// we need to ensure all commands are executed in the same transaction
close, err := opts.EnsureTx(ctx)
if err != nil {
return err
}
defer func() { err = close(ctx, err) }()
i.events = append(i.events, e.Events()...)
}
if i.next != nil {
err = i.next.Invoke(ctx, command, opts)
} else {
err = command.Execute(ctx, opts)
}
if err != nil {
return err
}
return nil
}
type traceInvoker struct {
next Invoker
}
func newTraceInvoker(next Invoker) *traceInvoker {
return &traceInvoker{next: next}
}
func (i *traceInvoker) Invoke(ctx context.Context, command Commander, opts *CommandOpts) (err error) {
ctx, span := tracer.Start(ctx, fmt.Sprintf("%T", command))
defer span.End()
if i.next != nil {
err = i.next.Invoke(ctx, command, opts)
} else {
err = command.Execute(ctx, opts)
}
if err != nil {
span.RecordError(err)
}
return err
}

39
backend/v3/domain/org.go Normal file
View File

@@ -0,0 +1,39 @@
package domain
import (
"context"
"time"
)
type Org struct {
ID string `json:"id"`
Name string `json:"name"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
type OrgRepository interface {
ByID(ctx context.Context, orgID string) (*Org, error)
Create(ctx context.Context, org *Org) error
On(id string) OrgOperation
}
type OrgOperation interface {
AdminRepository
DomainRepository
Update(ctx context.Context, org *Org) error
Delete(ctx context.Context) error
}
type AdminRepository interface {
AddAdmin(ctx context.Context, userID string, roles []string) error
SetAdminRoles(ctx context.Context, userID string, roles []string) error
RemoveAdmin(ctx context.Context, userID string) error
}
type DomainRepository interface {
AddDomain(ctx context.Context, domain string) error
SetDomainVerified(ctx context.Context, domain string) error
RemoveDomain(ctx context.Context, domain string) error
}

View File

@@ -0,0 +1,74 @@
package domain
import (
"context"
)
type AddOrgCommand struct {
ID string `json:"id"`
Name string `json:"name"`
Admins []AddAdminCommand `json:"admins"`
}
func NewAddOrgCommand(name string, admins ...AddAdminCommand) *AddOrgCommand {
return &AddOrgCommand{
Name: name,
Admins: admins,
}
}
// Execute implements Commander.
func (cmd *AddOrgCommand) Execute(ctx context.Context, opts *CommandOpts) (err error) {
if len(cmd.Admins) == 0 {
return ErrNoAdminSpecified
}
if err = cmd.ensureID(); err != nil {
return err
}
close, err := opts.EnsureTx(ctx)
if err != nil {
return err
}
defer func() { err = close(ctx, err) }()
err = orgRepo(opts.DB).Create(ctx, &Org{
ID: cmd.ID,
Name: cmd.Name,
})
if err != nil {
return err
}
return nil
}
var (
_ Commander = (*AddOrgCommand)(nil)
)
func (cmd *AddOrgCommand) ensureID() (err error) {
if cmd.ID != "" {
return nil
}
cmd.ID, err = generateID()
return err
}
type AddAdminCommand struct {
UserID string `json:"userId"`
Roles []string `json:"roles"`
}
// Execute implements Commander.
func (a *AddAdminCommand) Execute(ctx context.Context, opts *CommandOpts) (err error) {
close, err := opts.EnsureTx(ctx)
if err != nil {
return err
}
defer func() { err = close(ctx, err) }()
return nil
}
var (
_ Commander = (*AddAdminCommand)(nil)
)

View File

@@ -0,0 +1,82 @@
package domain
import (
"time"
"golang.org/x/exp/constraints"
)
type Operation interface {
// TextOperation |
// NumberOperation |
// BoolOperation
op()
}
type clause[F ~uint8, Op Operation] struct {
field F
op Op
}
func (c *clause[F, Op]) Field() F {
return c.field
}
func (c *clause[F, Op]) Operation() Op {
return c.op
}
type Text interface {
~string | ~[]byte
}
type TextOperation uint8
const (
TextOperationEqual TextOperation = iota
TextOperationNotEqual
TextOperationStartsWith
TextOperationStartsWithIgnoreCase
)
func (TextOperation) op() {}
type Number interface {
constraints.Integer | constraints.Float | constraints.Complex | time.Time
}
type NumberOperation uint8
const (
NumberOperationEqual NumberOperation = iota
NumberOperationNotEqual
NumberOperationLessThan
NumberOperationLessThanOrEqual
NumberOperationGreaterThan
NumberOperationGreaterThanOrEqual
)
func (NumberOperation) op() {}
type Bool interface {
~bool
}
type BoolOperation uint8
const (
BoolOperationIs BoolOperation = iota
BoolOperationNot
)
func (BoolOperation) op() {}
type ListOperation uint8
const (
ListOperationContains ListOperation = iota
ListOperationNotContains
)
func (ListOperation) op() {}

View File

@@ -0,0 +1,64 @@
package domain
import (
"context"
"github.com/zitadel/zitadel/backend/v3/storage/eventstore"
)
type SetEmailCommand struct {
UserID string `json:"userId"`
Email string `json:"email"`
verification Commander
}
var (
_ Commander = (*SetEmailCommand)(nil)
_ eventer = (*SetEmailCommand)(nil)
_ CreateHumanOpt = (*SetEmailCommand)(nil)
)
type SetEmailOpt interface {
applyOnSetEmail(*SetEmailCommand)
}
func NewSetEmailCommand(userID, email string, verificationType SetEmailOpt) *SetEmailCommand {
cmd := &SetEmailCommand{
UserID: userID,
Email: email,
}
verificationType.applyOnSetEmail(cmd)
return cmd
}
func (cmd *SetEmailCommand) Execute(ctx context.Context, opts *CommandOpts) error {
close, err := opts.EnsureTx(ctx)
if err != nil {
return err
}
defer func() { err = close(ctx, err) }()
// userStatement(opts.DB).Human().ByID(cmd.UserID).SetEmail(ctx, cmd.Email)
err = userRepo(opts.DB).Human().ByID(cmd.UserID).Exec().SetEmail(ctx, cmd.Email)
if err != nil {
return err
}
return opts.Invoke(ctx, cmd.verification)
}
// Events implements [eventer].
func (cmd *SetEmailCommand) Events() []*eventstore.Event {
return []*eventstore.Event{
{
AggregateType: "user",
AggregateID: cmd.UserID,
Type: "user.email.set",
Payload: cmd,
},
}
}
// applyOnCreateHuman implements [CreateHumanOpt].
func (cmd *SetEmailCommand) applyOnCreateHuman(createUserCmd *CreateUserCommand[Human]) {
createUserCmd.email = cmd
}

193
backend/v3/domain/user.go Normal file
View File

@@ -0,0 +1,193 @@
package domain
import (
"context"
"time"
v4 "github.com/zitadel/zitadel/backend/v3/storage/database/repository/stmt/v4"
)
type userColumns interface {
// TODO: move v4.columns to domain
InstanceIDColumn() column
OrgIDColumn() column
IDColumn() column
usernameColumn() column
CreatedAtColumn() column
UpdatedAtColumn() column
DeletedAtColumn() column
}
type userConditions interface {
InstanceIDCondition(instanceID string) v4.Condition
OrgIDCondition(orgID string) v4.Condition
IDCondition(userID string) v4.Condition
UsernameCondition(op v4.TextOperator, username string) v4.Condition
CreatedAtCondition(op v4.NumberOperator, createdAt time.Time) v4.Condition
UpdatedAtCondition(op v4.NumberOperator, updatedAt time.Time) v4.Condition
DeletedCondition(isDeleted bool) v4.Condition
DeletedAtCondition(op v4.NumberOperator, deletedAt time.Time) v4.Condition
}
type UserRepository interface {
userColumns
userConditions
// TODO: move condition to domain
WithCondition(condition v4.Condition) UserRepository
Get(ctx context.Context) (*User, error)
List(ctx context.Context) ([]*User, error)
Create(ctx context.Context, user *User) error
Delete(ctx context.Context) error
Human() HumanRepository
Machine() MachineRepository
}
type humanColumns interface {
FirstNameColumn() column
LastNameColumn() column
EmailAddressColumn() column
EmailVerifiedAtColumn() column
PhoneNumberColumn() column
PhoneVerifiedAtColumn() column
}
type humanConditions interface {
FirstNameCondition(op v4.TextOperator, firstName string) v4.Condition
LastNameCondition(op v4.TextOperator, lastName string) v4.Condition
EmailAddressCondition(op v4.TextOperator, email string) v4.Condition
EmailAddressVerifiedCondition(isVerified bool) v4.Condition
EmailVerifiedAtCondition(op v4.TextOperator, emailVerifiedAt string) v4.Condition
PhoneNumberCondition(op v4.TextOperator, phoneNumber string) v4.Condition
PhoneNumberVerifiedCondition(isVerified bool) v4.Condition
PhoneVerifiedAtCondition(op v4.TextOperator, phoneVerifiedAt string) v4.Condition
}
type HumanRepository interface {
humanColumns
humanConditions
GetEmail(ctx context.Context) (*Email, error)
// TODO: replace any with add email update columns
SetEmail(ctx context.Context, columns ...any) error
}
type machineColumns interface {
DescriptionColumn() column
}
type machineConditions interface {
DescriptionCondition(op v4.TextOperator, description string) v4.Condition
}
type MachineRepository interface {
machineColumns
machineConditions
}
// type UserRepository interface {
// // Get(ctx context.Context, clauses ...UserClause) (*User, error)
// // Search(ctx context.Context, clauses ...UserClause) ([]*User, error)
// UserQuery[UserOperation]
// Human() HumanQuery
// Machine() MachineQuery
// }
// type UserQuery[Op UserOperation] interface {
// ByID(id string) UserQuery[Op]
// Username(username string) UserQuery[Op]
// Exec() Op
// }
// type HumanQuery interface {
// UserQuery[HumanOperation]
// Email(op TextOperation, email string) HumanQuery
// HumanOperation
// }
// type MachineQuery interface {
// UserQuery[MachineOperation]
// MachineOperation
// }
// type UserClause interface {
// Field() UserField
// Operation() Operation
// Args() []any
// }
// type UserField uint8
// const (
// // Fields used for all users
// UserFieldInstanceID UserField = iota + 1
// UserFieldOrgID
// UserFieldID
// UserFieldUsername
// // Fields used for human users
// UserHumanFieldEmail
// UserHumanFieldEmailVerified
// // Fields used for machine users
// UserMachineFieldDescription
// )
// type userByIDClause struct {
// id string
// }
// func (c *userByIDClause) Field() UserField {
// return UserFieldID
// }
// func (c *userByIDClause) Operation() Operation {
// return TextOperationEqual
// }
// func (c *userByIDClause) Args() []any {
// return []any{c.id}
// }
// type UserOperation interface {
// Delete(ctx context.Context) error
// SetUsername(ctx context.Context, username string) error
// }
// type HumanOperation interface {
// UserOperation
// SetEmail(ctx context.Context, email string) error
// SetEmailVerified(ctx context.Context, email string) error
// GetEmail(ctx context.Context) (*Email, error)
// }
// type MachineOperation interface {
// UserOperation
// SetDescription(ctx context.Context, description string) error
// }
type User struct {
v4.User
}
// type userTraits interface {
// isUserTraits()
// }
// type Human struct {
// Email *Email `json:"email"`
// }
// func (*Human) isUserTraits() {}
// type Machine struct {
// Description string `json:"description"`
// }
// func (*Machine) isUserTraits() {}
// type Email struct {
// Address string `json:"address"`
// IsVerified bool `json:"isVerified"`
// }