diff --git a/dashcaddy-api/routes/config/assets.js b/dashcaddy-api/routes/config/assets.js index 8969d5c..4cf118a 100644 --- a/dashcaddy-api/routes/config/assets.js +++ b/dashcaddy-api/routes/config/assets.js @@ -4,6 +4,14 @@ const path = require('path'); const { LIMITS } = require('../../constants'); const { exists } = require('../../fs-helpers'); const { ValidationError } = require('../errors'); +/** + * Config assets routes factory + * @param {Object} deps - Explicit dependencies + * @param {Object} deps.servicesStateManager - Services state manager + * @param {Function} deps.asyncHandler - Async route handler wrapper + * @param {Object} deps.log - Logger instance + * @returns {express.Router} + */ // Image processing for favicon conversion (optional) let sharp, pngToIco; @@ -14,12 +22,12 @@ try { // Image processing libraries not available — favicon conversion disabled } -module.exports = function(ctx) { +module.exports = function({ servicesStateManager, asyncHandler, log }) { const router = express.Router(); // ===== ASSET UPLOAD ===== - router.post('/assets/upload', express.json({ limit: LIMITS.BODY_UPLOAD }), ctx.asyncHandler(async (req, res) => { + router.post('/assets/upload', express.json({ limit: LIMITS.BODY_UPLOAD }), asyncHandler(async (req, res) => { const { filename, data } = req.body; if (!filename || !data) { @@ -65,7 +73,7 @@ module.exports = function(ctx) { // Manage custom dashboard logo // Get current logo path, position, and title - router.get('/logo', ctx.asyncHandler(async (req, res) => { + router.get('/logo', asyncHandler(async (req, res) => { const config = await ctx.readConfig(); res.json({ success: true, @@ -100,7 +108,7 @@ module.exports = function(ctx) { // Upload custom logo(s) and/or update position and title // Supports: dataDark/dataLight (separate variants) or data (single logo for both) - router.post('/logo', express.json({ limit: LIMITS.BODY_UPLOAD }), ctx.asyncHandler(async (req, res) => { + router.post('/logo', express.json({ limit: LIMITS.BODY_UPLOAD }), asyncHandler(async (req, res) => { const { data, dataDark, dataLight, position, dashboardTitle } = req.body; if (!data && !dataDark && !dataLight && !position && !dashboardTitle) { @@ -159,7 +167,7 @@ module.exports = function(ctx) { }, 'logo-upload')); // Reset all branding to defaults - router.delete('/logo', ctx.asyncHandler(async (req, res) => { + router.delete('/logo', asyncHandler(async (req, res) => { const config = await ctx.readConfig(); const assetsPath = process.env.ASSETS_PATH || '/app/assets'; @@ -195,7 +203,7 @@ module.exports = function(ctx) { // Upload and convert favicon (PNG/SVG to ICO) // Get current favicon - router.get('/favicon', ctx.asyncHandler(async (req, res) => { + router.get('/favicon', asyncHandler(async (req, res) => { const config = await ctx.readConfig(); res.json({ success: true, @@ -205,7 +213,7 @@ module.exports = function(ctx) { }, 'favicon-get')); // Upload and convert favicon - router.post('/favicon', ctx.asyncHandler(async (req, res) => { + router.post('/favicon', asyncHandler(async (req, res) => { const { data } = req.body; if (!data) { @@ -267,7 +275,7 @@ module.exports = function(ctx) { }, 'favicon')); // Reset favicon to default - router.delete('/favicon', ctx.asyncHandler(async (req, res) => { + router.delete('/favicon', asyncHandler(async (req, res) => { const config = await ctx.readConfig(); // Delete custom favicon files diff --git a/dashcaddy-api/routes/config/backup.js b/dashcaddy-api/routes/config/backup.js index 9316332..d4749ed 100644 --- a/dashcaddy-api/routes/config/backup.js +++ b/dashcaddy-api/routes/config/backup.js @@ -5,8 +5,17 @@ const { CADDY } = require('../../constants'); const { exists } = require('../../fs-helpers'); const { ValidationError, AuthenticationError } = require('../errors'); -module.exports = function(ctx) { +module.exports = function({ configStateManager, servicesStateManager, asyncHandler, log }) { const express = require('express'); +/** + * Config backup routes factory + * @param {Object} deps - Explicit dependencies + * @param {Object} deps.configStateManager - Config state manager + * @param {Object} deps.servicesStateManager - Services state manager + * @param {Function} deps.asyncHandler - Async route handler wrapper + * @param {Object} deps.log - Logger instance + * @returns {express.Router} + */ const router = express.Router(); const THEMES_DIR = process.env.THEMES_DIR || path.join(path.dirname(ctx.SERVICES_FILE), 'themes'); @@ -28,7 +37,7 @@ module.exports = function(ctx) { // Unified v2.0 backup — server config + encryption key + themes (browser state added client-side) // Export all configuration as a downloadable JSON bundle - router.get('/backup/export', ctx.asyncHandler(async (req, res) => { + router.get('/backup/export', asyncHandler(async (req, res) => { const backup = { version: '2.0', exportedAt: new Date().toISOString(), @@ -72,7 +81,7 @@ module.exports = function(ctx) { backup.files[file.key] = { type: 'missing', data: null }; } } catch (e) { - ctx.log.warn('backup', `Could not backup ${file.key}`, { error: e.message }); + log.warn('backup', `Could not backup ${file.key}`, { error: e.message }); } } @@ -91,7 +100,7 @@ module.exports = function(ctx) { backup.totp = { qrCode: qrDataUrl, issuer: 'DashCaddy' }; } } catch (e) { - ctx.log.warn('backup', 'Could not include TOTP QR in backup', { error: e.message }); + log.warn('backup', 'Could not include TOTP QR in backup', { error: e.message }); } } @@ -110,14 +119,14 @@ module.exports = function(ctx) { } } } catch (e) { - ctx.log.warn('backup', 'Could not include assets in backup', { error: e.message }); + log.warn('backup', 'Could not include assets in backup', { error: e.message }); } // Include user-created themes try { backup.themes = readAllThemes(); } catch (e) { - ctx.log.warn('backup', 'Could not include themes in backup', { error: e.message }); + log.warn('backup', 'Could not include themes in backup', { error: e.message }); } // Set headers for file download @@ -126,11 +135,11 @@ module.exports = function(ctx) { res.setHeader('Content-Disposition', `attachment; filename="${backupFilename}"`); res.json(backup); - ctx.log.info('backup', 'Backup exported successfully'); + log.info('backup', 'Backup exported successfully'); }, 'backup-export')); // Preview what will be restored (without making changes) - router.post('/backup/preview', ctx.asyncHandler(async (req, res) => { + router.post('/backup/preview', asyncHandler(async (req, res) => { const backup = req.body; if (!backup || !backup.version || !backup.files) { @@ -195,7 +204,7 @@ module.exports = function(ctx) { }, 'backup-preview')); // Restore configuration from backup - router.post('/backup/restore', ctx.asyncHandler(async (req, res) => { + router.post('/backup/restore', asyncHandler(async (req, res) => { const { backup, options = {}, totpCode } = req.body; if (!backup || !backup.version || !backup.files) { @@ -274,7 +283,7 @@ module.exports = function(ctx) { await fsp.writeFile(filePath, content, 'utf8'); results.restored.push(key); - ctx.log.info('backup', `Restored: ${key}`, { path: filePath }); + log.info('backup', `Restored: ${key}`, { path: filePath }); } catch (e) { results.errors.push({ file: key, error: e.message }); } @@ -350,7 +359,7 @@ module.exports = function(ctx) { } } results.restored.push(`themes:${Object.keys(backup.themes).length}`); - ctx.log.info('backup', `Restored ${Object.keys(backup.themes).length} themes`); + log.info('backup', `Restored ${Object.keys(backup.themes).length} themes`); } catch (e) { results.errors.push({ file: 'themes', error: e.message }); } @@ -366,7 +375,7 @@ module.exports = function(ctx) { } results.encryptionKeyReloaded = true; } catch (e) { - ctx.log.warn('backup', 'Could not reload encryption key', { error: e.message }); + log.warn('backup', 'Could not reload encryption key', { error: e.message }); } } @@ -380,7 +389,7 @@ module.exports = function(ctx) { results }); - ctx.log.info('backup', 'Backup restore completed', { restored: results.restored.length, errors: results.errors.length }); + log.info('backup', 'Backup restore completed', { restored: results.restored.length, errors: results.errors.length }); }, 'backup-restore')); return router; diff --git a/dashcaddy-api/routes/config/index.js b/dashcaddy-api/routes/config/index.js index 8824e1e..7bf0a56 100644 --- a/dashcaddy-api/routes/config/index.js +++ b/dashcaddy-api/routes/config/index.js @@ -1,9 +1,22 @@ const express = require('express'); +/** + * Config routes aggregator + * @param {Object} ctx - Application context (for backward compatibility) + * @returns {express.Router} + */ module.exports = function(ctx) { const router = express.Router(); - router.use(require('./settings')(ctx)); - router.use(require('./assets')(ctx)); - router.use(require('./backup')(ctx)); + + const deps = { + configStateManager: ctx.configStateManager, + servicesStateManager: ctx.servicesStateManager, + asyncHandler: ctx.asyncHandler, + log: ctx.log + }; + + router.use(require('./settings')(deps)); + router.use(require('./assets')(deps)); + router.use(require('./backup')(deps)); return router; }; diff --git a/dashcaddy-api/routes/config/settings.js b/dashcaddy-api/routes/config/settings.js index ad0161d..0b7bc1e 100644 --- a/dashcaddy-api/routes/config/settings.js +++ b/dashcaddy-api/routes/config/settings.js @@ -3,14 +3,22 @@ const { validateConfig } = require('../../config-schema'); const { exists } = require('../../fs-helpers'); const { ValidationError } = require('../errors'); -module.exports = function(ctx) { +module.exports = function({ configStateManager, asyncHandler, log }) { +/** + * Config settings routes factory + * @param {Object} deps - Explicit dependencies + * @param {Object} deps.configStateManager - Config state manager + * @param {Function} deps.asyncHandler - Async route handler wrapper + * @param {Object} deps.log - Logger instance + * @returns {express.Router} + */ const express = require('express'); const router = express.Router(); // ===== DASHCADDY CONFIG ENDPOINTS ===== // Server-side config storage for setup wizard (shared across all browsers/machines) - router.get('/config', ctx.asyncHandler(async (req, res) => { + router.get('/config', asyncHandler(async (req, res) => { if (!await exists(ctx.CONFIG_FILE)) { return res.json({ setupComplete: false }); } @@ -19,7 +27,7 @@ module.exports = function(ctx) { res.json(config); }, 'config-get')); - router.post('/config', ctx.asyncHandler(async (req, res) => { + router.post('/config', asyncHandler(async (req, res) => { const incoming = req.body; if (!incoming || typeof incoming !== 'object') { @@ -55,12 +63,12 @@ module.exports = function(ctx) { await fsp.writeFile(ctx.CONFIG_FILE, JSON.stringify(config, null, 2), 'utf8'); ctx.loadSiteConfig(); // Refresh in-memory config - ctx.log.info('config', 'Config saved', { path: ctx.CONFIG_FILE }); + log.info('config', 'Config saved', { path: ctx.CONFIG_FILE }); res.json({ success: true, message: 'Configuration saved', config, warnings }); }, 'config-save')); - router.delete('/config', ctx.asyncHandler(async (req, res) => { + router.delete('/config', asyncHandler(async (req, res) => { if (await exists(ctx.CONFIG_FILE)) { await fsp.unlink(ctx.CONFIG_FILE); }