Skip to content

Consolidate getDiscogsReleaseIdBy* bridge functions into a single resolveDiscogsReleaseId service #981

@jakebromberg

Description

@jakebromberg

Problem

apps/backend/services/library.service.ts has three near-sibling functions that all do the same shape — "given some caller context (rotation id / legacy library id / library id), return the resolved Discogs release id, or NULL when the chain is incomplete":

Function Caller Bridge
getDiscogsReleaseIdByRotationId /library/rotation/:rotation_id/tracks (BS#944) rotation → library_identity (join via rotation.album_id)
getDiscogsReleaseIdByLegacyId /proxy/library/:libraryId/tracks (BS#836) legacy_release_id → library_identity (join via library.id)
(inline in proxy.controller.getAlbumMetadata) iOS metadata path library_id → library_identity directly

Each one:

  • Joins (or selects) the same library_identity table
  • Returns null when the FK chain is broken at one of three documented points (no album_id / no library_identity row / no discogs_release_id)
  • Has an identical docstring block listing the same null-fallback conditions

When a new data source for discogs_release_id lands (the upcoming rotation.discogs_release_id column from the tubafrenzy parity work — see the parent issue for that work), every site that resolves a Discogs release id needs the new fallback. Today that means editing all three sites, with all the risk of inconsistency that implies.

End state

A single resolveDiscogsReleaseId service function in apps/backend/services/library.service.ts:

type DiscogsReleaseIdSource =
  | { rotationId: number }
  | { legacyLibraryId: number }
  | { libraryId: number };

export async function resolveDiscogsReleaseId(source: DiscogsReleaseIdSource): Promise<number | null>;
  • Single canonical fallback chain inside the function (rotation column → library_identity → library.canonical_entity_id parse → null). Future sources land in one place.
  • One docstring describing the gates, not three.
  • Each existing callsite becomes resolveDiscogsReleaseId({ rotationId }) / ({ legacyLibraryId }) / ({ libraryId }).
  • No change to any HTTP endpoint surface — this is purely a service-layer refactor.

Acceptance

  • New resolveDiscogsReleaseId exists with the discriminated-source parameter shape above.
  • All three existing callsites migrated; old functions deleted.
  • Unit tests cover each source variant + the null-fallback paths.
  • No HTTP route changes; integration tests for /library/rotation/:rotation_id/tracks and /proxy/library/:libraryId/tracks still pass unchanged.
  • Docstring on the new function inherits the existing prose about null conditions.

Why now

Surfaced during the endpoint-discipline review on 2026-05-21 while scoping the tubafrenzy parity work for rotation.discogs_release_id. That work adds another source-of-truth column; consolidating the three bridge functions first makes the parity PR clean (one line change, not three).

Related

  • The parent tubafrenzy parity work (file separately)
  • BS#944 — /library/rotation/:rotation_id/tracks
  • BS#836 — /proxy/library/:libraryId/tracks
  • BS#802 — library_identity substrate (the read target this function gates)

Metadata

Metadata

Assignees

No one assigned

    Labels

    choreMaintenance and housekeepingenhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions