46 lines
2.5 KiB
Markdown
46 lines
2.5 KiB
Markdown
# Architect — F-012 Search: interface plus PostgreSQL FTS
|
|
|
|
## Decision
|
|
Split product search from product persistence behind a dedicated catalog port. The HTTP contract stays `GET /products/search`, while the implementation is swappable from PostgreSQL FTS to a future external engine without route changes.
|
|
|
|
## Domain/application
|
|
Add a `ProductSearchRepository` (or equivalent) port in catalog domain:
|
|
- Input: `q`, `limit`, `offset`, optional `brandSlug`, `activeOnly`.
|
|
- Output: `Product[]` for this slice to preserve current API payload shape.
|
|
|
|
`SearchProducts` must depend on the search port, not on `ProductRepository`. Product CRUD remains in `ProductRepository`.
|
|
|
|
## PostgreSQL implementation
|
|
Add `PgProductSearchRepository` under catalog infrastructure.
|
|
|
|
Search scope:
|
|
- Product: `catalog_products.name`, `description`, `seo_title`, `seo_description`.
|
|
- Brand: `brands_brands.name`, `slug`, `seo_title`, `seo_description`.
|
|
- Category: `categories_categories.name`, `slug`, `seo_title`, `seo_description` through `catalog_product_categories`.
|
|
|
|
Ranking/order:
|
|
- When `q` is present, use PostgreSQL full text search with `websearch_to_tsquery('spanish', q)` and `ts_rank_cd`.
|
|
- Stable ordering: rank descending, then `p.created_at DESC`, then `p.name ASC`, then `p.id ASC`.
|
|
- When `q` is absent, keep stable listing order by `p.created_at DESC`, `p.name ASC`, `p.id ASC`.
|
|
- Preserve pagination with bounded `limit` and `offset`.
|
|
- Preserve active-only public search and `brandSlug` filtering.
|
|
|
|
Migration:
|
|
- Add GIN expression indexes for product, brand, and category searchable text. Do not add extensions or new runtime dependencies.
|
|
- Keep migration reversible.
|
|
|
|
## Telemetry
|
|
Measure search duration in the route or use case using `performance.now()` and log a structured event through the existing logger:
|
|
- `event: 'catalog_search'`
|
|
- `queryPresent`, `brandSlug`, `limit`, `offset`, `durationMs`, `resultCount`
|
|
- If `q` is present, include a bounded/sanitized `query` value for popular-search analysis (trimmed max 200; never secrets).
|
|
|
|
## Tests/evidence
|
|
- Unit: `SearchProducts` calls the search port with defaults and active-only true.
|
|
- Unit or repository-level SQL construction: stable ordering and FTS query path should be represented in code and typechecked.
|
|
- Existing integration tests may remain skipped without `TEST_DATABASE_URL`.
|
|
- Required commands: `npm run lint`, `npm run typecheck`, `npm test`, `./scripts/verify.sh`.
|
|
|
|
## Out of scope
|
|
No Elasticsearch, Meilisearch, Algolia, typo tolerance beyond PostgreSQL FTS, search suggestions UI, cache, or Redis persistence in this ticket.
|