package users import ( "context" "errors" "strings" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" ) var ErrNotFound = errors.New("user not found") type Store struct { db *pgxpool.Pool } func NewStore(db *pgxpool.Pool) Store { return Store{db: db} } func (s Store) FindByEmail(ctx context.Context, email string) (User, error) { row := s.db.QueryRow(ctx, ` SELECT id::text, email, name, password_hash, role, status, created_at, updated_at FROM users WHERE lower(email) = lower($1) `, strings.TrimSpace(email)) return scanUser(row) } func (s Store) FindByID(ctx context.Context, id string) (User, error) { row := s.db.QueryRow(ctx, ` SELECT id::text, email, name, password_hash, role, status, created_at, updated_at FROM users WHERE id = $1 `, id) return scanUser(row) } func (s Store) List(ctx context.Context) ([]User, error) { rows, err := s.db.Query(ctx, ` SELECT id::text, email, name, password_hash, role, status, created_at, updated_at FROM users ORDER BY created_at DESC `) if err != nil { return nil, err } defer rows.Close() users := []User{} for rows.Next() { user, err := scanUser(rows) if err != nil { return nil, err } users = append(users, user) } return users, rows.Err() } func (s Store) UpsertSuperAdmin(ctx context.Context, email string, passwordHash string) (User, error) { row := s.db.QueryRow(ctx, ` INSERT INTO users (email, name, password_hash, role, status) VALUES ($1, 'Super Admin', $2, 'super_admin', 'active') ON CONFLICT (email) DO UPDATE SET password_hash = EXCLUDED.password_hash, role = 'super_admin', status = 'active', updated_at = now() RETURNING id::text, email, name, password_hash, role, status, created_at, updated_at `, strings.TrimSpace(email), passwordHash) return scanUser(row) } func scanUser(row pgx.Row) (User, error) { var user User err := row.Scan( &user.ID, &user.Email, &user.Name, &user.PasswordHash, &user.Role, &user.Status, &user.CreatedAt, &user.UpdatedAt, ) if errors.Is(err, pgx.ErrNoRows) { return User{}, ErrNotFound } if err != nil { return User{}, err } return user, nil }