Skip to content

Conversation

@AnujChhikara
Copy link
Contributor

@AnujChhikara AnujChhikara commented Jan 29, 2026

Date: 30 Jan 2026

Developer Name: @AnujChhikara


Issue Ticket Number

Tech Doc Link

Business Doc Link

Description

  • added the endpoint to update the application , its validator and update functionality
  • added check that user can only update in 24 hours once

Documentation Updated?

  • Yes
  • No

Under Feature Flag

  • Yes
  • No

Database Changes

  • Yes
  • No

Breaking Changes

  • Yes
  • No

Development Tested?

  • Yes
  • No

Screenshots

Screenshot 1

Test Coverage

Screenshot 1

Additional Notes

…ror handling

- Added a new error message for editing applications too soon.
- Implemented a function to build the update payload for applications.
- Updated the application update logic to include user authorization and time-based restrictions.
- Refactored the application validator to include comprehensive validation for update data.
- Adjusted routes to use the new validation function for application updates.
@AnujChhikara AnujChhikara self-assigned this Jan 29, 2026
@coderabbitai
Copy link

coderabbitai bot commented Jan 29, 2026

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

  • 🔍 Trigger a full review

Walkthrough

This PR implements a 24-hour edit cooldown mechanism for application updates. It introduces an error constant, refactors the update controller with a payload builder, expands validation to distinguish between update and feedback scenarios, adds authorization and cooldown checks in the model layer, and introduces a new PATCH route for applications while updating tests to match the revised signatures.

Changes

Cohort / File(s) Summary
Error Messaging
constants/application.ts
Added EDIT_TOO_SOON constant with "You can edit your application again after 24 hours from the last edit." message.
Controller Refactoring
controllers/applications.ts
Introduced buildApplicationUpdatePayload helper to normalize request data. Refactored updateApplication to accept a payload, use transactional result handling with switch-based status handling (notFound, unauthorized, tooSoon, success, default), and perform logging only on successful updates.
Validation & Routing
middlewares/validators/application.ts, routes/applications.ts
Added new validateApplicationFeedbackData validator with custom Joi schema. Expanded validateApplicationUpdateData with comprehensive nested field validation (professional, socialLink, etc.). Added PATCH /:applicationId route with update validation. Updated feedback route to use new feedback validator.
Model Update Logic
models/applications.ts
Modified updateApplication signature to include userId parameter. Implemented transactional update with validation: checks application existence, authorizes via userId comparison, validates 24-hour cooldown against lastEditAt, returns status object (notFound, unauthorized, tooSoon, or success).
Test Updates
test/integration/application.test.ts, test/unit/middlewares/application-validator.test.ts, test/unit/models/application.test.ts
Updated updateApplication calls to pass third userId parameter. Replaced validateApplicationUpdateData with validateApplicationFeedbackData in validator unit tests. Removed legacy updateApplication test case from model tests.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Controller
    participant Validator
    participant Model
    participant Firestore

    Client->>Controller: PATCH /applications/:id<br/>(update data, auth)
    Controller->>Validator: validateApplicationUpdateData()
    Validator-->>Controller: ✓ valid | ✗ 400
    Controller->>Controller: buildApplicationUpdatePayload()
    Controller->>Model: updateApplication(payload, id, userId)
    
    rect rgba(100, 150, 200, 0.5)
        Note over Model: Transaction Start
        Model->>Firestore: Get application by id
        alt Application not found
            Model-->>Controller: {status: notFound}
        else userId mismatch
            Model-->>Controller: {status: unauthorized}
        else lastEditAt < 24 hours ago
            Model-->>Controller: {status: tooSoon}
        else Valid
            Model->>Firestore: Update data + lastEditAt
            Model-->>Controller: {status: success}
        end
        Note over Model: Transaction End
    end
    
    Controller->>Firestore: Log update (on success only)
    Controller-->>Client: 200 success | 404/401/409 error
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • iamitprakash
  • MayankBansal12
  • Hariom01010

🐰 A cooldown arrives with a gentle hop,
Twenty-four hours to edit without stop,
Authorization checked, timestamps in place,
Feedback and updates now have their own space!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: improve application update logic and validation' accurately describes the main changes across the PR, which involve refactoring application update logic, adding validation, and implementing time-based edit restrictions.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description check ✅ Passed The pull request description is related to the changeset, describing the addition of application update endpoint, validator, and 24-hour edit restriction functionality.

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


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.

);
router.get("/:applicationId", authenticate, authorizeRoles([SUPERUSER]), applications.getApplicationById);
router.post("/", authenticate, applicationValidator.validateApplicationData, applications.addApplication);
router.patch("/:applicationId", authenticate, applicationValidator.validateApplicationUpdateData, applications.updateApplication);

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.

Copilot Autofix

AI 1 day ago

In general, the problem is fixed by adding a rate-limiting middleware to routes that perform authenticated, potentially expensive operations (like updating an application). In an Express app, this is typically done by using a well-known library such as express-rate-limit and applying a limiter either to all routes in the router or specifically to sensitive routes (e.g., write operations).

