Skip to content

GET /streams/:id/tags is documented but does not exist: dashboard tag chips cannot load #517

Description

@Xhristin3

Problem

GET /streams/:id/tags is documented as an existing endpoint but does not exist. The shared type contract in packages/types/src/stream.ts tells callers:

/**
 * ... May be `undefined` on endpoints that fetch a single stream
 * directly (create / update / findOne) — callers that want the
 * tags should hit `GET /streams/:id/tags`.
 */
tags?: Tag[]

The dashboard does exactly that: useStreamTags in app/hooks/useStreams.ts fetches `${NEXT_PUBLIC_API_URL}/streams/${id}/tags` with GET. But StreamTagsController (api/src/tags/tags.controller.ts) only defines POST /streams/:id/tags and DELETE /streams/:id/tags/:tagId — there is no GET handler. The request 404s, the tag editor on the streams dashboard falls into its error path, and per-stream tag chips can never load from this endpoint.

The tag data itself exists and is already returned inline on GET /streams (StreamsService.list batches tags via TagsService.listForStreamIds, issue #330), so the missing endpoint is purely a contract gap: the API, the shared types, and the app all describe a resource the server never serves.

Root cause

// api/src/tags/tags.controller.ts
@Controller("streams/:id/tags")
@UseGuards(StreamOwnershipGuard)
export class StreamTagsController {
  @Post() ...      // attach — exists
  @Delete(":tagId") ... // detach — exists
  // ← no @Get() handler
}

Why this is architecturally hard

  1. The repository layer already has the query material: TagsDbRepository.listForStreamIds() (api/src/tags/repository/tags-db.repository.ts) loads tags grouped by stream in one round trip, and StreamsService.list uses it. A single-stream tags endpoint should reuse that path, but listForStreamIds is batch-oriented — the service needs a thin single-stream wrapper and a decision on the response envelope (paginated like every other list endpoint, or a plain array).
  2. Ownership semantics are already settled (StreamOwnershipGuard on the controller), which makes this small; the genuinely open question is whether the endpoint returns PagedTags (matching app/lib/api/tags.ts's PagedTags shape) or the plain Tag[] that Stream.tags uses — the app's useStreamTags currently parses a PagedTags envelope, so the endpoint should match that or the hook must change.
  3. Contract coverage: the existing contract suite (tests/contracts/src/streams.contract.ts) does not cover tag routes, so whatever shape is chosen should be pinned by a contract test on both the provider side (api/src/contract-provider.spec.ts) and the consumer side (xstreamroll-sdk/__tests__/contract.consumer.test.ts) to prevent a re-drift.

Acceptance criteria

Contract

  • GET /streams/:id/tags exists under StreamOwnershipGuard, returns 200 for an owned stream and 403 for a non-owner (matching the existing guard semantics).
  • The response envelope matches what app/hooks/useStreams.ts useStreamTags parses (PagedTags-shaped), or the hook is updated to the agreed shape — the two must agree after the change.
  • The comment in packages/types/src/stream.ts about GET /streams/:id/tags matches reality.

Service

  • The endpoint reuses the existing batch tag-loading path (TagsService.listForStreamIds / TagsDbRepository.listForStreamIds) rather than issuing a per-tag query.

Tests

  • Controller/service test: an owned stream returns its attached tags; a stream with no tags returns an empty list (not an error).
  • A contract test for the endpoint passes in both the api provider suite and the sdk consumer suite.
  • useStreamTags-driven test in app (e.g. app/hooks/useStreams.test.tsx or the tag-editor tests) passes against the real endpoint shape.

Documentation

  • Swagger documents the endpoint with the ownership/403 semantics.

Out of scope

Tag search/filtering on the global GET /tags list, and changing the inline tags field on GET /streams.

Getting started

Real files in scope: api/src/tags/tags.controller.ts, api/src/tags/tags.service.ts, api/src/tags/repository/tags-db.repository.ts, app/hooks/useStreams.ts (useStreamTags), packages/types/src/stream.ts, tests/contracts/src/streams.contract.ts.

Verify with:

cd api && npm run typecheck && npm test
cd ../xstreamroll-sdk && npm test
cd ../app && npm run typecheck && npm test

Good first files to read: api/src/tags/tags.controller.ts, app/hooks/useStreams.ts (useStreamTags), api/src/tags/repository/tags-db.repository.ts (listForStreamIds).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

GrantFox OSSIssue tracked in GrantFox OSSMaybe RewardedIssue may be eligible for a GrantFox rewardThird CampaignCampaign: Third CampaignapiREST API design and endpointsbugSomething isn't workingfrontendRelated to app/ Next.js frontend

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions