fix(routes): restore ctx access in 15 route files broken by Phase 2.1 refactor

The modular refactor changed function signatures to destructured deps but
left internal ctx.* references intact, causing "ctx is not defined" errors
on /api/config, /api/logo, and many other endpoints. Also implements
loadTotpConfig and saveTotpConfig which were left as stubs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-30 03:01:29 -07:00
parent 01bf01d043
commit f865790fe1
16 changed files with 62 additions and 37 deletions

View File

@@ -17,7 +17,8 @@ const platformPaths = require('../../platform-paths');
* @param {Object} deps.log - Logger instance * @param {Object} deps.log - Logger instance
* @returns {Object} Helper functions * @returns {Object} Helper functions
*/ */
module.exports = function({ docker, caddy, credentialManager, servicesStateManager, fetchT, log }) { module.exports = function(ctx) {
const { docker, caddy, credentialManager, servicesStateManager, fetchT, log } = ctx;
async function checkPortConflicts(ports, excludeContainerName = null) { async function checkPortConflicts(ports, excludeContainerName = null) {
const conflicts = []; const conflicts = [];

View File

@@ -36,13 +36,14 @@ module.exports = function(ctx) {
}; };
// Initialize helpers with dependencies // Initialize helpers with dependencies
const helpers = initHelpers(deps); const helpers = initHelpers(ctx);
// Mount sub-routes with explicit dependencies // Mount sub-routes — pass full ctx so sub-routes can reference ctx.* properties
router.use(initDeploy({ ...deps, helpers })); const subCtx = Object.assign({}, ctx, { helpers });
router.use(initRemoval({ ...deps, helpers })); router.use(initDeploy(subCtx));
router.use(initTemplates({ ...deps, helpers })); router.use(initRemoval(subCtx));
router.use(initRestore({ ...deps, helpers })); router.use(initTemplates(subCtx));
router.use(initRestore(subCtx));
return router; return router;
}; };

View File

@@ -1,10 +1,9 @@
const express = require('express'); const express = require('express');
const { exists } = require('../../fs-helpers'); const { exists } = require('../../fs-helpers');
module.exports = function({ docker, caddy, servicesStateManager, asyncHandler, log, helpers }) { module.exports = function(ctx) {
const { docker, caddy, servicesStateManager, asyncHandler, errorResponse, log, helpers } = ctx;
const router = express.Router(); const router = express.Router();
// Remove deployed app
/** /**
* Apps removal routes factory * Apps removal routes factory
* @param {Object} deps - Explicit dependencies * @param {Object} deps - Explicit dependencies

View File

@@ -12,7 +12,8 @@ const { DOCKER } = require('../../constants');
* @param {Object} deps.helpers - Apps helpers module * @param {Object} deps.helpers - Apps helpers module
* @returns {express.Router} * @returns {express.Router}
*/ */
module.exports = function({ docker, caddy, servicesStateManager, asyncHandler, log, helpers }) { module.exports = function(ctx) {
const { docker, caddy, servicesStateManager, asyncHandler, log, helpers } = ctx;
const router = express.Router(); const router = express.Router();
/** /**

View File

@@ -10,7 +10,8 @@ const { exists } = require('../../fs-helpers');
*/ */
const { REGEX } = require('../../constants'); const { REGEX } = require('../../constants');
module.exports = function({ servicesStateManager, asyncHandler, helpers }) { module.exports = function(ctx) {
const { servicesStateManager, asyncHandler, helpers, docker, caddy, log, errorResponse } = ctx;
const router = express.Router(); const router = express.Router();
// Get available app templates // Get available app templates

View File

@@ -17,7 +17,8 @@ const { logError } = require('../../src/utils/logging');
* @param {Object} deps.helpers - Arr helpers module * @param {Object} deps.helpers - Arr helpers module
* @returns {express.Router} * @returns {express.Router}
*/ */
module.exports = function({ credentialManager, servicesStateManager, docker, fetchT, asyncHandler, errorResponse, log, helpers, notification, safeErrorMessage }) { module.exports = function(ctx) {
const { credentialManager, servicesStateManager, docker, fetchT, asyncHandler, errorResponse, log, helpers, notification, safeErrorMessage } = ctx;
const router = express.Router(); const router = express.Router();
// Ctx shim for backward compatibility // Ctx shim for backward compatibility

View File

@@ -24,14 +24,15 @@ module.exports = function(ctx) {
}; };
// Initialize helpers with dependencies // Initialize helpers with dependencies
const helpers = require('./helpers')(deps); const helpers = require('./helpers')(ctx);
// Mount sub-routes with explicit dependencies // Mount sub-routes — pass full ctx so sub-routes can reference ctx.* properties
router.use(require('./detect')({ ...deps, helpers })); const subCtx = Object.assign({}, ctx, { helpers });
router.use(require('./credentials')({ ...deps, helpers })); router.use(require('./detect')(subCtx));
router.use(require('./config')({ ...deps, helpers })); router.use(require('./credentials')(subCtx));
router.use(require('./smart-connect')({ ...deps, helpers })); router.use(require('./config')(subCtx));
router.use(require('./plex')({ ...deps, helpers })); router.use(require('./smart-connect')(subCtx));
router.use(require('./plex')(subCtx));
return router; return router;
}; };

View File

@@ -11,7 +11,8 @@ const { APP_PORTS } = require('../../constants');
* @param {Object} deps.helpers - Arr helpers module * @param {Object} deps.helpers - Arr helpers module
* @returns {express.Router} * @returns {express.Router}
*/ */
module.exports = function({ fetchT, asyncHandler, errorResponse, log, helpers }) { module.exports = function(ctx) {
const { fetchT, asyncHandler, errorResponse, log, helpers } = ctx;
const router = express.Router(); const router = express.Router();
// Plex Libraries endpoint // Plex Libraries endpoint

View File

@@ -12,7 +12,8 @@ const { APP_PORTS } = require('../../constants');
* @param {Object} deps.helpers - Arr helpers module * @param {Object} deps.helpers - Arr helpers module
* @returns {express.Router} * @returns {express.Router}
*/ */
module.exports = function({ credentialManager, fetchT, asyncHandler, errorResponse, log, helpers }) { module.exports = function(ctx) {
const { credentialManager, fetchT, asyncHandler, errorResponse, log, helpers } = ctx;
const router = express.Router(); const router = express.Router();
// Smart Connect: Unified orchestration endpoint // Smart Connect: Unified orchestration endpoint

View File

@@ -22,7 +22,8 @@ try {
// Image processing libraries not available — favicon conversion disabled // Image processing libraries not available — favicon conversion disabled
} }
module.exports = function({ servicesStateManager, asyncHandler, log }) { module.exports = function(ctx) {
const { servicesStateManager, asyncHandler, log } = ctx;
const router = express.Router(); const router = express.Router();
// ===== ASSET UPLOAD ===== // ===== ASSET UPLOAD =====

View File

@@ -35,8 +35,8 @@ module.exports = function(ctx) {
saveTotpConfig: ctx.saveTotpConfig saveTotpConfig: ctx.saveTotpConfig
}; };
router.use(require('./settings')(baseDeps)); router.use(require('./settings')(ctx));
router.use(require('./assets')({ ...baseDeps, CONFIG_FILE: ctx.CONFIG_FILE, readConfig: ctx.readConfig, saveConfig: ctx.saveConfig, errorResponse: ctx.errorResponse })); router.use(require('./assets')(ctx));
router.use(require('./backup')(backupDeps)); router.use(require('./backup')(backupDeps));
return router; return router;
}; };

View File

@@ -3,15 +3,13 @@ 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({ configStateManager, asyncHandler, log }) {
/** /**
* Config settings routes factory * Config settings routes factory
* @param {Object} deps - Explicit dependencies * @param {Object} ctx - Application context
* @param {Object} deps.configStateManager - Config state manager
* @param {Function} deps.asyncHandler - Async route handler wrapper
* @param {Object} deps.log - Logger instance
* @returns {express.Router} * @returns {express.Router}
*/ */
module.exports = function(ctx) {
const { configStateManager, asyncHandler, log } = ctx;
const express = require('express'); const express = require('express');
const router = express.Router(); const router = express.Router();

View File

@@ -14,7 +14,8 @@ const { DOCKER } = require('../../constants');
* @param {Object} deps.log - Logger instance * @param {Object} deps.log - Logger instance
* @returns {express.Router} * @returns {express.Router}
*/ */
module.exports = function({ docker, credentialManager, servicesStateManager, asyncHandler, errorResponse, log }) { module.exports = function(ctx) {
const { docker, credentialManager, servicesStateManager, asyncHandler, errorResponse, log } = ctx;
const router = express.Router(); const router = express.Router();
/** /**

View File

@@ -61,9 +61,9 @@ module.exports = function(ctx) {
res.json({ success: true, recipe: { id: req.params.recipeId, ...recipe } }); res.json({ success: true, recipe: { id: req.params.recipeId, ...recipe } });
}, 'recipe-template-detail')); }, 'recipe-template-detail'));
// Mount deploy and manage sub-routes // Mount deploy and manage sub-routes — pass full ctx for sub-routes that reference ctx.*
router.use(deployRoutes(deps)); router.use(deployRoutes(ctx));
router.use(manageRoutes(deps)); router.use(manageRoutes(ctx));
return router; return router;
}; };

View File

@@ -2,7 +2,8 @@ const express = require('express');
const { DOCKER } = require('../../constants'); const { DOCKER } = require('../../constants');
const { NotFoundError } = require('../../errors'); const { NotFoundError } = require('../../errors');
module.exports = function({ servicesStateManager, asyncHandler, log }) { module.exports = function(ctx) {
const { servicesStateManager, asyncHandler, log } = ctx;
const router = express.Router(); const router = express.Router();
/** /**
* Recipes management routes factory * Recipes management routes factory

View File

@@ -102,13 +102,25 @@ async function createApp() {
log.warn('server', 'CA cert not found — HTTPS calls may fail', { path: CA_CERT_PATH }); log.warn('server', 'CA cert not found — HTTPS calls may fail', { path: CA_CERT_PATH });
} }
// TOTP configuration // TOTP configuration (defaults, overridden by loadTotpConfig below)
let totpConfig = { let totpConfig = {
enabled: false, enabled: false,
sessionDuration: 'never', sessionDuration: 'never',
isSetUp: false isSetUp: false
}; };
// Load TOTP config from file
try {
if (fs.existsSync(config.TOTP_CONFIG_FILE)) {
const loaded = JSON.parse(fs.readFileSync(config.TOTP_CONFIG_FILE, 'utf8'));
delete loaded.secret; // secret belongs only in credential-manager
Object.assign(totpConfig, loaded);
log.info('config', 'TOTP config loaded', { enabled: totpConfig.enabled });
}
} catch (e) {
log.warn('config', 'Could not load TOTP config', { error: e.message });
}
// Tailscale configuration // Tailscale configuration
let tailscaleConfig = { let tailscaleConfig = {
enabled: false, enabled: false,
@@ -192,7 +204,12 @@ async function createApp() {
} }
async function saveTotpConfig() { async function saveTotpConfig() {
// Stub - will be implemented try {
const { writeJsonFile } = require('../fs-helpers');
await writeJsonFile(config.TOTP_CONFIG_FILE, totpConfig);
} catch (e) {
log.error('config', 'Could not save TOTP config', { error: e.message });
}
} }
async function loadNotificationConfig() { async function loadNotificationConfig() {