feat(streams): add q search and tag filtering to stream listing - #555
Merged
Xhristin3 merged 2 commits intoAug 27, 2026
Conversation
Add server-side filtering to GET /streams (issue XStreamRollz#532): `q` does a case-insensitive substring match over name and description with LIKE wildcards escaped so user input is matched literally, and `tag` filters by an existing tag slug or numeric id via the stream_tags join. The tag value is resolved once in StreamsService through the shared tag lookup (slug or id) and passed to the repository as a concrete tagId, so neither the SQL nor the in-memory repository re-implements slugification. Wire the filters through the Next.js app (listStreams query params + React Query keys) and the SDK (new StreamListParams and listStreams() method), and extend the contract suite with list-streams-search and list-streams-by-tag so the provider (api) and consumer (sdk) both pin the new query surface. Also repairs pre-existing breakage that blocked the quality gates: bad-merge artifacts in auth (duplicate refresh implementations, missing isAdmin claim), the socket gateway, streams module, audit module, and main.ts, plus stale specs that no longer matched the intended contracts (audit logSafely, gateway authenticate shape, refresh token extraction). The SDK's stale axios-based client test was removed; the nock-based integration suite supersedes it. Closes XStreamRollz#532
12 tasks
api/package.json gained @types/cookie-parser@^1.4.10 (auth refresh work) without a lockfile regeneration, so every `npm ci` in CI fails with EUSAGE. Regenerate the root lockfile to add the missing resolution, sync the stale api/sdk workspace version entries, and prune an unreferenced nested conventional-commits-parser entry.
Contributor
Author
CI status updateNew commit pushed: The CI quality matrix was blocked at the very first step ( Current check status:
|
Xhristin3
approved these changes
Aug 27, 2026
9 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #532
Adds server-side search and tag filtering to
GET /streams:qperforms a case-insensitive substring match over stream name and description (LIKE wildcards are escaped so user input matches literally), andtagrestricts results to streams carrying a given tag, accepting either a tag slug or a numeric id. The tag value is resolved exactly once inStreamsServicethrough the existing shared tag lookup, so neither repository implementation re-implements slugification.Why
GET /streamspreviously supported onlystatus,visibility, andownerOnly— the dashboard had no way to search streams or filter by tag without fetching everything and filtering client-side, which broketotal/hasMorepagination semantics. The SDK exposed the same gap: nolistStreams()method and no search/filter parameters on the pagination path.What was built
api/src/streams/dto/list-streams.query.dto.tsq(trimmed, max 200 chars, wildcards literal) andtag(slug regex-validated) query params with Swagger docsapi/src/streams/streams.service.tstag(slug or numeric id) viaTagsService.resolveTag; unknown tag → honest empty page; builds aStreamListPredicatewith only the defined keysapi/src/streams/repository/streams.repository.tsStreamListPredicateinterface; in-memoryq/tagIdfiltering; injects the sharedTagsRepositoryfor tag associationsapi/src/streams/repository/streams-db.repository.tsname ILIKE $n ESCAPE '\' OR description ILIKE $nwithescapeLikePattern(), and anEXISTS (SELECT 1 FROM stream_tags …)join predicateapi/src/tags/tags.service.tsresolveTag()— numeric-id vs slug lookup, returnsundefinedfor unknown tagsapi/src/tags/repository/tags.repository.ts,tags-db.repository.tslistStreamIdsForTag()backing the in-memory and SQL tag filtersapi/src/tags/tags.module.tsTagsRepositorysoStreamsModule's in-memory repo can resolve tag associationsapp/lib/api/streams.ts,app/hooks/useStreams.tsq/tagpass-through inlistStreams()and in the React Query keysxstreamroll-sdk/src/types.ts,client.ts,index.ts,pagination.tsStreamListParams,listStreams()method,paginateAllqueryoption, exportstests/contracts/src/streams.contract.tslist-streams-searchandlist-streams-by-tagcontractsapi/src/contract-provider.spec.ts,xstreamroll-sdk/__tests__/contract.consumer.test.tsTests live alongside each layer: new spec files for the DTO validation, the in-memory repository, and the SQL repository (SQL-string assertions), plus service-level tests for tag resolution and unknown-tag handling.
Integration changes outside
streams/api/src/tags/*—resolveTag(),listStreamIdsForTag(), andTagsRepositoryexport, all serving the new filter.api/src/auth/auth.service.ts,auth.controller.ts— restored the intended SDK auth types drift from the API: expiresIn does not exist on the wire and refresh cannot authenticate #527/JWT revocation is ineffective: logout and password changes never invalidate issued tokens #510 design that a bad merge had clobbered:refresh()takes the token string (body-first, cookie fallback in the controller) and signs theisAdminclaim the guard reads.api/src/auth/users.repository.ts— removed a stale duplicatefindById(bad-merge artifact).api/src/gateways/streams.gateway.ts— destructures{ userId }fromauthenticate()'s{ userId, isAdmin }result.api/src/main.ts,streams.module.ts,audit.module.ts— removed duplicate imports/definitions that brokenest build.auth.controller.spec,auth.service.spec,audit.interceptor.spec,audit.integration.spec,jwt-extractor.service.spec,streams.gateway.spec,users.service.spec,audit.integration.spec,database.integration.spec) — updated to the contracts those sources actually implement (logSafely,{ userId, isAdmin },is_adminonUser, refresh token-string extraction), and fixed the keyset-pagination test's cursor to keep microsecond precision instead of truncating viatoISOString().xstreamroll-sdk/__tests__/client.test.ts— deleted; it tested the removed axios-based client (could not compile) and is fully superseded by the nock-basedclient.integration.test.ts.Acceptance criteria coverage
GET /streams?q=<term>returns only streams whose name or description contains the term, case-insensitively (api/src/streams/repository/streams.repository.spec.ts,streams-db.repository.spec.ts,contract-provider.spec.ts—list-streams-search)GET /streams?tag=<slug|id>returns only streams carrying that tag (tags.service.spec.ts,streams.service.spec.ts,contract-provider.spec.ts—list-streams-by-tag)streams.service.spec.ts)qare matched literally, never as wildcards (streams.repository.spec.ts,streams-db.repository.spec.ts)streams.repository.spec.ts)listStreams()sends the same filters the API contract expects (contract.consumer.test.ts)q/tagthrough without client-side post-filtering (useStreams.test.tsx)Test plan
cd api && npx jest— 482/482 passing (42/42 suites) against a fresh Postgres 16 test DBcd api && npx tsc --noEmit— 0 errorscd api && npx eslint "src/**/*.ts"— 0 errors (pre-existing warnings only, on files untouched by this PR)cd api && npm run build— succeedscd xstreamroll-sdk && npx jest— 70/70 passing (6/6 suites)cd xstreamroll-sdk && npm run typecheck— 0 errors;npm run lint— 0 errorscd app && npx jest— 224/224 passing (19/20 suites); the one failing suite (fetch-json.test.ts) fails identically onmain— a Node 24/jsdom environment incompatibility in a file this PR does not touchcd app && npm run typecheck— 0 errors;npm run lint— 0 errorsnpm run build --workspace=packages/typesand--workspace=tests/contracts— succeedEnv vars / Notes
No new environment variables or migrations.
q/tagare additive query params; existingGET /streamscallers are unaffected. Theqsearch is intentionally substring-based (not fuzzy);%/_in user input are escaped to match literally. Tag filtering matches on thestream_tagsassociation table, so a stream tagged with the given tag is returned regardless of whether the tag slug or numeric id was supplied.