feat: apply partial updates and concurrent guard - #2928
Open
minottic wants to merge 1 commit into
Open
Conversation
There was a problem hiding this comment.
Hey - I've found 4 issues
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="src/policies/policies.v4.controller.ts" line_range="129-130" />
<code_context>
@Param("id") id: string,
@Body() updatePolicyDto: UpdatePolicyV4Dto,
): Promise<Policy | null> {
+ const isMergePatch =
+ request.headers["content-type"] === "application/merge-patch+json";
+ const unmodifiedSince = parseDate(request.headers["if-unmodified-since"]);
+
</code_context>
<issue_to_address>
**issue (bug_risk):** `application/merge-patch+json` requests with a charset parameter, such as `application/merge-patch+json; charset=UTF-8`, are treated as ordinary JSON because the raw content type is compared for exact equality. Null properties are therefore ignored instead of being reset as merge-patch requires.
**Triggers:** When a client includes a charset parameter in the Content-Type header.
**Suggested fix:** Parse the media type before comparing it, or use the framework's normalized content-type value.
```suggestion
const isMergePatch =
request.headers["content-type"]?.split(";")[0].trim() ===
"application/merge-patch+json";
```
</issue_to_address>
### Comment 2
<location path="src/policies/policies.service.ts" line_range="176-183" />
<code_context>
+ setFields.updatedAt = new Date();
- return this.policyModel
+ const queryFilter = withOCCFilter(filter, unmodifiedSince);
+ const updated = await this.policyModel
.findOneAndUpdate(
- filter,
+ queryFilter,
{ $set: setFields },
{ new: true, runValidators: true },
)
.exec();
+
+ if (!updated && unmodifiedSince) {
</code_context>
<issue_to_address>
**issue (bug_risk):** The OCC check uses `$lte` against a timestamp and then writes a new timestamp, so two concurrent updates whose initial `updatedAt` and generated update times fall in the same millisecond both match the filter and succeed. The concurrent guard therefore does not guarantee that one request receives HTTP 412.
**Triggers:** When concurrent updates are processed within the same millisecond.
**Suggested fix:** Use an atomic version field increment, or otherwise ensure every successful update changes the OCC value before another matching update can proceed.
</issue_to_address>
### Comment 3
<location path="src/policies/policies.service.ts" line_range="176" />
<code_context>
+ setFields.updatedAt = new Date();
- return this.policyModel
+ const queryFilter = withOCCFilter(filter, unmodifiedSince);
+ const updated = await this.policyModel
.findOneAndUpdate(
- filter,
+ queryFilter,
{ $set: setFields },
{ new: true, runValidators: true },
)
.exec();
+
+ if (!updated && unmodifiedSince) {
</code_context>
<issue_to_address>
**issue (bug_risk):** An `If-Unmodified-Since` value expressed as an HTTP-date has only second precision, while `updatedAt` is stored with millisecond precision; the `$lte` comparison rejects an unchanged policy whenever its stored timestamp has milliseconds later than the header's rounded-down second.
**Triggers:** When a client sends the standard second-precision HTTP-date form of If-Unmodified-Since.
**Suggested fix:** Compare timestamps at HTTP-date precision, or use a strong version/ETag-based concurrency token instead.
```suggestion
const queryFilter = withOCCFilter(
filter,
unmodifiedSince && new Date(unmodifiedSince.getTime() + 999),
);
```
</issue_to_address>
### Comment 4
<location path="src/policies/policies.service.ts" line_range="168-172" />
<code_context>
+ const flattened = this.flattenToDotPaths(updatePolicyDto);
+ // application/json: a null value means "do not change this field".
+ // application/merge-patch+json: a null value means "reset this field to null".
+ const setFields = Object.fromEntries(
+ Object.entries(flattened).filter(
+ ([, value]) => isMergePatch || value !== null,
+ ),
+ );
+ setFields.updatedBy = username;
+ setFields.updatedAt = new Date();
</code_context>
<issue_to_address>
**issue (bug_risk):** Merge-patch requests that set the required `isPublished` field to `null` are sent as `$set: { isPublished: null }`; Mongoose validation rejects this required field, so the request fails instead of resetting it to null or its default as the endpoint documentation promises.
**Triggers:** When an application/merge-patch+json request contains `isPublished: null`.
**Suggested fix:** Define the supported null/reset behavior for required fields and apply the schema default or reject the operation with a documented client error before issuing the update.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
Member
Author
|
this will need a change after #2929 is merged |
minottic
force-pushed
the
partial_update
branch
2 times, most recently
from
September 3, 2026 12:18
367bb7e to
cf2fb46
Compare
minottic
force-pushed
the
partial_update
branch
from
September 3, 2026 12:42
cf2fb46 to
1ce9d8c
Compare
minottic
force-pushed
the
partial_update
branch
from
September 3, 2026 20:10
1ce9d8c to
1710687
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Add support for merge-patch and unmodified since to policy
Tests included
Documentation
official documentation info
Summary by Sourcery
Enable merge-patch policy updates and protect conditional writes with optimistic concurrency control.
New Features:
Enhancements:
Tests: