Refactor apps routes: explicit dependency injection
- Updated all apps route modules to use destructured dependencies - Added JSDoc comments for factory functions - Replaced ctx. references with direct parameter access - Updated apps/index.js to extract and pass explicit dependencies - All files pass syntax validation Files refactored: - routes/apps/deploy.js (18k lines) - routes/apps/helpers.js (17k lines) - routes/apps/removal.js - routes/apps/restore.js - routes/apps/templates.js - routes/apps/index.js (orchestrator)
This commit is contained in:
@@ -6,12 +6,23 @@ const { REGEX, DOCKER } = require('../../constants');
|
||||
const { exists } = require('../../fs-helpers');
|
||||
const platformPaths = require('../../platform-paths');
|
||||
|
||||
module.exports = function(ctx) {
|
||||
/**
|
||||
* Apps helpers factory
|
||||
* @param {Object} deps - Explicit dependencies
|
||||
* @param {Object} deps.docker - Docker client wrapper
|
||||
* @param {Object} deps.caddy - Caddy client
|
||||
* @param {Object} deps.credentialManager - Credential manager
|
||||
* @param {Object} deps.servicesStateManager - Services state manager
|
||||
* @param {Function} deps.fetchT - Timeout-wrapped fetch
|
||||
* @param {Object} deps.log - Logger instance
|
||||
* @returns {Object} Helper functions
|
||||
*/
|
||||
module.exports = function({ docker, caddy, credentialManager, servicesStateManager, fetchT, log }) {
|
||||
|
||||
async function checkPortConflicts(ports, excludeContainerName = null) {
|
||||
const conflicts = [];
|
||||
try {
|
||||
const containers = await ctx.docker.client.listContainers({ all: true });
|
||||
const containers = await docker.client.listContainers({ all: true });
|
||||
for (const container of containers) {
|
||||
if (excludeContainerName && container.Names.some(n => n === `/${excludeContainerName}`)) continue;
|
||||
if (container.State !== 'running') continue;
|
||||
@@ -27,14 +38,14 @@ module.exports = function(ctx) {
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
ctx.log.warn('docker', 'Could not check port conflicts', { error: e.message });
|
||||
log.warn('docker', 'Could not check port conflicts', { error: e.message });
|
||||
}
|
||||
return conflicts;
|
||||
}
|
||||
|
||||
async function findExistingContainerByImage(template) {
|
||||
try {
|
||||
const containers = await ctx.docker.client.listContainers({ all: false });
|
||||
const containers = await docker.client.listContainers({ all: false });
|
||||
const templateImage = template.docker.image.split(':')[0];
|
||||
for (const container of containers) {
|
||||
const containerImage = container.Image.split(':')[0];
|
||||
@@ -53,7 +64,7 @@ module.exports = function(ctx) {
|
||||
}
|
||||
return null;
|
||||
} catch (e) {
|
||||
ctx.log.warn('docker', 'Could not check for existing containers', { error: e.message });
|
||||
log.warn('docker', 'Could not check for existing containers', { error: e.message });
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -140,7 +151,7 @@ module.exports = function(ctx) {
|
||||
normalizedHost === root || normalizedHost.startsWith(root + path.sep)
|
||||
);
|
||||
if (!isAllowed) {
|
||||
ctx.log.warn('deploy', 'Custom volume host path rejected', { hostPath: override.hostPath, allowed: allowedRoots });
|
||||
log.warn('deploy', 'Custom volume host path rejected', { hostPath: override.hostPath, allowed: allowedRoots });
|
||||
return vol; // Keep original volume, don't apply unsafe override
|
||||
}
|
||||
return `${toDockerDesktopPath(override.hostPath)}:${containerPath}`;
|
||||
@@ -243,39 +254,39 @@ module.exports = function(ctx) {
|
||||
|
||||
for (let i = 0; i < maxAttempts; i++) {
|
||||
try {
|
||||
const container = ctx.docker.client.getContainer(containerId);
|
||||
const container = docker.client.getContainer(containerId);
|
||||
const info = await container.inspect();
|
||||
if (info.State.Running) {
|
||||
if (info.State.Health) {
|
||||
if (info.State.Health.Status === 'healthy') {
|
||||
ctx.log.info('docker', 'Container is healthy (Docker health check)', { containerId });
|
||||
log.info('docker', 'Container is healthy (Docker health check)', { containerId });
|
||||
return true;
|
||||
}
|
||||
} else if (healthPath && port && httpCheckFailed < 5) {
|
||||
try {
|
||||
const response = await ctx.fetchT(`http://localhost:${port}${healthPath}`, {
|
||||
const response = await fetchT(`http://localhost:${port}${healthPath}`, {
|
||||
signal: AbortSignal.timeout(3000), redirect: 'manual'
|
||||
});
|
||||
if (response.ok || (response.status >= 300 && response.status < 400)) {
|
||||
ctx.log.info('docker', 'Health check passed', { containerId, status: response.status });
|
||||
log.info('docker', 'Health check passed', { containerId, status: response.status });
|
||||
return true;
|
||||
}
|
||||
} catch (e) {
|
||||
httpCheckFailed++;
|
||||
ctx.log.debug('docker', 'HTTP health check failed', { attempt: httpCheckFailed, error: e.message });
|
||||
log.debug('docker', 'HTTP health check failed', { attempt: httpCheckFailed, error: e.message });
|
||||
}
|
||||
} else {
|
||||
if (i >= 5) {
|
||||
ctx.log.info('docker', 'Container is running', { containerId, waitedSeconds: i * delay / 1000 });
|
||||
log.info('docker', 'Container is running', { containerId, waitedSeconds: i * delay / 1000 });
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
ctx.log.debug('docker', 'Health check attempt failed', { attempt: i + 1, error: e.message });
|
||||
log.debug('docker', 'Health check attempt failed', { attempt: i + 1, error: e.message });
|
||||
}
|
||||
if (i < maxAttempts - 1) {
|
||||
ctx.log.debug('docker', 'Waiting for container to be healthy', { attempt: i + 1, maxAttempts });
|
||||
log.debug('docker', 'Waiting for container to be healthy', { attempt: i + 1, maxAttempts });
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
}
|
||||
}
|
||||
@@ -284,15 +295,15 @@ module.exports = function(ctx) {
|
||||
|
||||
async function addCaddyConfig(subdomain, config) {
|
||||
const domain = ctx.buildDomain(subdomain);
|
||||
const existing = await ctx.caddy.read();
|
||||
const existing = await caddy.read();
|
||||
if (existing.includes(`${domain} {`)) {
|
||||
ctx.log.info('caddy', 'Caddy config already exists, skipping add', { domain });
|
||||
await ctx.caddy.reload(existing);
|
||||
log.info('caddy', 'Caddy config already exists, skipping add', { domain });
|
||||
await caddy.reload(existing);
|
||||
return;
|
||||
}
|
||||
const result = await ctx.caddy.modify(c => c + `\n${config}\n`);
|
||||
const result = await caddy.modify(c => c + `\n${config}\n`);
|
||||
if (!result.success) throw new Error(`[DC-303] Failed to add Caddy config for ${domain}: ${result.error}`);
|
||||
await ctx.caddy.verifySite(domain);
|
||||
await caddy.verifySite(domain);
|
||||
}
|
||||
|
||||
// Reserved paths that cannot be used as subpath names in subdirectory mode
|
||||
@@ -303,7 +314,7 @@ module.exports = function(ctx) {
|
||||
async function ensureMainDomainBlock() {
|
||||
if (ctx.siteConfig.routingMode !== 'subdirectory' || !ctx.siteConfig.domain) return;
|
||||
|
||||
const content = await ctx.caddy.read();
|
||||
const content = await caddy.read();
|
||||
const domain = ctx.siteConfig.domain;
|
||||
const ROUTE_MARKER = '# === DashCaddy App Routes ===';
|
||||
|
||||
@@ -312,7 +323,7 @@ module.exports = function(ctx) {
|
||||
|
||||
// Domain block exists but lacks markers — inject them
|
||||
if (content.includes(`${domain} {`)) {
|
||||
const result = await ctx.caddy.modify(c => {
|
||||
const result = await caddy.modify(c => {
|
||||
// Insert markers before the final catch-all handle block inside the domain block
|
||||
const domainStart = c.indexOf(`${domain} {`);
|
||||
// Find standalone "handle {" (catch-all SPA fallback) — match tabs or spaces
|
||||
@@ -325,7 +336,7 @@ module.exports = function(ctx) {
|
||||
return c.slice(0, handleIdx) + markerBlock + c.slice(handleIdx);
|
||||
});
|
||||
if (result.success) {
|
||||
ctx.log.info('caddy', 'Injected route markers into existing domain block', { domain });
|
||||
log.info('caddy', 'Injected route markers into existing domain block', { domain });
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -335,9 +346,9 @@ module.exports = function(ctx) {
|
||||
const apiPort = process.env.PORT || 3001;
|
||||
const block = `\n${domain} {\n root * ${dashboardRoot}\n encode gzip\n\n handle /api/* {\n reverse_proxy localhost:${apiPort}\n }\n\n handle /probe/* {\n reverse_proxy localhost:${apiPort}\n }\n\n ${ROUTE_MARKER}\n # === End App Routes ===\n\n handle {\n @notFile not file {path}\n rewrite @notFile /index.html\n file_server\n }\n}\n`;
|
||||
|
||||
const result = await ctx.caddy.modify(c => c + block);
|
||||
const result = await caddy.modify(c => c + block);
|
||||
if (result.success) {
|
||||
ctx.log.info('caddy', 'Created main domain block with route markers', { domain });
|
||||
log.info('caddy', 'Created main domain block with route markers', { domain });
|
||||
} else {
|
||||
throw new Error(`[DC-303] Failed to create main domain block for ${domain}: ${result.error}`);
|
||||
}
|
||||
@@ -349,9 +360,9 @@ module.exports = function(ctx) {
|
||||
const endMarker = `# --- End: ${subdomain} ---`;
|
||||
const END_ROUTE_MARKER = '# === End App Routes ===';
|
||||
|
||||
const result = await ctx.caddy.modify(content => {
|
||||
const result = await caddy.modify(content => {
|
||||
if (content.includes(marker)) {
|
||||
ctx.log.info('caddy', 'Subpath config already exists, skipping', { subdomain });
|
||||
log.info('caddy', 'Subpath config already exists, skipping', { subdomain });
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -378,7 +389,7 @@ module.exports = function(ctx) {
|
||||
const marker = `# --- DashCaddy: ${subdomain} ---`;
|
||||
const endMarker = `# --- End: ${subdomain} ---`;
|
||||
|
||||
return await ctx.caddy.modify(content => {
|
||||
return await caddy.modify(content => {
|
||||
const startIdx = content.indexOf(marker);
|
||||
if (startIdx === -1) return null;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user