Skip to content

fix(requests): enforce the quota when editing a request - #3378

Merged
gauthier-th merged 4 commits into
developfrom
fix/put-quota-check
Sep 23, 2026
Merged

gauthier-th merged 4 commits into
developfrom
fix/put-quota-check

Conversation

@fallenbagel

@fallenbagel fallenbagel commented Aug 12, 2026 •

Copy link
Copy Markdown
Member

Description

Editing a request never checked the requester's quota at all. A user could add seasons beyond their limit on a request that already existed, which is #633, and an admin could move a request onto a user with no room for it.

An edit is charged for every season getQuota did not already count against the owner. Reassignment pays in full because none of the request's seasons count towards the new owner yet, a declined request pays in full because declined requests are excluded from the count, and a request older than the quota window pays in full because the window filters on when the request was created. An ordinary edit inside the window pays only the difference. Requests created with the quota bypass keep it, and users whose quota is unlimited are unaffected.

The handler serializes on the request id from the url so both parties to a reassignment queue behind each other, then takes the owner lock from the PR below around the quota check and save. The prefix on that first key is load bearing, since a request id and a user id would otherwise stringify to the same key on the same lock instance.

How Has This Been Tested?

  • Via the attached unit test only

Screenshots / Logs (if applicable)

Checklist:

  • I have read and followed the contribution guidelines.
  • Disclosed any use of AI (see our policy)
  • I have updated the documentation accordingly.
  • All new and existing tests passed.
  • Successful build pnpm build
  • Translation keys pnpm i18n:extract
  • Database migration (if required)

Summary by CodeRabbit

Bug Fixes

  • Prevented conflicting simultaneous request updates, deletions, approvals, and retries.
  • Enforced movie and TV quotas when requests are reassigned.
  • Recalculated TV season usage accurately when changing requested seasons or ownership.
  • Preserved support for unlimited quotas and authorized quota bypasses.
  • Prevented declined or reassigned requests from incorrectly affecting quota usage.
  • Improved request status transitions and retry error handling.
  • Prevented duplicate media-server actions during concurrent retries.
  • Kept request details and selected seasons accurate after updates.
  • Restored orphaned media and TV season statuses when requests are deleted.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

The saved review history does not include the base for the last reviewed commit. This saved history cannot establish the base for an incremental review. Comment @coderabbitai full review to establish a new review baseline. No full review was started, and the last reviewed checkpoint was preserved.

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

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: 1664e401-0485-4c37-aee4-a5ec8383b499

📥 Commits

Reviewing files that changed from the base of the PR and between 5ae8b52 and 9d79f82.

📒 Files selected for processing (3)
  • server/entity/MediaRequest.ts
  • server/routes/request.test.ts
  • server/routes/request.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

The request handlers now serialize updates, deletion, retries, and status changes with request locks. PUT updates enforce movie and TV quotas during edits and reassignment. Tests cover quota rules, concurrency, override services, and TV season restoration.

Changes

Request update enforcement

Layer / File(s) Summary
Locked updates and quota accounting
server/entity/MediaRequest.ts, server/routes/request.ts
Request locks use the authorized user identity. PUT reloads requests inside locks, tracks ownership changes, and enforces movie and TV quotas before saving.
Serialized lifecycle actions
server/routes/request.ts
Delete, retry, and approve or decline operations perform lookup, validation, mutation, and response handling inside request locks.
Quota, ownership, and concurrency coverage
server/routes/request.test.ts
Tests cover quota limits, reassignment, concurrent deletes and status changes, retry dispatch, override services, and TV season restoration.

Estimated code review effort: 4 (Complex) | ~45 minutes

Suggested reviewers: gauthier-th

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant RequestRoute
  participant RequestLock
  participant RequestStore
  participant RadarrSonarr
  Caller->>RequestRoute: Update or retry request
  RequestRoute->>RequestLock: Acquire request lock
  RequestLock->>RequestStore: Reload and validate request
  RequestStore-->>RequestRoute: Current request state
  RequestRoute->>RequestStore: Save authorized mutation
  RequestRoute->>RadarrSonarr: Dispatch approved retry
  RequestRoute-->>Caller: Return updated request
Loading

Merge Risk: ⚪ Minimal · up to 9d79f

Request edits now enforce quotas safely, while concurrent lifecycle actions are serialized so stale updates do not overwrite one another. The covered request and quota changes are ready to merge.

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The quota enforcement changes are in scope for issue #633. However, request creation serialization and concurrency handling for approve, decline, retry, and delete operations are broader than the link… Remove the unrelated request-lifecycle concurrency changes, or link separate issues that justify request creation, approval, decline, retry, and delete serialization. Keep the quota enforcement and edit-request locking changes in this pull …
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the primary change: enforcing request quotas during request edits.
Linked Issues check ✅ Passed The changes address issue #633 by enforcing season quotas during request edits, including pending requests. The added quota, reassignment, and edit-path coverage supports the linked issue requirements…
Full details: Out of Scope Changes check