For this file, the least intrusive and most effective fix is to:

  1. Import express-rate-limit.
  2. Define a limiter instance tailored for these routes (e.g., a reasonable per-IP cap over a time window).
  3. Insert that limiter as middleware in the relevant routes’ middleware chains.

To cover the flagged route and the other similar, authenticated write routes, we can define a single limiter and apply it to all modifying endpoints (POST /, PATCH /:applicationId, PATCH /:applicationId/feedback, and PATCH /:applicationId/nudge). This preserves existing authentication/authorization/validation and only adds an extra guard against abuse. Concretely in routes/applications.ts, we will:

  • Add const rateLimit = require("express-rate-limit"); near the top.
  • Define a limiter, e.g. const applicationWriteLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 100 }); after the router is created.
  • Update the relevant route definitions to include applicationWriteLimiter before authenticate (or immediately after, either is acceptable as long as it runs early) in the middleware arrays.
Suggested changeset 2
routes/applications.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/routes/applications.ts b/routes/applications.ts
--- a/routes/applications.ts
+++ b/routes/applications.ts
@@ -5,9 +5,15 @@
 const applications = require("../controllers/applications");
 const { authorizeOwnOrSuperUser } = require("../middlewares/authorizeOwnOrSuperUser");
 const applicationValidator = require("../middlewares/validators/application");
+const rateLimit = require("express-rate-limit");
 
 const router = express.Router();
 
+const applicationWriteLimiter = rateLimit({
+  windowMs: 15 * 60 * 1000, // 15 minutes
+  max: 100, // limit each IP to 100 write requests per windowMs
+});
+
 router.get(
   "/",
   authenticate,
@@ -16,15 +19,16 @@
   applications.getAllOrUserApplication
 );
 router.get("/:applicationId", authenticate, authorizeRoles([SUPERUSER]), applications.getApplicationById);
-router.post("/", authenticate, applicationValidator.validateApplicationData, applications.addApplication);
-router.patch("/:applicationId", authenticate, applicationValidator.validateApplicationUpdateData, applications.updateApplication);
+router.post("/", applicationWriteLimiter, authenticate, applicationValidator.validateApplicationData, applications.addApplication);
+router.patch("/:applicationId", applicationWriteLimiter, authenticate, applicationValidator.validateApplicationUpdateData, applications.updateApplication);
 router.patch(
   "/:applicationId/feedback",
+  applicationWriteLimiter,
   authenticate,
   authorizeRoles([SUPERUSER]),
   applicationValidator.validateApplicationFeedbackData,
   applications.submitApplicationFeedback
 );
-router.patch("/:applicationId/nudge", authenticate, applications.nudgeApplication);
+router.patch("/:applicationId/nudge", applicationWriteLimiter, authenticate, applications.nudgeApplication);
 
 module.exports = router;
EOF
@@ -5,9 +5,15 @@
const applications = require("../controllers/applications");
const { authorizeOwnOrSuperUser } = require("../middlewares/authorizeOwnOrSuperUser");
const applicationValidator = require("../middlewares/validators/application");
const rateLimit = require("express-rate-limit");

const router = express.Router();

const applicationWriteLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 write requests per windowMs
});

router.get(
"/",
authenticate,
@@ -16,15 +19,16 @@
applications.getAllOrUserApplication
);
router.get("/:applicationId", authenticate, authorizeRoles([SUPERUSER]), applications.getApplicationById);
router.post("/", authenticate, applicationValidator.validateApplicationData, applications.addApplication);
router.patch("/:applicationId", authenticate, applicationValidator.validateApplicationUpdateData, applications.updateApplication);
router.post("/", applicationWriteLimiter, authenticate, applicationValidator.validateApplicationData, applications.addApplication);
router.patch("/:applicationId", applicationWriteLimiter, authenticate, applicationValidator.validateApplicationUpdateData, applications.updateApplication);
router.patch(
"/:applicationId/feedback",
applicationWriteLimiter,
authenticate,
authorizeRoles([SUPERUSER]),
applicationValidator.validateApplicationFeedbackData,
applications.submitApplicationFeedback
);
router.patch("/:applicationId/nudge", authenticate, applications.nudgeApplication);
router.patch("/:applicationId/nudge", applicationWriteLimiter, authenticate, applications.nudgeApplication);

module.exports = router;
package.json
Outside changed files

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/package.json b/package.json
--- a/package.json
+++ b/package.json
@@ -42,7 +42,8 @@
     "passport-github2": "0.1.12",
     "passport-google-oauth20": "^2.0.0",
     "rate-limiter-flexible": "5.0.3",
