const express = require('express'); const { validateURL, validateToken } = require('../../input-validator'); const { ValidationError } = require('../errors'); /** * 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', asyncHandler(async (req, res) => { const { service, apiKey, url, seedboxBaseUrl, qualityProfileId, qualityProfileName } = req.body; if (!service || !apiKey) { throw new ValidationError('Service name and API key required'); } const validServices = ['radarr', 'sonarr', 'prowlarr', 'lidarr', 'plex']; if (!validServices.includes(service)) { return errorResponse(res, 400, `Invalid service. Must be one of: ${validServices.join(', ')}`); } // Validate API key format try { validateToken(apiKey); } catch (e) { throw new ValidationError('Invalid API key format'); } // Validate URL if provided if (url) { try { validateURL(url); } catch (e) { throw new ValidationError('Invalid URL format'); } } // Determine credential key const credKey = service === 'plex' ? 'arr.plex.token' : `arr.${service}.apikey`; // Build metadata const metadata = { service, source: url ? 'external' : 'local', url: url || null, storedAt: new Date().toISOString() }; // Test connection if URL is known let connectionTest = null; let resolvedUrl = url; if (!resolvedUrl) { // Try to resolve URL from services.json try { 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; } catch (e) { /* ignore */ } } if (resolvedUrl) { connectionTest = await helpers.testServiceConnection(service, resolvedUrl, apiKey); if (connectionTest.success) { metadata.lastVerified = new Date().toISOString(); metadata.version = connectionTest.version; metadata.url = resolvedUrl; } } // Store quality profile preference if provided if (qualityProfileId) { metadata.qualityProfileId = qualityProfileId; metadata.qualityProfileName = qualityProfileName || null; } // Store the credential const stored = await credentialManager.store(credKey, apiKey, metadata); if (!stored) { return errorResponse(res, 500, 'Failed to store credential'); } // Optionally store seedbox base URL if (seedboxBaseUrl) { try { validateURL(seedboxBaseUrl); } catch (e) { throw new ValidationError('Invalid seedbox base URL'); } await credentialManager.store('arr.seedbox.baseurl', seedboxBaseUrl, { storedAt: new Date().toISOString() }); } log.info('arr', 'Stored API key', { service, verified: connectionTest?.success || false }); res.json({ success: true, message: `${service} API key stored`, connectionTest, url: resolvedUrl }); }, 'arr-credentials-store')); // List stored arr credentials (keys only, not values) 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 credentialManager.retrieve(credKey)); const metadata = await credentialManager.getMetadata(credKey); credentials[service] = { hasKey, url: metadata?.url || null, lastVerified: metadata?.lastVerified || null, version: metadata?.version || null, source: metadata?.source || null }; } // Get seedbox base URL 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', asyncHandler(async (req, res) => { const { service } = req.params; const credKey = service === 'plex' ? 'arr.plex.token' : `arr.${service}.apikey`; await credentialManager.delete(credKey); log.info('arr', 'Deleted credentials', { service }); res.json({ success: true, message: `${service} credentials removed` }); }, 'arr-credentials-delete')); return router; };