Search indexes — categories, tags, metadata
Job: what Middleware Backend writes on a Core property so Marketplace search can narrow in one Core list call, then finish price / dates / rooms / guests itself.
Frontend never calls these URLs. Winner for the guest is still Middleware properties. Contract for the list: fetchAll (same path on BookDirect Core).
tags=a,b is OR (whereIn). search_by + tags + categories together are AND. Query one tag family per Core call. City stays search_by=meta_key.city so it ANDs with amenities.
What lives where
| Store | What | Query |
|---|---|---|
| Category (one) | hotel | shortlet | apartment | categories=hotel |
| Tags | amenity_{slug}, optional mirrors location_city_{slug}, policy_*, star_rating_{n}, rules_minimum_stay_{n} | tags=amenity_swimming_pool — one prefix family |
| Linked metadata | city, amenity_{slug}=true, min_rate_ngn, lat/lng, check_in_time | search_by=meta_key.city&search=Lagos |
| Middleware | Second+ amenity AND, price range/sort, check-in/out, units_needed, guests vs maxOccupancy | After the Core narrow |
Do not tag stay dates or “rooms free.” Do not tag rules_check_in_14_00 / rules_check_out_12_00 — those stay check_in_time / check_out_time metadata.
Pet: one slug — amenity_pet_friendly. Do not also write rules_pet_allowed.
Middleware writes on create / update
Called by Middleware Backend, not by frontend.
PUT/POSTCore entityextra_data(full object):city,amenities: ["swimming_pool"](slugs),min_rate_ngn,star_rating,policy.- Linked metadata — one row per tick:
POST https://backend-dev.bookdirect.live/public/api/v1/nellalink/meta-data/entity/property/{property_uuid}
[
{ "meta_key": "city", "meta_value": "Lagos", "data_type": "string" },
{ "meta_key": "amenity_swimming_pool", "meta_value": "true", "data_type": "string" },
{ "meta_key": "min_rate_ngn", "meta_value": "30000", "data_type": "string" }
]- Tags (names
^[a-z0-9_]+$). Assignment is additive — remove stalelocation_city_*/ amenity tags when city or ticks change.
POST https://backend-dev.bookdirect.live/public/api/v1/nellalink/tag then tags: [...] on the entity.
| Tag | Mirror of |
|---|---|
amenity_swimming_pool | tick |
location_city_lagos | city slug (Lagos → lagos) |
policy_free_cancellation | policy.freeCancellation |
policy_refund_allowed | extra_data |
policy_group_booking_allowed | extra_data |
policy_caution_fee_fully_refundable | extra_data |
star_rating_5 | star_rating (optional) |
rules_minimum_stay_1 | policy.minStayNights (optional) |
Category: categories: [{ "name": "Hotel" }] → slug hotel.
Recompute min_rate_ngn when rates change (min of active plans, or shortlet base_rate_ngn).
IDOR: Middleware uses the server Core key + owner JWT on writes. Do not let the public browse key POST tags or metadata.
Example queries (Middleware → Core)
Host in playbooks: https://backend-dev.bookdirect.live/public/api/v1/nellalink. Same fetchAll as Nellalink test explorer.
1. City AND one amenity AND type (default Marketplace search)
Use this instead of listing every property.
GET https://backend-dev.bookdirect.live/public/api/v1/nellalink/smart-meta-manager/entity/property?search=Lagos&search_by=meta_key.city&tags=amenity_swimming_pool&categories=hotel&meta_key_to_retrieve=city,min_rate_ngn,amenity_swimming_pool,amenity_free_parking&per_page=20&page=1curl -sS -G 'https://backend-dev.bookdirect.live/public/api/v1/nellalink/smart-meta-manager/entity/property' \
--data-urlencode 'search=Lagos' \
--data-urlencode 'search_by=meta_key.city' \
--data-urlencode 'tags=amenity_swimming_pool' \
--data-urlencode 'categories=hotel' \
--data-urlencode 'meta_key_to_retrieve=city,min_rate_ngn,amenity_swimming_pool,amenity_free_parking' \
--data-urlencode 'per_page=20' \
--data-urlencode 'page=1' \
-H 'Accept: application/json' \
-H 'x-api-key: {ROUTE_FILE_MANAGER_X_API_KEY_CORE_BACKEND}'Then AND extra amenities in process: keep rows whose meta_data.amenity_free_parking is "true" (or extra_data.amenities contains free_parking). Filter/sort min_rate_ngn. Dates / units_needed / guests: availability on those UUIDs.
const slugs = ["swimming_pool", "free_parking"]; // from ?amenities=
const [narrowTag, ...andSlugs] = slugs;
const qs = new URLSearchParams({
search: "Lagos",
search_by: "meta_key.city",
tags: `amenity_${narrowTag}`,
categories: "hotel",
meta_key_to_retrieve: ["city", "min_rate_ngn", ...slugs.map((s) => `amenity_${s}`)].join(","),
per_page: "20",
page: "1",
});
const res = await fetch(
`https://backend-dev.bookdirect.live/public/api/v1/nellalink/smart-meta-manager/entity/property?${qs}`,
{ headers: { Accept: "application/json", "x-api-key": process.env.ROUTE_FILE_MANAGER_X_API_KEY_CORE_BACKEND! } },
);
const page = await res.json();
const rows = (page.data ?? []).filter((row) =>
andSlugs.every(
(s) =>
row.meta_data?.[`amenity_${s}`] === "true" ||
(row.extra_data?.amenities ?? []).includes(s),
),
);
// then min_rate_ngn range/sort; availability/check for dates/rooms; drop maxOccupancy < guestsDo not listAll every property. Do not send tags=amenity_swimming_pool,amenity_free_parking expecting AND.
2. City only (destination page)
GET https://backend-dev.bookdirect.live/public/api/v1/nellalink/smart-meta-manager/entity/property?search=Lagos&search_by=meta_key.city&categories=hotel&per_page=20&page=13. City AND one policy (no amenity)
GET https://backend-dev.bookdirect.live/public/api/v1/nellalink/smart-meta-manager/entity/property?search=Lagos&search_by=meta_key.city&tags=policy_free_cancellation&per_page=204. OR cities (use location tags — do not mix amenity tags here)
GET https://backend-dev.bookdirect.live/public/api/v1/nellalink/smart-meta-manager/entity/property?tags=location_city_lagos,location_city_uyo&categories=hotel&per_page=205. Do not use (location OR amenity)
GET .../entity/property?tags=location_city_lagos,amenity_swimming_poolReturns Lagos or pool. Wrong for Marketplace.
After Core: Middleware only
| Guest param | Not Core | Middleware |
|---|---|---|
| Second amenity AND | tags=a,b is OR | amenity_* / extra_data.amenities slugs |
minPrice / maxPrice / price sort | no numeric sort_by | min_rate_ngn (or cheapest plan until denormalized) |
check_in / check_out | no overlap engine | POST .../availability/check |
units_needed | units on room | available >= units_needed |
guests | drop plans with maxOccupancy < guests |
Frontend still calls one Middleware URL. Tick-list slugs: Frontend amenity catalog.