Skip to content

Standarized the API Response in Dashboard API-Controller#304

Open
Ayush4958 wants to merge 4 commits into
geturbackend:mainfrom
Ayush4958:pr-dashboard-controller
Open

Standarized the API Response in Dashboard API-Controller#304
Ayush4958 wants to merge 4 commits into
geturbackend:mainfrom
Ayush4958:pr-dashboard-controller

Conversation

@Ayush4958

@Ayush4958 Ayush4958 commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

🚀 Pull Request Description

I had added AppError and AppResponse Utility to entire backend's middleware of Dashboard controller which provide a standard way to operate. Migrated raw JSON response with standard utility.

Fixes #237

🛠️ Type of Change

  • 🐛 Bug fix (non-breaking change which fixes an issue)
  • ✨ New feature (non-breaking change which adds functionality)
  • 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • 📝 Documentation update
  • 🎨 UI/UX improvement (Frontend only)
  • ⚙️ Refactor / Chore

🧪 Testing & Validation

Backend Verification:

  • I have run npm test in the backend/ directory and all tests passed.
  • I have verified the API endpoints using Postman/Thunder Client.
  • New unit tests have been added (if applicable).

Frontend Verification:

  • I have run npm run lint in the frontend/ directory.
  • Verified the UI changes on different screen sizes (Responsive).
  • Checked for any console errors in the browser dev tools.

✅ Checklist

  • My code follows the code style of this project.
  • I have performed a self-review of my code.
  • I have commented my code, particularly in hard-to-understand areas.
  • My changes generate no new warnings or errors.
  • I have updated the documentation (README/Docs) accordingly.

Summary by CodeRabbit

  • Refactor
    • Standardized error handling across API endpoints to use centralized middleware and unified response wrapper for consistent status codes and messages.
    • Converted many handlers to forward errors to middleware instead of sending inline error responses.
  • Tests
    • Updated tests to assert middleware-style error propagation (errors passed to next) and aligned expectations with new response patterns.

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@Ayush4958, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 34 minutes and 48 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more credits in the billing tab to continue.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f3f9a67b-92ba-4367-bbaa-957c65704660

📥 Commits

Reviewing files that changed from the base of the PR and between d59b919 and 000aefd.

📒 Files selected for processing (6)
  • apps/dashboard-api/src/__tests__/auth.controller.test.js
  • apps/dashboard-api/src/app.js
  • apps/dashboard-api/src/controllers/admin.metrics.controller.js
  • apps/dashboard-api/src/controllers/auth.controller.js
  • apps/dashboard-api/src/controllers/events.controller.js
  • apps/dashboard-api/src/controllers/release.controller.js
📝 Walkthrough

Walkthrough

Controllers and tests in apps/dashboard-api were refactored to propagate errors via next(new AppError(...)), success responses use ApiResponse(...).send(res), and the global Express error handler was updated to recognize operational AppError instances.

Changes

Centralized Error Handling Migration

