/** * Response helpers - Standard API response formats * * Single source of truth for HTTP response shapes across DashCaddy. * Standard envelope: { success: true, ...data } or { success: false, error: "..." }. * * All routes should import from this module — do not call res.json/res.status * directly with the response shape, use these helpers instead. */ const { HTTP_STATUS } = require('../utilities/constants'); // ── Success helpers ──────────────────────────────────────────── /** * Standard success response. Use this in route handlers. * Wraps the data object with a `success: true` envelope. * @param {object} res Express response * @param {object} [data={}] fields to include in the response body * @param {number} [statusCode=200] HTTP status code */ function ok(res, data = {}, statusCode = HTTP_STATUS.OK) { return res.status(statusCode).json({ success: true, ...data }); } /** * Alias for `ok` — prefer `ok` in new code, but kept for code that imports as `success`. */ function success(res, data, statusCode) { return ok(res, data, statusCode); } /** * Success response with a human-readable message field. * Use when there's no data to return, just confirmation. */ function successMessage(res, message, statusCode = HTTP_STATUS.OK) { return res.status(statusCode).json({ success: true, message }); } /** * 201 Created response. */ function created(res, data = {}) { return res.status(HTTP_STATUS.CREATED).json({ success: true, ...data }); } /** * 204 No Content response. */ function noContent(res) { return res.status(HTTP_STATUS.NO_CONTENT).send(); } // ── Error helpers ────────────────────────────────────────────── /** * Standard error response. Use this in route handlers. * @param {object} res Express response * @param {number} statusCode HTTP status code * @param {string} message Human-readable error message * @param {object} [extras={}] additional fields to merge into the response * * DC-086: If extras.code is set, it's treated as a machine-readable error code * (e.g. 'DC-CONT-002'). If message looks like a DC code, it's auto-extracted. * * DC-062: Validate that `statusCode` is a valid HTTP status (integer in * 100..599) BEFORE calling res.status(). Without this guard, a caller who * passes (res, message, statusCode) instead of (res, statusCode, message) * ends up with res.status(), which throws * RangeError [ERR_HTTP_INVALID_STATUS_CODE] — Express catches that and * writes a 500 with an HTML stack trace to the client, which is the worst * possible failure mode (looks like a server crash, breaks CSRF and * content-type expectations, leaks the stack). Failing fast with a clear * TypeError names the call site early in the request lifecycle. */ function errorResponse(res, statusCode, message, extras = {}) { if ( typeof statusCode !== 'number' || !Number.isFinite(statusCode) || !Number.isInteger(statusCode) || statusCode < 100 || statusCode > 599 ) { throw new TypeError( `errorResponse(res, statusCode, message, extras): statusCode must be an integer HTTP status (100..599); received ${JSON.stringify(statusCode)} (message=${JSON.stringify(message)})` ); } if (typeof message !== 'string') { throw new TypeError( `errorResponse(res, statusCode, message, extras): message must be a string; received ${typeof message} ${JSON.stringify(message)}` ); } const body = { success: false, error: message, ...extras }; // DC-086: surface machine-readable code at top level for client handling if (extras.code) { body.code = extras.code; } return res.status(statusCode).json(body); } /** * Alias for `errorResponse` — kept for code that imports as `error`. */ function error(res, message, statusCode = HTTP_STATUS.INTERNAL_ERROR) { return res.status(statusCode).json({ success: false, error: message }); } /** * 400 Bad Request — invalid input from the user. */ function validationError(res, message) { return res.status(HTTP_STATUS.BAD_REQUEST).json({ success: false, error: message }); } /** * 401 Unauthorized — no valid credentials. */ function unauthorized(res, message = 'Unauthorized') { return res.status(HTTP_STATUS.UNAUTHORIZED).json({ success: false, error: message }); } /** * 403 Forbidden — credentials valid but permission denied. */ function forbidden(res, message = 'Forbidden') { return res.status(HTTP_STATUS.FORBIDDEN).json({ success: false, error: message }); } /** * 404 Not Found — resource doesn't exist. */ function notFound(res, message = 'Not found') { return res.status(HTTP_STATUS.NOT_FOUND).json({ success: false, error: message }); } /** * 409 Conflict — request conflicts with current state (e.g. duplicate). */ function conflict(res, message) { return res.status(HTTP_STATUS.CONFLICT).json({ success: false, error: message }); } module.exports = { // Success helpers ok, success, successMessage, created, noContent, // Error helpers errorResponse, error, validationError, unauthorized, forbidden, notFound, conflict, };