Skip to content

refactor(metadata): generic metadata-lookup utility#2633

Merged
enoch85 merged 8 commits into
developmentfrom
refactor/generic-metadata-lookup
Apr 8, 2026
Merged

refactor(metadata): generic metadata-lookup utility#2633
enoch85 merged 8 commits into
developmentfrom
refactor/generic-metadata-lookup

Conversation

@enoch85

@enoch85 enoch85 commented Apr 8, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Rename servarr-lookup.util.tsmetadata-lookup.util.ts and all exports (ServarrLookupCandidateMetadataLookupCandidate, etc.) so non-servarr services can reuse the same lookup pattern
  • Add MetadataLookupPolicy interface declaring which provider IDs each downstream service natively accepts (sonarr→tvdb, radarr→tmdb, seerr→tmdb)
  • Fix external-ID recovery when a provider is unavailable: resolveAllIds now skips only available provider keys, allowing TMDB to recover a TVDB ID via its external-ID endpoint when TVDB is unconfigured
  • Add unit tests for the utility functions and edge-case metadata resolution fixtures (TVDB unavailable, both IDs direct, no provider resolvable)
  • Remove dead tmdbIdService/tmdbIdHelper mocks from spec files

Test plan

  • All 724 server tests pass
  • Lint: 0 errors, 4 pre-existing UI warnings
  • Prettier formatted

enoch85 added 8 commits April 8, 2026 16:54
…utility

The lookup utility (candidates, match, format) is service-agnostic but
was named with a servarr prefix, preventing reuse by non-servarr
consumers (Seerr, Tautulli, Plex, Jellyfin). Rename file and exports
to reflect the generic purpose:

- servarr-lookup.util.ts → metadata-lookup.util.ts
- ServarrLookupCandidate → MetadataLookupCandidate
- findServarrLookupMatch → findMetadataLookupMatch
- formatServarrLookupCandidates → formatMetadataLookupCandidates

Add lookup policy interface to declare which provider IDs each
downstream service natively accepts (sonarr→tvdb, radarr→tmdb,
seerr→tmdb).

Fix external-ID recovery when a provider is unavailable: use
available provider keys instead of all provider keys in resolveAllIds,
so a direct TVDB ID can be recovered via TMDB's external-ID endpoint
when the TVDB provider is unconfigured.

Add unit tests for the utility and edge-case metadata resolution
fixtures. Remove dead tmdbIdService/tmdbIdHelper mocks from specs.
Remove unused buildMetadataLookupCandidates and
buildLookupCandidatesForService, make buildLookupCandidatesWithPolicy
private. Set providerMatchMode explicitly in static lookup policies.

Deduplicate resolveIds and resolveIdsFromMediaItem by delegating to
their *WithLookupPolicy counterparts. Add JSDoc to
resolveLookupPolicyProviderKeys clarifying registered-vs-available
provider key validation.
The modal fetched full metadata via /media-server/meta/{id} but only
used the summary prop from the parent card, which was often empty.
Prefer metadata.summary from the API response, falling back to the
card prop.

Also add summary to PlexMetadata interface and map it in
metadataToMediaItem so Plex metadata responses include it.
@enoch85 enoch85 merged commit be735be into development Apr 8, 2026
13 checks passed
@enoch85 enoch85 deleted the refactor/generic-metadata-lookup branch April 8, 2026 20:10
enoch85 added a commit that referenced this pull request Apr 8, 2026
Fix undefined `resolvedIds` reference (use `media.tmdbId`) and remove
non-existent `getSeriesByTmdbId` call in `refetchSeries` to align with
the metadata-lookup refactor from #2633.
@enoch85 enoch85 added this to the 3.5.0 milestone Apr 9, 2026 — with GitHub Codespaces
maintainerr-automation Bot added a commit that referenced this pull request Apr 9, 2026
* build(deps-dev): bump @types/node from 22.19.15 to 22.19.17 (#2630)

Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 22.19.15 to 22.19.17.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 22.19.17
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps-dev): bump postcss from 8.5.8 to 8.5.9 (#2629)

Bumps [postcss](https://github.com/postcss/postcss) from 8.5.8 to 8.5.9.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](postcss/postcss@8.5.8...8.5.9)

---
updated-dependencies:
- dependency-name: postcss
  dependency-version: 8.5.9
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps-dev): bump @typescript-eslint/parser from 8.57.2 to 8.58.1 (#2631)

Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 8.57.2 to 8.58.1.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.58.1/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-version: 8.58.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps-dev): bump vitest from 4.1.2 to 4.1.3 (#2632)

Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) from 4.1.2 to 4.1.3.
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.3/packages/vitest)

---
updated-dependencies:
- dependency-name: vitest
  dependency-version: 4.1.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* fix(jellyfin): clear stale collection link when media server collection is deleted

Re-throw 400/404 errors from Jellyfin getCollectionChildren instead of
swallowing them, so callers can detect when a collection no longer exists.

