feat: add username and email user search filters (#27922)

## Summary

User search can now resolve exact `email:` and `username:` terms through
`GET /api/v2/users` instead of only supporting fuzzy free-text matches.
The database query already had exact email and username filters; this
wires the public search parser and API handler to those filters so
clients can ask for a single user by email without fetching every user
or depending on substring matching.

This is the API half of coder/terraform-provider-coderd#403: that
provider PR adds `data.coderd_user.email`, and this PR gives it an
efficient exact lookup path.

## Testing

- `go test ./coderd/searchquery -run '^TestSearchUsers$' -count=1`
- `go test ./coderd -run '^TestGetUsersFilter$' -count=1`
- Live API test:
  - Built local enterprise Coder from this branch.
- Started Coder on `http://127.0.0.1:39991` against a clean Postgres
database.
  - Created `lookup-target@example.com`.
- Verified `GET /api/v2/users?q=email:LOOKUP-TARGET@EXAMPLE.COM&limit=2`
returned exactly one user:

```json
{
  "count": 1,
  "users": [
    {
      "id": "efc6f909-ce0a-4731-bd2f-6e4df417aaa7",
      "username": "lookup-target",
      "email": "lookup-target@example.com"
    }
  ]
}
```

---

![flow.ai](https://img.shields.io/badge/Built_with-flow.ai-6366f1)
![Codex](https://img.shields.io/badge/GPT--5-000000)

---------

Co-authored-by: Ethan Dickson <ethanndickson@gmail.com>
This commit is contained in:
Wyatt Fry
2026-08-16 18:18:02 +05:00
committed by GitHub
co-authored by Ethan Dickson
parent af90d8e2be
commit a005e5cd22
10 changed files with 160 additions and 52 deletions
+12
View File
@@ -60,6 +60,18 @@ WHERE
user_name ILIKE concat('%', @name, '%')
ELSE true
END
-- Filter by exact username
AND CASE
WHEN @exact_username :: text != '' THEN
lower(user_username) = lower(@exact_username)
ELSE true
END
-- Filter by exact email
AND CASE
WHEN @exact_email :: text != '' THEN
lower(user_email) = lower(@exact_email)
ELSE true
END
-- Filter by status
AND CASE
-- @status needs to be a text because it can be empty, If it was