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:
@@ -1,14 +1,23 @@
|
||||
const { APP_PORTS } = require('../../constants');
|
||||
|
||||
module.exports = function(ctx) {
|
||||
/**
|
||||
* Arr helpers factory
|
||||
* @param {Object} deps - Explicit dependencies
|
||||
* @param {Object} deps.docker - Docker client wrapper
|
||||
* @param {Object} deps.credentialManager - Credential manager
|
||||
* @param {Function} deps.fetchT - Timeout-wrapped fetch
|
||||
* @param {Object} deps.log - Logger instance
|
||||
* @returns {Object} Helper functions
|
||||
*/
|
||||
module.exports = function({ docker, credentialManager, fetchT, log }) {
|
||||
|
||||
// Helper: Extract API key from arr service config.xml
|
||||
async function getArrApiKey(containerName) {
|
||||
try {
|
||||
const container = await ctx.docker.findContainer(containerName);
|
||||
const container = await docker.findContainer(containerName);
|
||||
if (!container) return null;
|
||||
|
||||
const dockerContainer = ctx.docker.client.getContainer(container.Id);
|
||||
const dockerContainer = docker.client.getContainer(container.Id);
|
||||
const exec = await dockerContainer.exec({
|
||||
Cmd: ['cat', '/config/config.xml'],
|
||||
AttachStdout: true,
|
||||
@@ -28,7 +37,7 @@ module.exports = function(ctx) {
|
||||
stream.on('error', () => resolve(null));
|
||||
});
|
||||
} catch (error) {
|
||||
ctx.log.error('docker', 'Failed to get API key', { containerName, error: error.message });
|
||||
log.error('docker', 'Failed to get API key', { containerName, error: error.message });
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -36,14 +45,14 @@ module.exports = function(ctx) {
|
||||
// Helper: Get Plex token from container or config
|
||||
async function getPlexToken(containerName) {
|
||||
try {
|
||||
const containers = await ctx.docker.client.listContainers({ all: false });
|
||||
const containers = await docker.client.listContainers({ all: false });
|
||||
const container = containers.find(c =>
|
||||
c.Names.some(n => n.toLowerCase().includes(containerName.toLowerCase()) || n.toLowerCase().includes('plex'))
|
||||
);
|
||||
|
||||
if (!container) return null;
|
||||
|
||||
const dockerContainer = ctx.docker.client.getContainer(container.Id);
|
||||
const dockerContainer = docker.client.getContainer(container.Id);
|
||||
const exec = await dockerContainer.exec({
|
||||
Cmd: ['cat', '/config/Library/Application Support/Plex Media Server/Preferences.xml'],
|
||||
AttachStdout: true,
|
||||
@@ -62,7 +71,7 @@ module.exports = function(ctx) {
|
||||
stream.on('error', () => resolve(null));
|
||||
});
|
||||
} catch (error) {
|
||||
ctx.log.error('docker', 'Failed to get Plex token', { error: error.message });
|
||||
log.error('docker', 'Failed to get Plex token', { error: error.message });
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -84,16 +93,16 @@ module.exports = function(ctx) {
|
||||
|
||||
// Fall back to stored Plex token in credential manager
|
||||
if (!plexToken) {
|
||||
plexToken = await ctx.credentialManager.retrieve('arr.plex.token');
|
||||
plexToken = await credentialManager.retrieve('arr.plex.token');
|
||||
}
|
||||
|
||||
if (!plexToken) {
|
||||
ctx.log.error('arr', 'Could not get Plex token for Seerr auth (no container, no stored token)');
|
||||
log.error('arr', 'Could not get Plex token for Seerr auth (no container, no stored token)');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Authenticate with Seerr via Plex token
|
||||
const authRes = await ctx.fetchT(`${seerrUrl}/api/v1/auth/plex`, {
|
||||
const authRes = await fetchT(`${seerrUrl}/api/v1/auth/plex`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ authToken: plexToken }),
|
||||
@@ -101,20 +110,20 @@ module.exports = function(ctx) {
|
||||
});
|
||||
|
||||
if (!authRes.ok) {
|
||||
ctx.log.error('arr', 'Seerr Plex auth failed', { status: authRes.status });
|
||||
log.error('arr', 'Seerr Plex auth failed', { status: authRes.status });
|
||||
return null;
|
||||
}
|
||||
|
||||
const setCookie = authRes.headers.get('set-cookie');
|
||||
if (!setCookie) {
|
||||
ctx.log.error('arr', 'No session cookie returned from Seerr');
|
||||
log.error('arr', 'No session cookie returned from Seerr');
|
||||
return null;
|
||||
}
|
||||
|
||||
const sessionCookie = setCookie.split(';')[0];
|
||||
return { cookie: sessionCookie, plexToken };
|
||||
} catch (e) {
|
||||
ctx.log.error('arr', 'Could not get Seerr session', { error: e.message });
|
||||
log.error('arr', 'Could not get Seerr session', { error: e.message });
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -123,7 +132,7 @@ module.exports = function(ctx) {
|
||||
// Uses session cookie auth (Overseerr requires Plex-based admin session for settings)
|
||||
async function connectPlexToOverseerr(plexUrl, plexToken, overseerrUrl, sessionCookie) {
|
||||
// 1. Get Plex server identity (for return info)
|
||||
const identityRes = await ctx.fetchT(`${plexUrl}/identity`, {
|
||||
const identityRes = await fetchT(`${plexUrl}/identity`, {
|
||||
headers: { 'X-Plex-Token': plexToken, 'Accept': 'application/json' },
|
||||
signal: AbortSignal.timeout(10000)
|
||||
});
|
||||
@@ -139,7 +148,7 @@ module.exports = function(ctx) {
|
||||
useSsl: false
|
||||
};
|
||||
|
||||
const configRes = await ctx.fetchT(`${overseerrUrl}/api/v1/settings/plex`, {
|
||||
const configRes = await fetchT(`${overseerrUrl}/api/v1/settings/plex`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -154,19 +163,19 @@ module.exports = function(ctx) {
|
||||
|
||||
// 3. Trigger library sync — Overseerr will use the admin's Plex token to discover libraries
|
||||
try {
|
||||
await ctx.fetchT(`${overseerrUrl}/api/v1/settings/plex/sync`, {
|
||||
await fetchT(`${overseerrUrl}/api/v1/settings/plex/sync`, {
|
||||
method: 'POST',
|
||||
headers: { 'Cookie': sessionCookie },
|
||||
signal: AbortSignal.timeout(10000)
|
||||
});
|
||||
} catch (e) {
|
||||
ctx.log.warn('arr', 'Plex library sync trigger failed (non-fatal)', { error: e.message });
|
||||
log.warn('arr', 'Plex library sync trigger failed (non-fatal)', { error: e.message });
|
||||
}
|
||||
|
||||
// 4. Get discovered libraries
|
||||
let libraries = [];
|
||||
try {
|
||||
const libRes = await ctx.fetchT(`${overseerrUrl}/api/v1/settings/plex`, {
|
||||
const libRes = await fetchT(`${overseerrUrl}/api/v1/settings/plex`, {
|
||||
headers: { 'Cookie': sessionCookie },
|
||||
signal: AbortSignal.timeout(5000)
|
||||
});
|
||||
@@ -186,13 +195,13 @@ module.exports = function(ctx) {
|
||||
// Check existing apps to avoid duplicates
|
||||
let existingApps = [];
|
||||
try {
|
||||
const existingRes = await ctx.fetchT(`${prowlarrUrl}/api/v1/applications`, {
|
||||
const existingRes = await fetchT(`${prowlarrUrl}/api/v1/applications`, {
|
||||
headers: { 'X-Api-Key': prowlarrApiKey },
|
||||
signal: AbortSignal.timeout(10000)
|
||||
});
|
||||
existingApps = existingRes.ok ? await existingRes.json() : [];
|
||||
} catch (e) {
|
||||
ctx.log.warn('arr', 'Could not fetch existing Prowlarr apps', { error: e.message });
|
||||
log.warn('arr', 'Could not fetch existing Prowlarr apps', { error: e.message });
|
||||
}
|
||||
|
||||
for (const [appName, config] of Object.entries(apps)) {
|
||||
@@ -222,7 +231,7 @@ module.exports = function(ctx) {
|
||||
};
|
||||
|
||||
try {
|
||||
const res = await ctx.fetchT(`${prowlarrUrl}/api/v1/applications`, {
|
||||
const res = await fetchT(`${prowlarrUrl}/api/v1/applications`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -259,7 +268,7 @@ module.exports = function(ctx) {
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await ctx.fetchT(apiEndpoint, {
|
||||
const response = await fetchT(apiEndpoint, {
|
||||
method: 'GET',
|
||||
headers,
|
||||
signal: AbortSignal.timeout(15000)
|
||||
|
||||
Reference in New Issue
Block a user