4.7 KiB
search — nurse discovery
Client seam
client/src/services/search/·USE_SEARCH_MOCK = false(real) · 2 server ops Last verified: 2026-07-30 against commitd3ec723and swagger.v1.json (2026-07-29).
Two endpoints, both anonymous, both reading a projected index rather than joining live tables.
Endpoints
| Method | Path | Auth | Verdict |
|---|---|---|---|
| GET | /api/v1/search/nurses |
anonymous | wired · paginated — page + page_size |
| GET | /api/v1/nurses/{nurseId}/profile |
anonymous | wired |
No phantoms. POST /api/v1/admin_search/rebuild_index is the index rebuild one-shot and lives in
admin.md; nurses/{id}/reviews and /review_tags are in reviews.md;
nurses/{id}/trust_badge is in verification.md.
The one endpoint with snake_case query params
GET /api/v1/search/nurses is the only endpoint whose query parameters are snake_case:
service_category_id city_id district_id nurse_gender min_price max_price page page_size
service_category_id and city_id are required. This matters because it is also the only endpoint where
paging is declared as page_size, not pageSize — and model binding is case-insensitive, not
separator-insensitive, so sending pageSize here binds nothing and silently yields the default page size.
The client's searchClientApi already sends page_size correctly. See
../api-contract.md.
is_searchable — the four conditions
A nurse variant appears in results only when all four hold. Anything that flips one of them must
maintain the index in the same unit of work (ISearchIndexMaintainer):
- the nurse's verification aggregate is
approved— verification.md isAcceptingBookingsis true — profiles.md- the variant is active — catalog.md
- the nurse has at least one service area covering the queried city — service-areas.md
districtId = null means whole-city on both sides of the match. A customer filtering by district D
must see a nurse whose area names D and a nurse whose area is whole-city. A query that drops the
nulls silently hides every whole-city nurse. See geography.md.
Shape rules the JSON does not express
- The index row is denormalised (REQ-012, delivered):
nurseName,avatarUrlanddistanceKmare onNurseSearchResultDto, so the result card needs no per-row fetch.priceis a digit string. distanceKmis nullable — null when either side has no resolved coordinates. The card omits the distance chip rather than showing 0.GET nurses/{id}/profileaggregates identity, bio, specialties, the full services list and the latest review intoNursePublicProfileDto(REQ-012). One request builds the whole profile screen.attributeChipsis a server-composed display list, not a code vocabulary — render it, do not map it.inoMembershipon the public profile is a boolean summary of the INO verification step, not the step itself. The per-step detail is the still-open REQ-043.- The index is maintained inline inside each source write's transaction, not by a background job — so a profile change is visible in search immediately, and a failed index write fails the source write.
Search:Backendselects the implementation: unset orsql→SqlNurseSearch. Any other value throws at startup — Elasticsearch is deferred, and the config fails loudly rather than silently degrading.
Enums
| Vocabulary | Values |
|---|---|
NurseGender (filter + result field) |
male female |
SearchSort (client) |
rating — the only sort implemented |
nurse_gender accepts male/female; omit the param for "any" — there is no any value on this
filter, unlike requiredCaregiverGender on a booking request.
Open REQs
| REQ | Status | Effect |
|---|---|---|
| REQ-040 | open | No topReviewTag on the index row. The C2 card renders without the tag chip |
| REQ-041 | open | No free-text q over nurse/variant/category names. Discovery is filter-only |
| REQ-042 | open | NursePublicProfileDto has no nurseGender — the index row has it, the profile does not, so the C3 screen cannot show the gender chip |
| REQ-066 | open, narrower than filed | search/nurses is already anonymous. What is missing is the rate limit — SearchController carries no [EnableRateLimiting], so guest browse falls to the 100/min global per-IP limiter |
| REQ-067 | open, narrower than filed | nurses/{id}/profile is already anonymous. What is missing is the privacy review of the payload for unauthenticated callers |