Refactor arr routes: explicit dependency injection

- Updated all arr route modules to use destructured dependencies
- Added JSDoc comments for factory functions
- Replaced ctx. references with direct parameter access
- Updated arr/index.js to extract and pass explicit dependencies
- Maintained backward compatibility with context pattern
- All files pass syntax validation

Files refactored:
- routes/arr/detect.js
- routes/arr/credentials.js
- routes/arr/config.js (579 lines)
- routes/arr/smart-connect.js
- routes/arr/plex.js
- routes/arr/helpers.js
- routes/arr/index.js (orchestrator)
This commit is contained in:
Krystie
2026-03-29 21:30:20 -07:00
parent ac23b2e093
commit 6bde2eb62e
7 changed files with 217 additions and 132 deletions

View File

@@ -1,11 +1,22 @@
const express = require('express');
const { APP_PORTS } = require('../../constants');
module.exports = function(ctx, helpers) {
/**
* Arr smart-connect routes factory
* @param {Object} deps - Explicit dependencies
* @param {Object} deps.credentialManager - Credential manager
* @param {Function} deps.fetchT - Timeout-wrapped fetch
* @param {Function} deps.asyncHandler - Async route handler wrapper
* @param {Function} deps.errorResponse - Error response helper
* @param {Object} deps.log - Logger instance
* @param {Object} deps.helpers - Arr helpers module
* @returns {express.Router}
*/
module.exports = function({ credentialManager, fetchT, asyncHandler, errorResponse, log, helpers }) {
const router = express.Router();
// Smart Connect: Unified orchestration endpoint
router.post('/arr/smart-connect', ctx.asyncHandler(async (req, res) => {
router.post('/arr/smart-connect', asyncHandler(async (req, res) => {
const { services: inputServices, configurePlex, configureProwlarr, configureSeerr, saveCredentials } = req.body;
const steps = [];
const connectedServices = {}; // { radarr: { url, apiKey }, sonarr: { url, apiKey }, ... }
@@ -20,9 +31,9 @@ module.exports = function(ctx, helpers) {
// Fallback to stored credentials
if (!apiKey) {
const credKey = `arr.${svc}.apikey`;
apiKey = await ctx.credentialManager.retrieve(credKey);
apiKey = await credentialManager.retrieve(credKey);
if (!url) {
const metadata = await ctx.credentialManager.getMetadata(credKey);
const metadata = await credentialManager.getMetadata(credKey);
url = metadata?.url;
}
}
@@ -52,7 +63,7 @@ module.exports = function(ctx, helpers) {
// Save credentials
if (saveCredentials) {
const stored = await ctx.credentialManager.store(`arr.${svc}.apikey`, apiKey, {
const stored = await credentialManager.store(`arr.${svc}.apikey`, apiKey, {
service: svc, source: 'external', url,
lastVerified: new Date().toISOString(),
version: test.version
@@ -71,7 +82,7 @@ module.exports = function(ctx, helpers) {
let plexUrl = null;
if (configurePlex) {
plexToken = await helpers.getPlexToken('plex');
if (!plexToken) plexToken = await ctx.credentialManager.retrieve('arr.plex.token');
if (!plexToken) plexToken = await credentialManager.retrieve('arr.plex.token');
if (plexToken) {
// Get Plex URL
@@ -108,14 +119,14 @@ module.exports = function(ctx, helpers) {
const radarrBasePath = radarrUrlObj.pathname.replace(/\/+$/, '');
// Fetch quality profiles
const profilesRes = await ctx.fetchT(`${radarrUrl}/api/v3/qualityprofile`, {
const profilesRes = await fetchT(`${radarrUrl}/api/v3/qualityprofile`, {
headers: { 'X-Api-Key': connectedServices.radarr.apiKey },
signal: AbortSignal.timeout(10000)
});
const profiles = profilesRes.ok ? await profilesRes.json() : [];
// Use stored quality profile preference, fallback to first profile
const radarrMeta = await ctx.credentialManager.getMetadata('arr.radarr.apikey');
const radarrMeta = await credentialManager.getMetadata('arr.radarr.apikey');
let defaultProfile = profiles[0] || { id: 1, name: 'Any' };
if (radarrMeta?.qualityProfileId) {
const stored = profiles.find(p => p.id === radarrMeta.qualityProfileId);
@@ -123,7 +134,7 @@ module.exports = function(ctx, helpers) {
}
// Fetch root folders
const rootFoldersRes = await ctx.fetchT(`${radarrUrl}/api/v3/rootfolder`, {
const rootFoldersRes = await fetchT(`${radarrUrl}/api/v3/rootfolder`, {
headers: { 'X-Api-Key': connectedServices.radarr.apiKey },
signal: AbortSignal.timeout(10000)
});
@@ -151,7 +162,7 @@ module.exports = function(ctx, helpers) {
tags: []
};
const radarrRes = await ctx.fetchT(`${overseerrUrl}/api/v1/settings/radarr`, {
const radarrRes = await fetchT(`${overseerrUrl}/api/v1/settings/radarr`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Cookie': overseerrCookie },
body: JSON.stringify(radarrConfig),
@@ -175,21 +186,21 @@ module.exports = function(ctx, helpers) {
const sonarrUrlObj = new URL(sonarrUrl);
const sonarrBasePath = sonarrUrlObj.pathname.replace(/\/+$/, '');
const profilesRes = await ctx.fetchT(`${sonarrUrl}/api/v3/qualityprofile`, {
const profilesRes = await fetchT(`${sonarrUrl}/api/v3/qualityprofile`, {
headers: { 'X-Api-Key': connectedServices.sonarr.apiKey },
signal: AbortSignal.timeout(10000)
});
const profiles = profilesRes.ok ? await profilesRes.json() : [];
// Use stored quality profile preference, fallback to first profile
const sonarrMeta = await ctx.credentialManager.getMetadata('arr.sonarr.apikey');
const sonarrMeta = await credentialManager.getMetadata('arr.sonarr.apikey');
let defaultProfile = profiles[0] || { id: 1, name: 'Any' };
if (sonarrMeta?.qualityProfileId) {
const stored = profiles.find(p => p.id === sonarrMeta.qualityProfileId);
if (stored) defaultProfile = stored;
}
const rootFoldersRes = await ctx.fetchT(`${sonarrUrl}/api/v3/rootfolder`, {
const rootFoldersRes = await fetchT(`${sonarrUrl}/api/v3/rootfolder`, {
headers: { 'X-Api-Key': connectedServices.sonarr.apiKey },
signal: AbortSignal.timeout(10000)
});
@@ -198,7 +209,7 @@ module.exports = function(ctx, helpers) {
let languageProfileId = 1;
try {
const langRes = await ctx.fetchT(`${sonarrUrl}/api/v3/languageprofile`, {
const langRes = await fetchT(`${sonarrUrl}/api/v3/languageprofile`, {
headers: { 'X-Api-Key': connectedServices.sonarr.apiKey },
signal: AbortSignal.timeout(5000)
});
@@ -229,7 +240,7 @@ module.exports = function(ctx, helpers) {
tags: []
};
const sonarrRes = await ctx.fetchT(`${overseerrUrl}/api/v1/settings/sonarr`, {
const sonarrRes = await fetchT(`${overseerrUrl}/api/v1/settings/sonarr`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Cookie': overseerrCookie },
body: JSON.stringify(sonarrConfig),