-    "winston": "3.13.0"
+    "winston": "3.13.0",
+    "express-rate-limit": "^8.2.1"
   },
   "devDependencies": {
     "@types/chai": "4.3.16",
EOF
@@ -42,7 +42,8 @@
"passport-github2": "0.1.12",
"passport-google-oauth20": "^2.0.0",
"rate-limiter-flexible": "5.0.3",
"winston": "3.13.0"
"winston": "3.13.0",
"express-rate-limit": "^8.2.1"
},
"devDependencies": {
"@types/chai": "4.3.16",
This fix introduces these dependencies
Package Version Security advisories
express-rate-limit (npm) 8.2.1 None
Copilot is powered by AI and may make mistakes. Always verify output.
});
});

describe("updateApplication", function () {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

will refactor and add this test in the test pr of the edit applications

feedback: "some feedback",
};
await applicationValidator.validateApplicationUpdateData(req, res, nextSpy);
await applicationValidator.validateApplicationFeedbackData(req, res, nextSpy);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

need to update these as changes the validator name

};

const validateApplicationUpdateData = async (req: CustomRequest, res: CustomResponse, next: NextFunction) => {
const validateApplicationFeedbackData = async (req: CustomRequest, res: CustomResponse, next: NextFunction) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

update this validator name as need separate validator that valid user update flow and this one will validate the feedback api flow

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (1)
middlewares/validators/application.ts (1)

107-112: Fix misleading log message.

The error log says “recruiter data” but this validator handles application feedback. This will confuse debugging and alerting.

🔧 Suggested fix
-    logger.error(`Error in validating recruiter data: ${error}`);
+    logger.error(`Error in validating application feedback data: ${error}`);
🤖 Fix all issues with AI agents
In `@controllers/applications.ts`:
- Around line 125-133: The updateApplication handler currently calls
ApplicationModel.updateApplication even when
buildApplicationUpdatePayload(rawBody) returns an empty object, causing only
lastEditAt to change and producing misleading success logs; modify the
updateApplication function to check the returned dataToUpdate (from
buildApplicationUpdatePayload) and if it has no own enumerable properties (i.e.,
no updatable fields), respond with a 400/422 error and short-circuit without
calling ApplicationModel.updateApplication or logging success—otherwise proceed
as before using userId and applicationId.

In `@middlewares/validators/application.ts`:
- Around line 141-163: The update schema currently allows an empty object
because no keys are required; modify the schema (the joi.object() assigned to
schema in middlewares/validators/application.ts) to require at least one field
by adding a constraint such as .min(1) (or alternatively
.or('imageUrl','foundFrom','introduction','forFun','funFact','whyRds','numberOfHours','professional','socialLink'))
to the object chain so that empty payloads are rejected while keeping existing
validators like customWordCountValidator, professionalSchema and
socialLinkSchema intact.

In `@routes/applications.ts`:
- Around line 20-26: The two PATCH routes (router.patch("/:applicationId", ...
applications.updateApplication) and router.patch("/:applicationId/feedback", ...
applications.submitApplicationFeedback)) lack rate limiting; import and apply
the project's standard write-rate-limit middleware (symbol name
writeRateLimiter) to both routes and place it after authenticate and before the
validators/handlers so the order becomes: authenticate, writeRateLimiter,
applicationValidator.*, then the controller (applications.updateApplication /
applications.submitApplicationFeedback).

In `@test/integration/application.test.ts`:
- Around line 691-693: The test is mutating lastNudgeAt via
applicationModel.updateApplication which now enforces edit cooldown and touches
lastEditAt, coupling the test to edit logic; change the setup to directly update
the document (bypassing updateApplication) or use a dedicated test helper to
backdate lastNudgeAt so you only modify lastNudgeAt without triggering cooldown
logic—locate the call using applicationModel.updateApplication,
nudgeApplicationId and userId and replace it with a direct DB/document update or
helper that sets lastNudgeAt to twentyFiveHoursAgo while leaving lastEditAt
untouched.

Comment on lines 691 to 693
const twentyFiveHoursAgo = new Date(Date.now() - 25 * 60 * 60 * 1000).toISOString();
applicationModel.updateApplication({ lastNudgeAt: twentyFiveHoursAgo }, nudgeApplicationId).then(() => {
applicationModel.updateApplication({ lastNudgeAt: twentyFiveHoursAgo }, nudgeApplicationId, userId).then(() => {
chai
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Decouple nudge test setup from edit‑cooldown logic.

updateApplication now enforces edit cooldown and updates lastEditAt, so this setup mutation is coupled to edit logic and may become brittle if lastEditAt is present. Prefer a direct document update or a dedicated test helper for backdating lastNudgeAt.

🤖 Prompt for AI Agents
In `@test/integration/application.test.ts` around lines 691 - 693, The test is
mutating lastNudgeAt via applicationModel.updateApplication which now enforces
edit cooldown and touches lastEditAt, coupling the test to edit logic; change
the setup to directly update the document (bypassing updateApplication) or use a
dedicated test helper to backdate lastNudgeAt so you only modify lastNudgeAt
without triggering cooldown logic—locate the call using
applicationModel.updateApplication, nudgeApplicationId and userId and replace it
with a direct DB/document update or helper that sets lastNudgeAt to
twentyFiveHoursAgo while leaving lastEditAt untouched.

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.

2 participants