Migrate library/import and catalog/provider errors to translatable codes (phases 2+3/4) - #144
Merged
Merged
Conversation
…codes (phases 2+3/4)
Phases 2 and 3 of "Migrate API errors to error codes, domain by domain" —
library/import (highest-traffic flows, ImportWizard's own error surface)
and catalog/providers (TMDB/AniList/IGDB/Open Library/MusicBrainz).
Migrates every remaining bare NestJS exception in apps/api/src/library/,
import/, catalog/, games/, books/ and music/ (61 throw sites across 22
files) to AppException + a dedicated ErrorCode. games/books/music mostly
mirror library/'s entry-ownership shape (*LibraryService,
*.controller.ts "Query 'q' is required"), so their codes are shared rather
than duplicated per domain — same for catalog: CatalogItemNotFound/
CatalogProviderUnavailable cover every provider (AniList, IGDB, Open
Library, MusicBrainz), migrated once at the source in the two shared
utils every provider funnels through:
- common/http.util.ts's fetchJson() — used by TMDB, AniList and IGDB —
now builds the envelope directly, which is why tmdb.provider.ts needed
no changes despite having its own quota of BadGateway/NotFound cases.
- common/parse-enum-param.util.ts (catalog/games/books/music/stats/users
route param parsing) gets one ErrorCode.InvalidParam + params for the
label/value, rippling to every controller that calls it.
Also closes two more hardcoded-language leaks the original ticket didn't
catch: Steam's French "profil introuvable"/"bibliothèque inaccessible"
messages, and the free-import-quota French ForbiddenException.
32 new ErrorCode entries, fr/en translations, resolveApiError() wiring.
Existing specs updated — message-text assertions (`.rejects.toThrow("...")`)
were left alone since AppException's dev-facing `message` still carries
the same text where a test or genuine debugging need cares about it (e.g.
which provider failed); status/class-based assertions were switched to
`.rejects.toMatchObject({ code })`.
Remaining: phase 4 (social/reviews/comments/reports/lists/admin — the
tail), and the Swagger error-code decorator mentioned in the ticket's
"Also in scope".
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Covers admin, comments, lists, newsletter, notifications, reports, reviews, social, stats and users — every bare NestJS exception in apps/api/src is now an AppException with an ErrorCode, with fr/en translations wired into apps/web's resolver. The no-restricted-syntax guardrail flips from warn to error now that the migration is complete. Also fixes a real regression found along the way: AllExceptionsFilter was collapsing every 5xx AppException's code to a generic InternalError before the client ever saw it (the status>=500 branch ran before the AppException check), silently discarding codes already shipped in phases 2/3 like CatalogProviderUnavailable. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phases 2 and 3 of the "Migrate API errors to error codes, domain by domain" Quackback ticket (post_01m119rsxxecw9tbtm8mw37aqa), following its suggested order: library/import (highest-traffic flows,
ImportWizard.sveltealready tries to render API messages verbatim), then catalog/providers (TMDB/AniList/IGDB/Open Library/MusicBrainz).apps/api/src/library/,import/,catalog/, and (since they mirrorlibrary's exact entry-ownership shape)games/,books/,music/too.LibraryEntryNotFound/LibraryEntryForbidden/LibraryReplayNotFound/LibraryReplayForbiddencover media/games/books/music alike;CatalogItemNotFound/CatalogProviderUnavailablecover every catalogue provider;CatalogSearchQueryRequiredcovers the 3 identical "Query 'q' is required" controllers.common/http.util.ts'sfetchJson()(used by TMDB, AniList, IGDB) —tmdb.provider.tsneeded zero changes despite having its own share of BadGateway/NotFound cases, since they all flow through here.common/parse-enum-param.util.ts(catalog/games/books/music/stats/users route params) — oneErrorCode.InvalidParam+params.label/.value.ForbiddenException.ErrorCodeentries, fr/en translations,resolveApiError()wiring.A note on message text
AppException's dev-facingmessagewas preserved wherever a test (or genuine debugging value — "which provider failed") depended on the specific text, even when thecodeis shared across sites. Three specs initially broke this way (open-library,musicbrainz,library.service's season lookup) after I let the message default to the bare code string; fixed by passing the original text back through explicitly.Test plan
pnpm --filter @loomkeep/api exec jest— 821/821pnpm --filter @loomkeep/webvitest — 13/13 (translation completeness now covers 64 codes total)pnpm --filter @loomkeep/web check— 0 errorspnpm lint— 0 errors (104 expectedno-restricted-syntaxwarnings on the remaining phase-4 domains, down from 165)Remaining on the ticket
Phase 4 (social/reviews/comments/reports/lists/admin — the tail), and the Swagger error-code decorator mentioned in "Also in scope".
🤖 Generated with Claude Code