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
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.
68 lines
7.0 KiB
Markdown
68 lines
7.0 KiB
Markdown
# 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
|
|
|
|
6. **`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.
|
|
7. **`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.
|
|
8. **`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
|
|
|
|
- [x] `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.
|
|
- [x] 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`.
|
|
- [x] `read_search` tv emits `tvdb`/`tvmaze`/`imdb`, no `tmdb` identifier.
|
|
- [x] v1-details projection keeps `genre`-sourced genres and `seasonRequests` seasons.
|
|
- [x] `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`.
|