package store import ( "context" "errors" "github.com/jackc/pgconn" "github.com/jackc/pgerrcode" "github.com/jackc/pgx/v5" ) // CreateUser inserts a new user returning the created record or ErrDuplicate on conflict. func (s *Store) CreateUser(ctx context.Context, username, displayName, passwordHash string) (*User, error) { var u User err := s.pool.QueryRow(ctx, ` INSERT INTO users(username, display_name, password_hash) VALUES($1, $2, $3) RETURNING id, username, display_name, password_hash, avatar, created_at `, username, displayName, passwordHash).Scan( &u.ID, &u.Username, &u.DisplayName, &u.PasswordHash, &u.Avatar, &u.CreatedAt, ) if err != nil { var pgErr *pgconn.PgError if errors.As(err, &pgErr) && pgErr.Code == pgerrcode.UniqueViolation { return nil, ErrDuplicate } return nil, err } return &u, nil } // GetUserByUsername retrieves a user by username or returns ErrNotFound. func (s *Store) GetUserByUsername(ctx context.Context, username string) (*User, error) { var u User err := s.pool.QueryRow(ctx, ` SELECT id, username, display_name, password_hash, avatar, created_at FROM users WHERE username=$1 `, username).Scan( &u.ID, &u.Username, &u.DisplayName, &u.PasswordHash, &u.Avatar, &u.CreatedAt, ) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return nil, ErrNotFound } return nil, err } return &u, nil } // GetUserByID retrieves a user by ID or returns ErrNotFound. func (s *Store) GetUserByID(ctx context.Context, id int64) (*User, error) { var u User err := s.pool.QueryRow(ctx, ` SELECT id, username, display_name, password_hash, avatar, created_at FROM users WHERE id=$1 `, id).Scan( &u.ID, &u.Username, &u.DisplayName, &u.PasswordHash, &u.Avatar, &u.CreatedAt, ) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return nil, ErrNotFound } return nil, err } return &u, nil } // SearchUsers returns users matching query in username or display_name via ILIKE. // Empty query returns an empty slice. func (s *Store) SearchUsers(ctx context.Context, query string, limit int) ([]User, error) { if query == "" { return []User{}, nil } pattern := "%" + query + "%" rows, err := s.pool.Query(ctx, ` SELECT id, username, display_name, password_hash, avatar, created_at FROM users WHERE username ILIKE $1 OR display_name ILIKE $1 ORDER BY username LIMIT $2 `, pattern, limit) if err != nil { return nil, err } defer rows.Close() var users []User for rows.Next() { var u User if err := rows.Scan( &u.ID, &u.Username, &u.DisplayName, &u.PasswordHash, &u.Avatar, &u.CreatedAt, ); err != nil { return nil, err } users = append(users, u) } return users, nil }