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

@@ -2,11 +2,22 @@ const express = require('express');
const { validateURL, validateToken } = require('../../input-validator');
const { ValidationError } = require('../errors');
module.exports = function(ctx, helpers) {
/**
* Arr credentials routes factory
* @param {Object} deps - Explicit dependencies
* @param {Object} deps.credentialManager - Credential manager
* @param {Object} deps.servicesStateManager - Services state manager
* @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, servicesStateManager, asyncHandler, errorResponse, log, helpers }) {
const router = express.Router();
// Store arr service credentials
router.post('/arr/credentials', ctx.asyncHandler(async (req, res) => {
router.post('/arr/credentials', asyncHandler(async (req, res) => {
const { service, apiKey, url, seedboxBaseUrl, qualityProfileId, qualityProfileName } = req.body;
if (!service || !apiKey) {
@@ -15,7 +26,7 @@ module.exports = function(ctx, helpers) {
const validServices = ['radarr', 'sonarr', 'prowlarr', 'lidarr', 'plex'];
if (!validServices.includes(service)) {
return ctx.errorResponse(res, 400, `Invalid service. Must be one of: ${validServices.join(', ')}`);
return errorResponse(res, 400, `Invalid service. Must be one of: ${validServices.join(', ')}`);
}
// Validate API key format
@@ -50,7 +61,7 @@ module.exports = function(ctx, helpers) {
if (!resolvedUrl) {
// Try to resolve URL from services.json
try {
const services = await ctx.servicesStateManager.read();
const services = await servicesStateManager.read();
const svc = Array.isArray(services) ? services : services.services || [];
const found = svc.find(s => s.id === service && s.isExternal);
if (found?.externalUrl) resolvedUrl = found.externalUrl;
@@ -73,9 +84,9 @@ module.exports = function(ctx, helpers) {
}
// Store the credential
const stored = await ctx.credentialManager.store(credKey, apiKey, metadata);
const stored = await credentialManager.store(credKey, apiKey, metadata);
if (!stored) {
return ctx.errorResponse(res, 500, 'Failed to store credential');
return errorResponse(res, 500, 'Failed to store credential');
}
// Optionally store seedbox base URL
@@ -83,12 +94,12 @@ module.exports = function(ctx, helpers) {
try { validateURL(seedboxBaseUrl); } catch (e) {
throw new ValidationError('Invalid seedbox base URL');
}
await ctx.credentialManager.store('arr.seedbox.baseurl', seedboxBaseUrl, {
await credentialManager.store('arr.seedbox.baseurl', seedboxBaseUrl, {
storedAt: new Date().toISOString()
});
}
ctx.log.info('arr', 'Stored API key', { service, verified: connectionTest?.success || false });
log.info('arr', 'Stored API key', { service, verified: connectionTest?.success || false });
res.json({
success: true,
@@ -99,14 +110,14 @@ module.exports = function(ctx, helpers) {
}, 'arr-credentials-store'));
// List stored arr credentials (keys only, not values)
router.get('/arr/credentials', ctx.asyncHandler(async (req, res) => {
router.get('/arr/credentials', asyncHandler(async (req, res) => {
const services = ['radarr', 'sonarr', 'prowlarr', 'lidarr', 'plex'];
const credentials = {};
for (const service of services) {
const credKey = service === 'plex' ? 'arr.plex.token' : `arr.${service}.apikey`;
const hasKey = !!(await ctx.credentialManager.retrieve(credKey));
const metadata = await ctx.credentialManager.getMetadata(credKey);
const hasKey = !!(await credentialManager.retrieve(credKey));
const metadata = await credentialManager.getMetadata(credKey);
credentials[service] = {
hasKey,
@@ -118,17 +129,17 @@ module.exports = function(ctx, helpers) {
}
// Get seedbox base URL
const seedboxBaseUrl = await ctx.credentialManager.retrieve('arr.seedbox.baseurl');
const seedboxBaseUrl = await credentialManager.retrieve('arr.seedbox.baseurl');
res.json({ success: true, credentials, seedboxBaseUrl: seedboxBaseUrl || null });
}, 'arr-credentials-list'));
// Delete stored arr credentials
router.delete('/arr/credentials/:service', ctx.asyncHandler(async (req, res) => {
router.delete('/arr/credentials/:service', asyncHandler(async (req, res) => {
const { service } = req.params;
const credKey = service === 'plex' ? 'arr.plex.token' : `arr.${service}.apikey`;
await ctx.credentialManager.delete(credKey);
ctx.log.info('arr', 'Deleted credentials', { service });
await credentialManager.delete(credKey);
log.info('arr', 'Deleted credentials', { service });
res.json({ success: true, message: `${service} credentials removed` });
}, 'arr-credentials-delete'));