Files
ombi-mcp/docs/megaplans/m2/01-tv-identifier-namespaces.md
gronod eccbe48ff0
Build and publish / Test and build (darwin) (push) Successful in 2m3s
Build and publish / Test and build (linux) (push) Successful in 2m45s
Build and publish / Test and build (windows) (push) Successful in 3m13s
Build and publish / Build and publish Docker image (push) Successful in 2m30s
Route tvdb TV lookups through the v1 TVMaze info route
Ombi's v2 Search/tv/{tvdbId} route is a TMDB-keyed alias despite its parameter name, so read_media details and write_request_create season expansion resolved the wrong series for TVDB ids (Gitea issue #1). Repoint tvdb to the legacy v1 Search/tv/info route, label theMovieDbId by origin route (tvdb on TVMaze-backed v1 results, tmdb on the v2 engine) and emit the new tvmaze identifier namespace from seriesId.
2026-09-19 07:26:40 +01:00

7.0 KiB

Phase 01 — TV Identifier Namespaces

Objective

Fix Gitea issue #1 end-to-end in code and tests: read_media details must resolve the correct series for a TVDB id, TVDB season expansion must read the same show, and every TV identifier must be labelled by the namespace it actually carries rather than by the upstream field name.

Prerequisites

  • Phase 00 complete (this file and the index exist).
  • Source files to mutate: internal/tools/media.go, internal/tools/project.go, internal/tools/search.go, internal/tools/discover.go, internal/tools/library.go, internal/tools/requestswrite.go, internal/tools/families.go, internal/integration_test/mockombi_test.go, internal/integration_test/contract_test.go, internal/integration_test/live_test.go.
  • Root cause (verified against Ombi-app/Ombi develop + live 4.53.10):
    • GET /api/v2/Search/tv/{tvdbId} and GET /api/v2/Search/tv/moviedb/{moviedbid} are aliases: both call TvSearchEngineV2.GetShowInformation → _movieApi.GetTVInfo (TMDB). The tvdbId parameter name is vestigial — both routes are TMDB-keyed. TMDB 75150 is Passfire: The Series, exactly the wrong-show result observed in the issue.
    • v1 TV search (/api/v1/Search/tv/{term}, used by read_search) is TVMaze-backed: TvProfile maps Id ← show.externals.thetvdb and SeriesId ← show.id (TVMaze), and ProcessResult sets TheMovieDbId = Id. So theMovieDbId in v1 TV results is the TVDB id (Button Moon: TVDB 75150 per api.tvmaze.com), mislabeled tmdb in our projection.
    • The only real TVDB details route is legacy GET /api/v1/Search/tv/info/{tvdbId} → TvSearchEngine.GetShowInformation → TvMazeApi.ShowLookupByTheTvDbId, returning SearchTvShowViewModel with seasonRequests/episodes — sufficient for projectFullTV and expandSeasonNumbers.
    • The same bug existed in write_request_create TV season mode: expandSeasonNumbers read the TMDB-alias v2 route for provider:tvdb, expanding the wrong show's episodes.
    • v2-sourced TV data (discover browse, by_request, multi-search) IS TMDB-keyed — keep the tmdb label there. The same view-model field name carries different namespaces per origin; relabeling must be per call site.

Specification

Mutations

  1. internal/tools/media.go — mediaDetails tv case:
    • tvdb → GET /api/v1/Search/tv/info/{id} (was /api/v2/Search/tv/{id}), projection hint tvdb.
    • tmdb → keep /api/v2/Search/tv/moviedb/{id}, projection hint tmdb.
    • mediaByRequest tv keeps the v2 tv/request route with hint tmdb.
  2. internal/tools/project.go — identifier origin fix:
    • projectSearchMedia(m, media, tvIDNS) gains the per-call-site namespace hint for theMovieDbId on TV.
    • TV branch: theMovieDbId under the hinted namespace; theTvDbId→tvdb and imdbId→imdb unchanged; seriesId→tvmaze when present.
    • projectFullTV(m, tvIDNS) forwards the hint and only overwrites Genres/Credits when the richer genres/cast object arrays are present — the v1 model carries genre strings already handled by projectSearchMedia.
  3. Call-site hints: search.go searchText tv → tvdb (v1 TVMaze); media.go details tvdb → tvdb, tmdb/by_request → tmdb; discover.go browse tv (v2, TMDB) → tmdb; library.go recent tv → tmdb retained pending verification; multi-search tv id stays tmdb (v2 engine, Verify).
  4. internal/tools/requestswrite.go — expandSeasonNumbers: provider==tvdb reads GET /api/v1/Search/tv/info/{id}; tmdb keeps the moviedb route. The seasonRequests[].episodes[].episodeNumber shape matches the existing decode.
  5. internal/tools/families.go — Identifier.Namespace doc comment gains tvmaze.

