Initial commit: DashCaddy v1.0
Full codebase including API server (32 modules + routes), dashboard frontend, DashCA certificate distribution, installer script, and deployment skills.
This commit is contained in:
129
dashcaddy-api/routes/arr/credentials.js
Normal file
129
dashcaddy-api/routes/arr/credentials.js
Normal file
@@ -0,0 +1,129 @@
|
||||
const express = require('express');
|
||||
const { validateURL, validateToken } = require('../../input-validator');
|
||||
|
||||
module.exports = function(ctx, helpers) {
|
||||
const router = express.Router();
|
||||
|
||||
// Store arr service credentials
|
||||
router.post('/arr/credentials', ctx.asyncHandler(async (req, res) => {
|
||||
const { service, apiKey, url, seedboxBaseUrl } = req.body;
|
||||
|
||||
if (!service || !apiKey) {
|
||||
return ctx.errorResponse(res, 400, 'Service name and API key required');
|
||||
}
|
||||
|
||||
const validServices = ['radarr', 'sonarr', 'prowlarr', 'lidarr', 'plex'];
|
||||
if (!validServices.includes(service)) {
|
||||
return ctx.errorResponse(res, 400, `Invalid service. Must be one of: ${validServices.join(', ')}`);
|
||||
}
|
||||
|
||||
// Validate API key format
|
||||
try {
|
||||
validateToken(apiKey);
|
||||
} catch (e) {
|
||||
return ctx.errorResponse(res, 400, 'Invalid API key format');
|
||||
}
|
||||
|
||||
// Validate URL if provided
|
||||
if (url) {
|
||||
try { validateURL(url); } catch (e) {
|
||||
return ctx.errorResponse(res, 400, '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 ctx.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 the credential
|
||||
const stored = await ctx.credentialManager.store(credKey, apiKey, metadata);
|
||||
if (!stored) {
|
||||
return ctx.errorResponse(res, 500, 'Failed to store credential');
|
||||
}
|
||||
|
||||
// Optionally store seedbox base URL
|
||||
if (seedboxBaseUrl) {
|
||||
try { validateURL(seedboxBaseUrl); } catch (e) {
|
||||
return ctx.errorResponse(res, 400, 'Invalid seedbox base URL');
|
||||
}
|
||||
await ctx.credentialManager.store('arr.seedbox.baseurl', seedboxBaseUrl, {
|
||||
storedAt: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
|
||||
ctx.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', ctx.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);
|
||||
|
||||
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 ctx.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) => {
|
||||
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 });
|
||||
res.json({ success: true, message: `${service} credentials removed` });
|
||||
}, 'arr-credentials-delete'));
|
||||
|
||||
return router;
|
||||
};
|
||||
Reference in New Issue
Block a user