ML-176.1: support search in collection api controller

This commit is contained in:
Claudio Ortolina
2026-05-10 14:52:01 +01:00
parent 12747738af
commit a3bc6d7cf3
3 changed files with 141 additions and 11 deletions
@@ -1,10 +1,11 @@
---
id: ML-176.1
title: "API: accept search query parameter on GET /api/v1/collection"
status: To Do
assignee: []
status: Done
assignee:
- pi
created_date: "2026-05-10 13:35"
updated_date: "2026-05-10 13:41"
updated_date: "2026-05-10 13:49"
labels:
- api
- backend
@@ -44,12 +45,12 @@ The existing FTS5 search in `Records.Search` handles the query — no new search
<!-- AC:BEGIN -->
- [ ] #1 GET /api/v1/collection returns all records when q is absent (existing behavior unchanged)
- [ ] #2 GET /api/v1/collection?q=beatles returns only records matching FTS5 query for 'beatles'
- [ ] #3 GET /api/v1/collection?q= returns all records (empty string treated as no query)
- [ ] #4 GET /api/v1/collection?q=beatles&limit=5&offset=0 respects pagination with search
- [ ] #5 Response format matches existing schema: {total, limit, offset, records} with all record fields
- [ ] #6 Controller test covers: no q param, empty q, populated q, q with no matches, q with pagination
- [x] #1 GET /api/v1/collection returns all records when q is absent (existing behavior unchanged)
- [x] #2 GET /api/v1/collection?q=beatles returns only records matching FTS5 query for 'beatles'
- [x] #3 GET /api/v1/collection?q= returns all records (empty string treated as no query)
- [x] #4 GET /api/v1/collection?q=beatles&limit=5&offset=0 respects pagination with search
- [x] #5 Response format matches existing schema: {total, limit, offset, records} with all record fields
- [x] #6 Controller test covers: no q param, empty q, populated q, q with no matches, q with pagination
<!-- AC:END -->
## Implementation Plan
@@ -140,3 +141,52 @@ No cost impact. No external API calls, no additional storage, no compute changes
No documentation updates needed. The API endpoint already exists in `router.ex`. The `q` parameter is additive and optional — existing API consumers are unaffected. If project API docs exist externally, note that `GET /api/v1/collection` now accepts `?q=<search term>` as an optional parameter, but this is self-documenting via the REST pattern.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Implementation complete. Changes:
1. **Controller** (`lib/music_library_web/controllers/collection_controller.ex`):
- Added `query = normalize_query(params["q"])` in `index/2`
- Pass `query` to `search_records_count/1` and `search_records/2` instead of hardcoded `""`
- Added private `normalize_query/1` helper: nil → "", "" → "", otherwise passthrough
2. **Tests** (`test/music_library_web/controllers/collection_controller_test.exs`):
- 5 new tests added:
- `q` param matches → returns filtered results
- Empty `q` string → returns all records
- `q` with no matches → returns `total: 0`, empty `records`
- `q` with pagination → respects limit/offset on filtered results
- Response shape validation → confirms schema is unchanged
3. **Full test suite**: 968 passed (0 failures), no regressions
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
## Summary
Extended `GET /api/v1/collection` to accept an optional `q` query parameter for FTS5 full-text search.
### Changes
**Controller** (`lib/music_library_web/controllers/collection_controller.ex`):
- `index/2` now reads `params["q"]` and normalizes nil/empty → `""` via a new private `normalize_query/1` helper
- Passes the query string to the existing `Collection.search_records_count/1` and `Collection.search_records/2` pipeline (previously hardcoded `""`)
**Tests** (`test/music_library_web/controllers/collection_controller_test.exs`):
- 5 new tests: matching query, empty query string, no-match query, pagination with search, and response shape validation
### Key points
- No new search logic — reuses existing `Records.Search.build_search/3` which already handled empty queries as a no-op
- Response format unchanged — `CollectionJSON.index/1` returns the same shape
- No route/schema/migration changes required
- Full suite: 968 passed, 0 regressions
<!-- SECTION:FINAL_SUMMARY:END -->