Test mutations

  1. mockombi_test.go — register GET /api/v1/Search/tv/info/{id} (TVMaze-shape fixture: id/theMovieDbId=tvdb 81189, seriesId=tvmaze 169, genre string list, seasonRequests) and GET /api/v1/Search/tv/{term} (search-hit fixture with seriesId). The v2 Search/tv/{id} handler stays registered so the suite can assert it is never called.
  2. contract_test.go — new assertions:
    • read_media details provider:tvdb hits /api/v1/Search/tv/info/81189 exactly once and /api/v2/Search/tv/ zero times; emits tvdb/tvmaze/imdb, no tmdb; preserves genre strings and season structure.
    • write_request_create tv season mode with provider:tvdb reads the v1 info route once, never the v2 alias, and posts expanded seasons[].episodes to /api/v1/Request/tv.
    • read_search tv emits tvdb + tvmaze + imdb and no tmdb label.
  3. live_test.go — TestLiveSearchThenDetails becomes the issue-#1 regression: search "Button Moon", take the emitted tvdb id, details provider:tvdb must return the same title (skips without OMBI_URL). TestLiveSeasonExpansion switches to provider:tvdb since search no longer emits tmdb for TV.

Target artefacts

  • internal/tools/media.go — tvdb details repoint to the v1 info route; per-provider projection hints.
  • internal/tools/project.go — tvIDNS hint, seriesId→tvmaze, projectFullTV genre/credit guard.
  • internal/tools/search.go — searchText tv hint tvdb; remaining call sites pass tmdb.
  • internal/tools/discover.go — all v2 call sites pass tmdb.
  • internal/tools/library.go — recent tv keeps tmdb with an unverified-namespace comment.
  • internal/tools/requestswrite.go — tvdb season expansion via v1 info route.
  • internal/tools/families.go — namespace doc comment.
  • internal/integration_test/{mockombi_test,contract_test,live_test}.go — fixtures and assertions above.

Verification gates

  • read_media details {media:tv, provider:tvdb, id:81189} records exactly one GET /api/v1/Search/tv/info/81189 and zero GET /api/v2/Search/tv/ calls.
  • TV season expansion with provider:tvdb records one v1 info GET, zero v2 tv GETs, and posts explicit seasons[].episodes to /api/v1/Request/tv.
  • read_search tv emits tvdb/tvmaze/imdb, no tmdb identifier.
  • v1-details projection keeps genre-sourced genres and seasonRequests seasons.
  • CGO_ENABLED=0 go build ./..., go test ./..., go test -tags integration ./internal/integration_test/, go vet ./..., gofmt -l . clean.
  • Live read_media details {media:tv, provider:tvdb, id:75150} returns Button Moon.

Evidence labels

  • Documented — the route aliasing and field mappings are verified against upstream controller/engine/profile source and a live 4.53.10 instance (TMDB 75150 = Passfire: The Series; TVDB 75150 = Button Moon per api.tvmaze.com).
  • Design — emitting theMovieDbId under a call-site-supplied namespace and the new tvmaze namespace are intentional MCP contract decisions; the same upstream field name is never trusted.
  • Verify — read_library recent tv theMovieDbId namespace (kept tmdb), multi-search tv id namespace (kept tmdb), by_request externalProviderId namespace, and pre-TVMaze Ombi version drift. Consolidated in docs/schema/06-verification.md.