diff --git a/dashcaddy-api/routes/backups.js b/dashcaddy-api/routes/backups.js index e766b1d..7b0361a 100644 --- a/dashcaddy-api/routes/backups.js +++ b/dashcaddy-api/routes/backups.js @@ -1,37 +1,45 @@ const express = require('express'); +const { success } = require('../response-helpers'); -module.exports = function(ctx) { +/** + * Backups routes factory + * @param {Object} deps - Explicit dependencies + * @param {Object} deps.backupManager - Backup management module + * @param {Function} deps.asyncHandler - Async route handler wrapper + * @returns {express.Router} + */ +module.exports = function({ backupManager, asyncHandler }) { const router = express.Router(); // Get backup configuration - router.get('/backups/config', ctx.asyncHandler(async (req, res) => { - const config = ctx.backupManager.getConfig(); - res.json({ success: true, config }); + router.get('/backups/config', asyncHandler(async (req, res) => { + const config = backupManager.getConfig(); + success(res, { config }); }, 'backups-config-get')); // Update backup configuration - router.post('/backups/config', ctx.asyncHandler(async (req, res) => { - ctx.backupManager.updateConfig(req.body); - res.json({ success: true, message: 'Backup configuration updated' }); + router.post('/backups/config', asyncHandler(async (req, res) => { + backupManager.updateConfig(req.body); + success(res, { message: 'Backup configuration updated' }); }, 'backups-config-update')); // Execute manual backup - router.post('/backups/execute', ctx.asyncHandler(async (req, res) => { - const backup = await ctx.backupManager.executeBackup('manual', req.body); - res.json({ success: true, backup }); + router.post('/backups/execute', asyncHandler(async (req, res) => { + const backup = await backupManager.executeBackup('manual', req.body); + success(res, { backup }); }, 'backups-execute')); // Get backup history - router.get('/backups/history', ctx.asyncHandler(async (req, res) => { + router.get('/backups/history', asyncHandler(async (req, res) => { const limit = parseInt(req.query.limit) || 50; - const history = ctx.backupManager.getHistory(limit); - res.json({ success: true, history }); + const history = backupManager.getHistory(limit); + success(res, { history }); }, 'backups-history')); // Restore from backup - router.post('/backups/restore/:backupId', ctx.asyncHandler(async (req, res) => { - const result = await ctx.backupManager.restoreBackup(req.params.backupId, req.body); - res.json({ success: true, result }); + router.post('/backups/restore/:backupId', asyncHandler(async (req, res) => { + const result = await backupManager.restoreBackup(req.params.backupId, req.body); + success(res, { result }); }, 'backups-restore')); return router; diff --git a/dashcaddy-api/routes/credentials.js b/dashcaddy-api/routes/credentials.js index 11d95d4..cd53aef 100644 --- a/dashcaddy-api/routes/credentials.js +++ b/dashcaddy-api/routes/credentials.js @@ -1,21 +1,29 @@ const express = require('express'); +const { success, error: errorResponse } = require('../response-helpers'); -module.exports = function(ctx) { +/** + * Credentials routes factory + * @param {Object} deps - Explicit dependencies + * @param {Object} deps.credentialManager - Credential storage manager + * @param {Function} deps.asyncHandler - Async route handler wrapper + * @returns {express.Router} + */ +module.exports = function({ credentialManager, asyncHandler }) { const router = express.Router(); // List all stored credentials (keys only, no values) - router.get('/credentials/list', ctx.asyncHandler(async (req, res) => { - const keys = await ctx.credentialManager.list(); - res.json({ success: true, credentials: keys, count: keys.length }); + router.get('/credentials/list', asyncHandler(async (req, res) => { + const keys = await credentialManager.list(); + success(res, { credentials: keys, count: keys.length }); }, 'credentials-list')); // Rotate encryption key — re-encrypts all stored credentials - router.post('/credentials/rotate-key', ctx.asyncHandler(async (req, res) => { - const success = await ctx.credentialManager.rotateEncryptionKey(); - if (success) { - res.json({ success: true, message: 'Encryption key rotated, all credentials re-encrypted' }); + router.post('/credentials/rotate-key', asyncHandler(async (req, res) => { + const rotateSuccess = await credentialManager.rotateEncryptionKey(); + if (rotateSuccess) { + success(res, { message: 'Encryption key rotated, all credentials re-encrypted' }); } else { - ctx.errorResponse(res, 500, 'Key rotation failed'); + errorResponse(res, 'Key rotation failed', 500); } }, 'credentials-rotate')); diff --git a/dashcaddy-api/routes/errorlogs.js b/dashcaddy-api/routes/errorlogs.js index fe4ebcc..d9454ab 100644 --- a/dashcaddy-api/routes/errorlogs.js +++ b/dashcaddy-api/routes/errorlogs.js @@ -3,17 +3,26 @@ const fs = require('fs'); const fsp = require('fs').promises; const { exists } = require('../fs-helpers'); const { paginate, parsePaginationParams } = require('../pagination'); +const { success } = require('../response-helpers'); -module.exports = function(ctx) { +/** + * Error logs routes factory + * @param {Object} deps - Explicit dependencies + * @param {string} deps.ERROR_LOG_FILE - Path to error log file + * @param {Object} deps.auditLogger - Audit logger instance + * @param {Function} deps.asyncHandler - Async route handler wrapper + * @returns {express.Router} + */ +module.exports = function({ ERROR_LOG_FILE, auditLogger, asyncHandler }) { const router = express.Router(); // Get error logs - router.get('/error-logs', ctx.asyncHandler(async (req, res) => { - if (!await exists(ctx.ERROR_LOG_FILE)) { - return res.json({ success: true, logs: [] }); + router.get('/error-logs', asyncHandler(async (req, res) => { + if (!await exists(ERROR_LOG_FILE)) { + return success(res, { logs: [] }); } - const logContent = await fsp.readFile(ctx.ERROR_LOG_FILE, 'utf8'); + const logContent = await fsp.readFile(ERROR_LOG_FILE, 'utf8'); const logEntries = logContent.split('='.repeat(80)).filter(entry => entry.trim()); const logs = logEntries.map(entry => { @@ -31,37 +40,37 @@ module.exports = function(ctx) { return null; }).filter(Boolean); - res.json({ success: true, logs: logs.slice(-50).reverse() }); + success(res, { logs: logs.slice(-50).reverse() }); }, 'error-logs-get')); // Clear error logs - router.delete('/error-logs', ctx.asyncHandler(async (req, res) => { - if (await exists(ctx.ERROR_LOG_FILE)) { - await fsp.writeFile(ctx.ERROR_LOG_FILE, ''); + router.delete('/error-logs', asyncHandler(async (req, res) => { + if (await exists(ERROR_LOG_FILE)) { + await fsp.writeFile(ERROR_LOG_FILE, ''); } - res.json({ success: true, message: 'Error logs cleared' }); + success(res, { message: 'Error logs cleared' }); }, 'error-logs-clear')); // Audit log - router.get('/audit-logs', ctx.asyncHandler(async (req, res) => { + router.get('/audit-logs', asyncHandler(async (req, res) => { const paginationParams = parsePaginationParams(req.query); const action = req.query.action || ''; if (paginationParams) { // When paginating, fetch all matching entries and let pagination slice - const entries = await ctx.auditLogger.query({ limit: Number.MAX_SAFE_INTEGER, offset: 0, action }); + const entries = await auditLogger.query({ limit: Number.MAX_SAFE_INTEGER, offset: 0, action }); const result = paginate(entries, paginationParams); - res.json({ success: true, entries: result.data, pagination: result.pagination }); + success(res, { entries: result.data, pagination: result.pagination }); } else { const limit = parseInt(req.query.limit) || 50; const offset = parseInt(req.query.offset) || 0; - const entries = await ctx.auditLogger.query({ limit, offset, action }); - res.json({ success: true, entries }); + const entries = await auditLogger.query({ limit, offset, action }); + success(res, { entries }); } }, 'audit-log')); - router.delete('/audit-logs', ctx.asyncHandler(async (req, res) => { - await ctx.auditLogger.clear(); - res.json({ success: true, message: 'Audit log cleared' }); + router.delete('/audit-logs', asyncHandler(async (req, res) => { + await auditLogger.clear(); + success(res, { message: 'Audit log cleared' }); }, 'audit-log-clear')); return router; diff --git a/dashcaddy-api/routes/license.js b/dashcaddy-api/routes/license.js index 11656f2..43d5ba9 100644 --- a/dashcaddy-api/routes/license.js +++ b/dashcaddy-api/routes/license.js @@ -1,53 +1,59 @@ const express = require('express'); +const { success, error: errorResponse } = require('../response-helpers'); -module.exports = function(ctx) { +/** + * License routes factory + * @param {Object} deps - Explicit dependencies + * @param {Object} deps.licenseManager - License management module + * @param {Function} deps.asyncHandler - Async route handler wrapper + * @returns {express.Router} + */ +module.exports = function({ licenseManager, asyncHandler }) { const router = express.Router(); // Activate a license code - router.post('/activate', ctx.asyncHandler(async (req, res) => { + router.post('/activate', asyncHandler(async (req, res) => { const { code } = req.body; if (!code) { - return ctx.errorResponse(res, 400, 'License code is required'); + return errorResponse(res, 'License code is required', 400); } - const result = await ctx.licenseManager.activate(code); + const result = await licenseManager.activate(code); if (result.success) { - res.json({ - success: true, + success(res, { message: result.message, license: result.activation }); } else { - ctx.errorResponse(res, 400, result.message); + errorResponse(res, result.message, 400); } }, 'license-activate')); // Get current license status - router.get('/status', ctx.asyncHandler(async (req, res) => { - const status = ctx.licenseManager.getStatus(); - res.json({ success: true, license: status }); + router.get('/status', asyncHandler(async (req, res) => { + const status = licenseManager.getStatus(); + success(res, { license: status }); }, 'license-status')); // Deactivate current license - router.post('/deactivate', ctx.asyncHandler(async (req, res) => { - const result = await ctx.licenseManager.deactivate(); + router.post('/deactivate', asyncHandler(async (req, res) => { + const result = await licenseManager.deactivate(); if (result.success) { - res.json({ success: true, message: result.message }); + success(res, { message: result.message }); } else { - ctx.errorResponse(res, 400, result.message); + errorResponse(res, result.message, 400); } }, 'license-deactivate')); // Check if a specific feature is available (lightweight check for frontend) - router.get('/feature/:feature', ctx.asyncHandler(async (req, res) => { + router.get('/feature/:feature', asyncHandler(async (req, res) => { const { feature } = req.params; - const available = ctx.licenseManager.hasFeature(feature); - const status = ctx.licenseManager.getStatus(); + const available = licenseManager.hasFeature(feature); + const status = licenseManager.getStatus(); - res.json({ - success: true, + success(res, { feature, available, tier: status.tier, diff --git a/dashcaddy-api/routes/themes.js b/dashcaddy-api/routes/themes.js index db80d67..9949655 100644 --- a/dashcaddy-api/routes/themes.js +++ b/dashcaddy-api/routes/themes.js @@ -1,8 +1,14 @@ const express = require('express'); const fs = require('fs'); const path = require('path'); +const { success } = require('../response-helpers'); -module.exports = function(ctx) { +/** + * Themes routes factory + * Note: This route does not use asyncHandler - uses synchronous fs operations + * @returns {express.Router} + */ +module.exports = function() { const router = express.Router(); const THEMES_DIR = process.env.THEMES_DIR || path.join(path.dirname(process.env.SERVICES_FILE || '/app/services.json'), 'themes'); @@ -28,7 +34,7 @@ module.exports = function(ctx) { // Get all user themes router.get('/themes', (req, res) => { - res.json({ success: true, themes: readAllThemes() }); + success(res, { themes: readAllThemes() }); }); // Save a theme (create or update) @@ -48,7 +54,7 @@ module.exports = function(ctx) { if (lightBg) themeData.lightBg = true; fs.writeFileSync(path.join(THEMES_DIR, slug + '.json'), JSON.stringify(themeData, null, 2), 'utf8'); - res.json({ success: true, message: name + ' theme saved' }); + success(res, { message: name + ' theme saved' }); }); // Delete a theme @@ -64,7 +70,7 @@ module.exports = function(ctx) { const name = data.name || slug; fs.unlinkSync(filePath); - res.json({ success: true, message: name + ' theme deleted' }); + success(res, { message: name + ' theme deleted' }); }); return router; diff --git a/dashcaddy-api/server.js b/dashcaddy-api/server.js index 1f802d0..61780f5 100644 --- a/dashcaddy-api/server.js +++ b/dashcaddy-api/server.js @@ -1233,17 +1233,30 @@ apiRouter.use(monitoringRoutes({ apiRouter.use(updatesRoutes(ctx)); apiRouter.use('/tailscale', tailscaleRoutes(ctx)); apiRouter.use(sitesRoutes(ctx)); -apiRouter.use(credentialsRoutes(ctx)); +apiRouter.use(credentialsRoutes({ + credentialManager: ctx.credentialManager, + asyncHandler: ctx.asyncHandler +})); apiRouter.use(arrRoutes(ctx)); apiRouter.use(appsRoutes(ctx)); apiRouter.use(logsRoutes(ctx)); -apiRouter.use(backupsRoutes(ctx)); +apiRouter.use(backupsRoutes({ + backupManager: ctx.backupManager, + asyncHandler: ctx.asyncHandler +})); apiRouter.use('/ca', caRoutes(ctx)); apiRouter.use(browseRoutes(ctx)); -apiRouter.use(errorLogsRoutes(ctx)); -apiRouter.use('/license', licenseRoutes(ctx)); +apiRouter.use(errorLogsRoutes({ + ERROR_LOG_FILE: ctx.ERROR_LOG_FILE, + auditLogger: ctx.auditLogger, + asyncHandler: ctx.asyncHandler +})); +apiRouter.use('/license', licenseRoutes({ + licenseManager: ctx.licenseManager, + asyncHandler: ctx.asyncHandler +})); apiRouter.use('/recipes', recipesRoutes(ctx)); -apiRouter.use(themesRoutes(ctx)); +apiRouter.use(themesRoutes()); // No dependencies - standalone route // Inline routes on the API router apiRouter.get('/health', (req, res) => {