From 050aa7dd489e5ab3ece6222d1141ef6cb30e7b9a Mon Sep 17 00:00:00 2001 From: adlerhurst <27845747+adlerhurst@users.noreply.github.com> Date: Wed, 30 Apr 2025 09:30:48 +0200 Subject: [PATCH] v4 without errors --- backend/v3/domain/domain.go | 8 +- backend/v3/domain/email_verification.go | 18 ++-- backend/v3/domain/set_email.go | 5 +- backend/v3/domain/user.go | 79 +++++++++++---- .../database/repository/stmt/v4/column.go | 11 +++ .../repository/stmt/v4/inheritance.sql | 40 +++----- .../database/repository/stmt/v4/query.go | 66 +++++++++++++ .../database/repository/stmt/v4/user.go | 96 +++++++++++++------ .../database/repository/stmt/v4/user_human.go | 38 +++++--- .../repository/stmt/v4/user_machine.go | 37 ++++++- .../database/repository/stmt/v4/user_test.go | 46 ++++----- 11 files changed, 314 insertions(+), 130 deletions(-) create mode 100644 backend/v3/storage/database/repository/stmt/v4/query.go diff --git a/backend/v3/domain/domain.go b/backend/v3/domain/domain.go index 8f28acf00d..2b76c86273 100644 --- a/backend/v3/domain/domain.go +++ b/backend/v3/domain/domain.go @@ -15,7 +15,7 @@ var ( userCodeAlgorithm crypto.EncryptionAlgorithm tracer tracing.Tracer - // userRepo func(database.QueryExecutor) UserRepository + userRepo func(database.QueryExecutor) UserRepository instanceRepo func(database.QueryExecutor) InstanceRepository cryptoRepo func(database.QueryExecutor) CryptoRepository orgRepo func(database.QueryExecutor) OrgRepository @@ -39,9 +39,9 @@ func SetTracer(t tracing.Tracer) { tracer = t } -// func SetUserRepository(repo func(database.QueryExecutor) UserRepository) { -// userRepo = repo -// } +func SetUserRepository(repo func(database.QueryExecutor) UserRepository) { + userRepo = repo +} func SetInstanceRepository(repo func(database.QueryExecutor) InstanceRepository) { instanceRepo = repo diff --git a/backend/v3/domain/email_verification.go b/backend/v3/domain/email_verification.go index 61abfe93d2..9d9be0990f 100644 --- a/backend/v3/domain/email_verification.go +++ b/backend/v3/domain/email_verification.go @@ -2,8 +2,7 @@ package domain import ( "context" - - v4 "github.com/zitadel/zitadel/backend/v3/storage/database/repository/stmt/v4" + "time" ) type EmailVerifiedCommand struct { @@ -27,7 +26,8 @@ var ( // Execute implements [Commander] func (cmd *EmailVerifiedCommand) Execute(ctx context.Context, opts *CommandOpts) error { - return userRepo(opts.DB).Human().ByID(cmd.UserID).Exec().SetEmailVerified(ctx, cmd.Email.Address) + repo := userRepo(opts.DB).Human() + return repo.Update(ctx, repo.IDCondition(cmd.UserID), repo.SetEmailVerifiedAt(time.Time{})) } // applyOnSetEmail implements [SetEmailOpt] @@ -78,8 +78,9 @@ func (cmd *SendCodeCommand) ensureEmail(ctx context.Context, opts *CommandOpts) if cmd.Email != "" { return nil } - email, err := userRepo(opts.DB).Human().ByID(cmd.UserID).Exec().GetEmail(ctx) - if err != nil || email.IsVerified { + repo := userRepo(opts.DB).Human() + email, err := repo.GetEmail(ctx, repo.IDCondition(cmd.UserID)) + if err != nil || !email.VerifiedAt.IsZero() { return err } cmd.Email = email.Address @@ -137,10 +138,9 @@ func (cmd *ReturnCodeCommand) ensureEmail(ctx context.Context, opts *CommandOpts if cmd.Email != "" { return nil } - user := v4.UserRepository(opts.DB) - user.WithCondition(user.IDCondition(cmd.UserID)) - email, err := user.he.GetEmail(ctx) - if err != nil || email.IsVerified { + repo := userRepo(opts.DB).Human() + email, err := repo.GetEmail(ctx, repo.IDCondition(cmd.UserID)) + if err != nil || !email.VerifiedAt.IsZero() { return err } cmd.Email = email.Address diff --git a/backend/v3/domain/set_email.go b/backend/v3/domain/set_email.go index bae2b20313..af7a282591 100644 --- a/backend/v3/domain/set_email.go +++ b/backend/v3/domain/set_email.go @@ -38,7 +38,8 @@ func (cmd *SetEmailCommand) Execute(ctx context.Context, opts *CommandOpts) erro } defer func() { err = close(ctx, err) }() // userStatement(opts.DB).Human().ByID(cmd.UserID).SetEmail(ctx, cmd.Email) - err = userRepo(opts.DB).Human().ByID(cmd.UserID).Exec().SetEmail(ctx, cmd.Email) + repo := userRepo(opts.DB).Human() + err = repo.Update(ctx, repo.IDCondition(cmd.UserID), repo.SetEmailAddress(cmd.Email)) if err != nil { return err } @@ -59,6 +60,6 @@ func (cmd *SetEmailCommand) Events() []*eventstore.Event { } // applyOnCreateHuman implements [CreateHumanOpt]. -func (cmd *SetEmailCommand) applyOnCreateHuman(createUserCmd *CreateUserCommand[Human]) { +func (cmd *SetEmailCommand) applyOnCreateHuman(createUserCmd *CreateUserCommand) { createUserCmd.email = cmd } diff --git a/backend/v3/domain/user.go b/backend/v3/domain/user.go index 6adbc18e5a..5c07313eb6 100644 --- a/backend/v3/domain/user.go +++ b/backend/v3/domain/user.go @@ -9,13 +9,13 @@ import ( type userColumns interface { // TODO: move v4.columns to domain - InstanceIDColumn() column - OrgIDColumn() column - IDColumn() column - usernameColumn() column - CreatedAtColumn() column - UpdatedAtColumn() column - DeletedAtColumn() column + InstanceIDColumn() v4.Column + OrgIDColumn() v4.Column + IDColumn() v4.Column + usernameColumn() v4.Column + CreatedAtColumn() v4.Column + UpdatedAtColumn() v4.Column + DeletedAtColumn() v4.Column } type userConditions interface { @@ -29,30 +29,35 @@ type userConditions interface { DeletedAtCondition(op v4.NumberOperator, deletedAt time.Time) v4.Condition } +type userChanges interface { + SetUsername(username string) v4.Change +} + type UserRepository interface { userColumns userConditions + userChanges // TODO: move condition to domain - WithCondition(condition v4.Condition) UserRepository - Get(ctx context.Context) (*User, error) - List(ctx context.Context) ([]*User, error) - Create(ctx context.Context, user *User) error - Delete(ctx context.Context) error + Get(ctx context.Context, opts v4.QueryOption) (*User, error) + List(ctx context.Context, opts v4.QueryOption) ([]*User, error) + Delete(ctx context.Context, condition v4.Condition) error Human() HumanRepository Machine() MachineRepository } type humanColumns interface { - FirstNameColumn() column - LastNameColumn() column - EmailAddressColumn() column - EmailVerifiedAtColumn() column - PhoneNumberColumn() column - PhoneVerifiedAtColumn() column + userColumns + FirstNameColumn() v4.Column + LastNameColumn() v4.Column + EmailAddressColumn() v4.Column + EmailVerifiedAtColumn() v4.Column + PhoneNumberColumn() v4.Column + PhoneVerifiedAtColumn() v4.Column } type humanConditions interface { + userConditions FirstNameCondition(op v4.TextOperator, firstName string) v4.Condition LastNameCondition(op v4.TextOperator, lastName 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 } +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 { humanColumns 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 - 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 { - DescriptionColumn() column + userColumns + DescriptionColumn() v4.Column } type machineConditions interface { + userConditions DescriptionCondition(op v4.TextOperator, description string) v4.Condition } +type machineChanges interface { + userChanges + SetDescription(description string) v4.Change +} + type MachineRepository interface { machineColumns machineConditions + machineChanges + + Create(ctx context.Context, user *User) error + Update(ctx context.Context, condition v4.Condition, changes ...v4.Change) error } // type UserRepository interface { @@ -171,6 +203,11 @@ type User struct { v4.User } +type Email struct { + v4.Email + IsVerified bool +} + // type userTraits interface { // isUserTraits() // } diff --git a/backend/v3/storage/database/repository/stmt/v4/column.go b/backend/v3/storage/database/repository/stmt/v4/column.go index 5dec86469b..c19834db13 100644 --- a/backend/v3/storage/database/repository/stmt/v4/column.go +++ b/backend/v3/storage/database/repository/stmt/v4/column.go @@ -50,6 +50,17 @@ 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) } diff --git a/backend/v3/storage/database/repository/stmt/v4/inheritance.sql b/backend/v3/storage/database/repository/stmt/v4/inheritance.sql index 9d15188e28..f7fceb7aed 100644 --- a/backend/v3/storage/database/repository/stmt/v4/inheritance.sql +++ b/backend/v3/storage/database/repository/stmt/v4/inheritance.sql @@ -59,6 +59,8 @@ CREATE TABLE users ( -- , CONSTRAINT fk_instances FOREIGN KEY (instance_id) REFERENCES instances(id) ) INHERITS (org_objects); +CREATE INDEX idx_users_username ON users(username); + CREATE TRIGGER set_updated_at BEFORE UPDATE ON users @@ -74,6 +76,8 @@ CREATE TABLE human_users( , CONSTRAINT fk_instances FOREIGN KEY (instance_id) REFERENCES instances(id) ) INHERITS (users); +CREATE INDEX idx_human_users_username ON human_users(username); + CREATE TRIGGER set_updated_at BEFORE UPDATE ON human_users @@ -88,23 +92,15 @@ CREATE TABLE machine_users( , CONSTRAINT fk_instances FOREIGN KEY (instance_id) REFERENCES instances(id) ) INHERITS (users); +CREATE INDEX idx_machine_users_username ON machine_users(username); + CREATE TRIGGER set_updated_at BEFORE UPDATE ON machine_users FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); - -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 ( +CREATE VIEW users_view AS ( SELECT id , created_at @@ -113,27 +109,16 @@ SELECT , instance_id , org_id , username - , first_name - , last_name - , description -FROM ( -(SELECT - id - , created_at - , updated_at - , deleted_at - , instance_id - , org_id - , username + , tableoid::regclass::TEXT AS type , first_name , last_name , NULL AS description FROM - human_users) + human_users UNION -(SELECT +SELECT id , created_at , updated_at @@ -141,9 +126,10 @@ UNION , instance_id , org_id , username + , tableoid::regclass::TEXT AS type , NULL AS first_name , NULL AS last_name , description FROM - machine_users) -)); \ No newline at end of file + machine_users +); \ No newline at end of file diff --git a/backend/v3/storage/database/repository/stmt/v4/query.go b/backend/v3/storage/database/repository/stmt/v4/query.go new file mode 100644 index 0000000000..01f22e2c25 --- /dev/null +++ b/backend/v3/storage/database/repository/stmt/v4/query.go @@ -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 + } +} diff --git a/backend/v3/storage/database/repository/stmt/v4/user.go b/backend/v3/storage/database/repository/stmt/v4/user.go index b728fc82c8..4c620c43c6 100644 --- a/backend/v3/storage/database/repository/stmt/v4/user.go +++ b/backend/v3/storage/database/repository/stmt/v4/user.go @@ -29,17 +29,13 @@ type userTrait interface { 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,` + - ` h.first_name, h.last_name, h.email_address, h.email_verified_at, h.phone_number, h.phone_verified_at, m.description` + - ` FROM users u` + - ` 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` +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` + + ` FROM users_view` type user struct { builder statementBuilder client database.QueryExecutor - - condition Condition } func UserRepository(client database.QueryExecutor) *user { @@ -48,20 +44,17 @@ func UserRepository(client database.QueryExecutor) *user { } } -func (u *user) WithCondition(condition Condition) *user { - u.condition = condition - return u -} +func (u *user) List(ctx context.Context, opts ...QueryOption) (users []*User, err error) { + options := new(queryOpts) + for _, opt := range opts { + opt(options) + } -func (u *user) Get(ctx context.Context) (*User, error) { - u.builder.WriteString(userQuery) - u.writeCondition() - return scanUser(u.client.QueryRow(ctx, u.builder.String(), u.builder.args...)) -} - -func (u *user) List(ctx context.Context) (users []*User, err error) { - u.builder.WriteString(userQuery) - u.writeCondition() + u.builder.WriteString(queryUserStmt) + options.writeCondition(&u.builder) + options.writeOrderBy(&u.builder) + options.writeLimit(&u.builder) + options.writeOffset(&u.builder) rows, err := u.client.Query(ctx, u.builder.String(), u.builder.args...) if err != nil { @@ -87,7 +80,23 @@ func (u *user) List(ctx context.Context) (users []*User, err error) { 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 ( + // TODO: change to separate statements and tables createUserCte = `WITH user AS (` + `INSERT INTO users (instance_id, org_id, id, username, type) VALUES ($1, $2, $3, $4, $5)` + ` RETURNING *)` @@ -111,11 +120,24 @@ func (u *user) Create(ctx context.Context, user *User) error { u.builder.WriteString(createMachineStmt) 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 { - return column{name: "u.instance_id"} + return column{name: "instance_id"} } func (u *user) InstanceIDCondition(instanceID string) Condition { @@ -123,7 +145,7 @@ func (u *user) InstanceIDCondition(instanceID string) Condition { } func (u *user) OrgIDColumn() Column { - return column{name: "u.org_id"} + return column{name: "org_id"} } func (u *user) OrgIDCondition(orgID string) Condition { @@ -131,7 +153,7 @@ func (u *user) OrgIDCondition(orgID string) Condition { } func (u *user) IDColumn() Column { - return column{name: "u.id"} + return column{name: "id"} } func (u *user) IDCondition(userID string) Condition { @@ -140,7 +162,7 @@ func (u *user) IDCondition(userID string) Condition { func (u *user) UsernameColumn() Column { return ignoreCaseCol{ - column: column{name: "u.username"}, + column: column{name: "username"}, suffix: "_lower", } } @@ -154,7 +176,7 @@ func (u *user) UsernameCondition(op TextOperator, username string) Condition { } 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 { @@ -162,7 +184,7 @@ func (u *user) CreatedAtCondition(op NumberOperator, createdAt time.Time) Condit } 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 { @@ -170,7 +192,7 @@ func (u *user) UpdatedAtCondition(op NumberOperator, updatedAt time.Time) Condit } func (u *user) DeletedAtColumn() Column { - return column{name: "u.deleted_at"} + return column{name: "deleted_at"} } 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) } -func (u *user) writeCondition() { - if u.condition == nil { +func (u *user) writeCondition(condition Condition) { + if condition == nil { return } 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) { diff --git a/backend/v3/storage/database/repository/stmt/v4/user_human.go b/backend/v3/storage/database/repository/stmt/v4/user_human.go index 3aadb3ab1b..ab13eab18d 100644 --- a/backend/v3/storage/database/repository/stmt/v4/user_human.go +++ b/backend/v3/storage/database/repository/stmt/v4/user_human.go @@ -46,11 +46,11 @@ func (u *user) Human() *userHuman { 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 u.builder.WriteString(userEmailQuery) - u.writeCondition() + u.writeCondition(condition) err := u.client.QueryRow(ctx, u.builder.String(), u.builder.args...).Scan( &email.Address, @@ -63,10 +63,10 @@ func (u *userHuman) GetEmail(ctx context.Context) (*Email, error) { return &email, nil } -func (h userHuman) Update(ctx context.Context, changes ...Change) error { - h.builder.WriteString(`UPDATE human_users h SET `) +func (h userHuman) Update(ctx context.Context, condition Condition, changes ...Change) error { + h.builder.WriteString(`UPDATE human_users SET `) Changes(changes).writeTo(&h.builder) - h.writeCondition() + h.writeCondition(condition) stmt := h.builder.String() @@ -78,7 +78,7 @@ func (h userHuman) SetFirstName(firstName string) Change { } func (h userHuman) FirstNameColumn() Column { - return column{"h.first_name"} + return column{"first_name"} } 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 { - return column{"h.last_name"} + return column{"last_name"} } 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 { return ignoreCaseCol{ - column: column{"h.email_address"}, + column: column{"email_address"}, suffix: "_lower", } } @@ -109,7 +109,7 @@ func (h userHuman) EmailAddressCondition(op TextOperator, email string) Conditio } func (h userHuman) EmailVerifiedAtColumn() Column { - return column{"h.email_verified_at"} + return column{"email_verified_at"} } 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 { - return column{"h.phone_number"} + return column{"phone_number"} } 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 { - return column{"h.phone_verified_at"} + return column{"phone_verified_at"} } 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), ) } + +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) +} diff --git a/backend/v3/storage/database/repository/stmt/v4/user_machine.go b/backend/v3/storage/database/repository/stmt/v4/user_machine.go index 57bb7f14fb..c86f9493af 100644 --- a/backend/v3/storage/database/repository/stmt/v4/user_machine.go +++ b/backend/v3/storage/database/repository/stmt/v4/user_machine.go @@ -24,12 +24,34 @@ func (u *user) Machine() *userMachine { return &userMachine{user: u} } -func (m userMachine) Update(ctx context.Context, cols ...Change) (*Machine, error) { - return nil, nil +func (m userMachine) Update(ctx context.Context, condition Condition, changes ...Change) ([]*Machine, error) { + 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 { - return column{"m.description"} + return column{"description"} } 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 { 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) +} diff --git a/backend/v3/storage/database/repository/stmt/v4/user_test.go b/backend/v3/storage/database/repository/stmt/v4/user_test.go index e4c65efd1b..431599bccb 100644 --- a/backend/v3/storage/database/repository/stmt/v4/user_test.go +++ b/backend/v3/storage/database/repository/stmt/v4/user_test.go @@ -11,32 +11,38 @@ import ( func TestQueryUser(t *testing.T) { t.Run("User filters", func(t *testing.T) { user := v4.UserRepository(nil) - user.WithCondition( - v4.And( - v4.Or( - user.IDCondition("test"), - user.IDCondition("2"), + u, err := user.Get(context.Background(), + v4.WithCondition( + v4.And( + v4.Or( + 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) { user := v4.UserRepository(nil) machine := user.Machine() human := user.Human() - user.WithCondition( - v4.And( - user.UsernameCondition(v4.TextOperatorStartsWithIgnoreCase, "test"), - v4.Or( - machine.DescriptionCondition(v4.TextOperatorStartsWithIgnoreCase, "test"), - human.EmailAddressVerifiedCondition(true), - v4.IsNotNull(machine.DescriptionColumn()), - ), + email, err := human.GetEmail(context.Background(), v4.And( + user.UsernameCondition(v4.TextOperatorStartsWithIgnoreCase, "test"), + v4.Or( + machine.DescriptionCondition(v4.TextOperatorStartsWithIgnoreCase, "test"), + human.EmailAddressVerifiedCondition(true), + 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) { t.Run("update user", func(t *testing.T) { user := v4.UserRepository(nil) - user.WithCondition(user.IDCondition("test")).Human().Update( - context.Background(), - user.SetUsername("test"), - ) - + user.Update(context.Background(), user.IDCondition("test"), user.SetUsername("test")) }) }