Layer / File(s) Summary
Global error handler and operational error detection
apps/dashboard-api/src/app.js
Global error handler now checks err.isOperational && err.statusCode to return operational error status/error/message; non-operational errors log stack and return fixed 500 with "Something went wrong!" and the original message.
Test mocks: AppError, ApiResponse, and next
apps/dashboard-api/src/__tests__/*
Jest mocks updated to export an AppError class and ApiResponse helper; tests initialize/clear a shared next = jest.fn() and now destructure AppError from @urbackend/common.
Auth controller: AppError usage and tests
apps/dashboard-api/src/controllers/auth.controller.js, apps/dashboard-api/src/__tests__/auth.controller.test.js
Auth helpers/handlers now throw or forward AppError and accept next; tests call handlers with next and assert error paths via next(AppError) while preserving success assertions.
User auth controller: AppError and next integration
apps/dashboard-api/src/controllers/userAuth.controller.js, apps/dashboard-api/src/__tests__/userAuth.controller.test.js
All exported user auth handlers now accept next and use next(new AppError(...)) for validation/not-found/auth failures; tests updated to expect AppError propagation via next.
Webhook controller: validation, decryption, and tests
apps/dashboard-api/src/controllers/webhook.controller.js, apps/dashboard-api/src/__tests__/webhook.controller.test.js
Webhook handlers accept next and forward invalid IDs, missing resources, validation and decryption failures via next(new AppError(...)); tests updated to pass next and assert next(AppError) for error cases.
Billing webhook and events controller changes
apps/dashboard-api/src/controllers/billing.controller.js, apps/dashboard-api/src/controllers/events.controller.js, apps/dashboard-api/src/__tests__/billing.controller.test.js
Billing webhook signature validation now uses next(new AppError(401,...)) for missing/invalid signatures; events.track validation and unexpected errors are forwarded via AppError/next; tests updated accordingly.
Metrics and analytics: ApiResponse and next
apps/dashboard-api/src/controllers/admin.metrics.controller.js, apps/dashboard-api/src/controllers/analytics.controller.js
Metrics and analytics endpoints accept next, use ApiResponse(...).send(res) for success responses, and forward validation/unexpected errors via next(new AppError(...))/next(err).
Release controller: next-based errors
apps/dashboard-api/src/controllers/release.controller.js
Release handlers accept next; admin authorization and validation failures now call next(new AppError(...)), and unexpected errors are forwarded via next(err); success payloads use structured JSON.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

GSSOC'26, level:advanced, quality:exceptional, type:refactor, mentor:yash-pouranik, gssoc:approved

Suggested reviewers

  • yash-pouranik
  • Nitin-kumar-yadav1307

Poem

🐰 I hopped through controllers, tidied each nest,

AppError in paw and ApiResponse dressed,
next() now carries the burden of woe,
handlers send tidy success — nice and slow,
A little rabbit nods: tests pass, let's go!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Standarized the API Response in Dashboard API-Controller' is directly related to the main objective of the PR, which standardizes API responses using AppError and ApiResponse utilities across dashboard controllers.
Linked Issues check ✅ Passed The PR addresses issue #237 by implementing standardized error response handling via AppError and ApiResponse utilities across all dashboard controller methods, enforcing consistent JSON structure with success/error/message fields.
Out of Scope Changes check ✅ Passed All changes are scoped to dashboard API controllers and their tests. Error handling was refactored from inline responses to centralized middleware patterns, and response formatting was standardized—all directly aligned with standardizing error response schemas per issue #237.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (9)
apps/dashboard-api/src/controllers/auth.controller.js (1)

237-249: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep the success payloads on the standardized envelope.

This refactor standardized many failure paths, but these success responses still emit mixed shapes like { user: ... }, { message: ... }, and { success: true, user: ... } instead of the required { success, data, message }. That leaves the dashboard API contract inconsistent even though ApiResponse is already available in this controller. As per coding guidelines, All API endpoints must return response format: { success: bool, data: {}, message: "" }.

Also applies to: 311-311, 420-420, 440-440, 469-469, 509-514, 539-553, 584-584, 619-619

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/dashboard-api/src/controllers/auth.controller.js` around lines 237 -
249, The success responses (e.g., sendTokenResponse) currently return
inconsistent shapes; change them to the standardized envelope { success, data,
message } by wrapping the existing payload under data (e.g., data: { user: { ...
} }) and providing an appropriate message (or empty string) and the success
boolean; use the existing ApiResponse helper if available to build the response.
Update sendTokenResponse and the other listed success-return sites (lines
referenced: 311, 420, 440, 469, 509-514, 539-553, 584, 619) to return
res.status(statusCode).json({ success: true, data: <payload>, message:
"<optional message>" }) instead of the current mixed shapes so all endpoints
conform to the { success, data, message } contract.

Source: Coding guidelines

apps/dashboard-api/src/controllers/userAuth.controller.js (3)

168-186: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Scope the 401 remap to jwt.verify only.

The user lookup and model resolution run inside the same inner try, so a schema/DB failure is currently reported as “Invalid or Expired Token”. That masks operational errors and bypasses the centralized 5xx flow.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/dashboard-api/src/controllers/userAuth.controller.js` around lines 168 -
186, The current try-catch wraps jwt.verify and the DB lookup (getConnection,
getCompiledModel, Model.findOne), causing DB/schema failures to be remapped to a
401; separate the JWT verification from the DB work: call jwt.verify inside its
own try/catch and only convert JsonWebTokenError/TokenExpiredError into next(new
AppError(401, "Invalid or Expired Token")), then proceed to call getConnection,
getCompiledModel and Model.findOne outside that JWT-specific catch so any errors
from those (or other unexpected errors) bubble to the normal error path (i.e.,
call next(err) or let the outer catch produce a 5xx AppError) instead of
returning 401.

360-360: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

getAuthCollection is undefined in this module.

There’s no import or local definition for getAuthCollection, so both handlers will throw ReferenceError before reaching MongoDB. Reuse the existing getConnection(...) / getCompiledModel(...) path or import the helper explicitly.

Also applies to: 399-399

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/dashboard-api/src/controllers/userAuth.controller.js` at line 360,
getAuthCollection is referenced but not defined or imported, causing a
ReferenceError; update the handlers to either import the helper that exports
getAuthCollection or replace its usage with the existing connection path (use
getConnection(...) to obtain the MongoDB client/DB and then
getCompiledModel(...) or directly access the collection) so the code uses
defined symbols (getAuthCollection OR getConnection and getCompiledModel) and
removes the undefined reference in the handler code paths.

112-116: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

These success paths still break the controller response contract.

They return raw users, tokens, sessions, or message-only payloads instead of { success, data, message }, so the migration only standardized failures. Clients still have to branch on endpoint-specific shapes. As per coding guidelines, All API endpoints must return response format: { success: bool, data: {}, message: "" }.

Also applies to: 150-150, 183-183, 226-229, 269-269, 307-307, 329-337, 370-370, 406-406, 451-451, 477-477, 512-512, 539-539, 563-563

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/dashboard-api/src/controllers/userAuth.controller.js` around lines 112 -
116, Update all success responses in userAuth.controller.js to conform to the
unified response contract { success: bool, data: {}, message: "" }; for example
replace the current res.status(201).json({ message: "...", token, userId }) with
res.status(201).json({ success: true, data: { token, userId }, message: "User
registered successfully. Please verify your email." }); apply the same pattern
to every success path listed in the comment (lines
~112,150,183,226-229,269,307,329-337,370,406,451,477,512,539,563), preserving
existing HTTP status codes and original message text, and for responses that
previously returned only a message set data: {} while for those returning
user/session/token objects wrap them inside data under descriptive keys (e.g.,
data: { user }, data: { session }) so clients always receive { success, data,
message }.

Source: Coding guidelines

apps/dashboard-api/src/controllers/webhook.controller.js (1)

59-70: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Webhook success responses are still ad hoc.

These handlers now standardize failures, but their success bodies still vary between { data }, { message }, and custom top-level fields. That violates the controller contract and keeps webhook clients on endpoint-specific parsing rules. As per coding guidelines, All API endpoints must return response format: { success: bool, data: {}, message: "" }.

Also applies to: 110-110, 145-156, 211-223, 261-261, 307-315, 415-421

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/dashboard-api/src/controllers/webhook.controller.js` around lines 59 -
70, The success responses in webhook.controller.js are nonstandard; update the
res.status(...).json responses (e.g., the create response using webhook and
other handlers that currently return `{ message }`, `{ data }`, or custom
top-level fields) to the canonical format { success: true, data: { ... },
message: "..." } — for the create handler use { success: true, data: { _id:
webhook._id, projectId: webhook.projectId, name: webhook.name, url: webhook.url,
events: Object.fromEntries(webhook.events || new Map()), enabled:
webhook.enabled, createdAt: webhook.createdAt }, message: "Webhook created" }
and apply the same pattern to the corresponding
update/get/list/delete/enable/disable responses that call res.status(...).json
so every successful response from this controller follows { success, data,
message }.

Source: Coding guidelines

apps/dashboard-api/src/controllers/billing.controller.js (2)

202-244: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Webhook success branches still break the standardized response shape.

These returns omit data, while the controller contract for this repo requires { success, data, message }. Since this PR is standardizing controller responses, the webhook path should not keep the old shape.

Suggested fix
-            return res.json({ success: true, message: 'No subscription payload. Skipped.' });
+            return res.json({ success: true, data: {}, message: 'No subscription payload. Skipped.' });

-            return res.json({ success: true, message: 'No valid developer_id in notes. Skipped.' });
+            return res.json({ success: true, data: {}, message: 'No valid developer_id in notes. Skipped.' });

-            return res.json({ success: true, message: 'Developer not found. Skipped.' });
+            return res.json({ success: true, data: {}, message: 'Developer not found. Skipped.' });

-        res.json({ success: true, message: 'Webhook processed.' });
+        res.json({ success: true, data: {}, message: 'Webhook processed.' });

-        res.json({ success: true, message: 'Internal error. Logged.' });
+        res.json({ success: true, data: {}, message: 'Internal error. Logged.' });

As per coding guidelines, {apps/public-api,apps/dashboard-api}/src/controllers/**/*.js: All API endpoints must return response format: { success: bool, data: {}, message: "" }.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/dashboard-api/src/controllers/billing.controller.js` around lines 202 -
244, The webhook handler in billing.controller.js returns non-standard responses
(missing data) in several early-return and final res.json calls; update every
res.json in this function (including the early returns after payload/developer
checks, the success "Webhook processed." response, and the catch error response)
to follow the contract { success: bool, data: {}, message: string } — e.g., keep
the same success/message semantics but add data: {} (or contextual data when
appropriate), ensuring all branches (payload missing, invalid developer_id,
developer not found, processed, and error) return the standardized shape; locate
these calls by searching for res.json within the Razorpay webhook handler that
references payload, developerId, developer, and developer.save().

Source: Coding guidelines


183-195: ⚠️ Potential issue | 🟠 Major

Guard Razorpay webhook signature comparison and standardize webhook response shape

  • crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature)) throws when buffer lengths differ; the catch handler then returns 200 { success: true, ... }, so malformed x-razorpay-signature gets acknowledged instead of rejected with 401.
  • Webhook early-return/success/catch responses omit the required data: {} field (e.g., res.json({ success: true, message: ... })).
Suggested fix
             const signature = req.headers['x-razorpay-signature'];
             if (!signature) {
                 return next(new AppError(401, 'Missing webhook signature.'));
             }

             const rawBody = req.rawBody || JSON.stringify(req.body);
             const expectedSignature = crypto
                 .createHmac('sha256', webhookSecret)
                 .update(rawBody)
                 .digest('hex');

-            if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature))) {
+            const normalizedSignature = typeof signature === 'string' ? signature.trim() : '';
+            const isInvalidSignature =
+                normalizedSignature.length !== expectedSignature.length ||
+                !/^[a-f0-9]+$/i.test(normalizedSignature) ||
+                !crypto.timingSafeEqual(
+                    Buffer.from(normalizedSignature, 'utf8'),
+                    Buffer.from(expectedSignature, 'utf8'),
+                );
+
+            if (isInvalidSignature) {
                 return next(new AppError(401, 'Invalid webhook signature.'));
             }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/dashboard-api/src/controllers/billing.controller.js` around lines 183 -