Explanation

The quota enforcement changes are in scope for issue #633. However, request creation serialization and concurrency handling for approve, decline, retry, and delete operations are broader than the linked issue and are not required to fix the reported quota bypass.

Resolution

Remove the unrelated request-lifecycle concurrency changes, or link separate issues that justify request creation, approval, decline, retry, and delete serialization. Keep the quota enforcement and edit-request locking changes in this pull request.


A rabbit locks each request tight,
Seasons stay within their limit right.
Ownership changes follow the rule,
Tests guard every quota tool.
Orphaned seasons regain their state.

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

Copilot AI lite review requested due to automatic review settings August 12, 2026 06:02
@fallenbagel fallenbagel changed the title fix/put quota check fix(requests): enforce the quota when editing a request Aug 12, 2026

Copilot AI 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.

Pull request overview

This PR updates the /request/:requestId PUT handler to enforce quota constraints correctly (including during reassignment) and to serialize quota-sensitive edits via a per-user lock, with accompanying test coverage for quota edge cases.

Changes:

  • Wrap request edits in a user-keyed requestLock.dispatch(...) to prevent concurrent quota/race issues during PUT updates.
  • Add quota enforcement for TV season edits (delta vs full charge on reassignment) and for movie reassignment when the target user is at quota.
  • Refactor and expand request route tests with seed helpers and new quota-focused test cases.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
server/routes/request.ts Adds user-keyed locking and new quota/reassignment checks in the PUT request update flow.
server/routes/request.test.ts Adds seed helpers and introduces quota-specific PUT tests for TV seasons and reassignment scenarios.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread server/routes/request.ts Outdated
Copilot AI review requested due to automatic review settings August 12, 2026 06:32

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

Copilot AI review requested due to automatic review settings August 13, 2026 04:25
coderabbitai[bot]

This comment was marked as resolved.

This comment was marked as low quality.

Copilot AI review requested due to automatic review settings August 13, 2026 04:51

This comment was marked as low quality.

Copilot AI review requested due to automatic review settings August 13, 2026 05:33
@fallenbagel
fallenbagel marked this pull request as ready for review August 13, 2026 05:33
@fallenbagel
fallenbagel requested a review from a team as a code owner August 13, 2026 05:33
M0NsTeRRR
M0NsTeRRR previously approved these changes Sep 3, 2026
Base automatically changed from fix/request-quota-race to develop September 3, 2026 10:22
coderabbitai[bot]

This comment was marked as resolved.

@fallenbagel

Copy link
Copy Markdown
Member Author

Do not merge from this PR. Merge from #3380 once all upstack PRs are approved to avoid dismissal of approvals.

Comment thread server/routes/request.ts Outdated
gauthier-th
gauthier-th previously approved these changes Sep 15, 2026
@github-actions github-actions Bot added the merge conflict Cannot merge due to merge conflicts label Sep 21, 2026
@github-actions

Copy link
Copy Markdown

This pull request has merge conflicts. Please resolve the conflicts so the PR can be successfully reviewed and merged.

Editing a request never checked the requester's quota, so a user could add
seasons past their limit on an existing request and an admin could move a
request onto a user with no room for it. An edit is charged for every season
getQuota did not already count against the owner, so reassignment and a
request older than the quota window pay in full while an ordinary edit pays
the difference.

The handler serializes on the request id from the url so both parties to a
reassignment queue behind each other, then takes the owner lock around the
quota check and save.

Fixes #633
The lock that serializes request creation is keyed on requestBody.userId, which only callers who may
reassign a request are allowed to set, but the check enforcing that runs inside the lock. Any
unauthenticated caller could name a userId and queue on that user's key before being rejected.
Although today, the rejection throws before the callback awaits anything, so the lock is held for a
microtask with no query behind it, the key should still follow the same permission as the field it
comes from, so it is now the caller's own id unless they may set the other one.
Each of these read the request, check its status, then saved the result, with nothing holding the
row across the three steps. Two calls landing together both read the same pre-transition state and
both passed their guard, so an approve and a decline could both return 200, the later save quietly
overwriting the other while both notification hooks fired.
@github-actions github-actions Bot removed the merge conflict Cannot merge due to merge conflicts label Sep 22, 2026
@gauthier-th
gauthier-th merged commit 8f0a977 into develop Sep 23, 2026
15 checks passed
@gauthier-th
gauthier-th deleted the fix/put-quota-check branch September 23, 2026 11:58
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.

series request limit can be bypassed by users

5 participants