v4 without errors

This commit is contained in:
adlerhurst
2025-04-30 09:30:48 +02:00
parent 597781b389
commit 050aa7dd48
11 changed files with 314 additions and 130 deletions

View File

@@ -15,7 +15,7 @@ var (
userCodeAlgorithm crypto.EncryptionAlgorithm userCodeAlgorithm crypto.EncryptionAlgorithm
tracer tracing.Tracer tracer tracing.Tracer
// userRepo func(database.QueryExecutor) UserRepository userRepo func(database.QueryExecutor) UserRepository
instanceRepo func(database.QueryExecutor) InstanceRepository instanceRepo func(database.QueryExecutor) InstanceRepository
cryptoRepo func(database.QueryExecutor) CryptoRepository cryptoRepo func(database.QueryExecutor) CryptoRepository
orgRepo func(database.QueryExecutor) OrgRepository orgRepo func(database.QueryExecutor) OrgRepository
@@ -39,9 +39,9 @@ func SetTracer(t tracing.Tracer) {
tracer = t tracer = t
} }
// func SetUserRepository(repo func(database.QueryExecutor) UserRepository) { func SetUserRepository(repo func(database.QueryExecutor) UserRepository) {
// userRepo = repo userRepo = repo
// } }
func SetInstanceRepository(repo func(database.QueryExecutor) InstanceRepository) { func SetInstanceRepository(repo func(database.QueryExecutor) InstanceRepository) {
instanceRepo = repo instanceRepo = repo

View File

@@ -2,8 +2,7 @@ package domain
import ( import (
"context" "context"
"time"
v4 "github.com/zitadel/zitadel/backend/v3/storage/database/repository/stmt/v4"
) )
type EmailVerifiedCommand struct { type EmailVerifiedCommand struct {
@@ -27,7 +26,8 @@ var (
// Execute implements [Commander] // Execute implements [Commander]
func (cmd *EmailVerifiedCommand) Execute(ctx context.Context, opts *CommandOpts) error { func (cmd *EmailVerifiedCommand) Execute(ctx context.Context, opts *CommandOpts) error {
return userRepo(opts.DB).Human().ByID(cmd.UserID).Exec().SetEmailVerified(ctx, cmd.Email.Address) repo := userRepo(opts.DB).Human()
return repo.Update(ctx, repo.IDCondition(cmd.UserID), repo.SetEmailVerifiedAt(time.Time{}))
} }
// applyOnSetEmail implements [SetEmailOpt] // applyOnSetEmail implements [SetEmailOpt]
@@ -78,8 +78,9 @@ func (cmd *SendCodeCommand) ensureEmail(ctx context.Context, opts *CommandOpts)
if cmd.Email != "" { if cmd.Email != "" {
return nil return nil
} }
email, err := userRepo(opts.DB).Human().ByID(cmd.UserID).Exec().GetEmail(ctx) repo := userRepo(opts.DB).Human()
if err != nil || email.IsVerified { email, err := repo.GetEmail(ctx, repo.IDCondition(cmd.UserID))
if err != nil || !email.VerifiedAt.IsZero() {
return err return err
} }
cmd.Email = email.Address cmd.Email = email.Address
@@ -137,10 +138,9 @@ func (cmd *ReturnCodeCommand) ensureEmail(ctx context.Context, opts *CommandOpts
if cmd.Email != "" { if cmd.Email != "" {
return nil return nil
} }
user := v4.UserRepository(opts.DB) repo := userRepo(opts.DB).Human()
user.WithCondition(user.IDCondition(cmd.UserID)) email, err := repo.GetEmail(ctx, repo.IDCondition(cmd.UserID))
email, err := user.he.GetEmail(ctx) if err != nil || !email.VerifiedAt.IsZero() {
if err != nil || email.IsVerified {
return err return err
} }
cmd.Email = email.Address cmd.Email = email.Address

View File

@@ -38,7 +38,8 @@ func (cmd *SetEmailCommand) Execute(ctx context.Context, opts *CommandOpts) erro
} }
defer func() { err = close(ctx, err) }() defer func() { err = close(ctx, err) }()
// userStatement(opts.DB).Human().ByID(cmd.UserID).SetEmail(ctx, cmd.Email) // userStatement(opts.DB).Human().ByID(cmd.UserID).SetEmail(ctx, cmd.Email)
err = userRepo(opts.DB).Human().ByID(cmd.UserID).Exec().SetEmail(ctx, cmd.Email) repo := userRepo(opts.DB).Human()
err = repo.Update(ctx, repo.IDCondition(cmd.UserID), repo.SetEmailAddress(cmd.Email))
if err != nil { if err != nil {
return err return err
} }
@@ -59,6 +60,6 @@ func (cmd *SetEmailCommand) Events() []*eventstore.Event {
} }
// applyOnCreateHuman implements [CreateHumanOpt]. // applyOnCreateHuman implements [CreateHumanOpt].
func (cmd *SetEmailCommand) applyOnCreateHuman(createUserCmd *CreateUserCommand[Human]) { func (cmd *SetEmailCommand) applyOnCreateHuman(createUserCmd *CreateUserCommand) {
createUserCmd.email = cmd createUserCmd.email = cmd
} }

View File

@@ -9,13 +9,13 @@ import (
type userColumns interface { type userColumns interface {
// TODO: move v4.columns to domain // TODO: move v4.columns to domain
InstanceIDColumn() column InstanceIDColumn() v4.Column
OrgIDColumn() column OrgIDColumn() v4.Column
IDColumn() column IDColumn() v4.Column
usernameColumn() column usernameColumn() v4.Column
CreatedAtColumn() column CreatedAtColumn() v4.Column
UpdatedAtColumn() column UpdatedAtColumn() v4.Column
DeletedAtColumn() column DeletedAtColumn() v4.Column
} }
type userConditions interface { type userConditions interface {
@@ -29,30 +29,35 @@ type userConditions interface {
DeletedAtCondition(op v4.NumberOperator, deletedAt time.Time) v4.Condition DeletedAtCondition(op v4.NumberOperator, deletedAt time.Time) v4.Condition
} }
type userChanges interface {
SetUsername(username string) v4.Change
}
type UserRepository interface { type UserRepository interface {
userColumns userColumns
userConditions userConditions
userChanges
// TODO: move condition to domain // TODO: move condition to domain
WithCondition(condition v4.Condition) UserRepository Get(ctx context.Context, opts v4.QueryOption) (*User, error)
Get(ctx context.Context) (*User, error) List(ctx context.Context, opts v4.QueryOption) ([]*User, error)
List(ctx context.Context) ([]*User, error) Delete(ctx context.Context, condition v4.Condition) error
Create(ctx context.Context, user *User) error
Delete(ctx context.Context) error
Human() HumanRepository Human() HumanRepository
Machine() MachineRepository Machine() MachineRepository
} }
type humanColumns interface { type humanColumns interface {
FirstNameColumn() column userColumns
LastNameColumn() column FirstNameColumn() v4.Column
EmailAddressColumn() column LastNameColumn() v4.Column
EmailVerifiedAtColumn() column EmailAddressColumn() v4.Column
PhoneNumberColumn() column EmailVerifiedAtColumn() v4.Column
PhoneVerifiedAtColumn() column PhoneNumberColumn() v4.Column
PhoneVerifiedAtColumn() v4.Column
} }
type humanConditions interface { type humanConditions interface {
userConditions
FirstNameCondition(op v4.TextOperator, firstName string) v4.Condition FirstNameCondition(op v4.TextOperator, firstName string) v4.Condition
LastNameCondition(op v4.TextOperator, lastName string) v4.Condition LastNameCondition(op v4.TextOperator, lastName string) v4.Condition
EmailAddressCondition(op v4.TextOperator, email string) v4.Condition EmailAddressCondition(op v4.TextOperator, email string) v4.Condition
@@ -63,26 +68,53 @@ type humanConditions interface {
PhoneVerifiedAtCondition(op v4.TextOperator, phoneVerifiedAt string) v4.Condition PhoneVerifiedAtCondition(op v4.TextOperator, phoneVerifiedAt string) v4.Condition
} }
type humanChanges interface {
userChanges
SetFirstName(firstName string) v4.Change
SetLastName(lastName string) v4.Change
SetEmail(address string, verified *time.Time) v4.Change
SetEmailAddress(email string) v4.Change
SetEmailVerifiedAt(emailVerifiedAt time.Time) v4.Change
SetPhone(number string, verifiedAt *time.Time) v4.Change
SetPhoneNumber(phoneNumber string) v4.Change
SetPhoneVerifiedAt(phoneVerifiedAt time.Time) v4.Change
}
type HumanRepository interface { type HumanRepository interface {
humanColumns humanColumns
humanConditions humanConditions
humanChanges
GetEmail(ctx context.Context) (*Email, error) GetEmail(ctx context.Context, condition v4.Condition) (*Email, error)
// TODO: replace any with add email update columns // TODO: replace any with add email update columns
SetEmail(ctx context.Context, columns ...any) error Create(ctx context.Context, user *User) error
Update(ctx context.Context, condition v4.Condition, changes ...v4.Change) error
} }
type machineColumns interface { type machineColumns interface {
DescriptionColumn() column userColumns
DescriptionColumn() v4.Column
} }
type machineConditions interface { type machineConditions interface {
userConditions
DescriptionCondition(op v4.TextOperator, description string) v4.Condition DescriptionCondition(op v4.TextOperator, description string) v4.Condition
} }
type machineChanges interface {
userChanges
SetDescription(description string) v4.Change
}
type MachineRepository interface { type MachineRepository interface {
machineColumns machineColumns
machineConditions machineConditions
machineChanges
Create(ctx context.Context, user *User) error
Update(ctx context.Context, condition v4.Condition, changes ...v4.Change) error
} }
// type UserRepository interface { // type UserRepository interface {
@@ -171,6 +203,11 @@ type User struct {
v4.User v4.User
} }
type Email struct {
v4.Email
IsVerified bool
}
// type userTraits interface { // type userTraits interface {
// isUserTraits() // isUserTraits()
// } // }

View File

@@ -50,6 +50,17 @@ var _ Change = Changes(nil)
var _ Change = (*change[string])(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 { type Column interface {
writeTo(builder *statementBuilder) writeTo(builder *statementBuilder)
} }

View File

@@ -59,6 +59,8 @@ CREATE TABLE users (
-- , CONSTRAINT fk_instances FOREIGN KEY (instance_id) REFERENCES instances(id) -- , CONSTRAINT fk_instances FOREIGN KEY (instance_id) REFERENCES instances(id)
) INHERITS (org_objects); ) INHERITS (org_objects);
CREATE INDEX idx_users_username ON users(username);
CREATE TRIGGER set_updated_at CREATE TRIGGER set_updated_at
BEFORE UPDATE BEFORE UPDATE
ON users ON users
@@ -74,6 +76,8 @@ CREATE TABLE human_users(
, CONSTRAINT fk_instances FOREIGN KEY (instance_id) REFERENCES instances(id) , CONSTRAINT fk_instances FOREIGN KEY (instance_id) REFERENCES instances(id)
) INHERITS (users); ) INHERITS (users);
CREATE INDEX idx_human_users_username ON human_users(username);
CREATE TRIGGER set_updated_at CREATE TRIGGER set_updated_at
BEFORE UPDATE BEFORE UPDATE
ON human_users ON human_users
@@ -88,23 +92,15 @@ CREATE TABLE machine_users(
, CONSTRAINT fk_instances FOREIGN KEY (instance_id) REFERENCES instances(id) , CONSTRAINT fk_instances FOREIGN KEY (instance_id) REFERENCES instances(id)
) INHERITS (users); ) INHERITS (users);
CREATE INDEX idx_machine_users_username ON machine_users(username);
CREATE TRIGGER set_updated_at CREATE TRIGGER set_updated_at
BEFORE UPDATE BEFORE UPDATE
ON machine_users ON machine_users
FOR EACH ROW FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column(); EXECUTE FUNCTION update_updated_at_column();
CREATE VIEW users_view AS (
select u.*, hu.first_name, hu.last_name, mu.description from users u
left join human_users hu on u.instance_id = hu.instance_id and u.org_id = hu.org_id and u.id = hu.id
left join machine_users mu on u.instance_id = mu.instance_id and u.org_id = mu.org_id and u.id = mu.id
-- where
-- u.instance_id = 1
-- and u.org_id = 3
-- and u.id = 7
;
create view users_view as (
SELECT SELECT
id id
, created_at , created_at
@@ -113,27 +109,16 @@ SELECT
, instance_id , instance_id
, org_id , org_id
, username , username
, first_name , tableoid::regclass::TEXT AS type
, last_name
, description
FROM (
(SELECT
id
, created_at
, updated_at
, deleted_at
, instance_id
, org_id
, username
, first_name , first_name
, last_name , last_name
, NULL AS description , NULL AS description
FROM FROM
human_users) human_users
UNION UNION
(SELECT SELECT
id id
, created_at , created_at
, updated_at , updated_at
@@ -141,9 +126,10 @@ UNION
, instance_id , instance_id
, org_id , org_id
, username , username
, tableoid::regclass::TEXT AS type
, NULL AS first_name , NULL AS first_name
, NULL AS last_name , NULL AS last_name
, description , description
FROM FROM
machine_users) machine_users
)); );

View File

@@ -0,0 +1,66 @@
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
}
}

View File

@@ -29,17 +29,13 @@ type userTrait interface {
Type() UserType Type() UserType
} }
const userQuery = `SELECT u.instance_id, u.org_id, u.id, u.username, u.type, u.created_at, u.updated_at, u.deleted_at,` + const queryUserStmt = `SELECT instance_id, org_id, id, username, type, created_at, updated_at, deleted_at,` +
` h.first_name, h.last_name, h.email_address, h.email_verified_at, h.phone_number, h.phone_verified_at, m.description` + ` first_name, last_name, email_address, email_verified_at, phone_number, phone_verified_at, description` +
` FROM users u` + ` FROM users_view`
` LEFT JOIN user_humans h ON u.instance_id = h.instance_id AND u.org_id = h.org_id AND u.id = h.id` +
` LEFT JOIN user_machines m ON u.instance_id = m.instance_id AND u.org_id = m.org_id AND u.id = m.id`
type user struct { type user struct {
builder statementBuilder builder statementBuilder
client database.QueryExecutor client database.QueryExecutor
condition Condition
} }
func UserRepository(client database.QueryExecutor) *user { func UserRepository(client database.QueryExecutor) *user {
@@ -48,20 +44,17 @@ func UserRepository(client database.QueryExecutor) *user {
} }
} }
func (u *user) WithCondition(condition Condition) *user { func (u *user) List(ctx context.Context, opts ...QueryOption) (users []*User, err error) {
u.condition = condition options := new(queryOpts)
return u for _, opt := range opts {
} opt(options)
}
func (u *user) Get(ctx context.Context) (*User, error) { u.builder.WriteString(queryUserStmt)
u.builder.WriteString(userQuery) options.writeCondition(&u.builder)
u.writeCondition() options.writeOrderBy(&u.builder)
return scanUser(u.client.QueryRow(ctx, u.builder.String(), u.builder.args...)) options.writeLimit(&u.builder)
} options.writeOffset(&u.builder)
func (u *user) List(ctx context.Context) (users []*User, err error) {
u.builder.WriteString(userQuery)
u.writeCondition()
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 {
@@ -87,7 +80,23 @@ func (u *user) List(ctx context.Context) (users []*User, err error) {
return users, nil return users, nil
} }
func (u *user) Get(ctx context.Context, opts ...QueryOption) (*User, error) {
options := new(queryOpts)
for _, opt := range opts {
opt(options)
}
u.builder.WriteString(queryUserStmt)
options.writeCondition(&u.builder)
options.writeOrderBy(&u.builder)
options.writeLimit(&u.builder)
options.writeOffset(&u.builder)
return scanUser(u.client.QueryRow(ctx, u.builder.String(), u.builder.args...))
}
const ( const (
// TODO: change to separate statements and tables
createUserCte = `WITH user AS (` + createUserCte = `WITH user AS (` +
`INSERT INTO users (instance_id, org_id, id, username, type) VALUES ($1, $2, $3, $4, $5)` + `INSERT INTO users (instance_id, org_id, id, username, type) VALUES ($1, $2, $3, $4, $5)` +
` RETURNING *)` ` RETURNING *)`
@@ -111,11 +120,24 @@ func (u *user) Create(ctx context.Context, user *User) error {
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.CreatedAt, user.UpdatedAt) return u.client.QueryRow(ctx, u.builder.String(), u.builder.args...).Scan(&user.Dates.CreatedAt, &user.Dates.UpdatedAt)
}
func (u *user) Update(ctx context.Context, condition Condition, changes ...Change) error {
u.builder.WriteString("UPDATE users SET ")
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.writeCondition(condition)
return u.client.Exec(ctx, u.builder.String(), u.builder.args...)
} }
func (u *user) InstanceIDColumn() Column { func (u *user) InstanceIDColumn() Column {
return column{name: "u.instance_id"} return column{name: "instance_id"}
} }
func (u *user) InstanceIDCondition(instanceID string) Condition { func (u *user) InstanceIDCondition(instanceID string) Condition {
@@ -123,7 +145,7 @@ func (u *user) InstanceIDCondition(instanceID string) Condition {
} }
func (u *user) OrgIDColumn() Column { func (u *user) OrgIDColumn() Column {
return column{name: "u.org_id"} return column{name: "org_id"}
} }
func (u *user) OrgIDCondition(orgID string) Condition { func (u *user) OrgIDCondition(orgID string) Condition {
@@ -131,7 +153,7 @@ func (u *user) OrgIDCondition(orgID string) Condition {
} }
func (u *user) IDColumn() Column { func (u *user) IDColumn() Column {
return column{name: "u.id"} return column{name: "id"}
} }
func (u *user) IDCondition(userID string) Condition { func (u *user) IDCondition(userID string) Condition {
@@ -140,7 +162,7 @@ func (u *user) IDCondition(userID string) Condition {
func (u *user) UsernameColumn() Column { func (u *user) UsernameColumn() Column {
return ignoreCaseCol{ return ignoreCaseCol{
column: column{name: "u.username"}, column: column{name: "username"},
suffix: "_lower", suffix: "_lower",
} }
} }
@@ -154,7 +176,7 @@ func (u *user) UsernameCondition(op TextOperator, username string) Condition {
} }
func (u *user) CreatedAtColumn() Column { func (u *user) CreatedAtColumn() Column {
return column{name: "u.created_at"} return column{name: "created_at"}
} }
func (u *user) CreatedAtCondition(op NumberOperator, createdAt time.Time) Condition { func (u *user) CreatedAtCondition(op NumberOperator, createdAt time.Time) Condition {
@@ -162,7 +184,7 @@ func (u *user) CreatedAtCondition(op NumberOperator, createdAt time.Time) Condit
} }
func (u *user) UpdatedAtColumn() Column { func (u *user) UpdatedAtColumn() Column {
return column{name: "u.updated_at"} return column{name: "updated_at"}
} }
func (u *user) UpdatedAtCondition(op NumberOperator, updatedAt time.Time) Condition { func (u *user) UpdatedAtCondition(op NumberOperator, updatedAt time.Time) Condition {
@@ -170,7 +192,7 @@ func (u *user) UpdatedAtCondition(op NumberOperator, updatedAt time.Time) Condit
} }
func (u *user) DeletedAtColumn() Column { func (u *user) DeletedAtColumn() Column {
return column{name: "u.deleted_at"} return column{name: "deleted_at"}
} }
func (u *user) DeletedCondition(isDeleted bool) Condition { func (u *user) DeletedCondition(isDeleted bool) Condition {
@@ -184,12 +206,24 @@ func (u *user) DeletedAtCondition(op NumberOperator, deletedAt time.Time) Condit
return newNumberCondition(u.DeletedAtColumn(), op, deletedAt) return newNumberCondition(u.DeletedAtColumn(), op, deletedAt)
} }
func (u *user) writeCondition() { func (u *user) writeCondition(condition Condition) {
if u.condition == nil { if condition == nil {
return return
} }
u.builder.WriteString(" WHERE ") u.builder.WriteString(" WHERE ")
u.condition.writeTo(&u.builder) condition.writeTo(&u.builder)
}
func (u user) columns() Columns {
return Columns{
u.InstanceIDColumn(),
u.OrgIDColumn(),
u.IDColumn(),
u.UsernameColumn(),
u.CreatedAtColumn(),
u.UpdatedAtColumn(),
u.DeletedAtColumn(),
}
} }
func scanUser(scanner database.Scanner) (*User, error) { func scanUser(scanner database.Scanner) (*User, error) {

View File

@@ -46,11 +46,11 @@ func (u *user) Human() *userHuman {
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) (*Email, error) { func (u *userHuman) GetEmail(ctx context.Context, condition Condition) (*Email, error) {
var email Email var email Email
u.builder.WriteString(userEmailQuery) u.builder.WriteString(userEmailQuery)
u.writeCondition() 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,
@@ -63,10 +63,10 @@ func (u *userHuman) GetEmail(ctx context.Context) (*Email, error) {
return &email, nil return &email, nil
} }
func (h userHuman) Update(ctx context.Context, changes ...Change) error { func (h userHuman) Update(ctx context.Context, condition Condition, changes ...Change) error {
h.builder.WriteString(`UPDATE human_users h SET `) h.builder.WriteString(`UPDATE human_users SET `)
Changes(changes).writeTo(&h.builder) Changes(changes).writeTo(&h.builder)
h.writeCondition() h.writeCondition(condition)
stmt := h.builder.String() stmt := h.builder.String()
@@ -78,7 +78,7 @@ func (h userHuman) SetFirstName(firstName string) Change {
} }
func (h userHuman) FirstNameColumn() Column { func (h userHuman) FirstNameColumn() Column {
return column{"h.first_name"} return column{"first_name"}
} }
func (h userHuman) FirstNameCondition(op TextOperator, firstName string) Condition { func (h userHuman) FirstNameCondition(op TextOperator, firstName string) Condition {
@@ -90,7 +90,7 @@ func (h userHuman) SetLastName(lastName string) Change {
} }
func (h userHuman) LastNameColumn() Column { func (h userHuman) LastNameColumn() Column {
return column{"h.last_name"} return column{"last_name"}
} }
func (h userHuman) LastNameCondition(op TextOperator, lastName string) Condition { func (h userHuman) LastNameCondition(op TextOperator, lastName string) Condition {
@@ -99,7 +99,7 @@ func (h userHuman) LastNameCondition(op TextOperator, lastName string) Condition
func (h userHuman) EmailAddressColumn() Column { func (h userHuman) EmailAddressColumn() Column {
return ignoreCaseCol{ return ignoreCaseCol{
column: column{"h.email_address"}, column: column{"email_address"},
suffix: "_lower", suffix: "_lower",
} }
} }
@@ -109,7 +109,7 @@ func (h userHuman) EmailAddressCondition(op TextOperator, email string) Conditio
} }
func (h userHuman) EmailVerifiedAtColumn() Column { func (h userHuman) EmailVerifiedAtColumn() Column {
return column{"h.email_verified_at"} return column{"email_verified_at"}
} }
func (h *userHuman) EmailAddressVerifiedCondition(isVerified bool) Condition { func (h *userHuman) EmailAddressVerifiedCondition(isVerified bool) Condition {
@@ -144,7 +144,7 @@ func (h userHuman) SetEmail(address string, verified *time.Time) Change {
} }
func (h userHuman) PhoneNumberColumn() Column { func (h userHuman) PhoneNumberColumn() Column {
return column{"h.phone_number"} return column{"phone_number"}
} }
func (h userHuman) SetPhoneNumber(number string) Change { func (h userHuman) SetPhoneNumber(number string) Change {
@@ -156,7 +156,7 @@ func (h userHuman) PhoneNumberCondition(op TextOperator, phoneNumber string) Con
} }
func (h userHuman) PhoneVerifiedAtColumn() Column { func (h userHuman) PhoneVerifiedAtColumn() Column {
return column{"h.phone_verified_at"} return column{"phone_verified_at"}
} }
func (h userHuman) PhoneNumberVerifiedCondition(isVerified bool) Condition { func (h userHuman) PhoneNumberVerifiedCondition(isVerified bool) Condition {
@@ -185,3 +185,19 @@ func (h userHuman) SetPhone(number string, verifiedAt *time.Time) Change {
newUpdatePtrColumn(h.PhoneVerifiedAtColumn(), verifiedAt), newUpdatePtrColumn(h.PhoneVerifiedAtColumn(), verifiedAt),
) )
} }
func (h userHuman) columns() Columns {
return append(h.user.columns(),
h.FirstNameColumn(),
h.LastNameColumn(),
h.EmailAddressColumn(),
h.EmailVerifiedAtColumn(),
h.PhoneNumberColumn(),
h.PhoneVerifiedAtColumn(),
)
}
func (h userHuman) writeReturning(builder *statementBuilder) {
builder.WriteString(" RETURNING ")
h.columns().writeTo(builder)
}

View File

@@ -24,12 +24,34 @@ func (u *user) Machine() *userMachine {
return &userMachine{user: u} return &userMachine{user: u}
} }
func (m userMachine) Update(ctx context.Context, cols ...Change) (*Machine, error) { func (m userMachine) Update(ctx context.Context, condition Condition, changes ...Change) ([]*Machine, error) {
return nil, nil m.builder.WriteString("UPDATE user_machines SET ")
Changes(changes).writeTo(&m.builder)
m.writeCondition(condition)
m.writeReturning()
var machines []*Machine
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 { func (userMachine) DescriptionColumn() Column {
return column{"m.description"} return column{"description"}
} }
func (m userMachine) SetDescription(description string) Change { func (m userMachine) SetDescription(description string) Change {
@@ -39,3 +61,12 @@ func (m userMachine) SetDescription(description string) Change {
func (m userMachine) DescriptionCondition(op TextOperator, description string) Condition { func (m userMachine) DescriptionCondition(op TextOperator, description string) Condition {
return newTextCondition(m.DescriptionColumn(), op, description) return newTextCondition(m.DescriptionColumn(), op, description)
} }
func (m userMachine) columns() Columns {
return append(m.user.columns(), m.DescriptionColumn())
}
func (m *userMachine) writeReturning() {
m.builder.WriteString(" RETURNING ")
m.columns().writeTo(&m.builder)
}

View File

@@ -11,32 +11,38 @@ import (
func TestQueryUser(t *testing.T) { 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)
user.WithCondition( u, err := user.Get(context.Background(),
v4.And( v4.WithCondition(
v4.Or( v4.And(
user.IDCondition("test"), v4.Or(
user.IDCondition("2"), user.IDCondition("test"),
user.IDCondition("2"),
),
user.UsernameCondition(v4.TextOperatorStartsWithIgnoreCase, "test"),
), ),
user.UsernameCondition(v4.TextOperatorStartsWithIgnoreCase, "test"),
), ),
).Get(context.Background()) v4.WithOrderBy(user.CreatedAtColumn()),
)
assert.NoError(t, err)
assert.NotNil(t, u)
}) })
t.Run("machine and human filters", func(t *testing.T) { t.Run("machine and human filters", func(t *testing.T) {
user := v4.UserRepository(nil) user := v4.UserRepository(nil)
machine := user.Machine() machine := user.Machine()
human := user.Human() human := user.Human()
user.WithCondition( email, err := human.GetEmail(context.Background(), v4.And(
v4.And( user.UsernameCondition(v4.TextOperatorStartsWithIgnoreCase, "test"),
user.UsernameCondition(v4.TextOperatorStartsWithIgnoreCase, "test"), v4.Or(
v4.Or( machine.DescriptionCondition(v4.TextOperatorStartsWithIgnoreCase, "test"),
machine.DescriptionCondition(v4.TextOperatorStartsWithIgnoreCase, "test"), human.EmailAddressVerifiedCondition(true),
human.EmailAddressVerifiedCondition(true), v4.IsNotNull(machine.DescriptionColumn()),
v4.IsNotNull(machine.DescriptionColumn()),
),
), ),
) ))
human.GetEmail(context.Background())
assert.NoError(t, err)
assert.NotNil(t, email)
}) })
} }
@@ -56,10 +62,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.WithCondition(user.IDCondition("test")).Human().Update( user.Update(context.Background(), user.IDCondition("test"), user.SetUsername("test"))
context.Background(),
user.SetUsername("test"),
)
}) })
} }