195, The webhook handler currently uses crypto.timingSafeEqual on
Buffer.from(signature) and Buffer.from(expectedSignature) which throws if
lengths differ, causing malformed signatures to be acknowledged; update the
comparison in the billing webhook code (the block using signature, rawBody,
expectedSignature and crypto.timingSafeEqual) to first check
Buffer.byteLength(signature) === Buffer.byteLength(expectedSignature) (or
compare Buffer.from(signature).length === Buffer.from(expectedSignature).length)
and only call timingSafeEqual when lengths match, otherwise return next(new
AppError(401, 'Invalid webhook signature.')); also standardize all webhook JSON
responses in this controller (success, error, and catch paths) to include the
required data: {} field (e.g., res.json({ success: true, message: ..., data: {}
}) or the error responses likewise) so every early-return and catch path returns
the same shape.
apps/dashboard-api/src/controllers/analytics.controller.js (1)

12-20: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reuse one owner matcher across these analytics queries.

getGlobalStats explicitly compensates for projects whose owner is stored as either the raw JWT value or an ObjectId, but the follow-up Project.find(...) / distinct(...) calls switch back to a single form. For accounts with legacy owner values, totalProjects can include projects that totalRequests, totalWebhooks, totalUsers, recent activity, and north-star metrics then miss.

