Add explicit ::text cast to $4 parameter in email update case statement to ensure proper type handling before citext conversion and null comparison.
106 lines
3.3 KiB
Go
106 lines
3.3 KiB
Go
package user
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
)
|
|
|
|
type Repository interface {
|
|
List(ctx context.Context) ([]User, error)
|
|
Create(ctx context.Context, input CreateRequest, passwordHash string) (User, error)
|
|
Update(ctx context.Context, userID uuid.UUID, input UpdateRequest, passwordHash *string) (User, error)
|
|
Delete(ctx context.Context, userID uuid.UUID) error
|
|
SetActive(ctx context.Context, userID uuid.UUID, active bool) error
|
|
}
|
|
|
|
type PGRepository struct {
|
|
db *pgxpool.Pool
|
|
}
|
|
|
|
func NewPGRepository(db *pgxpool.Pool) *PGRepository {
|
|
return &PGRepository{db: db}
|
|
}
|
|
|
|
func (r *PGRepository) List(ctx context.Context) ([]User, error) {
|
|
rows, err := r.db.Query(ctx, `
|
|
select u.id, r.id, r.name, u.username::text, u.display_name, coalesce(u.email::text, ''), u.is_active
|
|
from users u
|
|
join roles r on r.id = u.role_id
|
|
where u.deleted_at is null
|
|
order by u.created_at desc
|
|
`)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var users []User
|
|
for rows.Next() {
|
|
var item User
|
|
if err := rows.Scan(&item.ID, &item.RoleID, &item.RoleName, &item.Username, &item.DisplayName, &item.Email, &item.IsActive); err != nil {
|
|
return nil, err
|
|
}
|
|
users = append(users, item)
|
|
}
|
|
return users, rows.Err()
|
|
}
|
|
|
|
func (r *PGRepository) Create(ctx context.Context, input CreateRequest, passwordHash string) (User, error) {
|
|
const query = `
|
|
insert into users (id, role_id, username, display_name, email, password_hash)
|
|
values ($1, (select id from roles where name = $2), $3, $4, nullif($5, ''), $6)
|
|
returning
|
|
id,
|
|
(select id from roles where name = $2),
|
|
$2,
|
|
username::text,
|
|
display_name,
|
|
coalesce(email::text, ''),
|
|
is_active
|
|
`
|
|
|
|
item := User{RoleName: input.Role}
|
|
err := r.db.QueryRow(ctx, query, uuid.New(), input.Role, input.Username, input.DisplayName, input.Email, passwordHash).
|
|
Scan(&item.ID, &item.RoleID, &item.RoleName, &item.Username, &item.DisplayName, &item.Email, &item.IsActive)
|
|
return item, err
|
|
}
|
|
|
|
func (r *PGRepository) SetActive(ctx context.Context, userID uuid.UUID, active bool) error {
|
|
_, err := r.db.Exec(ctx, `update users set is_active = $2, updated_at = now() where id = $1`, userID, active)
|
|
return err
|
|
}
|
|
|
|
func (r *PGRepository) Update(ctx context.Context, userID uuid.UUID, input UpdateRequest, passwordHash *string) (User, error) {
|
|
const query = `
|
|
update users u
|
|
set
|
|
role_id = coalesce((select id from roles where name = $2), u.role_id),
|
|
display_name = coalesce($3, u.display_name),
|
|
email = case when $4::text is null then u.email else nullif($4::text, '')::citext end,
|
|
password_hash = coalesce($5, u.password_hash),
|
|
is_active = coalesce($6, u.is_active),
|
|
updated_at = now()
|
|
where u.id = $1 and u.deleted_at is null
|
|
returning
|
|
u.id,
|
|
u.role_id,
|
|
(select name from roles where id = u.role_id),
|
|
u.username::text,
|
|
u.display_name,
|
|
coalesce(u.email::text, ''),
|
|
u.is_active
|
|
`
|
|
|
|
var item User
|
|
err := r.db.QueryRow(ctx, query, userID, input.Role, input.DisplayName, input.Email, passwordHash, input.IsActive).
|
|
Scan(&item.ID, &item.RoleID, &item.RoleName, &item.Username, &item.DisplayName, &item.Email, &item.IsActive)
|
|
return item, err
|
|
}
|
|
|
|
func (r *PGRepository) Delete(ctx context.Context, userID uuid.UUID) error {
|
|
_, err := r.db.Exec(ctx, `update users set deleted_at = now(), updated_at = now() where id = $1 and deleted_at is null`, userID)
|
|
return err
|
|
}
|