refactor: architecture cleanup — service layer, admin split, N+1 fix#89
Merged
refactor: architecture cleanup — service layer, admin split, N+1 fix#89
Conversation
Move all business logic from routes/requests.ts into services/requestService.ts: - sendToService, sendToRadarr, sendToSonarr, resolveServiceContext - findOrCreateMedia, getUserTagName, requestCollectionMovie - retryFailedRequests, promoteStaleStatuses, validateRequestBody Routes file is now thin HTTP handlers only. Fix inverted dependency: scheduler now imports from services/ not routes/.
…es 2-5)
Phase 2 — Split admin.ts (1380 lines) into 12 domain modules:
routes/admin/{settings,services,folderRules,users,logs,jobs,sync,
notifications,quality,danger,roles,keywords}.ts
Each module registers routes on the same Fastify instance (RBAC preserved)
Phase 4 — Extract mediaService.ts from routes/media.ts:
performLiveCheck, performLiveCheckWithTimeout, cacheLanguageData,
promoteMediaToAvailable — all moved to service layer
Phase 5 — Rewrite requestSync.ts with bulk queries:
Replace N+1 pattern (findUnique + findFirst per movie/series in nested loops)
with bulk findMany + in-memory Set lookups + createMany batch insert
Add utils/batch.ts chunk helper for SQLite parameter limit
Code review fixes:
- Replace hardcoded status list with COMPLETABLE_REQUEST_STATUSES constant
- Remove dead getCollection import from requestService
- Add 'added' field to SonarrSeries interface, remove unsafe double-cast
…Phase A)
Frontend:
- Extract useMediaDetailData, useMediaRequestActions, useEpisodeModal hooks
- Extract CollectionSection into its own component
- MediaDetailPage reduced from 986 to 714 lines (pure JSX + derivation)
Backend:
- Create safeNotify/safeUserNotify wrapper, replace 10 inline .catch patterns
- Split sync.ts (656 lines) into sync/{helpers,movieSync,tvSync,availabilitySync,index}.ts
- Remove dead imports from requestService.ts
- Replace notifyRequesters with safeUserNotify in sync/helpers.ts
Consolidate all sync-related code under services/sync/. Update imports in scheduler.ts and tmdb.ts routes. Re-export from sync/index.ts for backwards compatibility.
- Create providers/radarr/{client,types,index}.ts — RadarrClient implements ArrClient
- Create providers/sonarr/{client,types,index}.ts — SonarrClient implements ArrClient
- Add ArrClient interface to providers/types.ts with shared methods
- Add getArrClient, getArrClientForService, createArrClient, getServiceTypeForMedia to registry
- Migrate all 13 consumers from services/radarr.ts and services/sonarr.ts to provider registry
- Eliminate if/else dispatch in admin/services.ts (profiles, rootfolders)
- Delete services/radarr.ts and services/sonarr.ts
This was referenced Apr 7, 2026
…ces in business logic ArrClient interface extended with: - getAllMedia() with tags + hasFile fields for generic sync - checkAvailability() for live check — language extraction moved into providers - findByExternalId(), addMedia(), searchMedia() for request dispatch - getHistoryEntries() for normalized availability sync - getEpisodesNormalized() (optional, TV only) - Metadata properties: mediaType, serviceType, dbIdField, defaultRootFolder Sync pipeline unified: - movieSync.ts + tvSync.ts merged into mediaSync.ts (generic) - processSingleMedia handles both movie and TV via ArrClient interface - TV lookup: sequential tvdbId then negative tmdbId fallback (legacy rows) - Skip items with no valid externalId (tvdbId=0 guard) - availabilitySync.ts rewritten with generic syncServiceAvailability Request service unified: - sendToRadarr + sendToSonarr merged into sendToArrService - Uses client.findByExternalId/addMedia/searchMedia via interface - requestSync rewritten — uses getAllMedia() with tags, no unsafe casts Minor fixes: - radarr-sonarr.ts: errors now logged instead of swallowed - support.ts: parseId instead of parseInt - HomePage.tsx: DB-to-TMDB mapper extracted to utils/mediaMapper.ts Verification: zero grep results for RadarrClient/SonarrClient/RadarrMovie/SonarrSeries outside providers/ directory.
…arr example Complete rewrite covering: - Provider architecture with client.ts/types.ts/index.ts structure - Full ArrClient interface reference - Registry API (getArrClient, createArrClient, getServiceTypeForMedia) - Step-by-step guide to add a new *arr provider (Lidarr example) - What works automatically vs what needs additional work
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
Major architecture refactor addressing #88. Transforms the codebase from patch-on-patch spaghetti into a clean, modular provider-driven architecture.
Phase 1 — Extract RequestService
routes/requests.ts→services/requestService.tsservices/notroutes/Phase 2 — Split admin.ts (1380 lines → 12 modules)
routes/admin/{settings,services,folderRules,users,logs,jobs,sync,notifications,quality,danger,roles,keywords}.tsPhase 3 — ArrClient provider abstraction
ArrClientinterface with:getAllMedia,checkAvailability,findByExternalId,addMedia,searchMedia,getHistoryEntries,getEpisodesNormalizedproviders/radarr/andproviders/sonarr/(client.ts + types.ts + index.ts)getArrClient(),getArrClientForService(),createArrClient(),getServiceTypeForMedia()providers/Phase 4 — Generic sync pipeline
movieSync.ts+tvSync.tsmerged intomediaSync.ts(generic via ArrClient)availabilitySync.tsrewritten with genericsyncServiceAvailability()requestSync.tsrewritten — usesgetAllMedia()with tags, no unsafe castskeywordSync.tsmoved intosync/modulePhase 5 — MediaService + safeNotify + frontend hooks
mediaService.ts:performLiveCheckusesclient.checkAvailability()via interfacesafeNotify/safeUserNotifywrapper replaces 10+ inline.catch()patternssync.ts(656 lines) split intosync/{helpers,mediaSync,availabilitySync,keywordSync,index}.tsMediaDetailPage.tsx(986→714 lines): extracteduseMediaDetailData,useMediaRequestActions,useEpisodeModalhooks +CollectionSectioncomponentMinor fixes
radarr-sonarr.ts: errors logged instead of silently swallowedsupport.ts:parseIdinstead ofparseIntHomePage.tsx: DB-to-TMDB mapper extracted toutils/mediaMapper.tsDocumentation
docs/providers.mdfully rewritten with ArrClient interface, registry API, and step-by-step Lidarr exampleCloses #88
Test plan