Suggested fix
     const user_id = req.user._id;
     const userId = new mongoose.Types.ObjectId(user_id);
+    const ownerMatch = {
+      $or: [
+        { owner: user_id },
+        { owner: userId }
+      ]
+    };

     const [stats, dev] = await Promise.all([
       Project.aggregate([
-        { 
-          $match: { 
-            $or: [
-              { owner: user_id },
-              { owner: userId }
-            ]
-          } 
-        },
+        { $match: ownerMatch },
         {
           $group: {
             _id: null,
@@
-    const projects = await Project.find({ owner: user_id }).select("_id").lean();
+    const projects = await Project.find(ownerMatch).select("_id").lean();

Apply the same ownerMatch to the Project.find(...).distinct("_id") call in getRecentActivity and the Project.find(...) call in getNorthStar.

Also applies to: 42-46, 93-93, 275-276

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/dashboard-api/src/controllers/analytics.controller.js` around lines 12 -
20, The aggregation in getGlobalStats defines an ownerMatch that covers both
string and ObjectId JWT owner values, but subsequent queries (specifically the
Project.find(...).distinct("_id") in getRecentActivity and the Project.find(...)
in getNorthStar) revert to a single-form owner filter; update those calls to
reuse the same ownerMatch object (the one used in getGlobalStats) instead of
hardcoding owner/userId so all analytics use the identical matcher for owner
values. Locate ownerMatch in getGlobalStats and pass or reference it in the
Project.find(...).distinct("_id") call in getRecentActivity and the
Project.find(...) call in getNorthStar (and the similar spots noted around lines
42-46, 93, 275-276) so legacy ObjectId/string owners are consistently handled.
apps/dashboard-api/src/controllers/admin.metrics.controller.js (1)

115-120: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Reject impossible month values.

Line 115 only validates the YYYY-MM shape, so inputs like 2026-00 and 2026-13 still pass and Date.UTC silently rolls them into a different month. Reject anything outside 01-12 before building the cohort bounds.

Suggested fix
     const { month } = req.query; // e.g. "2026-05"
     if (!month || !/^\d{4}-\d{2}$/.test(month)) {
       return next(new AppError(400, 'Provide month as YYYY-MM (e.g. 2026-05)'));
     }

     const [year, mo] = month.split('-').map(Number);
+    if (mo < 1 || mo > 12) {
+      return next(new AppError(400, 'Provide month as YYYY-MM (e.g. 2026-05)'));
+    }
     const cohortStart = new Date(Date.UTC(year, mo - 1, 1));
     const cohortEnd = new Date(Date.UTC(year, mo, 1));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/dashboard-api/src/controllers/admin.metrics.controller.js` around lines
115 - 120, The regex only checks format but allows impossible months like "00"
or "13"; before creating cohortStart use the parsed values from
month.split('-').map(Number) (year and mo) and explicitly validate mo is an
integer between 1 and 12 (inclusive); if not, call next(new AppError(400,
'Provide month as YYYY-MM (e.g. 2026-05)')) to reject it, then continue to
construct cohortStart = new Date(Date.UTC(year, mo - 1, 1)).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/dashboard-api/src/app.js`:
- Around line 159-163: The 500 response currently includes err.message which can
leak internals; update the error handler around the res.status(500).json call to
return a generic message (e.g. "Internal server error" or "Something went
wrong") instead of err.message, and ensure the actual error is logged internally
(using your existing logger or console.error(err)) within the same
error-handling middleware so debugging info is retained server-side but not
exposed to clients.

In `@apps/dashboard-api/src/controllers/admin.metrics.controller.js`:
- Around line 50-52: In the catch block that currently calls next(err) in
admin.metrics.controller.js, import the AppError class and ensure all
non-AppError exceptions are wrapped before forwarding: replace next(err) with
logic like "if (!(err instanceof AppError)) err = new AppError('Internal server
error', 500);" then call next(err); this normalizes Mongoose/Mongo/runtime
errors to AppError so the global handler never exposes raw backend messages
while preserving existing AppError propagation.

In `@apps/dashboard-api/src/controllers/auth.controller.js`:
- Around line 409-412: Check for a missing Developer record before calling
bcrypt.compare: after calling
Developer.findById(req.user._id).select('+password') (the variable dev), if dev
is null return next(new AppError(404, "User not found")) (or an appropriate
AppError) instead of calling bcrypt.compare on dev.password; apply the same null
check and AppError return for the other occurrence noted around the 432-435
block so no raw errors or null dereferences reach the client.
- Around line 607-608: The catch block currently maps every error to a 403
AppError, hiding DB and other failures; change the catch to inspect the thrown
error and only convert JWT verification errors to a 403. Specifically, in the
catch surrounding Developer.findById(...) and sendTokenResponse(...), check
error.name (e.g., 'TokenExpiredError' or 'JsonWebTokenError') or other
JWT-specific indicators and call next(new AppError(403, "Invalid or expired
refresh token")) only for those cases, otherwise call next(err) to let
centralized 5xx handling handle DB/save or other operational errors.

In `@apps/dashboard-api/src/controllers/events.controller.js`:
- Around line 45-46: The catch block currently does next(err) which forwards raw
exceptions to the 500 handler; instead import/use the AppError class and wrap
unexpected errors before calling next: create a new AppError (e.g., new
AppError('Internal server error', 500)) or use AppError.withCause if available,
pass the original err as the cause or log it, then call next(wrappedAppError);
update the catch in the events controller (the catch handling err and next) to
perform this wrapping so Mongo/backend details are not sent to clients.

In `@apps/dashboard-api/src/controllers/release.controller.js`:
- Around line 69-73: The success responses in exports.getAllReleases (and the
other handlers returning raw JSON later in this file) must follow the standard
envelope; change the res.json(releases) success path to res.json({ success:
true, data: releases, message: "" }) and update the other success-returning
handlers (the ones around the later success return at lines 114-117) to the same
shape (success: boolean, data: <payload>, message: string) so all controller
endpoints in this file conform to the { success, data, message } contract.
- Around line 73-75: Replace direct forwarding of raw errors to next(err) in the
release controller catch blocks with normalization: if err is already an
instance of AppError call next(err), otherwise log the original error and
forward a new AppError (e.g., generic 500/Internal Server Error) so no
Mongo/driver messages are sent to clients; update both catch sites (the two
catch blocks in this controller) to perform this check and use AppError before
calling next.

In `@apps/dashboard-api/src/controllers/webhook.controller.js`:
- Around line 39-42: The validation error message extraction uses the outdated
Zod v3 property error.errors; update both validation branches to use Zod v4's
error.issues instead: replace validation.error.errors?.[0]?.message with
validation.error.issues?.[0]?.message in the createWebhook validation block
(where createWebhookSchema.safeParse is used) and likewise in the updateWebhook
validation branch (the other safeParse usage around lines ~183-186) so the
controller pulls the correct message from Zod's ZodError.

---

Outside diff comments:
In `@apps/dashboard-api/src/controllers/admin.metrics.controller.js`:
- Around line 115-120: The regex only checks format but allows impossible months
like "00" or "13"; before creating cohortStart use the parsed values from
month.split('-').map(Number) (year and mo) and explicitly validate mo is an
integer between 1 and 12 (inclusive); if not, call next(new AppError(400,
'Provide month as YYYY-MM (e.g. 2026-05)')) to reject it, then continue to
construct cohortStart = new Date(Date.UTC(year, mo - 1, 1)).

In `@apps/dashboard-api/src/controllers/analytics.controller.js`:
- Around line 12-20: The aggregation in getGlobalStats defines an ownerMatch
that covers both string and ObjectId JWT owner values, but subsequent queries
(specifically the Project.find(...).distinct("_id") in getRecentActivity and the
Project.find(...) in getNorthStar) revert to a single-form owner filter; update
those calls to reuse the same ownerMatch object (the one used in getGlobalStats)
instead of hardcoding owner/userId so all analytics use the identical matcher
for owner values. Locate ownerMatch in getGlobalStats and pass or reference it
in the Project.find(...).distinct("_id") call in getRecentActivity and the
Project.find(...) call in getNorthStar (and the similar spots noted around lines
42-46, 93, 275-276) so legacy ObjectId/string owners are consistently handled.

In `@apps/dashboard-api/src/controllers/auth.controller.js`:
- Around line 237-249: The success responses (e.g., sendTokenResponse) currently
return inconsistent shapes; change them to the standardized envelope { success,
data, message } by wrapping the existing payload under data (e.g., data: { user:
{ ... } }) and providing an appropriate message (or empty string) and the
success boolean; use the existing ApiResponse helper if available to build the
response. Update sendTokenResponse and the other listed success-return sites
(lines referenced: 311, 420, 440, 469, 509-514, 539-553, 584, 619) to return
res.status(statusCode).json({ success: true, data: <payload>, message:
"<optional message>" }) instead of the current mixed shapes so all endpoints
conform to the { success, data, message } contract.

In `@apps/dashboard-api/src/controllers/billing.controller.js`:
- Around line 202-244: The webhook handler in billing.controller.js returns
non-standard responses (missing data) in several early-return and final res.json
calls; update every res.json in this function (including the early returns after
payload/developer checks, the success "Webhook processed." response, and the
catch error response) to follow the contract { success: bool, data: {}, message:
string } — e.g., keep the same success/message semantics but add data: {} (or
contextual data when appropriate), ensuring all branches (payload missing,
invalid developer_id, developer not found, processed, and error) return the
standardized shape; locate these calls by searching for res.json within the
Razorpay webhook handler that references payload, developerId, developer, and
developer.save().
- Around line 183-195: The webhook handler currently uses crypto.timingSafeEqual
on Buffer.from(signature) and Buffer.from(expectedSignature) which throws if
lengths differ, causing malformed signatures to be acknowledged; update the
comparison in the billing webhook code (the block using signature, rawBody,
expectedSignature and crypto.timingSafeEqual) to first check
Buffer.byteLength(signature) === Buffer.byteLength(expectedSignature) (or
compare Buffer.from(signature).length === Buffer.from(expectedSignature).length)
and only call timingSafeEqual when lengths match, otherwise return next(new
AppError(401, 'Invalid webhook signature.')); also standardize all webhook JSON
responses in this controller (success, error, and catch paths) to include the
required data: {} field (e.g., res.json({ success: true, message: ..., data: {}
}) or the error responses likewise) so every early-return and catch path returns
the same shape.

In `@apps/dashboard-api/src/controllers/userAuth.controller.js`:
- Around line 168-186: The current try-catch wraps jwt.verify and the DB lookup
(getConnection, getCompiledModel, Model.findOne), causing DB/schema failures to
be remapped to a 401; separate the JWT verification from the DB work: call
jwt.verify inside its own try/catch and only convert
JsonWebTokenError/TokenExpiredError into next(new AppError(401, "Invalid or
Expired Token")), then proceed to call getConnection, getCompiledModel and
Model.findOne outside that JWT-specific catch so any errors from those (or other
unexpected errors) bubble to the normal error path (i.e., call next(err) or let
the outer catch produce a 5xx AppError) instead of returning 401.
- Line 360: getAuthCollection is referenced but not defined or imported, causing
a ReferenceError; update the handlers to either import the helper that exports
getAuthCollection or replace its usage with the existing connection path (use
getConnection(...) to obtain the MongoDB client/DB and then
getCompiledModel(...) or directly access the collection) so the code uses
defined symbols (getAuthCollection OR getConnection and getCompiledModel) and
removes the undefined reference in the handler code paths.
- Around line 112-116: Update all success responses in userAuth.controller.js to
conform to the unified response contract { success: bool, data: {}, message: ""
}; for example replace the current res.status(201).json({ message: "...", token,
userId }) with res.status(201).json({ success: true, data: { token, userId },
message: "User registered successfully. Please verify your email." }); apply the
same pattern to every success path listed in the comment (lines
~112,150,183,226-229,269,307,329-337,370,406,451,477,512,539,563), preserving
existing HTTP status codes and original message text, and for responses that
previously returned only a message set data: {} while for those returning
user/session/token objects wrap them inside data under descriptive keys (e.g.,
data: { user }, data: { session }) so clients always receive { success, data,
message }.

In `@apps/dashboard-api/src/controllers/webhook.controller.js`:
- Around line 59-70: The success responses in webhook.controller.js are
nonstandard; update the res.status(...).json responses (e.g., the create
response using webhook and other handlers that currently return `{ message }`,
`{ data }`, or custom top-level fields) to the canonical format { success: true,
data: { ... }, message: "..." } — for the create handler use { success: true,
data: { _id: webhook._id, projectId: webhook.projectId, name: webhook.name, url:
webhook.url, events: Object.fromEntries(webhook.events || new Map()), enabled:
webhook.enabled, createdAt: webhook.createdAt }, message: "Webhook created" }
and apply the same pattern to the corresponding
update/get/list/delete/enable/disable responses that call res.status(...).json
so every successful response from this controller follows { success, data,
message }.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4f6b699d-2161-41c2-a51f-2324c7980fc8

📥 Commits

Reviewing files that changed from the base of the PR and between 2d42729 and d5cc0a0.

📒 Files selected for processing (13)
  • apps/dashboard-api/src/__tests__/auth.controller.test.js
  • apps/dashboard-api/src/__tests__/billing.controller.test.js
  • apps/dashboard-api/src/__tests__/userAuth.controller.test.js
  • apps/dashboard-api/src/__tests__/webhook.controller.test.js
  • apps/dashboard-api/src/app.js
  • apps/dashboard-api/src/controllers/admin.metrics.controller.js
  • apps/dashboard-api/src/controllers/analytics.controller.js
  • apps/dashboard-api/src/controllers/auth.controller.js
  • apps/dashboard-api/src/controllers/billing.controller.js
  • apps/dashboard-api/src/controllers/events.controller.js
  • apps/dashboard-api/src/controllers/release.controller.js
  • apps/dashboard-api/src/controllers/userAuth.controller.js
  • apps/dashboard-api/src/controllers/webhook.controller.js

Comment thread apps/dashboard-api/src/app.js
Comment thread apps/dashboard-api/src/controllers/admin.metrics.controller.js Outdated
Comment thread apps/dashboard-api/src/controllers/auth.controller.js
Comment thread apps/dashboard-api/src/controllers/auth.controller.js Outdated
Comment thread apps/dashboard-api/src/controllers/events.controller.js Outdated
Comment thread apps/dashboard-api/src/controllers/release.controller.js
Comment thread apps/dashboard-api/src/controllers/release.controller.js
Comment thread apps/dashboard-api/src/controllers/webhook.controller.js

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
apps/dashboard-api/src/controllers/release.controller.js (1)

72-72: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Wrap list results in an object to keep the response contract stable.

Line 72 returns data: releases (array), while the controller contract specifies data as an object-shaped envelope. Prefer data: { releases } for consistency across endpoints and clients.
As per coding guidelines, {apps/public-api,apps/dashboard-api}/src/controllers/**/*.js: All API endpoints must return response format: { success: bool, data: {}, message: "" }.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/dashboard-api/src/controllers/release.controller.js` at line 72, The
response currently sends data: releases (an array) which violates the API
contract expecting data to be an object envelope; change the res.json call in
the release controller (the place that invokes res.json with success:true and
data: releases) to wrap the list as an object, e.g. data: { releases }, so the
response shape becomes { success: bool, data: {}, message: "" } and remains
consistent with other endpoints.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/dashboard-api/src/controllers/events.controller.js`:
- Around line 46-48: The catch block currently reassigns the caught parameter
`err` (e.g., `if (!(err instanceof AppError)) err = new AppError(...)`);
instead, create a new constant like `forwardedError` (or similar) that is set to
`err` if it's an AppError or to a new `AppError(500, 'Internal server error')`
otherwise, and pass that `forwardedError` to `next(forwardedError)` without
mutating the original `err` parameter; update the error-normalization logic in
the controller (the catch block that references `err` and `AppError`)
accordingly.

---

Duplicate comments:
In `@apps/dashboard-api/src/controllers/release.controller.js`:
- Line 72: The response currently sends data: releases (an array) which violates
the API contract expecting data to be an object envelope; change the res.json
call in the release controller (the place that invokes res.json with
success:true and data: releases) to wrap the list as an object, e.g. data: {
releases }, so the response shape becomes { success: bool, data: {}, message: ""
} and remains consistent with other endpoints.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 29a4b37f-e4c5-4f11-8cb6-0529e738d362

📥 Commits

Reviewing files that changed from the base of the PR and between d5cc0a0 and d59b919.

📒 Files selected for processing (3)
  • apps/dashboard-api/src/controllers/events.controller.js
  • apps/dashboard-api/src/controllers/release.controller.js
  • apps/dashboard-api/src/controllers/webhook.controller.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/dashboard-api/src/controllers/webhook.controller.js

Comment thread apps/dashboard-api/src/controllers/events.controller.js Outdated
@Ayush4958

Copy link
Copy Markdown
Contributor Author

@yash-pouranik ,
This PR is ready for review

@yash-pouranik

Copy link
Copy Markdown
Collaborator

yh lets see

@yash-pouranik

Copy link
Copy Markdown
Collaborator

Everything seems good to me
PLease verify @coderabbitai

@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the global error handler in app.js
cat apps/dashboard-api/src/app.js

# Check the AppError and ApiResponse utilities from common
fd -t f "AppError\|ApiResponse\|index" --full-path apps/dashboard-api/node_modules/@urbackend/common/src 2>/dev/null | head -20
fd -t f "index.js" --full-path apps/dashboard-api/node_modules/@urbackend/common 2>/dev/null | head -5

echo "=== Common package exports ==="
cat packages/common/src/index.js 2>/dev/null || cat packages/common/index.js 2>/dev/null || fd -t f "index" --full-path packages/common | head -5 | xargs cat 2>/dev/null

echo "=== AppError ==="
fd -t f "AppError" | head -5 | xargs cat 2>/dev/null

echo "=== ApiResponse ==="
fd -t f "ApiResponse" | head -5 | xargs cat 2>/dev/null

Length of output: 18530


🏁 Script executed:

#!/bin/bash
echo "=== auth.controller.js ==="
cat apps/dashboard-api/src/controllers/auth.controller.js

echo "=== events.controller.js ==="
cat apps/dashboard-api/src/controllers/events.controller.js

echo "=== release.controller.js ==="
cat apps/dashboard-api/src/controllers/release.controller.js

Length of output: 27447


🏁 Script executed:

#!/bin/bash
echo "=== webhook.controller.js ==="
cat apps/dashboard-api/src/controllers/webhook.controller.js

echo "=== billing.controller.js ==="
cat apps/dashboard-api/src/controllers/billing.controller.js

echo "=== admin.metrics.controller.js ==="
cat apps/dashboard-api/src/controllers/admin.metrics.controller.js

Length of output: 33247


🏁 Script executed:

#!/bin/bash
echo "=== analytics.controller.js ==="
cat apps/dashboard-api/src/controllers/analytics.controller.js

echo "=== userAuth.controller.js ==="
cat apps/dashboard-api/src/controllers/userAuth.controller.js

Length of output: 30362


🏁 Script executed:

#!/bin/bash
echo "=== Test: auth.controller.test.js ==="
cat apps/dashboard-api/src/__tests__/auth.controller.test.js

echo "=== Test: billing.controller.test.js ==="
cat apps/dashboard-api/src/__tests__/billing.controller.test.js

echo "=== Test: webhook.controller.test.js ==="
cat apps/dashboard-api/src/__tests__/webhook.controller.test.js

echo "=== Test: userAuth.controller.test.js ==="
cat apps/dashboard-api/src/__tests__/userAuth.controller.test.js

Length of output: 60765


🏁 Script executed:

#!/bin/bash
# Check for any remaining raw res.status().json() patterns in controllers that should have been migrated
echo "=== Checking for remaining raw error responses in controllers ==="
rg -n 'res\.status\s*\(\s*[45]\d\d\s*\)\s*\.json' apps/dashboard-api/src/controllers/ -A1

echo ""
echo "=== Checking for unhandled console.error (should use next) ==="
rg -n 'console\.error' apps/dashboard-api/src/controllers/

echo ""
echo "=== Checking for controllers NOT accepting next ==="
rg -n 'module\.exports\.\w+ = async \(req, res\)' apps/dashboard-api/src/controllers/

echo ""
echo "=== Checking routes to ensure they still map correctly ==="
fd -t f "route\|router" --full-path apps/dashboard-api/src/routes 2>/dev/null | head -10 | xargs cat 2>/dev/null
fd -t f "\.js$" apps/dashboard-api/src/routes 2>/dev/null | xargs cat 2>/dev/null

Length of output: 47270



@yash-pouranik Here's my full verification of the PR:


✅ What's Done Well

The core standardization goal is achieved across all targeted files:

Controller AppError ApiResponse Tests Updated
auth.controller.js
userAuth.controller.js
webhook.controller.js
billing.controller.js
events.controller.js
release.controller.js
analytics.controller.js
admin.metrics.controller.js

The global error handler in app.js correctly differentiates operational AppErrors from unhandled crashes and returns a consistent { success, error, message } shape. Routes are wired correctly.


⚠️ Issues Found

1. Dead code in billing.controller.jscreateCheckout (minor)

The Beta toggle does an early return next(new AppError(403, ...)), making the entire subsequent try/catch block unreachable dead code. It should either be removed or commented out clearly:

// BETA TOGGLE: payments disabled
return next(new AppError(403, 'Automatic payments are disabled during Public Beta...'));

// Everything below is unreachable ↓
try { ... } catch (err) { ... }

2. getAuthCollection is not defined (pre-existing bug, not introduced by this PR)

In userAuth.controller.js, two handlers reference an undefined function:

  • resetPasswordUser — Line 274: const collection = await getAuthCollection(project);
  • updateProfile — Line 302: const collection = await getAuthCollection(project);

These will throw a ReferenceError at runtime. While this is pre-existing, it should be tracked.

3. project.controller.js is NOT migrated (out of scope for this PR)

This is the largest controller in the app and still has ~60+ raw res.status(4xx/5xx).json(...) responses and no next parameter on any of its exported handlers. This will need a follow-up PR.

4. Inconsistent console.error in billing.controller.js

Several handlers (getProRequests, approveProRequest, etc.) still call console.error before delegating to next(AppError). Since the global error handler already logs unhandled errors via console.error("🔥 Unhandled Server Error:", err.stack), this creates duplicate log noise. Operational errors (4xx) should not be logged at the controller level.

5. Unconventional catch-block style in admin.metrics.controller.js

The inline reassignment pattern is slightly unusual:

// Current (works but unconventional)
} catch (err) { if (!(err instanceof AppError)) err = new AppError(500, "Internal server error");
    next(err);
}

// Cleaner alternative
} catch (err) {
    next(err instanceof AppError ? err : new AppError(500, "Internal server error"));
}

📋 Recommendation

The PR is functionally sound for its stated scope. I'd suggest opening a follow-up issue to track:

  1. Migrating project.controller.js
  2. Fixing the undefined getAuthCollection calls

Would you like me to open a follow-up GitHub issue for these items, @yash-pouranik?

@yash-pouranik

Copy link
Copy Markdown
Collaborator

some last checks
@Ayush4958

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Standardize Error Response Schemas Across All Middlewares

2 participants