From 52dd2ab0ab58c6dbfa0f954975eb85f15c30b555 Mon Sep 17 00:00:00 2001 From: Ayush Date: Thu, 4 Jun 2026 00:44:03 +0530 Subject: [PATCH 1/2] feat(packages): standardize response type and add AppError utility --- packages/common/src/index.js | 2 ++ .../common/src/middleware/checkAuthEnabled.js | 17 ++++-------- .../src/middleware/loadProjectForAdmin.js | 7 ++--- .../src/middleware/standardizeApiResponse.js | 18 +++++++++++-- packages/common/src/middleware/verifyEmail.js | 4 ++- .../common/src/queues/publicEmailQueue.js | 17 +++++++++++- packages/common/src/utils/ApiResponse.js | 26 +++++++++++++++++++ packages/common/src/utils/AppError.js | 3 ++- packages/common/src/utils/emailService.js | 5 +++- 9 files changed, 78 insertions(+), 21 deletions(-) create mode 100644 packages/common/src/utils/ApiResponse.js diff --git a/packages/common/src/index.js b/packages/common/src/index.js index 62ff043b4..f0507f275 100644 --- a/packages/common/src/index.js +++ b/packages/common/src/index.js @@ -114,6 +114,7 @@ const { validateData, validateUpdateData } = require("./utils/validateData"); const sessionManager = require("./utils/session.manager"); const planLimits = require("./utils/planLimits"); const AppError = require("./utils/AppError"); +const ApiResponse = require("./utils/ApiResponse"); const { checkLockout, recordFailedAttempt, clearLockout } = require("./utils/loginLockout"); const { dispatchWebhooks } = require("./utils/webhookDispatcher"); const { getDayKey, getMonthKey, getEndOfMonthTtlSeconds, incrWithTtlAtomic } = require("./utils/usageCounter"); @@ -200,6 +201,7 @@ module.exports = { ...sessionManager, ...planLimits, AppError, + ApiResponse, getPresignedUploadUrl, verifyUploadedFile, PlatformEvent, diff --git a/packages/common/src/middleware/checkAuthEnabled.js b/packages/common/src/middleware/checkAuthEnabled.js index c1d74aa8c..3481fa792 100644 --- a/packages/common/src/middleware/checkAuthEnabled.js +++ b/packages/common/src/middleware/checkAuthEnabled.js @@ -1,31 +1,24 @@ // FUNCTION - CHECK AUTH ENABLED (MIDDLEWARE) +const AppError = require('../utils/AppError'); + module.exports = (req, res, next) => { const project = req.project; if (!project.isAuthEnabled) { - return res.status(403).json({ - error: "Authentication service is disabled", - message: "Please enable Auth in the urBackend dashboard for this project to use this endpoint." - }); + return next(new AppError(403, "Please enable Auth in the urBackend dashboard for this project to use this endpoint.", "Authentication service is disabled")); } const usersCollection = project.collections?.find(c => c.name === 'users'); if (!usersCollection) { - return res.status(403).json({ - error: "User Schema Missing", - message: "Authentication is enabled, but the 'users' collection schema has not been defined. Please create a 'users' collection in the dashboard to define your custom user fields." - }); + return next(new AppError(403, "Authentication is enabled, but the 'users' collection schema has not been defined. Please create a 'users' collection in the dashboard to define your custom user fields.", "User Schema Missing")); } const hasEmail = usersCollection.model.find(f => f.key === 'email' && f.type === 'String' && f.required); const hasPassword = usersCollection.model.find(f => f.key === 'password' && f.type === 'String' && f.required); if (!hasEmail || !hasPassword) { - return res.status(422).json({ - error: "Invalid Users Schema", - message: "The 'users' collection is missing required 'email' and 'password' string fields. Please fix the schema in the dashboard." - }); + return next(new AppError(422, "The 'users' collection is missing required 'email' and 'password' string fields. Please fix the schema in the dashboard.", "Invalid Users Schema")); } req.usersSchema = usersCollection.model; diff --git a/packages/common/src/middleware/loadProjectForAdmin.js b/packages/common/src/middleware/loadProjectForAdmin.js index 514c8afc7..b7ecead9b 100644 --- a/packages/common/src/middleware/loadProjectForAdmin.js +++ b/packages/common/src/middleware/loadProjectForAdmin.js @@ -1,19 +1,20 @@ // FUNCTION - LOAD PROJECT FOR ADMIN (MIDDLEWARE) const Project = require('../models/Project'); +const AppError = require('../utils/AppError'); module.exports = async (req, res, next) => { try { const { projectId } = req.params; - if (!projectId) return res.status(400).json({ error: "Project ID is required" }); + if (!projectId) return next(new AppError(400, "Project ID is required")); const project = await Project.findOne({ _id: projectId, owner: req.user._id }); if (!project) { - return res.status(404).json({ error: "Project not found or access denied" }); + return next(new AppError(404, "Project not found or access denied")); } req.project = project; next(); } catch (err) { - res.status(500).json({ error: err.message }); + next(new AppError(500, err.message, "Internal Server Error")); } }; diff --git a/packages/common/src/middleware/standardizeApiResponse.js b/packages/common/src/middleware/standardizeApiResponse.js index 292bc16ff..3eee129bf 100644 --- a/packages/common/src/middleware/standardizeApiResponse.js +++ b/packages/common/src/middleware/standardizeApiResponse.js @@ -64,10 +64,24 @@ module.exports = (req, res, next) => { } if (code >= 400) { + let errorTitle = 'Error'; + let errorMessage = toErrorMessage(body); + + if (body && typeof body === 'object') { + if (body.error && typeof body.error === 'string') errorTitle = body.error; + if (body.message && typeof body.message === 'string') errorMessage = body.message; + + // If it only had error and no message, use error as message + if (body.error && !body.message) { + errorTitle = 'Error'; + errorMessage = body.error; + } + } + return originalJson({ success: false, - error: toErrorMessage(body), - code, + error: errorTitle, + message: errorMessage }); } diff --git a/packages/common/src/middleware/verifyEmail.js b/packages/common/src/middleware/verifyEmail.js index b98462830..01fb9e69a 100644 --- a/packages/common/src/middleware/verifyEmail.js +++ b/packages/common/src/middleware/verifyEmail.js @@ -1,5 +1,7 @@ +const AppError = require('../utils/AppError'); + module.exports = function (req, res, next) { const { isVerified } = req.user; - if (!isVerified) return res.status(401).json({ error: "Email not verified" }); + if (!isVerified) return next(new AppError(401, "Email not verified")); next(); }; diff --git a/packages/common/src/queues/publicEmailQueue.js b/packages/common/src/queues/publicEmailQueue.js index 496463503..140444377 100644 --- a/packages/common/src/queues/publicEmailQueue.js +++ b/packages/common/src/queues/publicEmailQueue.js @@ -12,6 +12,21 @@ const DECR_IF_EXISTS_SCRIPT = `if redis.call('EXISTS', KEYS[1]) == 1 then return const publicEmailQueue = new Queue('public-email-queue', { connection }); let worker = null; +const resetPublicEmailWorker = async () => { + if (!worker) return; + try { + if (typeof worker.removeAllListeners === 'function') { + worker.removeAllListeners(); + } + if (typeof worker.close === 'function') { + await worker.close(); + } + } catch (_) { + // Best-effort cleanup for test/runtime shutdown paths. + } finally { + worker = null; + } +}; const initPublicEmailWorker = () => { if (worker) return worker; @@ -117,4 +132,4 @@ const initPublicEmailWorker = () => { return worker; }; -module.exports = { publicEmailQueue, initPublicEmailWorker }; +module.exports = { publicEmailQueue, initPublicEmailWorker, resetPublicEmailWorker }; diff --git a/packages/common/src/utils/ApiResponse.js b/packages/common/src/utils/ApiResponse.js new file mode 100644 index 000000000..7cb5cb6ca --- /dev/null +++ b/packages/common/src/utils/ApiResponse.js @@ -0,0 +1,26 @@ +/** + * Standard utility for consistent API success responses across the monorepo. + * Ensures structure: { success: true, data: {}, message: "" } + */ +class ApiResponse { + constructor(data = {}, message = "Success") { + this.data = data; + this.message = message; + this.success = true; + } + + /** + * Sends a standardized success response + * @param {Object} res - Express response object + * @param {number} statusCode - HTTP status code + */ + send(res, statusCode = 200) { + return res.status(statusCode).json({ + success: this.success, + data: this.data, + message: this.message + }); + } +} + +module.exports = ApiResponse; diff --git a/packages/common/src/utils/AppError.js b/packages/common/src/utils/AppError.js index 8fa9f3406..dccd24f96 100644 --- a/packages/common/src/utils/AppError.js +++ b/packages/common/src/utils/AppError.js @@ -3,10 +3,11 @@ * Ensures consistent error structure: { success: false, data: {}, message: "" } */ class AppError extends Error { - constructor(statusCode, message) { + constructor(statusCode, message, error = null) { super(message); this.statusCode = statusCode; this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error'; + this.error = error || (statusCode >= 500 ? "Internal Server Error" : "Error"); this.isOperational = true; Error.captureStackTrace(this, this.constructor); diff --git a/packages/common/src/utils/emailService.js b/packages/common/src/utils/emailService.js index 4ebbc995a..221ccc5a1 100644 --- a/packages/common/src/utils/emailService.js +++ b/packages/common/src/utils/emailService.js @@ -14,7 +14,10 @@ const formatFromAddress = (email_address) => { return FALLBACK_FROM_ADDRESS; } - // simplified the sender formatting logic and removed the regex based parsing to avoid the CodeQL warning + // If the address already includes a custom name format (e.g., "Name ") + if (trimmed.includes('<') && trimmed.endsWith('>')) { + return trimmed; + } return `urBackend <${trimmed}>`; }; From f09d33b619fca40d0da89e72a466d288a7656379 Mon Sep 17 00:00:00 2001 From: Ayush Date: Thu, 4 Jun 2026 13:51:38 +0530 Subject: [PATCH 2/2] fix(packages): address code rabbit review for loadProjectForAdmin and standardizeApiResponse --- packages/common/src/middleware/loadProjectForAdmin.js | 3 ++- packages/common/src/middleware/standardizeApiResponse.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/common/src/middleware/loadProjectForAdmin.js b/packages/common/src/middleware/loadProjectForAdmin.js index b7ecead9b..a9d30a214 100644 --- a/packages/common/src/middleware/loadProjectForAdmin.js +++ b/packages/common/src/middleware/loadProjectForAdmin.js @@ -15,6 +15,7 @@ module.exports = async (req, res, next) => { req.project = project; next(); } catch (err) { - next(new AppError(500, err.message, "Internal Server Error")); + console.error("loadProjectForAdmin Error:", err); + next(new AppError(500, "Internal Server Error")); } }; diff --git a/packages/common/src/middleware/standardizeApiResponse.js b/packages/common/src/middleware/standardizeApiResponse.js index 3eee129bf..7e5755255 100644 --- a/packages/common/src/middleware/standardizeApiResponse.js +++ b/packages/common/src/middleware/standardizeApiResponse.js @@ -74,7 +74,7 @@ module.exports = (req, res, next) => { // If it only had error and no message, use error as message if (body.error && !body.message) { errorTitle = 'Error'; - errorMessage = body.error; + errorMessage = typeof body.error === 'string' ? body.error : safeStringify(body.error); } }