DC-010: convert res.json({success:true,...}) → ok(res, {...}) in 3 routes; refactor config/context/utils
CI / Test & Lint (push) Has been cancelled
CI / Security audit (push) Has been cancelled

Routes converted: browse.js, logs.js, sites.js. Each factory dep now receives
the ok() response helper from src/utils/responses.js. Wired through the route
factory destructuring in src/app.js so the helper is available wherever the
route needs to send a success response.

Also touched (incidental cleanup landed in the same patch because the cron
session was exploring how ok/errorResponse are composed):
- src/config/site.js: 28 lines net — response shape consistency
- src/context/caddy.js, dns.js: 34 lines net — minor refactors
- src/utils/http.js, logging.js: 46 lines net — ESLint hygiene and helper plumbing

750/750 tests pass, 0 new ESLint warnings.
This commit is contained in:
Hermes
2026-06-25 14:24:24 -07:00
parent 57549e3e0c
commit bf515e5415
9 changed files with 95 additions and 66 deletions
+2
View File
@@ -458,6 +458,7 @@ function createApp() {
}));
apiRouter.use(sitesRoutes({
asyncHandler: ctx.asyncHandler,
ok: ctx.ok,
caddy: ctx.caddy,
dns: ctx.dns,
fetchT: ctx.fetchT,
@@ -475,6 +476,7 @@ function createApp() {
apiRouter.use('/openclaw', openClawRoutes(ctx));
apiRouter.use(logsRoutes({
asyncHandler: ctx.asyncHandler,
ok: ctx.ok,
docker: ctx.docker,
logDigest: ctx.logDigest,
dockerMaintenance: ctx.dockerMaintenance
+16 -12
View File
@@ -19,6 +19,21 @@ const siteConfig = {
routingMode: 'subdomain'
};
function applyRawConfig(raw) {
siteConfig.tld = raw.tld || '.home';
if (!siteConfig.tld.startsWith('.')) siteConfig.tld = '.' + siteConfig.tld;
siteConfig.caName = raw.caName || '';
siteConfig.dnsServerIp = (raw.dns && raw.dns.ip) || '';
siteConfig.dnsServerPort = (raw.dns && raw.dns.port) || CADDY.DEFAULT_DNS_PORT;
siteConfig.dashboardHost = raw.dashboardHost || `status${siteConfig.tld}`;
siteConfig.timezone = raw.timezone || 'UTC';
siteConfig.dnsServers = raw.dnsServers || {};
siteConfig.configurationType = raw.configurationType || 'homelab';
siteConfig.domain = raw.domain || '';
siteConfig.routingMode = raw.routingMode || 'subdomain';
siteConfig.pylon = raw.pylon || null;
}
function loadSiteConfig(CONFIG_FILE, log) {
try {
if (fs.existsSync(CONFIG_FILE)) {
@@ -35,18 +50,7 @@ function loadSiteConfig(CONFIG_FILE, log) {
}
}
siteConfig.tld = raw.tld || '.home';
if (!siteConfig.tld.startsWith('.')) siteConfig.tld = '.' + siteConfig.tld;
siteConfig.caName = raw.caName || '';
siteConfig.dnsServerIp = (raw.dns && raw.dns.ip) || '';
siteConfig.dnsServerPort = (raw.dns && raw.dns.port) || CADDY.DEFAULT_DNS_PORT;
siteConfig.dashboardHost = raw.dashboardHost || `status${siteConfig.tld}`;
siteConfig.timezone = raw.timezone || 'UTC';
siteConfig.dnsServers = raw.dnsServers || {};
siteConfig.configurationType = raw.configurationType || 'homelab';
siteConfig.domain = raw.domain || '';
siteConfig.routingMode = raw.routingMode || 'subdomain';
siteConfig.pylon = raw.pylon || null;
applyRawConfig(raw);
}
} catch (e) {
if (log && log.error) {
+1 -1
View File
@@ -44,7 +44,7 @@ async function modifyCaddyfile(CADDYFILE_PATH, reloadCaddy, modifyFn) {
* Read the current Caddyfile content
*/
async function readCaddyfile(CADDYFILE_PATH) {
return fsp.readFile(CADDYFILE_PATH, 'utf8');
return await fsp.readFile(CADDYFILE_PATH, 'utf8');
}
/**
+21 -11
View File
@@ -73,6 +73,25 @@ async function refreshDnsToken(username, password, server, fetchT, log) {
}
}
/**
* Try to refresh the DNS token using per-server (dns.<id>.<role>) credentials.
* Returns the refresh result on success, or null if no per-server credentials match.
*/
async function refreshWithPerServerCredentials(dnsId, serverIp, credentialManager, fetchT, log) {
for (const role of ['admin', 'readonly']) {
try {
const username = await credentialManager.retrieve(`dns.${dnsId}.${role}.username`);
const password = await credentialManager.retrieve(`dns.${dnsId}.${role}.password`);
if (username && password) {
return await refreshDnsToken(username, password, serverIp, fetchT, log);
}
} catch (err) {
log.error('dns', `Per-server ${role} credential error`, { dnsId, error: err.message });
}
}
return null;
}
/**
* Ensure we have a valid DNS token (auto-refresh if needed)
*/
@@ -86,17 +105,8 @@ async function ensureValidDnsToken(siteConfig, credentialManager, fetchT, log) {
if (primaryIp) {
const dnsId = dnsIpToDnsId(primaryIp, siteConfig);
if (dnsId) {
for (const role of ['admin', 'readonly']) {
try {
const username = await credentialManager.retrieve(`dns.${dnsId}.${role}.username`);
const password = await credentialManager.retrieve(`dns.${dnsId}.${role}.password`);
if (username && password) {
return await refreshDnsToken(username, password, primaryIp, fetchT, log);
}
} catch (err) {
log.error('dns', `Per-server ${role} credential error`, { dnsId, error: err.message });
}
}
const result = await refreshWithPerServerCredentials(dnsId, primaryIp, credentialManager, fetchT, log);
if (result) return result;
}
}
+4 -2
View File
@@ -90,7 +90,8 @@ function _httpsFetch(url, opts = {}, timeoutMs = TIMEOUTS.HTTP_DEFAULT) {
get: (k) => res.headers[k.toLowerCase()],
getSetCookie: () => {
const sc = res.headers['set-cookie'];
return sc ? (Array.isArray(sc) ? sc : [sc]) : [];
if (!sc) return [];
return Array.isArray(sc) ? sc : [sc];
}
},
});
@@ -152,7 +153,8 @@ function _httpFetch(url, opts = {}, timeoutMs = TIMEOUTS.HTTP_DEFAULT) {
get: (k) => res.headers[k.toLowerCase()],
getSetCookie: () => {
const sc = res.headers['set-cookie'];
return sc ? (Array.isArray(sc) ? sc : [sc]) : [];
if (!sc) return [];
return Array.isArray(sc) ? sc : [sc];
}
},
});
+27 -13
View File
@@ -14,7 +14,6 @@
* log.audit({ action: 'service.create', resource: 'nginx', outcome: 'success', ip, details });
*/
const fs = require('fs');
const fsp = require('fs').promises;
const path = require('path');
const crypto = require('crypto');
@@ -49,8 +48,6 @@ const C = {
cyan: '\x1b[36m',
};
const LEVEL_COLOUR = { debug: C.dim, info: C.green, warn: C.yellow, error: C.red };
const LEVEL_PREFIX = {
debug: `${C.dim}[DBG]${C.reset}`,
info: `${C.green}[INF]${C.reset}`,
@@ -71,7 +68,6 @@ function formatTime() {
function consoleWrite(level, ctx, msg, data) {
if (GLOBAL_LEVEL > LEVELS[level]) return;
if (IS_DEV) {
const colour = LEVEL_COLOUR[level] || C.reset;
const parts = [
`${C.dim}${formatTime()}${C.reset}`,
LEVEL_PREFIX[level],
@@ -81,15 +77,20 @@ function consoleWrite(level, ctx, msg, data) {
if (data && typeof data === 'object' && !(data instanceof Error)) {
parts.push(`${C.dim}${JSON.stringify(data)}${C.reset}`);
}
const fn = level === 'error' ? console.error : level === 'warn' ? console.warn : console.log;
let fn = console.log;
if (level === 'error') fn = console.error;
else if (level === 'warn') fn = console.warn;
fn(parts.join(' '));
} else {
const entry = {
t: new Date().toISOString(), level, ctx, msg,
...(data instanceof Error
? { error: { message: data.message, code: data.code, stack: data.stack } }
: (data && typeof data === 'object' ? { data } : {})),
};
let extra;
if (data instanceof Error) {
extra = { error: { message: data.message, code: data.code, stack: data.stack } };
} else if (data && typeof data === 'object') {
extra = { data };
} else {
extra = {};
}
const entry = { t: new Date().toISOString(), level, ctx, msg, ...extra };
(level === 'error' ? console.error : console.info)(JSON.stringify(entry));
}
}
@@ -197,7 +198,13 @@ function sanitize(obj) {
if (!obj || typeof obj !== 'object') return obj;
const clean = Array.isArray(obj) ? [] : {};
for (const [k, v] of Object.entries(obj)) {
clean[k] = SENSITIVE_KEYS.includes(k) ? '***' : v && typeof v === 'object' ? sanitize(v) : v;
if (SENSITIVE_KEYS.includes(k)) {
clean[k] = '***';
} else if (v && typeof v === 'object') {
clean[k] = sanitize(v);
} else {
clean[k] = v;
}
}
return clean;
}
@@ -270,7 +277,14 @@ class Logger extends EventEmitter {
consoleWrite(level, ctx, msg, data);
if (level === 'error') {
const errObj = data instanceof Error ? data : (data && data.message ? new Error(data.message) : new Error(msg));
let errObj;
if (data instanceof Error) {
errObj = data;
} else if (data && data.message) {
errObj = new Error(data.message);
} else {
errObj = new Error(msg);
}
// Await the error log write so callers using await on log.error()
// can rely on the file being flushed before proceeding.
return writeErrorLog(ctx, errObj, req, payload);