feat: add user search

This commit is contained in:
Krispire Admin
2026-05-31 10:53:37 +05:30
parent 0969887008
commit d3ad443fde
4 changed files with 118 additions and 1 deletions
+54
View File
@@ -152,6 +152,60 @@ func (r *UserRepo) UpdatePassword(ctx context.Context, id, newHash string) error
return nil
}
// Search returns users whose name or handle matches `q` (ILIKE
// substring). Leading "@" is stripped — callers can pass either
// "@foo" or "foo". Ordering bubbles handle-prefix matches above
// name-prefix above the rest, then alphabetic by name as the
// tiebreaker.
//
// Postgres ILIKE is fine at our user-table scale (low six figures
// max). When the table grows enough to warrant fuzzy / trigram
// matching, add a pg_trgm GIN index and switch to similarity().
func (r *UserRepo) Search(ctx context.Context, q string, limit int) ([]model.User, error) {
cleaned := strings.TrimPrefix(strings.TrimSpace(q), "@")
if cleaned == "" {
return []model.User{}, nil
}
if limit <= 0 || limit > 50 {
limit = 20
}
substring := "%" + cleaned + "%"
prefix := cleaned + "%"
const sql = `
SELECT id, email, handle, password_hash, name, avatar_url, role, plan,
email_verified_at, token_version, created_at, updated_at
FROM users
WHERE name ILIKE $1 OR handle::text ILIKE $1
ORDER BY
CASE
WHEN handle::text ILIKE $2 THEN 0
WHEN name ILIKE $2 THEN 1
ELSE 2
END,
name
LIMIT $3`
rows, err := r.pg.Pool.Query(ctx, sql, substring, prefix, limit)
if err != nil {
return nil, fmt.Errorf("search users: %w", err)
}
defer rows.Close()
out := []model.User{}
for rows.Next() {
var u model.User
if err := rows.Scan(
&u.ID, &u.Email, &u.Handle, &u.PasswordHash, &u.Name, &u.AvatarURL,
&u.Role, &u.Plan, &u.EmailVerifiedAt, &u.TokenVersion,
&u.CreatedAt, &u.UpdatedAt,
); err != nil {
return nil, fmt.Errorf("scan user: %w", err)
}
out = append(out, u)
}
return out, rows.Err()
}
// GetReadingPrefs returns the raw JSONB bytes for the user's reading
// prefs (empty object when never set). The frontend owns the shape, so
// we round-trip the payload verbatim — no server-side schema enforced.