refactor: extract hardcoded route paths into shared constants#2762
refactor: extract hardcoded route paths into shared constants#2762Logvin wants to merge 1 commit intoseerr-team:developfrom
Conversation
Replace all hardcoded API and proxy path strings with centralized constants and a helper function. There are no behavior changes — this is a pure refactor to improve maintainability. Paths like /api/v1, /api-docs, /imageproxy, and /avatarproxy are now defined once and referenced everywhere. This makes the codebase easier to update if these paths ever need to change for things like API versioning, base path configuration, or reverse proxy flexibility.
📝 WalkthroughWalkthroughThis PR refactors API endpoint management by creating centralized route constants and a URL builder utility, then systematically replacing 150+ hardcoded Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
src/utils/apiUrl.ts (1)
1-5: Consider exportingAPI_BASEand adding path validation.The utility is clean and focused. Two optional improvements:
Export
API_BASE: Some call sites may need just the base path (e.g., for conditional logic or documentation). The server-side equivalent inserver/constants/routes.tsexportsAPI_BASE_PATHdirectly.Path validation: The function assumes
pathstarts with/. Paths without a leading slash would produce malformed URLs (e.g.,apiUrl('discover/tv')→/api/v1discover/tv).🔧 Optional: Export constant and add defensive check
-const API_BASE = '/api/v1'; +export const API_BASE = '/api/v1'; export function apiUrl(path: string): string { + if (path && !path.startsWith('/')) { + path = '/' + path; + } return `${API_BASE}${path}`; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/utils/apiUrl.ts` around lines 1 - 5, Export the API_BASE constant and make apiUrl robust to missing leading slashes: export API_BASE (previously const API_BASE) so other modules can import it, and update the apiUrl(path: string) function to validate the path (e.g., if path is empty return API_BASE, and if path does not start with '/' prepend '/' before concatenation) to avoid producing malformed URLs when callers pass 'discover/tv' instead of '/discover/tv'.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/components/UserList/JellyfinImportModal.tsx`:
- Line 57: JellyfinImportModal is interpolating `children` (a React.ReactNode)
into the query string for apiUrl(`/user?take=${children}`), which can be
non-numeric; replace that with a validated numeric value (e.g., use a prop/state
like `take`, `pageSize` or `takeCount` or compute `const take =
Number(props.take ?? DEFAULT_TAKE)`), validate/fallback if NaN, and then call
`apiUrl(`/user?take=${take}`)`; reference `apiUrl`, `JellyfinImportModal`, and
`children` in your change so you replace the `children` usage with the numeric
`take` variable.
In
`@src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsWebPush/index.tsx`:
- Line 78: The SWR key is being constructed unconditionally with
apiUrl(`/user/${user?.id}/pushSubscriptions`) which yields /user/undefined/...
on initial render; update the SWR call in the UserNotificationsWebPush component
to only pass a concrete URL when user is defined (e.g., use user ?
apiUrl(`/user/${user.id}/pushSubscriptions`) : null) so the fetch is skipped
until user is loaded, mirroring the guarded pattern used at the earlier SWR
call.
---
Nitpick comments:
In `@src/utils/apiUrl.ts`:
- Around line 1-5: Export the API_BASE constant and make apiUrl robust to
missing leading slashes: export API_BASE (previously const API_BASE) so other
modules can import it, and update the apiUrl(path: string) function to validate
the path (e.g., if path is empty return API_BASE, and if path does not start
with '/' prepend '/' before concatenation) to avoid producing malformed URLs
when callers pass 'discover/tv' instead of '/discover/tv'.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 8c03cdca-bdbb-4e4c-8512-f14374f49ecf
📒 Files selected for processing (140)
server/constants/routes.tsserver/index.tsserver/routes/index.tssrc/components/AppDataWarning/index.tsxsrc/components/Blocklist/index.tsxsrc/components/BlocklistBlock/index.tsxsrc/components/BlocklistModal/index.tsxsrc/components/BlocklistedTagsBadge/index.tsxsrc/components/BlocklistedTagsSelector/index.tsxsrc/components/CollectionDetails/index.tsxsrc/components/Common/CachedImage/index.tsxsrc/components/CompanyTag/index.tsxsrc/components/Discover/CreateSlider/index.tsxsrc/components/Discover/DiscoverMovieGenre/index.tsxsrc/components/Discover/DiscoverMovieKeyword/index.tsxsrc/components/Discover/DiscoverMovieLanguage/index.tsxsrc/components/Discover/DiscoverMovies/index.tsxsrc/components/Discover/DiscoverNetwork/index.tsxsrc/components/Discover/DiscoverSliderEdit/index.tsxsrc/components/Discover/DiscoverStudio/index.tsxsrc/components/Discover/DiscoverTv/index.tsxsrc/components/Discover/DiscoverTvGenre/index.tsxsrc/components/Discover/DiscoverTvKeyword/index.tsxsrc/components/Discover/DiscoverTvLanguage/index.tsxsrc/components/Discover/DiscoverTvUpcoming.tsxsrc/components/Discover/DiscoverWatchlist/index.tsxsrc/components/Discover/MovieGenreList/index.tsxsrc/components/Discover/MovieGenreSlider/index.tsxsrc/components/Discover/PlexWatchlistSlider/index.tsxsrc/components/Discover/RecentRequestsSlider/index.tsxsrc/components/Discover/RecentlyAddedSlider/index.tsxsrc/components/Discover/Trending.tsxsrc/components/Discover/TvGenreList/index.tsxsrc/components/Discover/TvGenreSlider/index.tsxsrc/components/Discover/Upcoming.tsxsrc/components/Discover/index.tsxsrc/components/GenreTag/index.tsxsrc/components/IssueDetails/IssueComment/index.tsxsrc/components/IssueDetails/index.tsxsrc/components/IssueList/IssueItem/index.tsxsrc/components/IssueList/index.tsxsrc/components/IssueModal/CreateIssueModal/index.tsxsrc/components/KeywordTag/index.tsxsrc/components/LanguageSelector/index.tsxsrc/components/Layout/UserDropdown/MiniQuotaDisplay/index.tsxsrc/components/Layout/UserDropdown/index.tsxsrc/components/Layout/VersionStatus/index.tsxsrc/components/Layout/index.tsxsrc/components/Login/AddEmailModal.tsxsrc/components/Login/JellyfinLogin.tsxsrc/components/Login/LocalLogin.tsxsrc/components/Login/index.tsxsrc/components/ManageSlideOver/index.tsxsrc/components/MovieDetails/MovieCast/index.tsxsrc/components/MovieDetails/MovieCrew/index.tsxsrc/components/MovieDetails/MovieRecommendations.tsxsrc/components/MovieDetails/MovieSimilar.tsxsrc/components/MovieDetails/index.tsxsrc/components/PersonDetails/index.tsxsrc/components/RegionSelector/index.tsxsrc/components/RequestBlock/index.tsxsrc/components/RequestButton/index.tsxsrc/components/RequestCard/index.tsxsrc/components/RequestList/RequestItem/index.tsxsrc/components/RequestList/index.tsxsrc/components/RequestModal/AdvancedRequester/index.tsxsrc/components/RequestModal/CollectionRequestModal.tsxsrc/components/RequestModal/MovieRequestModal.tsxsrc/components/RequestModal/SearchByNameModal/index.tsxsrc/components/RequestModal/TvRequestModal.tsxsrc/components/ResetPassword/RequestResetLink.tsxsrc/components/ResetPassword/index.tsxsrc/components/Search/index.tsxsrc/components/Selector/CertificationSelector.tsxsrc/components/Selector/index.tsxsrc/components/Settings/Notifications/NotificationsDiscord.tsxsrc/components/Settings/Notifications/NotificationsEmail.tsxsrc/components/Settings/Notifications/NotificationsGotify/index.tsxsrc/components/Settings/Notifications/NotificationsNtfy/index.tsxsrc/components/Settings/Notifications/NotificationsPushbullet/index.tsxsrc/components/Settings/Notifications/NotificationsPushover/index.tsxsrc/components/Settings/Notifications/NotificationsSlack/index.tsxsrc/components/Settings/Notifications/NotificationsTelegram.tsxsrc/components/Settings/Notifications/NotificationsWebPush/index.tsxsrc/components/Settings/Notifications/NotificationsWebhook/index.tsxsrc/components/Settings/OverrideRule/OverrideRuleModal.tsxsrc/components/Settings/OverrideRule/OverrideRuleTiles.tsxsrc/components/Settings/RadarrModal/index.tsxsrc/components/Settings/SettingsAbout/index.tsxsrc/components/Settings/SettingsJellyfin.tsxsrc/components/Settings/SettingsJobsCache/index.tsxsrc/components/Settings/SettingsLogs/index.tsxsrc/components/Settings/SettingsMain/index.tsxsrc/components/Settings/SettingsMetadata.tsxsrc/components/Settings/SettingsNetwork/index.tsxsrc/components/Settings/SettingsPlex.tsxsrc/components/Settings/SettingsServices.tsxsrc/components/Settings/SettingsUsers/index.tsxsrc/components/Settings/SonarrModal/index.tsxsrc/components/Setup/JellyfinSetup.tsxsrc/components/Setup/LoginWithPlex.tsxsrc/components/Setup/SetupLogin.tsxsrc/components/Setup/index.tsxsrc/components/StatusChecker/index.tsxsrc/components/TitleCard/ErrorCard.tsxsrc/components/TitleCard/TmdbTitleCard.tsxsrc/components/TitleCard/index.tsxsrc/components/TvDetails/Season/index.tsxsrc/components/TvDetails/TvCast/index.tsxsrc/components/TvDetails/TvCrew/index.tsxsrc/components/TvDetails/TvRecommendations.tsxsrc/components/TvDetails/TvSimilar.tsxsrc/components/TvDetails/index.tsxsrc/components/UserList/BulkEditModal.tsxsrc/components/UserList/JellyfinImportModal.tsxsrc/components/UserList/PlexImportModal.tsxsrc/components/UserList/index.tsxsrc/components/UserProfile/UserSettings/UserGeneralSettings/index.tsxsrc/components/UserProfile/UserSettings/UserLinkedAccountsSettings/LinkJellyfinModal.tsxsrc/components/UserProfile/UserSettings/UserLinkedAccountsSettings/index.tsxsrc/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsDiscord.tsxsrc/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsEmail.tsxsrc/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsPushbullet.tsxsrc/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsPushover.tsxsrc/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsTelegram.tsxsrc/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsWebPush/index.tsxsrc/components/UserProfile/UserSettings/UserNotificationSettings/index.tsxsrc/components/UserProfile/UserSettings/UserPasswordChange/index.tsxsrc/components/UserProfile/UserSettings/UserPermissions/index.tsxsrc/components/UserProfile/UserSettings/index.tsxsrc/components/UserProfile/index.tsxsrc/context/SettingsContext.tsxsrc/hooks/useRequestOverride.tssrc/hooks/useUser.tssrc/pages/_app.tsxsrc/pages/collection/[collectionId]/index.tsxsrc/pages/movie/[movieId]/index.tsxsrc/pages/tv/[tvId]/index.tsxsrc/utils/apiUrl.tssrc/utils/pushSubscriptionHelpers.ts
|
|
||
| const { data: existingUsers } = useSWR<UserResultsResponse>( | ||
| `/api/v1/user?take=${children}` | ||
| apiUrl(`/user?take=${children}`) |
There was a problem hiding this comment.
Use a numeric source for take instead of children
Line 57 builds take from children (React.ReactNode), which can be non-numeric/undefined and cause incomplete existing-user results (allowing already-imported users to reappear).
💡 Proposed fix
interface JellyfinImportProps {
onCancel?: () => void;
onComplete?: () => void;
children?: React.ReactNode;
+ userFetchTake?: number;
}
const JellyfinImportModal: React.FC<JellyfinImportProps> = ({
onCancel,
onComplete,
children,
+ userFetchTake = 1000,
}) => {
@@
const { data: existingUsers } = useSWR<UserResultsResponse>(
- apiUrl(`/user?take=${children}`)
+ apiUrl(`/user?take=${userFetchTake}`)
);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/UserList/JellyfinImportModal.tsx` at line 57,
JellyfinImportModal is interpolating `children` (a React.ReactNode) into the
query string for apiUrl(`/user?take=${children}`), which can be non-numeric;
replace that with a validated numeric value (e.g., use a prop/state like `take`,
`pageSize` or `takeCount` or compute `const take = Number(props.take ??
DEFAULT_TAKE)`), validate/fallback if NaN, and then call
`apiUrl(`/user?take=${take}`)`; reference `apiUrl`, `JellyfinImportModal`, and
`children` in your change so you replace the `children` usage with the numeric
`take` variable.
There was a problem hiding this comment.
@0xSysR3ll, understood! The children-as-take issue is indeed pre-existing and unrelated to this refactor. Would you like me to open a follow-up GitHub issue to track it for a future fix?
🧠 Learnings used
Learnt from: afonsojramos
Repo: seerr-team/seerr PR: 2690
File: src/components/UserProfile/UserSettings/UserLinkedAccountsSettings/LinkJellyfinModal.tsx:59-62
Timestamp: 2026-03-18T13:53:44.567Z
Learning: In `src/components/UserProfile/UserSettings/UserLinkedAccountsSettings/LinkJellyfinModal.tsx`, Jellyfin and Emby are mutually exclusive in practice — only one can be configured as the media server at a time. Therefore, deriving `mediaServerName` from `settings.currentSettings.primaryMediaServer` is always correct and does not need a separate prop for the selected server type.
Learnt from: fallenbagel
Repo: seerr-team/seerr PR: 2212
File: server/routes/auth.ts:744-756
Timestamp: 2026-03-19T10:47:13.570Z
Learning: In seerr-team/seerr (`server/routes/auth.ts`), Jellyfin/Emby user creation always sets `email: account.User.Name` (i.e., username as the email field) as a deliberate fallback because Jellyfin does not store email addresses. This applies to all Jellyfin user creation paths: regular login, user import, and Quick Connect. The `userEmailRequired` warning at the `/me` endpoint is the designed mechanism to prompt users to provide a real email when the admin has enabled email notifications. Do not flag `email: account.User.Name` in Jellyfin user creation blocks as a bug or inconsistency.
Learnt from: michaelhthomas
Repo: seerr-team/seerr PR: 2715
File: server/routes/auth.ts:864-880
Timestamp: 2026-03-20T18:39:29.322Z
Learning: Repo seerr-team/seerr — OIDC required-claims semantics: In server/routes/auth.ts, the hasRequiredClaims check is intentionally strict (claim value must be boolean true). Non-boolean claim values should fail for now; future PRs may add granular control. This preference should guide future reviews and suggestions.
Learnt from: michaelhthomas
Repo: seerr-team/seerr PR: 2715
File: src/components/UserProfile/UserSettings/UserLinkedAccountsSettings/index.tsx:91-114
Timestamp: 2026-03-20T20:33:29.810Z
Learning: Repo seerr-team/seerr — In src/components/UserProfile/UserSettings/UserLinkedAccountsSettings/index.tsx (users/[userId]/settings/linked-accounts), it is acceptable to clear all query parameters after the OIDC callback using router.replace(router.pathname, undefined, { shallow: true }); the dynamic route param [userId] remains intact, and this simpler approach is preferred in this codebase.
Learnt from: fallenbagel
Repo: seerr-team/seerr PR: 2731
File: server/lib/i18n/extractMessages.ts:71-71
Timestamp: 2026-03-20T01:39:15.788Z
Learning: In seerr-team/seerr, the server-side i18n extraction script (`server/lib/i18n/extractMessages.ts`) intentionally calls `saveMessages()` without a `.catch()` handler, mirroring the pre-existing pattern in the frontend extractor. This is acceptable because Node.js 15+ exits with a non-zero code on unhandled promise rejections by default, ensuring CI correctly detects failures. Do not flag this as an issue in future reviews.
Learnt from: michaelhthomas
Repo: seerr-team/seerr PR: 2715
File: src/components/UserProfile/UserSettings/UserLinkedAccountsSettings/index.tsx:248-250
Timestamp: 2026-03-26T16:06:26.280Z
Learning: Repo seerr-team/seerr — In server/routes/user/usersettings.ts, the DELETE endpoints for media servers (`/linked-accounts/plex` and `/linked-accounts/jellyfin`) require the user to have a local email and password; having multiple linked accounts alone is not sufficient. Client UI should gate media-server unlink on `hasPassword` (and primary-admin restriction) rather than `accounts.length > 1`.
Learnt from: gauthier-th
Repo: seerr-team/seerr PR: 2600
File: src/components/Discover/AvailableMedia/index.tsx:9-9
Timestamp: 2026-03-26T15:49:55.253Z
Learning: In this repo, when a React component imports the Next.js error page from 'app/pages/_error', import it with an explicit local alias (e.g., `import ErrorPage from 'app/pages/_error'`) rather than using the identifier `ErrorPage`/`Error` directly in a way that could shadow the global `Error` constructor. This avoids shadowing issues and satisfies Biome’s `noShadowRestrictedNames` rule.
There was a problem hiding this comment.
Agreed, pre-existing. I'll file a separate issue for this so it doesn't get lost.
There was a problem hiding this comment.
Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!
| createdAt: Date; | ||
| }[] | ||
| >(`/api/v1/user/${user?.id}/pushSubscriptions`, { revalidateOnMount: true }); | ||
| >(apiUrl(`/user/${user?.id}/pushSubscriptions`), { revalidateOnMount: true }); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsWebPush/index.tsx | sed -n '1,100p'Repository: seerr-team/seerr
Length of output: 4620
🏁 Script executed:
cat -n src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsWebPush/index.tsx | sed -n '240,280p'Repository: seerr-team/seerr
Length of output: 1747
🏁 Script executed:
cat -n src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsWebPush/index.tsx | sed -n '100,150p'Repository: seerr-team/seerr
Length of output: 1852
Guard the device-subscriptions SWR key until user is loaded.
Line 78 builds a concrete URL unconditionally, unlike the guarded pattern at line 68. When user is undefined on initial render, this produces /user/undefined/pushSubscriptions and triggers an unnecessary bogus fetch.
🛠️ Proposed fix
const { data: dataDevices, mutate: revalidateDevices } = useSWR<
{
endpoint: string;
p256dh: string;
auth: string;
userAgent: string;
createdAt: Date;
}[]
- >(apiUrl(`/user/${user?.id}/pushSubscriptions`), { revalidateOnMount: true });
+ >(
+ user ? apiUrl(`/user/${user.id}/pushSubscriptions`) : null,
+ { revalidateOnMount: true }
+ );📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| >(apiUrl(`/user/${user?.id}/pushSubscriptions`), { revalidateOnMount: true }); | |
| const { data: dataDevices, mutate: revalidateDevices } = useSWR< | |
| { | |
| endpoint: string; | |
| p256dh: string; | |
| auth: string; | |
| userAgent: string; | |
| createdAt: Date; | |
| }[] | |
| >( | |
| user ? apiUrl(`/user/${user.id}/pushSubscriptions`) : null, | |
| { revalidateOnMount: true } | |
| ); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsWebPush/index.tsx`
at line 78, The SWR key is being constructed unconditionally with
apiUrl(`/user/${user?.id}/pushSubscriptions`) which yields /user/undefined/...
on initial render; update the SWR call in the UserNotificationsWebPush component
to only pass a concrete URL when user is defined (e.g., use user ?
apiUrl(`/user/${user.id}/pushSubscriptions`) : null) so the fetch is skipped
until user is loaded, mirroring the guarded pattern used at the earlier SWR
call.
There was a problem hiding this comment.
@0xSysR3ll, understood — the unguarded SWR key is a pre-existing issue and not introduced by this refactor. Would you like me to open a follow-up GitHub issue to track the fix separately?
🧠 Learnings used
Learnt from: michaelhthomas
Repo: seerr-team/seerr PR: 2715
File: src/components/UserProfile/UserSettings/UserLinkedAccountsSettings/index.tsx:91-114
Timestamp: 2026-03-20T20:33:29.810Z
Learning: Repo seerr-team/seerr — In src/components/UserProfile/UserSettings/UserLinkedAccountsSettings/index.tsx (users/[userId]/settings/linked-accounts), it is acceptable to clear all query parameters after the OIDC callback using router.replace(router.pathname, undefined, { shallow: true }); the dynamic route param [userId] remains intact, and this simpler approach is preferred in this codebase.
Learnt from: afonsojramos
Repo: seerr-team/seerr PR: 2690
File: src/components/Login/index.tsx:73-77
Timestamp: 2026-03-18T13:54:35.854Z
Learning: In `src/components/Login/index.tsx` (PR `#2690`), the state where `enabledAuthMethods` is empty AND `localLogin` is false is considered an admin misconfiguration that cannot occur in practice. The `SettingsUsers` settings UI enforces a validation rule requiring at least one of `localLogin` or one of the enabled auth method flags (plexEnabled, jellyfinEnabled, embyEnabled) to be active before saving. Therefore, the `'plex'` fallback in `defaultMethod` computation is unreachable in a correctly configured Seerr instance and does not need a defensive misconfiguration UI.
Learnt from: michaelhthomas
Repo: seerr-team/seerr PR: 2715
File: src/components/UserProfile/UserSettings/UserLinkedAccountsSettings/index.tsx:248-250
Timestamp: 2026-03-26T16:06:26.280Z
Learning: Repo seerr-team/seerr — In server/routes/user/usersettings.ts, the DELETE endpoints for media servers (`/linked-accounts/plex` and `/linked-accounts/jellyfin`) require the user to have a local email and password; having multiple linked accounts alone is not sufficient. Client UI should gate media-server unlink on `hasPassword` (and primary-admin restriction) rather than `accounts.length > 1`.
Learnt from: michaelhthomas
Repo: seerr-team/seerr PR: 2715
File: server/routes/auth.ts:864-880
Timestamp: 2026-03-20T18:39:29.322Z
Learning: Repo seerr-team/seerr — OIDC required-claims semantics: In server/routes/auth.ts, the hasRequiredClaims check is intentionally strict (claim value must be boolean true). Non-boolean claim values should fail for now; future PRs may add granular control. This preference should guide future reviews and suggestions.
Learnt from: fallenbagel
Repo: seerr-team/seerr PR: 2731
File: server/lib/i18n/extractMessages.ts:71-71
Timestamp: 2026-03-20T01:39:15.788Z
Learning: In seerr-team/seerr, the server-side i18n extraction script (`server/lib/i18n/extractMessages.ts`) intentionally calls `saveMessages()` without a `.catch()` handler, mirroring the pre-existing pattern in the frontend extractor. This is acceptable because Node.js 15+ exits with a non-zero code on unhandled promise rejections by default, ensuring CI correctly detects failures. Do not flag this as an issue in future reviews.
Learnt from: gauthier-th
Repo: seerr-team/seerr PR: 2600
File: src/components/Discover/AvailableMedia/index.tsx:9-9
Timestamp: 2026-03-26T15:49:55.253Z
Learning: In this repo, when a React component imports the Next.js error page from 'app/pages/_error', import it with an explicit local alias (e.g., `import ErrorPage from 'app/pages/_error'`) rather than using the identifier `ErrorPage`/`Error` directly in a way that could shadow the global `Error` constructor. This avoids shadowing issues and satisfies Biome’s `noShadowRestrictedNames` rule.
There was a problem hiding this comment.
Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!
Description
Replace all hardcoded API and proxy path strings (
/api/v1,/api-docs,/imageproxy,/avatarproxy) with centralized constants and a helper function. No behavior changes, pure mechanical refactor.Why
There are 201 occurrences of
/api/v1scattered across 73 client files, plus hardcoded mounts in the server. That's 201 places where a single path is repeated as a raw string literal. If any of these paths ever need to change, every occurrence has to be found and updated manually. That's error-prone and hard to verify.Extracting these into shared constants is a well-established refactoring pattern:
After this change,
/api/v1is defined in exactly 2 places (one server constant, one client constant). Everything else references them. This makes the codebase easier to maintain and opens the door for things like API versioning, base path configuration, or reverse proxy flexibility without having to touch every file again.What changed
server/constants/routes.ts- exportsAPI_BASE_PATH,API_DOCS_PATH,IMAGE_PROXY_PATH,AVATAR_PROXY_PATHsrc/utils/apiUrl.ts- helper function that prepends the API base to a pathserver/index.ts- route mounts now reference constantsserver/routes/index.ts- deprecated route paths reference constantssrc/components/Common/CachedImage/index.tsx- image proxy paths reference constants/api/v1string literals replaced withapiUrl()callsHow Has This Been Tested?
pnpm buildsucceedspnpm i18n:extractproduced no changes (expected, no user-facing text was modified)Screenshots / Logs (if applicable)
N/A. No visual changes, this is a pure code refactor.
Checklist:
pnpm buildpnpm i18n:extractAI Disclosure: This PR was developed with Claude Code as a pair programming tool. I directed the approach, defined the constants/helper pattern, reviewed all 140 file changes, and ran an independent review pass to catch issues. I'm the author, Claude is my editor.
Summary by CodeRabbit