Backend child of #383 (Auto-Tune DetailPanel milestone).
Adds GET /api/v1/channels/auto-tune/members?axis=&value=&pageNum=&pageSize= — lists the distinct content sources a proposed auto-tune channel's server-generated SmartCollection resolves to.
What
New MediatR query GetAutoTuneChannelMembers + handler; new ChannelController GET action.
Runs the server-owned AutoTuneAxisMap.GenerateQuery(axis,value) through ISearchIndex.Search (client never sends Lucene, per #69 PR1), then rolls matching leaf items up to distinct sources: parent shows for the TV axes (ItemCount = query-matching episodes, not total), movies for the movie-genre axis.
Reuses existing PagedLibraryBrowseItemsResponseModel / LibraryBrowseItemResponseModel DTOs (no new schema) + LibraryBrowseItemMapper.GetShows/GetMovies. Title-ordered, paged, clamped (pageNum>=0, pageSize default 100 / max 200).
Why search-index, not EF
The built channel's playout comes from the SmartCollection via ISearchIndex (GetSmartCollectionItems); the DetailPanel preview must run the same query through the same index to be faithful. #69's EF preview enumeration is a different granularity (candidate axis values). See docs/decisions.md 2026-07-17 (#384).
Tests / verification
9 new tests (handler: query-gen, blank-value short-circuit, episode->distinct-show roll-up with matching-count, paging, movie axis, empty; controller: route literal, mapping, paging clamp). Local pass green (140 in touched namespaces).
OpenAPI + endpoint-index regenerated; npm run check:api green (d.ts unchanged — reuses existing schema).
Backend child of #383 (Auto-Tune DetailPanel milestone).
Adds `GET /api/v1/channels/auto-tune/members?axis=&value=&pageNum=&pageSize=` — lists the distinct content sources a proposed auto-tune channel's server-generated SmartCollection resolves to.
## What
- New MediatR query `GetAutoTuneChannelMembers` + handler; new `ChannelController` GET action.
- Runs the server-owned `AutoTuneAxisMap.GenerateQuery(axis,value)` through `ISearchIndex.Search` (client never sends Lucene, per #69 PR1), then rolls matching leaf items up to distinct sources: **parent shows** for the TV axes (`ItemCount` = query-matching episodes, not total), **movies** for the movie-genre axis.
- Reuses existing `PagedLibraryBrowseItemsResponseModel` / `LibraryBrowseItemResponseModel` DTOs (no new schema) + `LibraryBrowseItemMapper.GetShows/GetMovies`. Title-ordered, paged, clamped (pageNum>=0, pageSize default 100 / max 200).
## Why search-index, not EF
The built channel's playout comes from the SmartCollection via `ISearchIndex` (`GetSmartCollectionItems`); the DetailPanel preview must run the same query through the same index to be faithful. #69's EF preview enumeration is a different granularity (candidate axis *values*). See `docs/decisions.md` 2026-07-17 (#384).
## Tests / verification
- 9 new tests (handler: query-gen, blank-value short-circuit, episode->distinct-show roll-up with matching-count, paging, movie axis, empty; controller: route literal, mapping, paging clamp). Local pass green (140 in touched namespaces).
- OpenAPI + endpoint-index regenerated; `npm run check:api` green (d.ts unchanged — reuses existing schema).
## Docs
api-conventions §5 (endpoint inventory), decisions.md 2026-07-17, domain-model.
Read-only, catalog-read tier -> cold independent review acceptable per the skip rubric.
fixes #384
Verdict: MERGEABLE. Core logic verified correct: episode->distinct-parent-show roll-up with query-matching (not total) ItemCount; total derived from the metadata join (no counted-but-dropped index/DB drift); deterministic title+id ordering (no page overlap/gap); 10k-cap under-report documented and matching the GetSmartCollectionItems precedent; catalog-read auth tier correct; movie ItemCount=1 intended.
One Low found and fixed in follow-up commit 567486cf:
L1 — a crafted out-of-range numeric ?axis=5 binds past [ApiController]'s auto-400, then GenerateQuery's throw surfaced as a 500. Now short-circuited to an empty result in the handler (matching #69's EnumerateAxis _ => []), with a regression test asserting no search runs. Also tidied the redundant pageSize lower clamp (N4).
The fix commit is a pure defensive guard + test (no concurrency/auth/migration surface); self-verified green (42 tests in the touched namespaces). Nits N2/N3 (multi-metadata-row title edge, no-metadata omission) left as accepted staleness — practically unreachable.
**Independent cold-context adversarial review** (review-only, cross-context agent) — initial diff `ab1982ef`.
**Verdict: MERGEABLE.** Core logic verified correct: episode->distinct-parent-show roll-up with query-matching (not total) ItemCount; `total` derived from the metadata join (no counted-but-dropped index/DB drift); deterministic title+id ordering (no page overlap/gap); 10k-cap under-report documented and matching the `GetSmartCollectionItems` precedent; catalog-read auth tier correct; movie ItemCount=1 intended.
One **Low** found and **fixed** in follow-up commit `567486cf`:
- **L1** — a crafted out-of-range numeric `?axis=5` binds past `[ApiController]`'s auto-400, then `GenerateQuery`'s `throw` surfaced as a 500. Now short-circuited to an empty result in the handler (matching #69's `EnumerateAxis _ => []`), with a regression test asserting no search runs. Also tidied the redundant pageSize lower clamp (N4).
The fix commit is a pure defensive guard + test (no concurrency/auth/migration surface); self-verified green (42 tests in the touched namespaces). Nits N2/N3 (multi-metadata-row title edge, no-metadata omission) left as accepted staleness — practically unreachable.
Review-verdict: MERGEABLE @ 567486cf004f2114337f85e50681dc1343433543
Rebased onto d38181cb (main advanced with #380/#73). Both commits replayed byte-identical in code; the only rebase conflict was in docs/decisions.md/docs/domain-model.md (both sessions appended) — resolved by keeping all entries. No source change since the reviewed 567486cf, so the prior MERGEABLE verdict + L1 fix stand. Re-verified on the rebased tree: 42 tests green (touched namespaces), check:api clean.
**Rebased onto `d38181cb`** (main advanced with #380/#73). Both commits replayed **byte-identical in code**; the only rebase conflict was in `docs/decisions.md`/`docs/domain-model.md` (both sessions appended) — resolved by keeping all entries. No source change since the reviewed `567486cf`, so the prior MERGEABLE verdict + L1 fix stand. Re-verified on the rebased tree: 42 tests green (touched namespaces), `check:api` clean.
Review-verdict: MERGEABLE @ 1be45fbe8862d43f75f600702e7276c1028cf7be
timothy
merged commit ed4b0d45b1 into main2026-07-17 12:43:50 +02:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Backend child of #383 (Auto-Tune DetailPanel milestone).
Adds
GET /api/v1/channels/auto-tune/members?axis=&value=&pageNum=&pageSize=— lists the distinct content sources a proposed auto-tune channel's server-generated SmartCollection resolves to.What
GetAutoTuneChannelMembers+ handler; newChannelControllerGET action.AutoTuneAxisMap.GenerateQuery(axis,value)throughISearchIndex.Search(client never sends Lucene, per #69 PR1), then rolls matching leaf items up to distinct sources: parent shows for the TV axes (ItemCount= query-matching episodes, not total), movies for the movie-genre axis.PagedLibraryBrowseItemsResponseModel/LibraryBrowseItemResponseModelDTOs (no new schema) +LibraryBrowseItemMapper.GetShows/GetMovies. Title-ordered, paged, clamped (pageNum>=0, pageSize default 100 / max 200).Why search-index, not EF
The built channel's playout comes from the SmartCollection via
ISearchIndex(GetSmartCollectionItems); the DetailPanel preview must run the same query through the same index to be faithful. #69's EF preview enumeration is a different granularity (candidate axis values). Seedocs/decisions.md2026-07-17 (#384).Tests / verification
npm run check:apigreen (d.ts unchanged — reuses existing schema).Docs
api-conventions §5 (endpoint inventory), decisions.md 2026-07-17, domain-model.
Read-only, catalog-read tier -> cold independent review acceptable per the skip rubric.
fixes #384
Independent cold-context adversarial review (review-only, cross-context agent) — initial diff
ab1982ef.Verdict: MERGEABLE. Core logic verified correct: episode->distinct-parent-show roll-up with query-matching (not total) ItemCount;
totalderived from the metadata join (no counted-but-dropped index/DB drift); deterministic title+id ordering (no page overlap/gap); 10k-cap under-report documented and matching theGetSmartCollectionItemsprecedent; catalog-read auth tier correct; movie ItemCount=1 intended.One Low found and fixed in follow-up commit
567486cf:?axis=5binds past[ApiController]'s auto-400, thenGenerateQuery'sthrowsurfaced as a 500. Now short-circuited to an empty result in the handler (matching #69'sEnumerateAxis _ => []), with a regression test asserting no search runs. Also tidied the redundant pageSize lower clamp (N4).The fix commit is a pure defensive guard + test (no concurrency/auth/migration surface); self-verified green (42 tests in the touched namespaces). Nits N2/N3 (multi-metadata-row title edge, no-metadata omission) left as accepted staleness — practically unreachable.
Review-verdict: MERGEABLE @ 567486cf004f2114337f85e50681dc1343433543
567486cf00to1be45fbe88Rebased onto
d38181cb(main advanced with #380/#73). Both commits replayed byte-identical in code; the only rebase conflict was indocs/decisions.md/docs/domain-model.md(both sessions appended) — resolved by keeping all entries. No source change since the reviewed567486cf, so the prior MERGEABLE verdict + L1 fix stand. Re-verified on the rebased tree: 42 tests green (touched namespaces),check:apiclean.Review-verdict: MERGEABLE @
1be45fbe88