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:
Krystie
2026-03-29 21:36:15 -07:00
parent 6bde2eb62e
commit a4788c3f28
6 changed files with 229 additions and 153 deletions

View File

@@ -1,35 +1,46 @@
const express = require('express');
const { exists } = require('../../fs-helpers');
module.exports = function(ctx, helpers) {
module.exports = function({ docker, caddy, servicesStateManager, asyncHandler, log, helpers }) {
const router = express.Router();
// Remove deployed app
router.delete('/apps/:appId', ctx.asyncHandler(async (req, res) => {
/**
* Apps removal routes factory
* @param {Object} deps - Explicit dependencies
* @param {Object} deps.docker - Docker client wrapper
* @param {Object} deps.caddy - Caddy client
* @param {Object} deps.servicesStateManager - Services state manager
* @param {Function} deps.asyncHandler - Async route handler wrapper
* @param {Object} deps.log - Logger instance
* @param {Object} deps.helpers - Apps helpers module
* @returns {express.Router}
*/
router.delete('/apps/:appId', asyncHandler(async (req, res) => {
const { appId } = req.params;
const { containerId, subdomain, ip, deleteContainer } = req.query;
const shouldDeleteContainer = deleteContainer === 'true';
const results = { container: null, dns: null, caddy: null, service: null };
try {
ctx.log.info('deploy', 'Removing app', { appId, containerId, subdomain, deleteContainer: shouldDeleteContainer });
log.info('deploy', 'Removing app', { appId, containerId, subdomain, deleteContainer: shouldDeleteContainer });
if (containerId && shouldDeleteContainer) {
try {
const container = ctx.docker.client.getContainer(containerId);
try { await container.stop(); ctx.log.info('docker', 'Container stopped', { containerId }); }
catch (stopError) { ctx.log.debug('docker', 'Container stop note', { containerId, note: stopError.message }); }
const container = docker.client.getContainer(containerId);
try { await container.stop(); log.info('docker', 'Container stopped', { containerId }); }
catch (stopError) { log.debug('docker', 'Container stop note', { containerId, note: stopError.message }); }
await container.remove({ force: true });
results.container = 'removed';
ctx.log.info('docker', 'Container removed', { containerId });
log.info('docker', 'Container removed', { containerId });
// Prune dangling images after removal
try {
const pruneResult = await ctx.docker.client.pruneImages({ filters: { dangling: { true: true } } });
const pruneResult = await docker.client.pruneImages({ filters: { dangling: { true: true } } });
if (pruneResult.SpaceReclaimed > 0) {
ctx.log.info('docker', 'Pruned dangling images after removal', { spaceReclaimed: Math.round(pruneResult.SpaceReclaimed / 1024 / 1024) + 'MB' });
log.info('docker', 'Pruned dangling images after removal', { spaceReclaimed: Math.round(pruneResult.SpaceReclaimed / 1024 / 1024) + 'MB' });
}
} catch (pruneErr) {
ctx.log.debug('docker', 'Image prune after removal failed', { error: pruneErr.message });
log.debug('docker', 'Image prune after removal failed', { error: pruneErr.message });
}
} catch (error) {
results.container = error.message.includes('no such container') ? 'already removed' : error.message;
@@ -53,7 +64,7 @@ module.exports = function(ctx, helpers) {
token: ctx.dns.getToken(), domain, type: 'A', ipAddress: recordIp
});
results.dns = dnsResult.status === 'ok' ? 'deleted' : (dnsResult.errorMessage || 'failed');
ctx.log.info('dns', 'DNS record removal', { result: results.dns });
log.info('dns', 'DNS record removal', { result: results.dns });
} catch (error) {
results.dns = error.message;
}
@@ -66,7 +77,7 @@ module.exports = function(ctx, helpers) {
if (shouldDeleteContainer && subdomain) {
try {
// Check if this service was deployed in subdirectory mode
const services = await ctx.servicesStateManager.read();
const services = await servicesStateManager.read();
const serviceList = Array.isArray(services) ? services : [];
const service = serviceList.find(s => s.id === subdomain);
@@ -79,14 +90,14 @@ module.exports = function(ctx, helpers) {
const domain = ctx.buildDomain(subdomain);
const escapedDomain = domain.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const siteBlockRegex = new RegExp(`\\n?${escapedDomain}\\s*\\{[^{}]*(?:\\{[^{}]*(?:\\{[^{}]*\\}[^{}]*)*\\}[^{}]*)*\\}\\s*`, 'g');
const caddyResult = await ctx.caddy.modify(currentContent => {
const caddyResult = await caddy.modify(currentContent => {
const replaced = currentContent.replace(siteBlockRegex, '\n');
if (replaced.length === currentContent.length) return null;
return replaced.replace(/\n{3,}/g, '\n\n');
});
results.caddy = caddyResult.success ? 'removed' : (caddyResult.rolledBack ? 'removed (reload failed)' : 'not found');
}
ctx.log.info('caddy', 'Caddy config removal', { result: results.caddy });
log.info('caddy', 'Caddy config removal', { result: results.caddy });
} catch (error) {
results.caddy = error.message;
}
@@ -97,7 +108,7 @@ module.exports = function(ctx, helpers) {
try {
if (await exists(ctx.SERVICES_FILE)) {
let removed = false;
await ctx.servicesStateManager.update(services => {
await servicesStateManager.update(services => {
const initialLength = services.length;
const filtered = services.filter(s => s.id !== subdomain);
removed = filtered.length !== initialLength;
@@ -105,15 +116,15 @@ module.exports = function(ctx, helpers) {
});
results.service = removed ? 'removed' : 'not found';
}
ctx.log.info('deploy', 'Service config removal', { result: results.service });
log.info('deploy', 'Service config removal', { result: results.service });
} catch (error) {
results.service = error.message;
}
res.json({ success: true, message: `App ${appId} removal completed`, results });
} catch (error) {
await ctx.logError('app-removal', error);
ctx.errorResponse(res, 500, ctx.safeErrorMessage(error), { results });
await logError('app-removal', error);
errorResponse(res, 500, ctx.safeErrorMessage(error), { results });
}
}, 'apps-delete'));