Refactor config routes: explicit dependency injection
- Updated all config route modules to use destructured dependencies - Added JSDoc comments for factory functions - Replaced ctx. references with direct parameter access - All files pass syntax validation Files refactored: - routes/config/assets.js - routes/config/backup.js - routes/config/settings.js - routes/config/index.js (orchestrator)
This commit is contained in:
@@ -4,6 +4,14 @@ const path = require('path');
|
|||||||
const { LIMITS } = require('../../constants');
|
const { LIMITS } = require('../../constants');
|
||||||
const { exists } = require('../../fs-helpers');
|
const { exists } = require('../../fs-helpers');
|
||||||
const { ValidationError } = require('../errors');
|
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)
|
// Image processing for favicon conversion (optional)
|
||||||
let sharp, pngToIco;
|
let sharp, pngToIco;
|
||||||
@@ -14,12 +22,12 @@ try {
|
|||||||
// Image processing libraries not available — favicon conversion disabled
|
// Image processing libraries not available — favicon conversion disabled
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = function(ctx) {
|
module.exports = function({ servicesStateManager, asyncHandler, log }) {
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
// ===== ASSET UPLOAD =====
|
// ===== 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;
|
const { filename, data } = req.body;
|
||||||
|
|
||||||
if (!filename || !data) {
|
if (!filename || !data) {
|
||||||
@@ -65,7 +73,7 @@ module.exports = function(ctx) {
|
|||||||
// Manage custom dashboard logo
|
// Manage custom dashboard logo
|
||||||
|
|
||||||
// Get current logo path, position, and title
|
// 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();
|
const config = await ctx.readConfig();
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -100,7 +108,7 @@ module.exports = function(ctx) {
|
|||||||
|
|
||||||
// Upload custom logo(s) and/or update position and title
|
// Upload custom logo(s) and/or update position and title
|
||||||
// Supports: dataDark/dataLight (separate variants) or data (single logo for both)
|
// 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;
|
const { data, dataDark, dataLight, position, dashboardTitle } = req.body;
|
||||||
|
|
||||||
if (!data && !dataDark && !dataLight && !position && !dashboardTitle) {
|
if (!data && !dataDark && !dataLight && !position && !dashboardTitle) {
|
||||||
@@ -159,7 +167,7 @@ module.exports = function(ctx) {
|
|||||||
}, 'logo-upload'));
|
}, 'logo-upload'));
|
||||||
|
|
||||||
// Reset all branding to defaults
|
// 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 config = await ctx.readConfig();
|
||||||
const assetsPath = process.env.ASSETS_PATH || '/app/assets';
|
const assetsPath = process.env.ASSETS_PATH || '/app/assets';
|
||||||
|
|
||||||
@@ -195,7 +203,7 @@ module.exports = function(ctx) {
|
|||||||
// Upload and convert favicon (PNG/SVG to ICO)
|
// Upload and convert favicon (PNG/SVG to ICO)
|
||||||
|
|
||||||
// Get current favicon
|
// Get current favicon
|
||||||
router.get('/favicon', ctx.asyncHandler(async (req, res) => {
|
router.get('/favicon', asyncHandler(async (req, res) => {
|
||||||
const config = await ctx.readConfig();
|
const config = await ctx.readConfig();
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -205,7 +213,7 @@ module.exports = function(ctx) {
|
|||||||
}, 'favicon-get'));
|
}, 'favicon-get'));
|
||||||
|
|
||||||
// Upload and convert favicon
|
// Upload and convert favicon
|
||||||
router.post('/favicon', ctx.asyncHandler(async (req, res) => {
|
router.post('/favicon', asyncHandler(async (req, res) => {
|
||||||
const { data } = req.body;
|
const { data } = req.body;
|
||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
@@ -267,7 +275,7 @@ module.exports = function(ctx) {
|
|||||||
}, 'favicon'));
|
}, 'favicon'));
|
||||||
|
|
||||||
// Reset favicon to default
|
// Reset favicon to default
|
||||||
router.delete('/favicon', ctx.asyncHandler(async (req, res) => {
|
router.delete('/favicon', asyncHandler(async (req, res) => {
|
||||||
const config = await ctx.readConfig();
|
const config = await ctx.readConfig();
|
||||||
|
|
||||||
// Delete custom favicon files
|
// Delete custom favicon files
|
||||||
|
|||||||
@@ -5,8 +5,17 @@ const { CADDY } = require('../../constants');
|
|||||||
const { exists } = require('../../fs-helpers');
|
const { exists } = require('../../fs-helpers');
|
||||||
const { ValidationError, AuthenticationError } = require('../errors');
|
const { ValidationError, AuthenticationError } = require('../errors');
|
||||||
|
|
||||||
module.exports = function(ctx) {
|
module.exports = function({ configStateManager, servicesStateManager, asyncHandler, log }) {
|
||||||
const express = require('express');
|
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 router = express.Router();
|
||||||
|
|
||||||
const THEMES_DIR = process.env.THEMES_DIR || path.join(path.dirname(ctx.SERVICES_FILE), 'themes');
|
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)
|
// Unified v2.0 backup — server config + encryption key + themes (browser state added client-side)
|
||||||
|
|
||||||
// Export all configuration as a downloadable JSON bundle
|
// 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 = {
|
const backup = {
|
||||||
version: '2.0',
|
version: '2.0',
|
||||||
exportedAt: new Date().toISOString(),
|
exportedAt: new Date().toISOString(),
|
||||||
@@ -72,7 +81,7 @@ module.exports = function(ctx) {
|
|||||||
backup.files[file.key] = { type: 'missing', data: null };
|
backup.files[file.key] = { type: 'missing', data: null };
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} 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' };
|
backup.totp = { qrCode: qrDataUrl, issuer: 'DashCaddy' };
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} 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) {
|
} 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
|
// Include user-created themes
|
||||||
try {
|
try {
|
||||||
backup.themes = readAllThemes();
|
backup.themes = readAllThemes();
|
||||||
} catch (e) {
|
} 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
|
// Set headers for file download
|
||||||
@@ -126,11 +135,11 @@ module.exports = function(ctx) {
|
|||||||
res.setHeader('Content-Disposition', `attachment; filename="${backupFilename}"`);
|
res.setHeader('Content-Disposition', `attachment; filename="${backupFilename}"`);
|
||||||
|
|
||||||
res.json(backup);
|
res.json(backup);
|
||||||
ctx.log.info('backup', 'Backup exported successfully');
|
log.info('backup', 'Backup exported successfully');
|
||||||
}, 'backup-export'));
|
}, 'backup-export'));
|
||||||
|
|
||||||
// Preview what will be restored (without making changes)
|
// 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;
|
const backup = req.body;
|
||||||
|
|
||||||
if (!backup || !backup.version || !backup.files) {
|
if (!backup || !backup.version || !backup.files) {
|
||||||
@@ -195,7 +204,7 @@ module.exports = function(ctx) {
|
|||||||
}, 'backup-preview'));
|
}, 'backup-preview'));
|
||||||
|
|
||||||
// Restore configuration from backup
|
// 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;
|
const { backup, options = {}, totpCode } = req.body;
|
||||||
|
|
||||||
if (!backup || !backup.version || !backup.files) {
|
if (!backup || !backup.version || !backup.files) {
|
||||||
@@ -274,7 +283,7 @@ module.exports = function(ctx) {
|
|||||||
|
|
||||||
await fsp.writeFile(filePath, content, 'utf8');
|
await fsp.writeFile(filePath, content, 'utf8');
|
||||||
results.restored.push(key);
|
results.restored.push(key);
|
||||||
ctx.log.info('backup', `Restored: ${key}`, { path: filePath });
|
log.info('backup', `Restored: ${key}`, { path: filePath });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
results.errors.push({ file: key, error: e.message });
|
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}`);
|
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) {
|
} catch (e) {
|
||||||
results.errors.push({ file: 'themes', error: e.message });
|
results.errors.push({ file: 'themes', error: e.message });
|
||||||
}
|
}
|
||||||
@@ -366,7 +375,7 @@ module.exports = function(ctx) {
|
|||||||
}
|
}
|
||||||
results.encryptionKeyReloaded = true;
|
results.encryptionKeyReloaded = true;
|
||||||
} catch (e) {
|
} 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
|
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'));
|
}, 'backup-restore'));
|
||||||
|
|
||||||
return router;
|
return router;
|
||||||
|
|||||||
@@ -1,9 +1,22 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Config routes aggregator
|
||||||
|
* @param {Object} ctx - Application context (for backward compatibility)
|
||||||
|
* @returns {express.Router}
|
||||||
|
*/
|
||||||
module.exports = function(ctx) {
|
module.exports = function(ctx) {
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
router.use(require('./settings')(ctx));
|
|
||||||
router.use(require('./assets')(ctx));
|
const deps = {
|
||||||
router.use(require('./backup')(ctx));
|
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;
|
return router;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,14 +3,22 @@ const { validateConfig } = require('../../config-schema');
|
|||||||
const { exists } = require('../../fs-helpers');
|
const { exists } = require('../../fs-helpers');
|
||||||
const { ValidationError } = require('../errors');
|
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 express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
// ===== DASHCADDY CONFIG ENDPOINTS =====
|
// ===== DASHCADDY CONFIG ENDPOINTS =====
|
||||||
// Server-side config storage for setup wizard (shared across all browsers/machines)
|
// 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)) {
|
if (!await exists(ctx.CONFIG_FILE)) {
|
||||||
return res.json({ setupComplete: false });
|
return res.json({ setupComplete: false });
|
||||||
}
|
}
|
||||||
@@ -19,7 +27,7 @@ module.exports = function(ctx) {
|
|||||||
res.json(config);
|
res.json(config);
|
||||||
}, 'config-get'));
|
}, 'config-get'));
|
||||||
|
|
||||||
router.post('/config', ctx.asyncHandler(async (req, res) => {
|
router.post('/config', asyncHandler(async (req, res) => {
|
||||||
const incoming = req.body;
|
const incoming = req.body;
|
||||||
|
|
||||||
if (!incoming || typeof incoming !== 'object') {
|
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');
|
await fsp.writeFile(ctx.CONFIG_FILE, JSON.stringify(config, null, 2), 'utf8');
|
||||||
ctx.loadSiteConfig(); // Refresh in-memory config
|
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 });
|
res.json({ success: true, message: 'Configuration saved', config, warnings });
|
||||||
}, 'config-save'));
|
}, 'config-save'));
|
||||||
|
|
||||||
router.delete('/config', ctx.asyncHandler(async (req, res) => {
|
router.delete('/config', asyncHandler(async (req, res) => {
|
||||||
if (await exists(ctx.CONFIG_FILE)) {
|
if (await exists(ctx.CONFIG_FILE)) {
|
||||||
await fsp.unlink(ctx.CONFIG_FILE);
|
await fsp.unlink(ctx.CONFIG_FILE);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user