refactor: Phase 1 code cleanup - constants, logging, and repository organization

This commit is contained in:
2026-03-28 18:54:39 -07:00
parent f1b0ac43d0
commit 6c3848102b
24 changed files with 17078 additions and 50 deletions

View File

@@ -479,5 +479,100 @@ module.exports = function(ctx, helpers) {
});
}, 'arr-auto-setup'));
// Fetch quality profiles from an arr service (Radarr/Sonarr)
router.get('/arr/quality-profiles', ctx.asyncHandler(async (req, res) => {
const { service, url, apiKey } = req.query;
if (!service || !['radarr', 'sonarr'].includes(service)) {
return ctx.errorResponse(res, 400, 'Service must be radarr or sonarr');
}
// Resolve API key: from query param, or from stored credentials
let resolvedKey = apiKey;
let resolvedUrl = url;
if (!resolvedKey) {
resolvedKey = await ctx.credentialManager.retrieve(`arr.${service}.apikey`);
}
if (!resolvedKey) {
resolvedKey = await ctx.credentialManager.retrieve(`service.${service}.apikey`);
}
if (!resolvedUrl) {
const metadata = await ctx.credentialManager.getMetadata(`arr.${service}.apikey`);
resolvedUrl = metadata?.url;
}
if (!resolvedUrl) {
try {
const services = await ctx.servicesStateManager.read();
const svcList = Array.isArray(services) ? services : services.services || [];
const found = svcList.find(s => s.id === service);
if (found?.externalUrl) resolvedUrl = found.externalUrl;
else if (found?.url) resolvedUrl = found.url;
} catch (e) { /* ignore */ }
}
if (!resolvedKey || !resolvedUrl) {
return ctx.errorResponse(res, 400, 'Could not resolve API key or URL for this service');
}
const baseUrl = resolvedUrl.replace(/\/+$/, '');
try {
const profilesRes = await ctx.fetchT(`${baseUrl}/api/v3/qualityprofile`, {
headers: { 'X-Api-Key': resolvedKey, 'Accept': 'application/json' },
signal: AbortSignal.timeout(10000)
});
if (!profilesRes.ok) {
return ctx.errorResponse(res, profilesRes.status === 401 ? 401 : 502,
profilesRes.status === 401 ? 'Invalid API key' : `Failed to fetch profiles (HTTP ${profilesRes.status})`);
}
const profiles = await profilesRes.json();
const mapped = profiles.map(p => ({ id: p.id, name: p.name }));
// Load stored profile preference
const metadata = await ctx.credentialManager.getMetadata(`arr.${service}.apikey`);
const storedProfileId = metadata?.qualityProfileId || null;
res.json({ success: true, profiles: mapped, storedProfileId });
} catch (e) {
if (e.cause?.code === 'ECONNREFUSED') {
return ctx.errorResponse(res, 502, 'Connection refused — is the service running?');
}
if (e.name === 'AbortError') {
return ctx.errorResponse(res, 504, 'Connection timeout');
}
return ctx.errorResponse(res, 500, e.message);
}
}, 'arr-quality-profiles'));
// Save quality profile preference (without re-storing API key)
router.post('/arr/quality-profiles', ctx.asyncHandler(async (req, res) => {
const { service, qualityProfileId, qualityProfileName } = req.body;
if (!service || !['radarr', 'sonarr'].includes(service)) {
return ctx.errorResponse(res, 400, 'Service must be radarr or sonarr');
}
if (!qualityProfileId) {
return ctx.errorResponse(res, 400, 'qualityProfileId required');
}
const credKey = `arr.${service}.apikey`;
const existing = await ctx.credentialManager.getMetadata(credKey);
if (!existing) {
return ctx.errorResponse(res, 404, 'No stored credentials for this service');
}
// Merge quality profile into existing metadata
existing.qualityProfileId = qualityProfileId;
existing.qualityProfileName = qualityProfileName || null;
await ctx.credentialManager.storeMetadata(credKey, existing);
res.json({ success: true, message: `Quality profile updated for ${service}` });
}, 'arr-quality-profile-save'));
return router;
};