In getCollectionMediaMetadata, verify the collection is truly gone via
getCollection before clearing the mediaServerId link. This double-check
prevents false positives from resetting collection counters. If the
verification call itself fails transiently, the link is preserved.

* refactor(metadata): generic metadata-lookup utility (#2633)

* fix(jellyfin): exclude virtual episodes from child queries (#2624)

Virtual episodes in Jellyfin are placeholders for unaired content.
Including them caused ended shows to appear as having episodes,
preventing proper cleanup.

Closes #2558

* feat: clean up empty Sonarr shows after season actions (#2618)

* fix(server): import SeerrApiModule into ActionsModule

* fix(jellyfin): lower collection mutation batch size

* build(deps): bump nodemailer from 8.0.4 to 8.0.5 (#2635)

Bumps [nodemailer](https://github.com/nodemailer/nodemailer) from 8.0.4 to 8.0.5.
- [Release notes](https://github.com/nodemailer/nodemailer/releases)
- [Changelog](https://github.com/nodemailer/nodemailer/blob/master/CHANGELOG.md)
- [Commits](nodemailer/nodemailer@v8.0.4...v8.0.5)

---
updated-dependencies:
- dependency-name: nodemailer
  dependency-version: 8.0.5
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps): bump the nestjs group with 3 updates (#2639)

Bumps the nestjs group with 3 updates: [@nestjs/serve-static](https://github.com/nestjs/serve-static), [@nestjs/swagger](https://github.com/nestjs/swagger) and [@nestjs/cli](https://github.com/nestjs/nest-cli).


Updates `@nestjs/serve-static` from 5.0.4 to 5.0.5
- [Release notes](https://github.com/nestjs/serve-static/releases)
- [Commits](nestjs/serve-static@5.0.4...5.0.5)

Updates `@nestjs/swagger` from 11.2.6 to 11.2.7
- [Release notes](https://github.com/nestjs/swagger/releases)
- [Commits](nestjs/swagger@11.2.6...11.2.7)

Updates `@nestjs/cli` from 11.0.18 to 11.0.19
- [Release notes](https://github.com/nestjs/nest-cli/releases)
- [Commits](nestjs/nest-cli@11.0.18...11.0.19)

---
updated-dependencies:
- dependency-name: "@nestjs/serve-static"
  dependency-version: 5.0.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: nestjs
- dependency-name: "@nestjs/swagger"
  dependency-version: 11.2.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: nestjs
- dependency-name: "@nestjs/cli"
  dependency-version: 11.0.19
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: nestjs
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps): bump the react group with 2 updates (#2640)

Bumps the react group with 2 updates: [react](https://github.com/facebook/react/tree/HEAD/packages/react) and [react-dom](https://github.com/facebook/react/tree/HEAD/packages/react-dom).


Updates `react` from 19.2.4 to 19.2.5
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v19.2.5/packages/react)

Updates `react-dom` from 19.2.4 to 19.2.5
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v19.2.5/packages/react-dom)

---
updated-dependencies:
- dependency-name: react
  dependency-version: 19.2.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: react
- dependency-name: react-dom
  dependency-version: 19.2.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: react
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps-dev): bump vitest from 4.1.3 to 4.1.4 (#2641)

Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) from 4.1.3 to 4.1.4.
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.4/packages/vitest)

---
updated-dependencies:
- dependency-name: vitest
  dependency-version: 4.1.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps): bump axios from 1.14.0 to 1.15.0 (#2642)

Bumps [axios](https://github.com/axios/axios) from 1.14.0 to 1.15.0.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](axios/axios@v1.14.0...v1.15.0)

---
updated-dependencies:
- dependency-name: axios
  dependency-version: 1.15.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* fix(collections): make Delete Latest send a real sort param (#2634)

Delete Latest was defined as the empty-string "no sort" fallback with
sortParams: undefined. In CollectionMediaPage.fetchCollectionMediaPage,
passing undefined as the override triggered the `= sortParams` default
parameter fallback, so the request sent for Delete Latest was identical
to Delete Soonest.

Introduce a dedicated deleteSoonest.desc option and filter the empty
fallback out when delete sorts are present. Both options still hit the
fast addDate branch in getCollectionMediaWithServerDataAndPaging, so
performance is unchanged.

Fixes #2634

* fix(collections): auto-load next page when viewport already filled

The infinite-scroll hook only fetched more pages in response to a
scroll event. On large viewports that could display all 30 initial
items without producing a scrollbar, no scroll event ever fired and
the list got stuck at 30 until the window was resized.

Re-check isNearBottom() after each append and also listen for resize,
so additional pages cascade in until the document is actually
scrollable (or totalSize is reached).

Fixes #2637

* fix: carry over valid PR 2534 hardening fixes (#2617)

* fix(metadata): validate direct provider IDs by year with cross-provider fallback (#2643)

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: maintainerr-automation[bot] <261505141+maintainerr-automation[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: enoch85 <mailto@danielhansson.nu>
@maintainerr-automation

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 3.5.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant