diff --git a/dashcaddy-api/src/utilities/error-codes.js b/dashcaddy-api/src/utilities/error-codes.js new file mode 100644 index 0000000..2ddda2c --- /dev/null +++ b/dashcaddy-api/src/utilities/error-codes.js @@ -0,0 +1,141 @@ +/** + * DC-086: Structured error code system for consistent API error responses. + * + * Format: DC-[MODULE]-[NUMBER] + * Modules: AUTH, CONTAINER, SERVICE, DNS, CADDY, CA, BACKUP, CONFIG, + * BILL, HEALTH, NETWORK, SYSTEM, GENERAL + * + * Usage in routes: + * const { ErrorCodes } = require('../src/utilities/error-codes'); + * errorResponse(res, 400, ErrorCodes.CONTAINER.INVALID_ID, 'Container ID has invalid characters'); + * + * Clients can use the machine-readable code for i18n and error-specific handling + * while the human message provides immediate context. + */ + +const ErrorCodes = { + // ── General ── + GENERAL: { + INVALID_INPUT: 'DC-GEN-001', + NOT_FOUND: 'DC-GEN-002', + RATE_LIMITED: 'DC-GEN-003', + INTERNAL: 'DC-GEN-004', + UNAUTHORIZED: 'DC-GEN-005', + FORBIDDEN: 'DC-GEN-006', + CONFLICT: 'DC-GEN-007', + TIMEOUT: 'DC-GEN-008', + }, + + // ── Authentication ── + AUTH: { + NO_SESSION: 'DC-AUTH-001', + INVALID_TOKEN: 'DC-AUTH-002', + SESSION_EXPIRED: 'DC-AUTH-003', + TOTP_REQUIRED: 'DC-AUTH-004', + TOTP_INVALID: 'DC-AUTH-005', + PROVIDER_DISABLED: 'DC-AUTH-006', + INVITE_EXPIRED: 'DC-AUTH-007', + INVITE_INVALID: 'DC-AUTH-008', + KEY_REVOKED: 'DC-AUTH-009', + LAST_ADMIN: 'DC-AUTH-010', + }, + + // ── Containers ── + CONTAINER: { + NOT_FOUND: 'DC-CONT-001', + INVALID_ID: 'DC-CONT-002', + INVALID_NAME: 'DC-CONT-003', + INVALID_IMAGE: 'DC-CONT-004', + ALREADY_RUNNING: 'DC-CONT-005', + ALREADY_STOPPED: 'DC-CONT-006', + START_FAILED: 'DC-CONT-007', + STOP_FAILED: 'DC-CONT-008', + DELETE_FAILED: 'DC-CONT-009', + INVALID_RESOURCES: 'DC-CONT-010', + DOCKER_UNREACHABLE: 'DC-CONT-011', + }, + + // ── Services ── + SERVICE: { + NOT_FOUND: 'DC-SVC-001', + INVALID_ID: 'DC-SVC-002', + INVALID_SUBDOMAIN: 'DC-SVC-003', + INVALID_PORT: 'DC-SVC-004', + DUPLICATE_ID: 'DC-SVC-005', + INVALID_URL: 'DC-SVC-006', + INVALID_PROTOCOL: 'DC-SVC-007', + PORT_IN_USE: 'DC-SVC-008', + DEPENDENCY_CYCLE: 'DC-SVC-009', + }, + + // ── DNS ── + DNS: { + INVALID_RECORD: 'DC-DNS-001', + INVALID_ZONE: 'DC-DNS-002', + PROVIDER_ERROR: 'DC-DNS-003', + PROPAGATION_TIMEOUT: 'DC-DNS-004', + INVALID_CREDENTIALS: 'DC-DNS-005', + }, + + // ── Caddy / Reverse Proxy ── + CADDY: { + ADMIN_UNREACHABLE: 'DC-CAD-001', + CONFIG_INVALID: 'DC-CAD-002', + RELOAD_FAILED: 'DC-CAD-003', + SITE_EXISTS: 'DC-CAD-004', + SITE_NOT_FOUND: 'DC-CAD-005', + }, + + // ── Certificate Authority ── + CA: { + NOT_INITIALIZED: 'DC-CA-001', + INVALID_DOMAIN: 'DC-CA-002', + CERT_NOT_FOUND: 'DC-CA-003', + GENERATION_FAILED: 'DC-CA-004', + INVALID_FORMAT: 'DC-CA-005', + }, + + // ── Backup ── + BACKUP: { + NO_SCHEDULE: 'DC-BAK-001', + BACKUP_FAILED: 'DC-BAK-002', + RESTORE_FAILED: 'DC-BAK-003', + INVALID_CONFIG: 'DC-BAK-004', + }, + + // ── Billing / License ── + BILL: { + CHECKOUT_FAILED: 'DC-BILL-001', + LICENSE_INVALID: 'DC-BILL-002', + LICENSE_EXPIRED: 'DC-BILL-003', + LICENSE_NOT_FOUND: 'DC-BILL-004', + FEATURE_LOCKED: 'DC-BILL-005', + WEBHOOK_INVALID: 'DC-BILL-006', + }, + + // ── Health Monitoring ── + HEALTH: { + CHECK_FAILED: 'DC-HLT-001', + INCIDENT_NOT_FOUND: 'DC-HLT-002', + INVALID_SEVERITY: 'DC-HLT-003', + }, + + // ── Network ── + NETWORK: { + INVALID_IP: 'DC-NET-001', + INVALID_CIDR: 'DC-NET-002', + INVALID_HOSTNAME: 'DC-NET-003', + GATEWAY_TIMEOUT: 'DC-NET-004', + }, + + // ── System / Config ── + SYSTEM: { + CONFIG_INVALID: 'DC-SYS-001', + CONFIG_SAVE_FAILED: 'DC-SYS-002', + STARTUP_FAILED: 'DC-SYS-003', + DATA_DIR_UNSAFE: 'DC-SYS-004', + DISK_FULL: 'DC-SYS-005', + }, +}; + +module.exports = { ErrorCodes }; diff --git a/dashcaddy-api/src/utils/responses.js b/dashcaddy-api/src/utils/responses.js index d980a46..0cf97c5 100644 --- a/dashcaddy-api/src/utils/responses.js +++ b/dashcaddy-api/src/utils/responses.js @@ -59,9 +59,17 @@ function noContent(res) { * @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. */ function errorResponse(res, statusCode, message, extras = {}) { - return res.status(statusCode).json({ success: false, error: message, ...extras }); + 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); } /**