mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 23:17:33 +00:00
move files
This commit is contained in:
@@ -3,7 +3,6 @@ package domain
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
v4 "github.com/zitadel/zitadel/backend/v3/storage/database/repository/stmt/v4"
|
|
||||||
"github.com/zitadel/zitadel/backend/v3/storage/eventstore"
|
"github.com/zitadel/zitadel/backend/v3/storage/eventstore"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -20,10 +19,8 @@ var (
|
|||||||
func NewCreateHumanCommand(username string, opts ...CreateHumanOpt) *CreateUserCommand {
|
func NewCreateHumanCommand(username string, opts ...CreateHumanOpt) *CreateUserCommand {
|
||||||
cmd := &CreateUserCommand{
|
cmd := &CreateUserCommand{
|
||||||
user: &User{
|
user: &User{
|
||||||
User: v4.User{
|
|
||||||
Username: username,
|
Username: username,
|
||||||
Traits: &v4.Human{},
|
Traits: &Human{},
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,45 +1,45 @@
|
|||||||
package domain_test
|
package domain_test
|
||||||
|
|
||||||
import (
|
// import (
|
||||||
"context"
|
// "context"
|
||||||
"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"
|
||||||
|
|
||||||
. "github.com/zitadel/zitadel/backend/v3/domain"
|
// . "github.com/zitadel/zitadel/backend/v3/domain"
|
||||||
"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/tracing"
|
// "github.com/zitadel/zitadel/backend/v3/telemetry/tracing"
|
||||||
)
|
// )
|
||||||
|
|
||||||
func TestExample(t *testing.T) {
|
// func TestExample(t *testing.T) {
|
||||||
ctx := context.Background()
|
// ctx := context.Background()
|
||||||
|
|
||||||
// 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)) }()
|
||||||
|
|
||||||
SetUserRepository(repository.User)
|
// SetUserRepository(repository.User)
|
||||||
SetInstanceRepository(repository.Instance)
|
// SetInstanceRepository(repository.Instance)
|
||||||
SetCryptoRepository(repository.Crypto)
|
// SetCryptoRepository(repository.Crypto)
|
||||||
|
|
||||||
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)
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
|
@@ -14,7 +14,7 @@ func NewEmailVerifiedCommand(userID string, isVerified bool) *EmailVerifiedComma
|
|||||||
return &EmailVerifiedCommand{
|
return &EmailVerifiedCommand{
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
Email: &Email{
|
Email: &Email{
|
||||||
IsVerified: isVerified,
|
VerifiedAt: time.Time{},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,82 +0,0 @@
|
|||||||
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() {}
|
|
@@ -4,82 +4,129 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
v4 "github.com/zitadel/zitadel/backend/v3/storage/database/repository/stmt/v4"
|
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||||
)
|
)
|
||||||
|
|
||||||
type userColumns interface {
|
type userColumns interface {
|
||||||
// TODO: move v4.columns to domain
|
// InstanceIDColumn returns the column for the instance id field.
|
||||||
InstanceIDColumn() v4.Column
|
InstanceIDColumn() database.Column
|
||||||
OrgIDColumn() v4.Column
|
// OrgIDColumn returns the column for the org id field.
|
||||||
IDColumn() v4.Column
|
OrgIDColumn() database.Column
|
||||||
usernameColumn() v4.Column
|
// IDColumn returns the column for the id field.
|
||||||
CreatedAtColumn() v4.Column
|
IDColumn() database.Column
|
||||||
UpdatedAtColumn() v4.Column
|
// UsernameColumn returns the column for the username field.
|
||||||
DeletedAtColumn() v4.Column
|
UsernameColumn() database.Column
|
||||||
|
// CreatedAtColumn returns the column for the created at field.
|
||||||
|
CreatedAtColumn() database.Column
|
||||||
|
// UpdatedAtColumn returns the column for the updated at field.
|
||||||
|
UpdatedAtColumn() database.Column
|
||||||
|
// DeletedAtColumn returns the column for the deleted at field.
|
||||||
|
DeletedAtColumn() database.Column
|
||||||
}
|
}
|
||||||
|
|
||||||
type userConditions interface {
|
type userConditions interface {
|
||||||
InstanceIDCondition(instanceID string) v4.Condition
|
// InstanceIDCondition returns an equal filter on the instance id field.
|
||||||
OrgIDCondition(orgID string) v4.Condition
|
InstanceIDCondition(instanceID string) database.Condition
|
||||||
IDCondition(userID string) v4.Condition
|
// OrgIDCondition returns an equal filter on the org id field.
|
||||||
UsernameCondition(op v4.TextOperator, username string) v4.Condition
|
OrgIDCondition(orgID string) database.Condition
|
||||||
CreatedAtCondition(op v4.NumberOperator, createdAt time.Time) v4.Condition
|
// IDCondition returns an equal filter on the id field.
|
||||||
UpdatedAtCondition(op v4.NumberOperator, updatedAt time.Time) v4.Condition
|
IDCondition(userID string) database.Condition
|
||||||
DeletedCondition(isDeleted bool) v4.Condition
|
// UsernameCondition returns a filter on the username field.
|
||||||
DeletedAtCondition(op v4.NumberOperator, deletedAt time.Time) v4.Condition
|
UsernameCondition(op database.TextOperation, username string) database.Condition
|
||||||
|
// CreatedAtCondition returns a filter on the created at field.
|
||||||
|
CreatedAtCondition(op database.NumberOperation, createdAt time.Time) database.Condition
|
||||||
|
// UpdatedAtCondition returns a filter on the updated at field.
|
||||||
|
UpdatedAtCondition(op database.NumberOperation, updatedAt time.Time) database.Condition
|
||||||
|
// DeletedAtCondition filters for deleted users is isDeleted is set to true otherwise only not deleted users must be filtered.
|
||||||
|
DeletedCondition(isDeleted bool) database.Condition
|
||||||
|
// DeletedAtCondition filters for deleted users based on the given parameters.
|
||||||
|
DeletedAtCondition(op database.NumberOperation, deletedAt time.Time) database.Condition
|
||||||
}
|
}
|
||||||
|
|
||||||
type userChanges interface {
|
type userChanges interface {
|
||||||
SetUsername(username string) v4.Change
|
// SetUsername sets the username column.
|
||||||
|
SetUsername(username string) database.Change
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserRepository interface {
|
type UserRepository interface {
|
||||||
userColumns
|
userColumns
|
||||||
userConditions
|
userConditions
|
||||||
userChanges
|
userChanges
|
||||||
// TODO: move condition to domain
|
// Get returns a user based on the given condition.
|
||||||
Get(ctx context.Context, opts v4.QueryOption) (*User, error)
|
Get(ctx context.Context, opts ...database.QueryOption) (*User, error)
|
||||||
List(ctx context.Context, opts v4.QueryOption) ([]*User, error)
|
// List returns a list of users based on the given condition.
|
||||||
Delete(ctx context.Context, condition v4.Condition) error
|
List(ctx context.Context, opts ...database.QueryOption) ([]*User, error)
|
||||||
|
// Create creates a new user.
|
||||||
|
Create(ctx context.Context, user *User) error
|
||||||
|
// Delete removes users based on the given condition.
|
||||||
|
Delete(ctx context.Context, condition database.Condition) error
|
||||||
|
// Human returns the [HumanRepository].
|
||||||
Human() HumanRepository
|
Human() HumanRepository
|
||||||
|
// Machine returns the [MachineRepository].
|
||||||
Machine() MachineRepository
|
Machine() MachineRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
type humanColumns interface {
|
type humanColumns interface {
|
||||||
userColumns
|
userColumns
|
||||||
FirstNameColumn() v4.Column
|
// FirstNameColumn returns the column for the first name field.
|
||||||
LastNameColumn() v4.Column
|
FirstNameColumn() database.Column
|
||||||
EmailAddressColumn() v4.Column
|
// LastNameColumn returns the column for the last name field.
|
||||||
EmailVerifiedAtColumn() v4.Column
|
LastNameColumn() database.Column
|
||||||
PhoneNumberColumn() v4.Column
|
// EmailAddressColumn returns the column for the email address field.
|
||||||
PhoneVerifiedAtColumn() v4.Column
|
EmailAddressColumn() database.Column
|
||||||
|
// EmailVerifiedAtColumn returns the column for the email verified at field.
|
||||||
|
EmailVerifiedAtColumn() database.Column
|
||||||
|
// PhoneNumberColumn returns the column for the phone number field.
|
||||||
|
PhoneNumberColumn() database.Column
|
||||||
|
// PhoneVerifiedAtColumn returns the column for the phone verified at field.
|
||||||
|
PhoneVerifiedAtColumn() database.Column
|
||||||
}
|
}
|
||||||
|
|
||||||
type humanConditions interface {
|
type humanConditions interface {
|
||||||
userConditions
|
userConditions
|
||||||
FirstNameCondition(op v4.TextOperator, firstName string) v4.Condition
|
// FirstNameCondition returns a filter on the first name field.
|
||||||
LastNameCondition(op v4.TextOperator, lastName string) v4.Condition
|
FirstNameCondition(op database.TextOperation, firstName string) database.Condition
|
||||||
EmailAddressCondition(op v4.TextOperator, email string) v4.Condition
|
// LastNameCondition returns a filter on the last name field.
|
||||||
EmailAddressVerifiedCondition(isVerified bool) v4.Condition
|
LastNameCondition(op database.TextOperation, lastName string) database.Condition
|
||||||
EmailVerifiedAtCondition(op v4.TextOperator, emailVerifiedAt string) v4.Condition
|
// EmailAddressCondition returns a filter on the email address field.
|
||||||
PhoneNumberCondition(op v4.TextOperator, phoneNumber string) v4.Condition
|
EmailAddressCondition(op database.TextOperation, email string) database.Condition
|
||||||
PhoneNumberVerifiedCondition(isVerified bool) v4.Condition
|
// EmailVerifiedCondition returns a filter that checks if the email is verified or not.
|
||||||
PhoneVerifiedAtCondition(op v4.TextOperator, phoneVerifiedAt string) v4.Condition
|
EmailVerifiedCondition(isVerified bool) database.Condition
|
||||||
|
// EmailVerifiedAtCondition returns a filter on the email verified at field.
|
||||||
|
EmailVerifiedAtCondition(op database.NumberOperation, emailVerifiedAt time.Time) database.Condition
|
||||||
|
|
||||||
|
// PhoneNumberCondition returns a filter on the phone number field.
|
||||||
|
PhoneNumberCondition(op database.TextOperation, phoneNumber string) database.Condition
|
||||||
|
// PhoneVerifiedCondition returns a filter that checks if the phone is verified or not.
|
||||||
|
PhoneVerifiedCondition(isVerified bool) database.Condition
|
||||||
|
// PhoneVerifiedAtCondition returns a filter on the phone verified at field.
|
||||||
|
PhoneVerifiedAtCondition(op database.NumberOperation, phoneVerifiedAt time.Time) database.Condition
|
||||||
}
|
}
|
||||||
|
|
||||||
type humanChanges interface {
|
type humanChanges interface {
|
||||||
userChanges
|
userChanges
|
||||||
SetFirstName(firstName string) v4.Change
|
// SetFirstName sets the first name field of the human.
|
||||||
SetLastName(lastName string) v4.Change
|
SetFirstName(firstName string) database.Change
|
||||||
|
// SetLastName sets the last name field of the human.
|
||||||
|
SetLastName(lastName string) database.Change
|
||||||
|
|
||||||
SetEmail(address string, verified *time.Time) v4.Change
|
// SetEmail sets the email address and verified field of the email
|
||||||
SetEmailAddress(email string) v4.Change
|
// if verifiedAt is nil the email is not verified
|
||||||
SetEmailVerifiedAt(emailVerifiedAt time.Time) v4.Change
|
SetEmail(address string, verifiedAt *time.Time) database.Change
|
||||||
|
// SetEmailAddress sets the email address field of the email
|
||||||
|
SetEmailAddress(email string) database.Change
|
||||||
|
// SetEmailVerifiedAt sets the verified column of the email
|
||||||
|
// if at is zero the statement uses the database timestamp
|
||||||
|
SetEmailVerifiedAt(at time.Time) database.Change
|
||||||
|
|
||||||
SetPhone(number string, verifiedAt *time.Time) v4.Change
|
// SetPhone sets the phone number and verified field
|
||||||
SetPhoneNumber(phoneNumber string) v4.Change
|
// if verifiedAt is nil the phone is not verified
|
||||||
SetPhoneVerifiedAt(phoneVerifiedAt time.Time) v4.Change
|
SetPhone(number string, verifiedAt *time.Time) database.Change
|
||||||
|
// SetPhoneNumber sets the phone number field
|
||||||
|
SetPhoneNumber(phoneNumber string) database.Change
|
||||||
|
// SetPhoneVerifiedAt sets the verified field of the phone
|
||||||
|
// if at is zero the statement uses the database timestamp
|
||||||
|
SetPhoneVerifiedAt(at time.Time) database.Change
|
||||||
}
|
}
|
||||||
|
|
||||||
type HumanRepository interface {
|
type HumanRepository interface {
|
||||||
@@ -87,144 +134,95 @@ type HumanRepository interface {
|
|||||||
humanConditions
|
humanConditions
|
||||||
humanChanges
|
humanChanges
|
||||||
|
|
||||||
GetEmail(ctx context.Context, condition v4.Condition) (*Email, error)
|
// Get returns an email based on the given condition.
|
||||||
// TODO: replace any with add email update columns
|
GetEmail(ctx context.Context, condition database.Condition) (*Email, error)
|
||||||
Create(ctx context.Context, user *User) error
|
// Update updates human users based on the given condition and changes.
|
||||||
Update(ctx context.Context, condition v4.Condition, changes ...v4.Change) error
|
Update(ctx context.Context, condition database.Condition, changes ...database.Change) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type machineColumns interface {
|
type machineColumns interface {
|
||||||
userColumns
|
userColumns
|
||||||
DescriptionColumn() v4.Column
|
// DescriptionColumn returns the column for the description field.
|
||||||
|
DescriptionColumn() database.Column
|
||||||
}
|
}
|
||||||
|
|
||||||
type machineConditions interface {
|
type machineConditions interface {
|
||||||
userConditions
|
userConditions
|
||||||
DescriptionCondition(op v4.TextOperator, description string) v4.Condition
|
// DescriptionCondition returns a filter on the description field.
|
||||||
|
DescriptionCondition(op database.TextOperation, description string) database.Condition
|
||||||
}
|
}
|
||||||
|
|
||||||
type machineChanges interface {
|
type machineChanges interface {
|
||||||
userChanges
|
userChanges
|
||||||
SetDescription(description string) v4.Change
|
// SetDescription sets the description field of the machine.
|
||||||
|
SetDescription(description string) database.Change
|
||||||
}
|
}
|
||||||
|
|
||||||
type MachineRepository interface {
|
type MachineRepository interface {
|
||||||
|
// Update updates machine users based on the given condition and changes.
|
||||||
|
Update(ctx context.Context, condition database.Condition, changes ...database.Change) error
|
||||||
|
|
||||||
machineColumns
|
machineColumns
|
||||||
machineConditions
|
machineConditions
|
||||||
machineChanges
|
machineChanges
|
||||||
|
|
||||||
Create(ctx context.Context, user *User) error
|
|
||||||
Update(ctx context.Context, condition v4.Condition, changes ...v4.Change) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// type UserRepository interface {
|
type UserTraits interface {
|
||||||
// // Get(ctx context.Context, clauses ...UserClause) (*User, error)
|
Type() UserType
|
||||||
// // Search(ctx context.Context, clauses ...UserClause) ([]*User, error)
|
}
|
||||||
|
|
||||||
// UserQuery[UserOperation]
|
type UserType string
|
||||||
// Human() HumanQuery
|
|
||||||
// Machine() MachineQuery
|
|
||||||
// }
|
|
||||||
|
|
||||||
// type UserQuery[Op UserOperation] interface {
|
const (
|
||||||
// ByID(id string) UserQuery[Op]
|
UserTypeHuman UserType = "human"
|
||||||
// Username(username string) UserQuery[Op]
|
UserTypeMachine UserType = "machine"
|
||||||
// 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 {
|
type User struct {
|
||||||
v4.User
|
InstanceID string
|
||||||
|
OrgID string
|
||||||
|
ID string
|
||||||
|
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
|
DeletedAt time.Time
|
||||||
|
|
||||||
|
Username string
|
||||||
|
|
||||||
|
Traits UserTraits
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Human struct {
|
||||||
|
FirstName string `json:"firstName"`
|
||||||
|
LastName string `json:"lastName"`
|
||||||
|
Email *Email `json:"email,omitempty"`
|
||||||
|
Phone *Phone `json:"phone,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type implements [UserTraits].
|
||||||
|
func (h *Human) Type() UserType {
|
||||||
|
return UserTypeHuman
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ UserTraits = (*Human)(nil)
|
||||||
|
|
||||||
type Email struct {
|
type Email struct {
|
||||||
v4.Email
|
Address string `json:"address"`
|
||||||
IsVerified bool
|
VerifiedAt time.Time `json:"verifiedAt"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// type userTraits interface {
|
type Phone struct {
|
||||||
// isUserTraits()
|
Number string `json:"number"`
|
||||||
// }
|
VerifiedAt time.Time `json:"verifiedAt"`
|
||||||
|
}
|
||||||
|
|
||||||
// type Human struct {
|
type Machine struct {
|
||||||
// Email *Email `json:"email"`
|
Description string `json:"description"`
|
||||||
// }
|
}
|
||||||
|
|
||||||
// func (*Human) isUserTraits() {}
|
// Type implements [UserTraits].
|
||||||
|
func (m *Machine) Type() UserType {
|
||||||
|
return UserTypeMachine
|
||||||
|
}
|
||||||
|
|
||||||
// type Machine struct {
|
var _ UserTraits = (*Machine)(nil)
|
||||||
// Description string `json:"description"`
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (*Machine) isUserTraits() {}
|
|
||||||
|
|
||||||
// type Email struct {
|
|
||||||
// Address string `json:"address"`
|
|
||||||
// IsVerified bool `json:"isVerified"`
|
|
||||||
// }
|
|
||||||
|
51
backend/v3/storage/database/change.go
Normal file
51
backend/v3/storage/database/change.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package database
|
||||||
|
|
||||||
|
type Change interface {
|
||||||
|
Write(builder *StatementBuilder)
|
||||||
|
}
|
||||||
|
|
||||||
|
type change[V Value] struct {
|
||||||
|
column Column
|
||||||
|
value V
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Change = (*change[string])(nil)
|
||||||
|
|
||||||
|
func NewChange[V Value](col Column, value V) Change {
|
||||||
|
return &change[V]{
|
||||||
|
column: col,
|
||||||
|
value: value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewChangePtr[V Value](col Column, value *V) Change {
|
||||||
|
if value == nil {
|
||||||
|
return NewChange(col, NullInstruction)
|
||||||
|
}
|
||||||
|
return NewChange(col, *value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write implements [Change].
|
||||||
|
func (c change[V]) Write(builder *StatementBuilder) {
|
||||||
|
c.column.Write(builder)
|
||||||
|
builder.WriteString(" = ")
|
||||||
|
builder.WriteArg(c.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Changes []Change
|
||||||
|
|
||||||
|
func NewChanges(cols ...Change) Change {
|
||||||
|
return Changes(cols)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write implements [Change].
|
||||||
|
func (m Changes) Write(builder *StatementBuilder) {
|
||||||
|
for i, col := range m {
|
||||||
|
if i > 0 {
|
||||||
|
builder.WriteString(", ")
|
||||||
|
}
|
||||||
|
col.Write(builder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Change = Changes(nil)
|
55
backend/v3/storage/database/column.go
Normal file
55
backend/v3/storage/database/column.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package database
|
||||||
|
|
||||||
|
type Columns []Column
|
||||||
|
|
||||||
|
// Write implements [Column].
|
||||||
|
func (m Columns) Write(builder *StatementBuilder) {
|
||||||
|
for i, col := range m {
|
||||||
|
if i > 0 {
|
||||||
|
builder.WriteString(", ")
|
||||||
|
}
|
||||||
|
col.Write(builder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Column interface {
|
||||||
|
Write(builder *StatementBuilder)
|
||||||
|
}
|
||||||
|
|
||||||
|
type column struct {
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewColumn(name string) Column {
|
||||||
|
return column{name: name}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write implements [Column].
|
||||||
|
func (c column) Write(builder *StatementBuilder) {
|
||||||
|
builder.WriteString(c.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Column = (*column)(nil)
|
||||||
|
|
||||||
|
type ignoreCaseColumn interface {
|
||||||
|
Column
|
||||||
|
WriteIgnoreCase(builder *StatementBuilder)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIgnoreCaseColumn(name, suffix string) ignoreCaseColumn {
|
||||||
|
return ignoreCaseCol{
|
||||||
|
column: column{name: name},
|
||||||
|
suffix: suffix,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ignoreCaseCol struct {
|
||||||
|
column
|
||||||
|
suffix string
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteIgnoreCase implements [ignoreCaseColumn].
|
||||||
|
func (c ignoreCaseCol) WriteIgnoreCase(builder *StatementBuilder) {
|
||||||
|
c.column.Write(builder)
|
||||||
|
builder.WriteString(c.suffix)
|
||||||
|
}
|
@@ -1,15 +1,15 @@
|
|||||||
package v4
|
package database
|
||||||
|
|
||||||
type Condition interface {
|
type Condition interface {
|
||||||
writeTo(builder *statementBuilder)
|
Write(builder *StatementBuilder)
|
||||||
}
|
}
|
||||||
|
|
||||||
type and struct {
|
type and struct {
|
||||||
conditions []Condition
|
conditions []Condition
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeTo implements [Condition].
|
// Write implements [Condition].
|
||||||
func (a *and) writeTo(builder *statementBuilder) {
|
func (a *and) Write(builder *StatementBuilder) {
|
||||||
if len(a.conditions) > 1 {
|
if len(a.conditions) > 1 {
|
||||||
builder.WriteString("(")
|
builder.WriteString("(")
|
||||||
defer builder.WriteString(")")
|
defer builder.WriteString(")")
|
||||||
@@ -18,7 +18,7 @@ func (a *and) writeTo(builder *statementBuilder) {
|
|||||||
if i > 0 {
|
if i > 0 {
|
||||||
builder.WriteString(" AND ")
|
builder.WriteString(" AND ")
|
||||||
}
|
}
|
||||||
condition.writeTo(builder)
|
condition.(Condition).Write(builder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,8 +32,8 @@ type or struct {
|
|||||||
conditions []Condition
|
conditions []Condition
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeTo implements [Condition].
|
// Write implements [Condition].
|
||||||
func (o *or) writeTo(builder *statementBuilder) {
|
func (o *or) Write(builder *StatementBuilder) {
|
||||||
if len(o.conditions) > 1 {
|
if len(o.conditions) > 1 {
|
||||||
builder.WriteString("(")
|
builder.WriteString("(")
|
||||||
defer builder.WriteString(")")
|
defer builder.WriteString(")")
|
||||||
@@ -42,7 +42,7 @@ func (o *or) writeTo(builder *statementBuilder) {
|
|||||||
if i > 0 {
|
if i > 0 {
|
||||||
builder.WriteString(" OR ")
|
builder.WriteString(" OR ")
|
||||||
}
|
}
|
||||||
condition.writeTo(builder)
|
condition.(Condition).Write(builder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,9 +56,9 @@ type isNull struct {
|
|||||||
column Column
|
column Column
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeTo implements [Condition].
|
// Write implements [Condition].
|
||||||
func (i *isNull) writeTo(builder *statementBuilder) {
|
func (i *isNull) Write(builder *StatementBuilder) {
|
||||||
i.column.writeTo(builder)
|
i.column.Write(builder)
|
||||||
builder.WriteString(" IS NULL")
|
builder.WriteString(" IS NULL")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,40 +72,40 @@ type isNotNull struct {
|
|||||||
column Column
|
column Column
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeTo implements [Condition].
|
// Write implements [Condition].
|
||||||
func (i *isNotNull) writeTo(builder *statementBuilder) {
|
func (i *isNotNull) Write(builder *StatementBuilder) {
|
||||||
i.column.writeTo(builder)
|
i.column.Write(builder)
|
||||||
builder.WriteString(" IS NOT NULL")
|
builder.WriteString(" IS NOT NULL")
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsNotNull(column Column) *isNotNull {
|
func IsNotNull(column Column) *isNotNull {
|
||||||
return &isNotNull{column: column}
|
return &isNotNull{column: column.(Column)}
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Condition = (*isNotNull)(nil)
|
var _ Condition = (*isNotNull)(nil)
|
||||||
|
|
||||||
type valueCondition func(builder *statementBuilder)
|
type valueCondition func(builder *StatementBuilder)
|
||||||
|
|
||||||
func newTextCondition[V Text](col Column, op TextOperator, value V) Condition {
|
func NewTextCondition[V Text](col Column, op TextOperation, value V) Condition {
|
||||||
return valueCondition(func(builder *statementBuilder) {
|
return valueCondition(func(builder *StatementBuilder) {
|
||||||
writeTextOperation(builder, col, op, value)
|
writeTextOperation(builder, col, op, value)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func newNumberCondition[V Number](col Column, op NumberOperator, value V) Condition {
|
func NewNumberCondition[V Number](col Column, op NumberOperation, value V) Condition {
|
||||||
return valueCondition(func(builder *statementBuilder) {
|
return valueCondition(func(builder *StatementBuilder) {
|
||||||
writeNumberOperation(builder, col, op, value)
|
writeNumberOperation(builder, col, op, value)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func newBooleanCondition[V Boolean](col Column, value V) Condition {
|
func NewBooleanCondition[V Boolean](col Column, value V) Condition {
|
||||||
return valueCondition(func(builder *statementBuilder) {
|
return valueCondition(func(builder *StatementBuilder) {
|
||||||
writeBooleanOperation(builder, col, value)
|
writeBooleanOperation(builder, col, value)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeTo implements [Condition].
|
// Write implements [Condition].
|
||||||
func (c valueCondition) writeTo(builder *statementBuilder) {
|
func (c valueCondition) Write(builder *StatementBuilder) {
|
||||||
c(builder)
|
c(builder)
|
||||||
}
|
}
|
||||||
|
|
139
backend/v3/storage/database/operators.go
Normal file
139
backend/v3/storage/database/operators.go
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/exp/constraints"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Value interface {
|
||||||
|
Boolean | Number | Text | Instruction
|
||||||
|
}
|
||||||
|
|
||||||
|
type Operation interface {
|
||||||
|
BooleanOperation | NumberOperation | TextOperation
|
||||||
|
}
|
||||||
|
|
||||||
|
type Text interface {
|
||||||
|
~string | ~[]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type TextOperation uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
// TextOperationEqual compares two strings for equality.
|
||||||
|
TextOperationEqual TextOperation = iota + 1
|
||||||
|
// TextOperationEqualIgnoreCase compares two strings for equality, ignoring case.
|
||||||
|
TextOperationEqualIgnoreCase
|
||||||
|
// TextOperationNotEqual compares two strings for inequality.
|
||||||
|
TextOperationNotEqual
|
||||||
|
// TextOperationNotEqualIgnoreCase compares two strings for inequality, ignoring case.
|
||||||
|
TextOperationNotEqualIgnoreCase
|
||||||
|
// TextOperationStartsWith checks if the first string starts with the second.
|
||||||
|
TextOperationStartsWith
|
||||||
|
// TextOperationStartsWithIgnoreCase checks if the first string starts with the second, ignoring case.
|
||||||
|
TextOperationStartsWithIgnoreCase
|
||||||
|
)
|
||||||
|
|
||||||
|
var textOperations = map[TextOperation]string{
|
||||||
|
TextOperationEqual: " = ",
|
||||||
|
TextOperationEqualIgnoreCase: " LIKE ",
|
||||||
|
TextOperationNotEqual: " <> ",
|
||||||
|
TextOperationNotEqualIgnoreCase: " NOT LIKE ",
|
||||||
|
TextOperationStartsWith: " LIKE ",
|
||||||
|
TextOperationStartsWithIgnoreCase: " LIKE ",
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeTextOperation[T Text](builder *StatementBuilder, col Column, op TextOperation, value T) {
|
||||||
|
switch op {
|
||||||
|
case TextOperationEqual, TextOperationNotEqual:
|
||||||
|
col.Write(builder)
|
||||||
|
builder.WriteString(textOperations[op])
|
||||||
|
builder.WriteString(builder.AppendArg(value))
|
||||||
|
case TextOperationEqualIgnoreCase, TextOperationNotEqualIgnoreCase:
|
||||||
|
if ignoreCaseCol, ok := col.(ignoreCaseColumn); ok {
|
||||||
|
ignoreCaseCol.WriteIgnoreCase(builder)
|
||||||
|
} else {
|
||||||
|
builder.WriteString("LOWER(")
|
||||||
|
col.Write(builder)
|
||||||
|
builder.WriteString(")")
|
||||||
|
}
|
||||||
|
builder.WriteString(textOperations[op])
|
||||||
|
builder.WriteString("LOWER(")
|
||||||
|
builder.WriteString(builder.AppendArg(value))
|
||||||
|
builder.WriteString(")")
|
||||||
|
case TextOperationStartsWith:
|
||||||
|
col.Write(builder)
|
||||||
|
builder.WriteString(textOperations[op])
|
||||||
|
builder.WriteString(builder.AppendArg(value))
|
||||||
|
builder.WriteString(" || '%'")
|
||||||
|
case TextOperationStartsWithIgnoreCase:
|
||||||
|
if ignoreCaseCol, ok := col.(ignoreCaseColumn); ok {
|
||||||
|
ignoreCaseCol.WriteIgnoreCase(builder)
|
||||||
|
} else {
|
||||||
|
builder.WriteString("LOWER(")
|
||||||
|
col.Write(builder)
|
||||||
|
builder.WriteString(")")
|
||||||
|
}
|
||||||
|
builder.WriteString(textOperations[op])
|
||||||
|
builder.WriteString("LOWER(")
|
||||||
|
builder.WriteString(builder.AppendArg(value))
|
||||||
|
builder.WriteString(")")
|
||||||
|
builder.WriteString(" || '%'")
|
||||||
|
default:
|
||||||
|
panic("unsupported text operation")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Number interface {
|
||||||
|
constraints.Integer | constraints.Float | constraints.Complex | time.Time | time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
type NumberOperation uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
// NumberOperationEqual compares two numbers for equality.
|
||||||
|
NumberOperationEqual NumberOperation = iota + 1
|
||||||
|
// NumberOperationNotEqual compares two numbers for inequality.
|
||||||
|
NumberOperationNotEqual
|
||||||
|
// NumberOperationLessThan compares two numbers to check if the first is less than the second.
|
||||||
|
NumberOperationLessThan
|
||||||
|
// NumberOperationLessThanOrEqual compares two numbers to check if the first is less than or equal to the second.
|
||||||
|
NumberOperationAtLeast
|
||||||
|
// NumberOperationGreaterThan compares two numbers to check if the first is greater than the second.
|
||||||
|
NumberOperationGreaterThan
|
||||||
|
// NumberOperationGreaterThanOrEqual compares two numbers to check if the first is greater than or equal to the second.
|
||||||
|
NumberOperationAtMost
|
||||||
|
)
|
||||||
|
|
||||||
|
var numberOperations = map[NumberOperation]string{
|
||||||
|
NumberOperationEqual: " = ",
|
||||||
|
NumberOperationNotEqual: " <> ",
|
||||||
|
NumberOperationLessThan: " < ",
|
||||||
|
NumberOperationAtLeast: " <= ",
|
||||||
|
NumberOperationGreaterThan: " > ",
|
||||||
|
NumberOperationAtMost: " >= ",
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeNumberOperation[T Number](builder *StatementBuilder, col Column, op NumberOperation, value T) {
|
||||||
|
col.Write(builder)
|
||||||
|
builder.WriteString(numberOperations[op])
|
||||||
|
builder.WriteString(builder.AppendArg(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
type Boolean interface {
|
||||||
|
~bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type BooleanOperation uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
BooleanOperationIsTrue BooleanOperation = iota + 1
|
||||||
|
BooleanOperationIsFalse
|
||||||
|
)
|
||||||
|
|
||||||
|
func writeBooleanOperation[T Boolean](builder *StatementBuilder, col Column, value T) {
|
||||||
|
col.Write(builder)
|
||||||
|
builder.WriteString(" IS ")
|
||||||
|
builder.WriteString(builder.AppendArg(value))
|
||||||
|
}
|
66
backend/v3/storage/database/query.go
Normal file
66
backend/v3/storage/database/query.go
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package database
|
||||||
|
|
||||||
|
type QueryOption func(opts *QueryOpts)
|
||||||
|
|
||||||
|
func WithCondition(condition Condition) QueryOption {
|
||||||
|
return func(opts *QueryOpts) {
|
||||||
|
opts.Condition = condition
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithOrderBy(orderBy ...Column) QueryOption {
|
||||||
|
return func(opts *QueryOpts) {
|
||||||
|
opts.OrderBy = orderBy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithLimit(limit uint32) QueryOption {
|
||||||
|
return func(opts *QueryOpts) {
|
||||||
|
opts.Limit = limit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithOffset(offset uint32) QueryOption {
|
||||||
|
return func(opts *QueryOpts) {
|
||||||
|
opts.Offset = offset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type QueryOpts struct {
|
||||||
|
Condition Condition
|
||||||
|
OrderBy Columns
|
||||||
|
Limit uint32
|
||||||
|
Offset uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opts *QueryOpts) WriteCondition(builder *StatementBuilder) {
|
||||||
|
if opts.Condition == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
builder.WriteString(" WHERE ")
|
||||||
|
opts.Condition.Write(builder)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opts *QueryOpts) WriteOrderBy(builder *StatementBuilder) {
|
||||||
|
if len(opts.OrderBy) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
builder.WriteString(" ORDER BY ")
|
||||||
|
Columns(opts.OrderBy).Write(builder)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opts *QueryOpts) WriteLimit(builder *StatementBuilder) {
|
||||||
|
if opts.Limit == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
builder.WriteString(" LIMIT ")
|
||||||
|
builder.WriteArg(opts.Limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opts *QueryOpts) WriteOffset(builder *StatementBuilder) {
|
||||||
|
if opts.Offset == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
builder.WriteString(" OFFSET ")
|
||||||
|
builder.WriteArg(opts.Offset)
|
||||||
|
}
|
@@ -1,89 +0,0 @@
|
|||||||
package v4
|
|
||||||
|
|
||||||
type Change interface {
|
|
||||||
Column
|
|
||||||
}
|
|
||||||
|
|
||||||
type change[V Value] struct {
|
|
||||||
column Column
|
|
||||||
value V
|
|
||||||
}
|
|
||||||
|
|
||||||
func newChange[V Value](col Column, value V) Change {
|
|
||||||
return &change[V]{
|
|
||||||
column: col,
|
|
||||||
value: value,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newUpdatePtrColumn[V Value](col Column, value *V) Change {
|
|
||||||
if value == nil {
|
|
||||||
return newChange(col, nullDBInstruction)
|
|
||||||
}
|
|
||||||
return newChange(col, *value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// writeTo implements [Change].
|
|
||||||
func (c change[V]) writeTo(builder *statementBuilder) {
|
|
||||||
c.column.writeTo(builder)
|
|
||||||
builder.WriteString(" = ")
|
|
||||||
builder.writeArg(c.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Changes []Change
|
|
||||||
|
|
||||||
func newChanges(cols ...Change) Change {
|
|
||||||
return Changes(cols)
|
|
||||||
}
|
|
||||||
|
|
||||||
// writeTo implements [Change].
|
|
||||||
func (m Changes) writeTo(builder *statementBuilder) {
|
|
||||||
for i, col := range m {
|
|
||||||
if i > 0 {
|
|
||||||
builder.WriteString(", ")
|
|
||||||
}
|
|
||||||
col.writeTo(builder)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ Change = Changes(nil)
|
|
||||||
|
|
||||||
var _ Change = (*change[string])(nil)
|
|
||||||
|
|
||||||
type Columns []Column
|
|
||||||
|
|
||||||
func (m Columns) writeTo(builder *statementBuilder) {
|
|
||||||
for i, col := range m {
|
|
||||||
if i > 0 {
|
|
||||||
builder.WriteString(", ")
|
|
||||||
}
|
|
||||||
col.writeTo(builder)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Column interface {
|
|
||||||
writeTo(builder *statementBuilder)
|
|
||||||
}
|
|
||||||
|
|
||||||
type column struct {
|
|
||||||
name string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c column) writeTo(builder *statementBuilder) {
|
|
||||||
builder.WriteString(c.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ignoreCaseColumn interface {
|
|
||||||
Column
|
|
||||||
writeIgnoreCaseTo(builder *statementBuilder)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ignoreCaseCol struct {
|
|
||||||
column
|
|
||||||
suffix string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c ignoreCaseCol) writeIgnoreCaseTo(builder *statementBuilder) {
|
|
||||||
c.column.writeTo(builder)
|
|
||||||
builder.WriteString(c.suffix)
|
|
||||||
}
|
|
@@ -1,139 +0,0 @@
|
|||||||
package v4
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/exp/constraints"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Value interface {
|
|
||||||
Boolean | Number | Text | databaseInstruction
|
|
||||||
}
|
|
||||||
|
|
||||||
type Operator interface {
|
|
||||||
BooleanOperator | NumberOperator | TextOperator
|
|
||||||
}
|
|
||||||
|
|
||||||
type Text interface {
|
|
||||||
~string | ~[]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
type TextOperator uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
// TextOperatorEqual compares two strings for equality.
|
|
||||||
TextOperatorEqual TextOperator = iota + 1
|
|
||||||
// TextOperatorEqualIgnoreCase compares two strings for equality, ignoring case.
|
|
||||||
TextOperatorEqualIgnoreCase
|
|
||||||
// TextOperatorNotEqual compares two strings for inequality.
|
|
||||||
TextOperatorNotEqual
|
|
||||||
// TextOperatorNotEqualIgnoreCase compares two strings for inequality, ignoring case.
|
|
||||||
TextOperatorNotEqualIgnoreCase
|
|
||||||
// TextOperatorStartsWith checks if the first string starts with the second.
|
|
||||||
TextOperatorStartsWith
|
|
||||||
// TextOperatorStartsWithIgnoreCase checks if the first string starts with the second, ignoring case.
|
|
||||||
TextOperatorStartsWithIgnoreCase
|
|
||||||
)
|
|
||||||
|
|
||||||
var textOperators = map[TextOperator]string{
|
|
||||||
TextOperatorEqual: " = ",
|
|
||||||
TextOperatorEqualIgnoreCase: " LIKE ",
|
|
||||||
TextOperatorNotEqual: " <> ",
|
|
||||||
TextOperatorNotEqualIgnoreCase: " NOT LIKE ",
|
|
||||||
TextOperatorStartsWith: " LIKE ",
|
|
||||||
TextOperatorStartsWithIgnoreCase: " LIKE ",
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeTextOperation[T Text](builder *statementBuilder, col Column, op TextOperator, value T) {
|
|
||||||
switch op {
|
|
||||||
case TextOperatorEqual, TextOperatorNotEqual:
|
|
||||||
col.writeTo(builder)
|
|
||||||
builder.WriteString(textOperators[op])
|
|
||||||
builder.WriteString(builder.appendArg(value))
|
|
||||||
case TextOperatorEqualIgnoreCase, TextOperatorNotEqualIgnoreCase:
|
|
||||||
if ignoreCaseCol, ok := col.(ignoreCaseColumn); ok {
|
|
||||||
ignoreCaseCol.writeIgnoreCaseTo(builder)
|
|
||||||
} else {
|
|
||||||
builder.WriteString("LOWER(")
|
|
||||||
col.writeTo(builder)
|
|
||||||
builder.WriteString(")")
|
|
||||||
}
|
|
||||||
builder.WriteString(textOperators[op])
|
|
||||||
builder.WriteString("LOWER(")
|
|
||||||
builder.WriteString(builder.appendArg(value))
|
|
||||||
builder.WriteString(")")
|
|
||||||
case TextOperatorStartsWith:
|
|
||||||
col.writeTo(builder)
|
|
||||||
builder.WriteString(textOperators[op])
|
|
||||||
builder.WriteString(builder.appendArg(value))
|
|
||||||
builder.WriteString(" || '%'")
|
|
||||||
case TextOperatorStartsWithIgnoreCase:
|
|
||||||
if ignoreCaseCol, ok := col.(ignoreCaseColumn); ok {
|
|
||||||
ignoreCaseCol.writeIgnoreCaseTo(builder)
|
|
||||||
} else {
|
|
||||||
builder.WriteString("LOWER(")
|
|
||||||
col.writeTo(builder)
|
|
||||||
builder.WriteString(")")
|
|
||||||
}
|
|
||||||
builder.WriteString(textOperators[op])
|
|
||||||
builder.WriteString("LOWER(")
|
|
||||||
builder.WriteString(builder.appendArg(value))
|
|
||||||
builder.WriteString(")")
|
|
||||||
builder.WriteString(" || '%'")
|
|
||||||
default:
|
|
||||||
panic("unsupported text operation")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Number interface {
|
|
||||||
constraints.Integer | constraints.Float | constraints.Complex | time.Time | time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
type NumberOperator uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
// NumberOperatorEqual compares two numbers for equality.
|
|
||||||
NumberOperatorEqual NumberOperator = iota + 1
|
|
||||||
// NumberOperatorNotEqual compares two numbers for inequality.
|
|
||||||
NumberOperatorNotEqual
|
|
||||||
// NumberOperatorLessThan compares two numbers to check if the first is less than the second.
|
|
||||||
NumberOperatorLessThan
|
|
||||||
// NumberOperatorLessThanOrEqual compares two numbers to check if the first is less than or equal to the second.
|
|
||||||
NumberOperatorAtLeast
|
|
||||||
// NumberOperatorGreaterThan compares two numbers to check if the first is greater than the second.
|
|
||||||
NumberOperatorGreaterThan
|
|
||||||
// NumberOperatorGreaterThanOrEqual compares two numbers to check if the first is greater than or equal to the second.
|
|
||||||
NumberOperatorAtMost
|
|
||||||
)
|
|
||||||
|
|
||||||
var numberOperators = map[NumberOperator]string{
|
|
||||||
NumberOperatorEqual: " = ",
|
|
||||||
NumberOperatorNotEqual: " <> ",
|
|
||||||
NumberOperatorLessThan: " < ",
|
|
||||||
NumberOperatorAtLeast: " <= ",
|
|
||||||
NumberOperatorGreaterThan: " > ",
|
|
||||||
NumberOperatorAtMost: " >= ",
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeNumberOperation[T Number](builder *statementBuilder, col Column, op NumberOperator, value T) {
|
|
||||||
col.writeTo(builder)
|
|
||||||
builder.WriteString(numberOperators[op])
|
|
||||||
builder.WriteString(builder.appendArg(value))
|
|
||||||
}
|
|
||||||
|
|
||||||
type Boolean interface {
|
|
||||||
~bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type BooleanOperator uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
BooleanOperatorIsTrue BooleanOperator = iota + 1
|
|
||||||
BooleanOperatorIsFalse
|
|
||||||
)
|
|
||||||
|
|
||||||
func writeBooleanOperation[T Boolean](builder *statementBuilder, col Column, value T) {
|
|
||||||
col.writeTo(builder)
|
|
||||||
builder.WriteString(" IS ")
|
|
||||||
builder.WriteString(builder.appendArg(value))
|
|
||||||
}
|
|
@@ -4,7 +4,6 @@ type Org struct {
|
|||||||
InstanceID string
|
InstanceID string
|
||||||
ID string
|
ID string
|
||||||
Name string
|
Name string
|
||||||
Dates
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetOrg struct{}
|
type GetOrg struct{}
|
||||||
|
@@ -1,66 +0,0 @@
|
|||||||
package v4
|
|
||||||
|
|
||||||
type queryOpts struct {
|
|
||||||
condition Condition
|
|
||||||
orderBy Columns
|
|
||||||
limit uint32
|
|
||||||
offset uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (opts *queryOpts) writeCondition(builder *statementBuilder) {
|
|
||||||
if opts.condition == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
builder.WriteString(" WHERE ")
|
|
||||||
opts.condition.writeTo(builder)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (opts *queryOpts) writeOrderBy(builder *statementBuilder) {
|
|
||||||
if len(opts.orderBy) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
builder.WriteString(" ORDER BY ")
|
|
||||||
opts.orderBy.writeTo(builder)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (opts *queryOpts) writeLimit(builder *statementBuilder) {
|
|
||||||
if opts.limit == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
builder.WriteString(" LIMIT ")
|
|
||||||
builder.writeArg(opts.limit)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (opts *queryOpts) writeOffset(builder *statementBuilder) {
|
|
||||||
if opts.offset == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
builder.WriteString(" OFFSET ")
|
|
||||||
builder.writeArg(opts.offset)
|
|
||||||
}
|
|
||||||
|
|
||||||
type QueryOption func(*queryOpts)
|
|
||||||
|
|
||||||
func WithCondition(condition Condition) QueryOption {
|
|
||||||
return func(opts *queryOpts) {
|
|
||||||
opts.condition = condition
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithOrderBy(orderBy ...Column) QueryOption {
|
|
||||||
return func(opts *queryOpts) {
|
|
||||||
opts.orderBy = orderBy
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithLimit(limit uint32) QueryOption {
|
|
||||||
return func(opts *queryOpts) {
|
|
||||||
opts.limit = limit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithOffset(offset uint32) QueryOption {
|
|
||||||
return func(opts *queryOpts) {
|
|
||||||
opts.offset = offset
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,46 +0,0 @@
|
|||||||
package v4
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type databaseInstruction string
|
|
||||||
|
|
||||||
const (
|
|
||||||
nowDBInstruction databaseInstruction = "NOW()"
|
|
||||||
nullDBInstruction databaseInstruction = "NULL"
|
|
||||||
)
|
|
||||||
|
|
||||||
type statementBuilder struct {
|
|
||||||
strings.Builder
|
|
||||||
args []any
|
|
||||||
existingArgs map[any]string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *statementBuilder) writeArg(arg any) {
|
|
||||||
b.WriteString(b.appendArg(arg))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *statementBuilder) appendArg(arg any) (placeholder string) {
|
|
||||||
if b.existingArgs == nil {
|
|
||||||
b.existingArgs = make(map[any]string)
|
|
||||||
}
|
|
||||||
if placeholder, ok := b.existingArgs[arg]; ok {
|
|
||||||
return placeholder
|
|
||||||
}
|
|
||||||
if instruction, ok := arg.(databaseInstruction); ok {
|
|
||||||
return string(instruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
b.args = append(b.args, arg)
|
|
||||||
placeholder = "$" + strconv.Itoa(len(b.args))
|
|
||||||
b.existingArgs[arg] = placeholder
|
|
||||||
return placeholder
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *statementBuilder) appendArgs(args ...any) {
|
|
||||||
for _, arg := range args {
|
|
||||||
b.appendArg(arg)
|
|
||||||
}
|
|
||||||
}
|
|
@@ -4,59 +4,55 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/backend/v3/domain"
|
||||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Dates struct {
|
|
||||||
CreatedAt time.Time
|
|
||||||
UpdatedAt time.Time
|
|
||||||
DeletedAt time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
type User struct {
|
|
||||||
InstanceID string
|
|
||||||
OrgID string
|
|
||||||
ID string
|
|
||||||
Username string
|
|
||||||
Traits userTrait
|
|
||||||
Dates
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserType string
|
|
||||||
|
|
||||||
type userTrait interface {
|
|
||||||
userTrait()
|
|
||||||
Type() UserType
|
|
||||||
}
|
|
||||||
|
|
||||||
const queryUserStmt = `SELECT instance_id, org_id, id, username, type, created_at, updated_at, deleted_at,` +
|
const queryUserStmt = `SELECT instance_id, org_id, id, username, type, created_at, updated_at, deleted_at,` +
|
||||||
` first_name, last_name, email_address, email_verified_at, phone_number, phone_verified_at, description` +
|
` first_name, last_name, email_address, email_verified_at, phone_number, phone_verified_at, description` +
|
||||||
` FROM users_view`
|
` FROM users_view`
|
||||||
|
|
||||||
type user struct {
|
type user struct {
|
||||||
builder statementBuilder
|
builder database.StatementBuilder
|
||||||
client database.QueryExecutor
|
client database.QueryExecutor
|
||||||
}
|
}
|
||||||
|
|
||||||
func UserRepository(client database.QueryExecutor) *user {
|
func UserRepository(client database.QueryExecutor) domain.UserRepository {
|
||||||
return &user{
|
return &user{
|
||||||
client: client,
|
client: client,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *user) List(ctx context.Context, opts ...QueryOption) (users []*User, err error) {
|
var _ domain.UserRepository = (*user)(nil)
|
||||||
options := new(queryOpts)
|
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
// repository
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
|
||||||
|
// Human implements [domain.UserRepository].
|
||||||
|
func (u *user) Human() domain.HumanRepository {
|
||||||
|
return &userHuman{user: u}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Machine implements [domain.UserRepository].
|
||||||
|
func (u *user) Machine() domain.MachineRepository {
|
||||||
|
return &userMachine{user: u}
|
||||||
|
}
|
||||||
|
|
||||||
|
// List implements [domain.UserRepository].
|
||||||
|
func (u *user) List(ctx context.Context, opts ...database.QueryOption) (users []*domain.User, err error) {
|
||||||
|
options := new(database.QueryOpts)
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
opt(options)
|
opt(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
u.builder.WriteString(queryUserStmt)
|
u.builder.WriteString(queryUserStmt)
|
||||||
options.writeCondition(&u.builder)
|
options.WriteCondition(&u.builder)
|
||||||
options.writeOrderBy(&u.builder)
|
options.WriteOrderBy(&u.builder)
|
||||||
options.writeLimit(&u.builder)
|
options.WriteLimit(&u.builder)
|
||||||
options.writeOffset(&u.builder)
|
options.WriteOffset(&u.builder)
|
||||||
|
|
||||||
rows, err := u.client.Query(ctx, u.builder.String(), u.builder.args...)
|
rows, err := u.client.Query(ctx, u.builder.String(), u.builder.Args()...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -80,142 +76,157 @@ func (u *user) List(ctx context.Context, opts ...QueryOption) (users []*User, er
|
|||||||
return users, nil
|
return users, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *user) Get(ctx context.Context, opts ...QueryOption) (*User, error) {
|
// Get implements [domain.UserRepository].
|
||||||
options := new(queryOpts)
|
func (u *user) Get(ctx context.Context, opts ...database.QueryOption) (*domain.User, error) {
|
||||||
|
options := new(database.QueryOpts)
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
opt(options)
|
opt(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
u.builder.WriteString(queryUserStmt)
|
u.builder.WriteString(queryUserStmt)
|
||||||
options.writeCondition(&u.builder)
|
options.WriteCondition(&u.builder)
|
||||||
options.writeOrderBy(&u.builder)
|
options.WriteOrderBy(&u.builder)
|
||||||
options.writeLimit(&u.builder)
|
options.WriteLimit(&u.builder)
|
||||||
options.writeOffset(&u.builder)
|
options.WriteOffset(&u.builder)
|
||||||
|
|
||||||
return scanUser(u.client.QueryRow(ctx, u.builder.String(), u.builder.args...))
|
return scanUser(u.client.QueryRow(ctx, u.builder.String(), u.builder.Args()...))
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// TODO: change to separate statements and tables
|
createHumanStmt = `INSERT INTO human_users (instance_id, org_id, user_id, username, first_name, last_name, email_address, email_verified_at, phone_number, phone_verified_at)` +
|
||||||
createUserCte = `WITH user AS (` +
|
` VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)` +
|
||||||
`INSERT INTO users (instance_id, org_id, id, username, type) VALUES ($1, $2, $3, $4, $5)` +
|
` RETURNING created_at, updated_at`
|
||||||
` RETURNING *)`
|
createMachineStmt = `INSERT INTO user_machines (instance_id, org_id, user_id, username, description)` +
|
||||||
createHumanStmt = createUserCte + ` INSERT INTO user_humans h (instance_id, org_id, user_id, first_name, last_name, email_address, email_verified_at, phone_number, phone_verified_at)` +
|
` VALUES ($1, $2, $3, $4, $5)` +
|
||||||
` SELECT u.instance_id, u.org_id, u.id, $6, $7, $8, $9, $10, $11` +
|
` RETURNING created_at, updated_at`
|
||||||
` FROM user u` +
|
|
||||||
` RETURNING u.created_at, u.updated_at, u.deleted_at`
|
|
||||||
createMachineStmt = createUserCte + ` INSERT INTO user_machines (instance_id, org_id, user_id, description)` +
|
|
||||||
` SELECT u.instance_id, u.org_id, u.id, $6` +
|
|
||||||
` FROM user u` +
|
|
||||||
` RETURNING u.created_at, u.updated_at`
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (u *user) Create(ctx context.Context, user *User) error {
|
// Create implements [domain.UserRepository].
|
||||||
u.builder.appendArgs(user.InstanceID, user.OrgID, user.ID, user.Username, user.Traits.Type())
|
func (u *user) Create(ctx context.Context, user *domain.User) error {
|
||||||
|
u.builder.AppendArgs(user.InstanceID, user.OrgID, user.ID, user.Username, user.Traits.Type())
|
||||||
switch trait := user.Traits.(type) {
|
switch trait := user.Traits.(type) {
|
||||||
case *Human:
|
case *domain.Human:
|
||||||
u.builder.WriteString(createHumanStmt)
|
u.builder.WriteString(createHumanStmt)
|
||||||
u.builder.appendArgs(trait.FirstName, trait.LastName, trait.Email.Address, trait.Email.VerifiedAt, trait.Phone.Number, trait.Phone.VerifiedAt)
|
u.builder.AppendArgs(trait.FirstName, trait.LastName, trait.Email.Address, trait.Email.VerifiedAt, trait.Phone.Number, trait.Phone.VerifiedAt)
|
||||||
case *Machine:
|
case *domain.Machine:
|
||||||
u.builder.WriteString(createMachineStmt)
|
u.builder.WriteString(createMachineStmt)
|
||||||
u.builder.appendArgs(trait.Description)
|
u.builder.AppendArgs(trait.Description)
|
||||||
}
|
}
|
||||||
return u.client.QueryRow(ctx, u.builder.String(), u.builder.args...).Scan(&user.Dates.CreatedAt, &user.Dates.UpdatedAt)
|
return u.client.QueryRow(ctx, u.builder.String(), u.builder.Args()...).Scan(&user.CreatedAt, &user.UpdatedAt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *user) Update(ctx context.Context, condition Condition, changes ...Change) error {
|
// Delete implements [domain.UserRepository].
|
||||||
u.builder.WriteString("UPDATE users SET ")
|
func (u *user) Delete(ctx context.Context, condition database.Condition) error {
|
||||||
Changes(changes).writeTo(&u.builder)
|
|
||||||
u.writeCondition(condition)
|
|
||||||
return u.client.Exec(ctx, u.builder.String(), u.builder.args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *user) Delete(ctx context.Context, condition Condition) error {
|
|
||||||
u.builder.WriteString("DELETE FROM users")
|
u.builder.WriteString("DELETE FROM users")
|
||||||
u.writeCondition(condition)
|
u.writeCondition(condition)
|
||||||
return u.client.Exec(ctx, u.builder.String(), u.builder.args...)
|
return u.client.Exec(ctx, u.builder.String(), u.builder.Args()...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *user) InstanceIDColumn() Column {
|
// -------------------------------------------------------------
|
||||||
return column{name: "instance_id"}
|
// changes
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
|
||||||
|
// SetUsername implements [domain.userChanges].
|
||||||
|
func (u user) SetUsername(username string) database.Change {
|
||||||
|
return database.NewChange(u.UsernameColumn(), username)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *user) InstanceIDCondition(instanceID string) Condition {
|
// -------------------------------------------------------------
|
||||||
return newTextCondition(u.InstanceIDColumn(), TextOperatorEqual, instanceID)
|
// conditions
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
|
||||||
|
// InstanceIDCondition implements [domain.userConditions].
|
||||||
|
func (u user) InstanceIDCondition(instanceID string) database.Condition {
|
||||||
|
return database.NewTextCondition(u.InstanceIDColumn(), database.TextOperationEqual, instanceID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *user) OrgIDColumn() Column {
|
// OrgIDCondition implements [domain.userConditions].
|
||||||
return column{name: "org_id"}
|
func (u user) OrgIDCondition(orgID string) database.Condition {
|
||||||
|
return database.NewTextCondition(u.OrgIDColumn(), database.TextOperationEqual, orgID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *user) OrgIDCondition(orgID string) Condition {
|
// IDCondition implements [domain.userConditions].
|
||||||
return newTextCondition(u.OrgIDColumn(), TextOperatorEqual, orgID)
|
func (u user) IDCondition(userID string) database.Condition {
|
||||||
|
return database.NewTextCondition(u.IDColumn(), database.TextOperationEqual, userID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *user) IDColumn() Column {
|
// UsernameCondition implements [domain.userConditions].
|
||||||
return column{name: "id"}
|
func (u user) UsernameCondition(op database.TextOperation, username string) database.Condition {
|
||||||
|
return database.NewTextCondition(u.UsernameColumn(), op, username)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *user) IDCondition(userID string) Condition {
|
// CreatedAtCondition implements [domain.userConditions].
|
||||||
return newTextCondition(u.IDColumn(), TextOperatorEqual, userID)
|
func (u user) CreatedAtCondition(op database.NumberOperation, createdAt time.Time) database.Condition {
|
||||||
|
return database.NewNumberCondition(u.CreatedAtColumn(), op, createdAt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *user) UsernameColumn() Column {
|
// UpdatedAtCondition implements [domain.userConditions].
|
||||||
return ignoreCaseCol{
|
func (u user) UpdatedAtCondition(op database.NumberOperation, updatedAt time.Time) database.Condition {
|
||||||
column: column{name: "username"},
|
return database.NewNumberCondition(u.UpdatedAtColumn(), op, updatedAt)
|
||||||
suffix: "_lower",
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u user) SetUsername(username string) Change {
|
// DeletedCondition implements [domain.userConditions].
|
||||||
return newChange(u.UsernameColumn(), username)
|
func (u user) DeletedCondition(isDeleted bool) database.Condition {
|
||||||
}
|
|
||||||
|
|
||||||
func (u *user) UsernameCondition(op TextOperator, username string) Condition {
|
|
||||||
return newTextCondition(u.UsernameColumn(), op, username)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *user) CreatedAtColumn() Column {
|
|
||||||
return column{name: "created_at"}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *user) CreatedAtCondition(op NumberOperator, createdAt time.Time) Condition {
|
|
||||||
return newNumberCondition(u.CreatedAtColumn(), op, createdAt)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *user) UpdatedAtColumn() Column {
|
|
||||||
return column{name: "updated_at"}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *user) UpdatedAtCondition(op NumberOperator, updatedAt time.Time) Condition {
|
|
||||||
return newNumberCondition(u.UpdatedAtColumn(), op, updatedAt)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *user) DeletedAtColumn() Column {
|
|
||||||
return column{name: "deleted_at"}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *user) DeletedCondition(isDeleted bool) Condition {
|
|
||||||
if isDeleted {
|
if isDeleted {
|
||||||
return IsNotNull(u.DeletedAtColumn())
|
return database.IsNotNull(u.DeletedAtColumn())
|
||||||
}
|
}
|
||||||
return IsNull(u.DeletedAtColumn())
|
return database.IsNull(u.DeletedAtColumn())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *user) DeletedAtCondition(op NumberOperator, deletedAt time.Time) Condition {
|
// DeletedAtCondition implements [domain.userConditions].
|
||||||
return newNumberCondition(u.DeletedAtColumn(), op, deletedAt)
|
func (u user) DeletedAtCondition(op database.NumberOperation, deletedAt time.Time) database.Condition {
|
||||||
|
return database.NewNumberCondition(u.DeletedAtColumn(), op, deletedAt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *user) writeCondition(condition Condition) {
|
// -------------------------------------------------------------
|
||||||
|
// columns
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
|
||||||
|
// InstanceIDColumn implements [domain.userColumns].
|
||||||
|
func (user) InstanceIDColumn() database.Column {
|
||||||
|
return database.NewColumn("instance_id")
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrgIDColumn implements [domain.userColumns].
|
||||||
|
func (user) OrgIDColumn() database.Column {
|
||||||
|
return database.NewColumn("org_id")
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDColumn implements [domain.userColumns].
|
||||||
|
func (user) IDColumn() database.Column {
|
||||||
|
return database.NewColumn("id")
|
||||||
|
}
|
||||||
|
|
||||||
|
// UsernameColumn implements [domain.userColumns].
|
||||||
|
func (user) UsernameColumn() database.Column {
|
||||||
|
return database.NewIgnoreCaseColumn("username", "_lower")
|
||||||
|
}
|
||||||
|
|
||||||
|
// FirstNameColumn implements [domain.userColumns].
|
||||||
|
func (user) CreatedAtColumn() database.Column {
|
||||||
|
return database.NewColumn("created_at")
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatedAtColumn implements [domain.userColumns].
|
||||||
|
func (user) UpdatedAtColumn() database.Column {
|
||||||
|
return database.NewColumn("updated_at")
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeletedAtColumn implements [domain.userColumns].
|
||||||
|
func (user) DeletedAtColumn() database.Column {
|
||||||
|
return database.NewColumn("deleted_at")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *user) writeCondition(condition database.Condition) {
|
||||||
if condition == nil {
|
if condition == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
u.builder.WriteString(" WHERE ")
|
u.builder.WriteString(" WHERE ")
|
||||||
condition.writeTo(&u.builder)
|
condition.Write(&u.builder)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u user) columns() Columns {
|
func (u user) columns() database.Columns {
|
||||||
return Columns{
|
return database.Columns{
|
||||||
u.InstanceIDColumn(),
|
u.InstanceIDColumn(),
|
||||||
u.OrgIDColumn(),
|
u.OrgIDColumn(),
|
||||||
u.IDColumn(),
|
u.IDColumn(),
|
||||||
@@ -226,14 +237,14 @@ func (u user) columns() Columns {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func scanUser(scanner database.Scanner) (*User, error) {
|
func scanUser(scanner database.Scanner) (*domain.User, error) {
|
||||||
var (
|
var (
|
||||||
user User
|
user domain.User
|
||||||
human Human
|
human domain.Human
|
||||||
email Email
|
email domain.Email
|
||||||
phone Phone
|
phone domain.Phone
|
||||||
machine Machine
|
machine domain.Machine
|
||||||
typ UserType
|
typ domain.UserType
|
||||||
)
|
)
|
||||||
err := scanner.Scan(
|
err := scanner.Scan(
|
||||||
&user.InstanceID,
|
&user.InstanceID,
|
||||||
@@ -241,9 +252,9 @@ func scanUser(scanner database.Scanner) (*User, error) {
|
|||||||
&user.ID,
|
&user.ID,
|
||||||
&user.Username,
|
&user.Username,
|
||||||
&typ,
|
&typ,
|
||||||
&user.Dates.CreatedAt,
|
&user.CreatedAt,
|
||||||
&user.Dates.UpdatedAt,
|
&user.UpdatedAt,
|
||||||
&user.Dates.DeletedAt,
|
&user.DeletedAt,
|
||||||
&human.FirstName,
|
&human.FirstName,
|
||||||
&human.LastName,
|
&human.LastName,
|
||||||
&email.Address,
|
&email.Address,
|
||||||
@@ -257,7 +268,7 @@ func scanUser(scanner database.Scanner) (*User, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch typ {
|
switch typ {
|
||||||
case UserTypeHuman:
|
case domain.UserTypeHuman:
|
||||||
if email.Address != "" {
|
if email.Address != "" {
|
||||||
human.Email = &email
|
human.Email = &email
|
||||||
}
|
}
|
||||||
@@ -265,7 +276,7 @@ func scanUser(scanner database.Scanner) (*User, error) {
|
|||||||
human.Phone = &phone
|
human.Phone = &phone
|
||||||
}
|
}
|
||||||
user.Traits = &human
|
user.Traits = &human
|
||||||
case UserTypeMachine:
|
case domain.UserTypeMachine:
|
||||||
user.Traits = &machine
|
user.Traits = &machine
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -3,58 +3,33 @@ package v4
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/backend/v3/domain"
|
||||||
|
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Human struct {
|
// -------------------------------------------------------------
|
||||||
FirstName string
|
// repository
|
||||||
LastName string
|
// -------------------------------------------------------------
|
||||||
Email *Email
|
|
||||||
Phone *Phone
|
|
||||||
}
|
|
||||||
|
|
||||||
const UserTypeHuman UserType = "human"
|
|
||||||
|
|
||||||
func (Human) userTrait() {}
|
|
||||||
|
|
||||||
func (h Human) Type() UserType {
|
|
||||||
return UserTypeHuman
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ userTrait = (*Human)(nil)
|
|
||||||
|
|
||||||
type Email struct {
|
|
||||||
Address string
|
|
||||||
Verification
|
|
||||||
}
|
|
||||||
|
|
||||||
type Phone struct {
|
|
||||||
Number string
|
|
||||||
Verification
|
|
||||||
}
|
|
||||||
|
|
||||||
type Verification struct {
|
|
||||||
VerifiedAt time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
type userHuman struct {
|
type userHuman struct {
|
||||||
*user
|
*user
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *user) Human() *userHuman {
|
var _ domain.HumanRepository = (*userHuman)(nil)
|
||||||
return &userHuman{user: u}
|
|
||||||
}
|
|
||||||
|
|
||||||
const userEmailQuery = `SELECT h.email_address, h.email_verified_at FROM user_humans h`
|
const userEmailQuery = `SELECT h.email_address, h.email_verified_at FROM user_humans h`
|
||||||
|
|
||||||
func (u *userHuman) GetEmail(ctx context.Context, condition Condition) (*Email, error) {
|
// GetEmail implements [domain.HumanRepository].
|
||||||
var email Email
|
func (u *userHuman) GetEmail(ctx context.Context, condition database.Condition) (*domain.Email, error) {
|
||||||
|
var email domain.Email
|
||||||
|
|
||||||
u.builder.WriteString(userEmailQuery)
|
u.builder.WriteString(userEmailQuery)
|
||||||
u.writeCondition(condition)
|
u.writeCondition(condition)
|
||||||
|
|
||||||
err := u.client.QueryRow(ctx, u.builder.String(), u.builder.args...).Scan(
|
err := u.client.QueryRow(ctx, u.builder.String(), u.builder.Args()...).Scan(
|
||||||
&email.Address,
|
&email.Address,
|
||||||
&email.Verification.VerifiedAt,
|
&email.VerifiedAt,
|
||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -63,130 +38,158 @@ func (u *userHuman) GetEmail(ctx context.Context, condition Condition) (*Email,
|
|||||||
return &email, nil
|
return &email, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h userHuman) Update(ctx context.Context, condition Condition, changes ...Change) error {
|
// Update implements [domain.HumanRepository].
|
||||||
|
func (h userHuman) Update(ctx context.Context, condition database.Condition, changes ...database.Change) error {
|
||||||
h.builder.WriteString(`UPDATE human_users SET `)
|
h.builder.WriteString(`UPDATE human_users SET `)
|
||||||
Changes(changes).writeTo(&h.builder)
|
database.Changes(changes).Write(&h.builder)
|
||||||
h.writeCondition(condition)
|
h.writeCondition(condition)
|
||||||
|
|
||||||
stmt := h.builder.String()
|
stmt := h.builder.String()
|
||||||
|
|
||||||
return h.client.Exec(ctx, stmt, h.builder.args...)
|
return h.client.Exec(ctx, stmt, h.builder.Args()...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h userHuman) SetFirstName(firstName string) Change {
|
// -------------------------------------------------------------
|
||||||
return newChange(h.FirstNameColumn(), firstName)
|
// changes
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
|
||||||
|
// SetFirstName implements [domain.humanChanges].
|
||||||
|
func (h userHuman) SetFirstName(firstName string) database.Change {
|
||||||
|
return database.NewChange(h.FirstNameColumn(), firstName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h userHuman) FirstNameColumn() Column {
|
// SetLastName implements [domain.humanChanges].
|
||||||
return column{"first_name"}
|
func (h userHuman) SetLastName(lastName string) database.Change {
|
||||||
|
return database.NewChange(h.LastNameColumn(), lastName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h userHuman) FirstNameCondition(op TextOperator, firstName string) Condition {
|
// SetEmail implements [domain.humanChanges].
|
||||||
return newTextCondition(h.FirstNameColumn(), op, firstName)
|
func (h userHuman) SetEmail(address string, verified *time.Time) database.Change {
|
||||||
}
|
return database.NewChanges(
|
||||||
|
|
||||||
func (h userHuman) SetLastName(lastName string) Change {
|
|
||||||
return newChange(h.LastNameColumn(), lastName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h userHuman) LastNameColumn() Column {
|
|
||||||
return column{"last_name"}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h userHuman) LastNameCondition(op TextOperator, lastName string) Condition {
|
|
||||||
return newTextCondition(h.LastNameColumn(), op, lastName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h userHuman) EmailAddressColumn() Column {
|
|
||||||
return ignoreCaseCol{
|
|
||||||
column: column{"email_address"},
|
|
||||||
suffix: "_lower",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h userHuman) EmailAddressCondition(op TextOperator, email string) Condition {
|
|
||||||
return newTextCondition(h.EmailAddressColumn(), op, email)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h userHuman) EmailVerifiedAtColumn() Column {
|
|
||||||
return column{"email_verified_at"}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *userHuman) EmailAddressVerifiedCondition(isVerified bool) Condition {
|
|
||||||
if isVerified {
|
|
||||||
return IsNotNull(h.EmailVerifiedAtColumn())
|
|
||||||
}
|
|
||||||
return IsNull(h.EmailVerifiedAtColumn())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h userHuman) EmailVerifiedAtCondition(op TextOperator, emailVerifiedAt string) Condition {
|
|
||||||
return newTextCondition(h.EmailVerifiedAtColumn(), op, emailVerifiedAt)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h userHuman) SetEmailAddress(address string) Change {
|
|
||||||
return newChange(h.EmailAddressColumn(), address)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetEmailVerified sets the verified column of the email
|
|
||||||
// if at is zero the statement uses the database timestamp
|
|
||||||
func (h userHuman) SetEmailVerified(at time.Time) Change {
|
|
||||||
if at.IsZero() {
|
|
||||||
return newChange(h.EmailVerifiedAtColumn(), nowDBInstruction)
|
|
||||||
}
|
|
||||||
return newChange(h.EmailVerifiedAtColumn(), at)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h userHuman) SetEmail(address string, verified *time.Time) Change {
|
|
||||||
return newChanges(
|
|
||||||
h.SetEmailAddress(address),
|
h.SetEmailAddress(address),
|
||||||
newUpdatePtrColumn(h.EmailVerifiedAtColumn(), verified),
|
database.NewChangePtr(h.EmailVerifiedAtColumn(), verified),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h userHuman) PhoneNumberColumn() Column {
|
// SetEmailAddress implements [domain.humanChanges].
|
||||||
return column{"phone_number"}
|
func (h userHuman) SetEmailAddress(address string) database.Change {
|
||||||
|
return database.NewChange(h.EmailAddressColumn(), address)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h userHuman) SetPhoneNumber(number string) Change {
|
// SetEmailVerifiedAt implements [domain.humanChanges].
|
||||||
return newChange(h.PhoneNumberColumn(), number)
|
func (h userHuman) SetEmailVerifiedAt(at time.Time) database.Change {
|
||||||
}
|
|
||||||
|
|
||||||
func (h userHuman) PhoneNumberCondition(op TextOperator, phoneNumber string) Condition {
|
|
||||||
return newTextCondition(h.PhoneNumberColumn(), op, phoneNumber)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h userHuman) PhoneVerifiedAtColumn() Column {
|
|
||||||
return column{"phone_verified_at"}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h userHuman) PhoneNumberVerifiedCondition(isVerified bool) Condition {
|
|
||||||
if isVerified {
|
|
||||||
return IsNotNull(h.PhoneVerifiedAtColumn())
|
|
||||||
}
|
|
||||||
return IsNull(h.PhoneVerifiedAtColumn())
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetPhoneVerified sets the verified column of the phone
|
|
||||||
// if at is zero the statement uses the database timestamp
|
|
||||||
func (h userHuman) SetPhoneVerified(at time.Time) Change {
|
|
||||||
if at.IsZero() {
|
if at.IsZero() {
|
||||||
return newChange(h.PhoneVerifiedAtColumn(), nowDBInstruction)
|
return database.NewChange(h.EmailVerifiedAtColumn(), database.NowInstruction)
|
||||||
}
|
}
|
||||||
return newChange(h.PhoneVerifiedAtColumn(), at)
|
return database.NewChange(h.EmailVerifiedAtColumn(), at)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h userHuman) PhoneVerifiedAtCondition(op TextOperator, phoneVerifiedAt string) Condition {
|
// SetPhone implements [domain.humanChanges].
|
||||||
return newTextCondition(h.PhoneVerifiedAtColumn(), op, phoneVerifiedAt)
|
func (h userHuman) SetPhone(number string, verifiedAt *time.Time) database.Change {
|
||||||
}
|
return database.NewChanges(
|
||||||
|
|
||||||
func (h userHuman) SetPhone(number string, verifiedAt *time.Time) Change {
|
|
||||||
return newChanges(
|
|
||||||
h.SetPhoneNumber(number),
|
h.SetPhoneNumber(number),
|
||||||
newUpdatePtrColumn(h.PhoneVerifiedAtColumn(), verifiedAt),
|
database.NewChangePtr(h.PhoneVerifiedAtColumn(), verifiedAt),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h userHuman) columns() Columns {
|
// SetPhoneNumber implements [domain.humanChanges].
|
||||||
|
func (h userHuman) SetPhoneNumber(number string) database.Change {
|
||||||
|
return database.NewChange(h.PhoneNumberColumn(), number)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPhoneVerifiedAt implements [domain.humanChanges].
|
||||||
|
func (h userHuman) SetPhoneVerifiedAt(at time.Time) database.Change {
|
||||||
|
if at.IsZero() {
|
||||||
|
return database.NewChange(h.PhoneVerifiedAtColumn(), database.NowInstruction)
|
||||||
|
}
|
||||||
|
return database.NewChange(h.PhoneVerifiedAtColumn(), at)
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
// conditions
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
|
||||||
|
// FirstNameCondition implements [domain.humanConditions].
|
||||||
|
func (h userHuman) FirstNameCondition(op database.TextOperation, firstName string) database.Condition {
|
||||||
|
return database.NewTextCondition(h.FirstNameColumn(), op, firstName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LastNameCondition implements [domain.humanConditions].
|
||||||
|
func (h userHuman) LastNameCondition(op database.TextOperation, lastName string) database.Condition {
|
||||||
|
return database.NewTextCondition(h.LastNameColumn(), op, lastName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EmailAddressCondition implements [domain.humanConditions].
|
||||||
|
func (h userHuman) EmailAddressCondition(op database.TextOperation, email string) database.Condition {
|
||||||
|
return database.NewTextCondition(h.EmailAddressColumn(), op, email)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EmailVerifiedCondition implements [domain.humanConditions].
|
||||||
|
func (h *userHuman) EmailVerifiedCondition(isVerified bool) database.Condition {
|
||||||
|
if isVerified {
|
||||||
|
return database.IsNotNull(h.EmailVerifiedAtColumn())
|
||||||
|
}
|
||||||
|
return database.IsNull(h.EmailVerifiedAtColumn())
|
||||||
|
}
|
||||||
|
|
||||||
|
// EmailVerifiedAtCondition implements [domain.humanConditions].
|
||||||
|
func (h userHuman) EmailVerifiedAtCondition(op database.NumberOperation, verifiedAt time.Time) database.Condition {
|
||||||
|
return database.NewNumberCondition(h.EmailVerifiedAtColumn(), op, verifiedAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PhoneNumberCondition implements [domain.humanConditions].
|
||||||
|
func (h userHuman) PhoneNumberCondition(op database.TextOperation, phoneNumber string) database.Condition {
|
||||||
|
return database.NewTextCondition(h.PhoneNumberColumn(), op, phoneNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PhoneVerifiedCondition implements [domain.humanConditions].
|
||||||
|
func (h userHuman) PhoneVerifiedCondition(isVerified bool) database.Condition {
|
||||||
|
if isVerified {
|
||||||
|
return database.IsNotNull(h.PhoneVerifiedAtColumn())
|
||||||
|
}
|
||||||
|
return database.IsNull(h.PhoneVerifiedAtColumn())
|
||||||
|
}
|
||||||
|
|
||||||
|
// PhoneVerifiedAtCondition implements [domain.humanConditions].
|
||||||
|
func (h userHuman) PhoneVerifiedAtCondition(op database.NumberOperation, verifiedAt time.Time) database.Condition {
|
||||||
|
return database.NewNumberCondition(h.PhoneVerifiedAtColumn(), op, verifiedAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
// columns
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
|
||||||
|
// FirstNameColumn implements [domain.humanColumns].
|
||||||
|
func (h userHuman) FirstNameColumn() database.Column {
|
||||||
|
return database.NewColumn("first_name")
|
||||||
|
}
|
||||||
|
|
||||||
|
// LastNameColumn implements [domain.humanColumns].
|
||||||
|
func (h userHuman) LastNameColumn() database.Column {
|
||||||
|
return database.NewColumn("last_name")
|
||||||
|
}
|
||||||
|
|
||||||
|
// EmailAddressColumn implements [domain.humanColumns].
|
||||||
|
func (h userHuman) EmailAddressColumn() database.Column {
|
||||||
|
return database.NewIgnoreCaseColumn("email_address", "_lower")
|
||||||
|
}
|
||||||
|
|
||||||
|
// EmailVerifiedAtColumn implements [domain.humanColumns].
|
||||||
|
func (h userHuman) EmailVerifiedAtColumn() database.Column {
|
||||||
|
return database.NewColumn("email_verified_at")
|
||||||
|
}
|
||||||
|
|
||||||
|
// PhoneNumberColumn implements [domain.humanColumns].
|
||||||
|
func (h userHuman) PhoneNumberColumn() database.Column {
|
||||||
|
return database.NewColumn("phone_number")
|
||||||
|
}
|
||||||
|
|
||||||
|
// PhoneVerifiedAtColumn implements [domain.humanColumns].
|
||||||
|
func (h userHuman) PhoneVerifiedAtColumn() database.Column {
|
||||||
|
return database.NewColumn("phone_verified_at")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h userHuman) columns() database.Columns {
|
||||||
return append(h.user.columns(),
|
return append(h.user.columns(),
|
||||||
h.FirstNameColumn(),
|
h.FirstNameColumn(),
|
||||||
h.LastNameColumn(),
|
h.LastNameColumn(),
|
||||||
@@ -197,7 +200,7 @@ func (h userHuman) columns() Columns {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h userHuman) writeReturning(builder *statementBuilder) {
|
func (h userHuman) writeReturning(builder *database.StatementBuilder) {
|
||||||
builder.WriteString(" RETURNING ")
|
builder.WriteString(" RETURNING ")
|
||||||
h.columns().writeTo(builder)
|
h.columns().Write(builder)
|
||||||
}
|
}
|
||||||
|
@@ -1,72 +1,64 @@
|
|||||||
package v4
|
package v4
|
||||||
|
|
||||||
import "context"
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
type Machine struct {
|
"github.com/zitadel/zitadel/backend/v3/domain"
|
||||||
Description string
|
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||||
}
|
)
|
||||||
|
|
||||||
func (Machine) userTrait() {}
|
|
||||||
|
|
||||||
func (m Machine) Type() UserType {
|
|
||||||
return UserTypeMachine
|
|
||||||
}
|
|
||||||
|
|
||||||
const UserTypeMachine UserType = "machine"
|
|
||||||
|
|
||||||
var _ userTrait = (*Machine)(nil)
|
|
||||||
|
|
||||||
type userMachine struct {
|
type userMachine struct {
|
||||||
*user
|
*user
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *user) Machine() *userMachine {
|
var _ domain.MachineRepository = (*userMachine)(nil)
|
||||||
return &userMachine{user: u}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m userMachine) Update(ctx context.Context, condition Condition, changes ...Change) ([]*Machine, error) {
|
// -------------------------------------------------------------
|
||||||
|
// repository
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
|
||||||
|
// Update implements [domain.MachineRepository].
|
||||||
|
func (m userMachine) Update(ctx context.Context, condition database.Condition, changes ...database.Change) (err error) {
|
||||||
m.builder.WriteString("UPDATE user_machines SET ")
|
m.builder.WriteString("UPDATE user_machines SET ")
|
||||||
Changes(changes).writeTo(&m.builder)
|
database.Changes(changes).Write(&m.builder)
|
||||||
m.writeCondition(condition)
|
m.writeCondition(condition)
|
||||||
m.writeReturning()
|
m.writeReturning()
|
||||||
|
|
||||||
var machines []*Machine
|
return m.client.Exec(ctx, m.builder.String(), m.builder.Args()...)
|
||||||
rows, err := m.client.Query(ctx, m.builder.String(), m.builder.args...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
for rows.Next() {
|
|
||||||
machine := new(Machine)
|
|
||||||
if err := rows.Scan(&machine.Description); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
machines = append(machines, machine)
|
|
||||||
}
|
|
||||||
if err := rows.Err(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return machines, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (userMachine) DescriptionColumn() Column {
|
// -------------------------------------------------------------
|
||||||
return column{"description"}
|
// changes
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
|
||||||
|
// SetDescription implements [domain.machineChanges].
|
||||||
|
func (m userMachine) SetDescription(description string) database.Change {
|
||||||
|
return database.NewChange(m.DescriptionColumn(), description)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m userMachine) SetDescription(description string) Change {
|
// -------------------------------------------------------------
|
||||||
return newChange(m.DescriptionColumn(), description)
|
// conditions
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
|
||||||
|
// DescriptionCondition implements [domain.machineConditions].
|
||||||
|
func (m userMachine) DescriptionCondition(op database.TextOperation, description string) database.Condition {
|
||||||
|
return database.NewTextCondition(m.DescriptionColumn(), op, description)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m userMachine) DescriptionCondition(op TextOperator, description string) Condition {
|
// -------------------------------------------------------------
|
||||||
return newTextCondition(m.DescriptionColumn(), op, description)
|
// columns
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
|
||||||
|
// DescriptionColumn implements [domain.machineColumns].
|
||||||
|
func (m userMachine) DescriptionColumn() database.Column {
|
||||||
|
return database.NewColumn("description")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m userMachine) columns() Columns {
|
func (m userMachine) columns() database.Columns {
|
||||||
return append(m.user.columns(), m.DescriptionColumn())
|
return append(m.user.columns(), m.DescriptionColumn())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *userMachine) writeReturning() {
|
func (m *userMachine) writeReturning() {
|
||||||
m.builder.WriteString(" RETURNING ")
|
m.builder.WriteString(" RETURNING ")
|
||||||
m.columns().writeTo(&m.builder)
|
m.columns().Write(&m.builder)
|
||||||
}
|
}
|
||||||
|
@@ -5,6 +5,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||||
v4 "github.com/zitadel/zitadel/backend/v3/storage/database/repository/stmt/v4"
|
v4 "github.com/zitadel/zitadel/backend/v3/storage/database/repository/stmt/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -12,16 +13,16 @@ func TestQueryUser(t *testing.T) {
|
|||||||
t.Run("User filters", func(t *testing.T) {
|
t.Run("User filters", func(t *testing.T) {
|
||||||
user := v4.UserRepository(nil)
|
user := v4.UserRepository(nil)
|
||||||
u, err := user.Get(context.Background(),
|
u, err := user.Get(context.Background(),
|
||||||
v4.WithCondition(
|
database.WithCondition(
|
||||||
v4.And(
|
database.And(
|
||||||
v4.Or(
|
database.Or(
|
||||||
user.IDCondition("test"),
|
user.IDCondition("test"),
|
||||||
user.IDCondition("2"),
|
user.IDCondition("2"),
|
||||||
),
|
),
|
||||||
user.UsernameCondition(v4.TextOperatorStartsWithIgnoreCase, "test"),
|
user.UsernameCondition(database.TextOperationStartsWithIgnoreCase, "test"),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
v4.WithOrderBy(user.CreatedAtColumn()),
|
database.WithOrderBy(user.CreatedAtColumn()),
|
||||||
)
|
)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@@ -32,12 +33,12 @@ func TestQueryUser(t *testing.T) {
|
|||||||
user := v4.UserRepository(nil)
|
user := v4.UserRepository(nil)
|
||||||
machine := user.Machine()
|
machine := user.Machine()
|
||||||
human := user.Human()
|
human := user.Human()
|
||||||
email, err := human.GetEmail(context.Background(), v4.And(
|
email, err := human.GetEmail(context.Background(), database.And(
|
||||||
user.UsernameCondition(v4.TextOperatorStartsWithIgnoreCase, "test"),
|
user.UsernameCondition(database.TextOperationStartsWithIgnoreCase, "test"),
|
||||||
v4.Or(
|
database.Or(
|
||||||
machine.DescriptionCondition(v4.TextOperatorStartsWithIgnoreCase, "test"),
|
machine.DescriptionCondition(database.TextOperationStartsWithIgnoreCase, "test"),
|
||||||
human.EmailAddressVerifiedCondition(true),
|
human.EmailVerifiedCondition(true),
|
||||||
v4.IsNotNull(machine.DescriptionColumn()),
|
database.IsNotNull(machine.DescriptionColumn()),
|
||||||
),
|
),
|
||||||
))
|
))
|
||||||
|
|
||||||
@@ -62,6 +63,6 @@ func TestArg(t *testing.T) {
|
|||||||
func TestWriteUser(t *testing.T) {
|
func TestWriteUser(t *testing.T) {
|
||||||
t.Run("update user", func(t *testing.T) {
|
t.Run("update user", func(t *testing.T) {
|
||||||
user := v4.UserRepository(nil)
|
user := v4.UserRepository(nil)
|
||||||
user.Update(context.Background(), user.IDCondition("test"), user.SetUsername("test"))
|
user.Human().Update(context.Background(), user.IDCondition("test"), user.SetUsername("test"))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@@ -31,17 +31,17 @@ var _ domain.UserOperation = (*userOperation)(nil)
|
|||||||
|
|
||||||
func UserIDQuery(id string) domain.UserClause {
|
func UserIDQuery(id string) domain.UserClause {
|
||||||
return textClause[string]{
|
return textClause[string]{
|
||||||
clause: clause[domain.TextOperation]{
|
clause: clause[database.TextOperation]{
|
||||||
field: userFields[domain.UserFieldID],
|
field: userFields[domain.UserFieldID],
|
||||||
op: domain.TextOperationEqual,
|
op: database.TextOperationEqual,
|
||||||
},
|
},
|
||||||
value: id,
|
value: id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func HumanEmailQuery(op domain.TextOperation, email string) domain.UserClause {
|
func HumanEmailQuery(op database.TextOperation, email string) domain.UserClause {
|
||||||
return textClause[string]{
|
return textClause[string]{
|
||||||
clause: clause[domain.TextOperation]{
|
clause: clause[database.TextOperation]{
|
||||||
field: userFields[domain.UserHumanFieldEmail],
|
field: userFields[domain.UserHumanFieldEmail],
|
||||||
op: op,
|
op: op,
|
||||||
},
|
},
|
||||||
@@ -49,9 +49,9 @@ func HumanEmailQuery(op domain.TextOperation, email string) domain.UserClause {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func HumanEmailVerifiedQuery(op domain.BoolOperation) domain.UserClause {
|
func HumanEmailVerifiedQuery(op database.BoolOperation) domain.UserClause {
|
||||||
return boolClause[domain.BoolOperation]{
|
return boolClause[database.BoolOperation]{
|
||||||
clause: clause[domain.BoolOperation]{
|
clause: clause[database.BoolOperation]{
|
||||||
field: userFields[domain.UserHumanFieldEmailVerified],
|
field: userFields[domain.UserHumanFieldEmailVerified],
|
||||||
op: op,
|
op: op,
|
||||||
},
|
},
|
||||||
|
50
backend/v3/storage/database/statement.go
Normal file
50
backend/v3/storage/database/statement.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Instruction string
|
||||||
|
|
||||||
|
const (
|
||||||
|
NowInstruction Instruction = "NOW()"
|
||||||
|
NullInstruction Instruction = "NULL"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StatementBuilder struct {
|
||||||
|
strings.Builder
|
||||||
|
args []any
|
||||||
|
existingArgs map[any]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *StatementBuilder) WriteArg(arg any) {
|
||||||
|
b.WriteString(b.AppendArg(arg))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *StatementBuilder) AppendArg(arg any) (placeholder string) {
|
||||||
|
if b.existingArgs == nil {
|
||||||
|
b.existingArgs = make(map[any]string)
|
||||||
|
}
|
||||||
|
if placeholder, ok := b.existingArgs[arg]; ok {
|
||||||
|
return placeholder
|
||||||
|
}
|
||||||
|
if instruction, ok := arg.(Instruction); ok {
|
||||||
|
return string(instruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.args = append(b.args, arg)
|
||||||
|
placeholder = "$" + strconv.Itoa(len(b.args))
|
||||||
|
b.existingArgs[arg] = placeholder
|
||||||
|
return placeholder
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *StatementBuilder) AppendArgs(args ...any) {
|
||||||
|
for _, arg := range args {
|
||||||
|
b.AppendArg(arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *StatementBuilder) Args() []any {
|
||||||
|
return b.args
|
||||||
|
}
|
Reference in New Issue
Block a user