feat(requests): allow admins to bypass user quota limits#2026
feat(requests): allow admins to bypass user quota limits#2026
Conversation
|
This pull request has merge conflicts. Please resolve the conflicts so the PR can be successfully reviewed and merged. |
|
When you have time @0xSysR3ll can you rebase so we can review it ? |
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds an Changes
Sequence DiagramsequenceDiagram
participant User
participant UI as AdvancedRequester
participant Modal as RequestModal
participant API as Backend API
participant DB as Database
participant Quota as Quota Calculator
User->>UI: Toggle ignoreQuota (if visible)
UI->>Modal: onChange(overrides with ignoreQuota)
User->>Modal: Submit request
Modal->>API: POST /api/v1/request (payload includes ignoreQuota)
API->>API: Verify Permission.MANAGE_REQUESTS if ignoreQuota requested
alt bypass allowed
API->>DB: Save MediaRequest(ignoreQuota = true)
API->>Modal: Respond 201 Created
else bypass denied
API-->>Modal: Respond 403 RequestPermissionError
end
Note over Quota,DB: Quota calculations query DB WHERE ignoreQuota = false
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ 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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
src/components/RequestModal/AdvancedRequester/index.tsx (1)
308-319:⚠️ Potential issue | 🟠 MajorInclude quota bypass in the “has advanced options” render check.
If there is only one server/profile/folder and no alternate requester to show, this component returns
nullbefore rendering the new bypass toggle, leaving admins unable to enable the feature.🐛 Proposed fix
+ const canRenderIgnoreQuota = + currentHasPermission([Permission.MANAGE_REQUESTS]) && + !!quota && + ((type === 'movie' && (quota.movie.limit ?? 0) > 0) || + (type === 'tv' && (quota.tv.limit ?? 0) > 0)); + if ( (!data || selectedServer === null || (data.filter((server) => server.is4k === is4k).length < 2 && (!serverData || @@ (serverData.languageProfiles ?? []).length < 2 && !serverData.tags?.length)))) && - (!selectedUser || (filteredUserData ?? []).length < 2) + (!selectedUser || (filteredUserData ?? []).length < 2) && + !canRenderIgnoreQuota ) { return null; } @@ - {currentHasPermission([Permission.MANAGE_REQUESTS]) && - quota && - ((type === 'movie' && quota.movie.limit && quota.movie.limit > 0) || - (type === 'tv' && quota.tv.limit && quota.tv.limit > 0)) && ( + {canRenderIgnoreQuota && ( <div className="mb-2"> <label htmlFor="ignoreQuota"> {intl.formatMessage(messages.ignoreQuotaTitle)} </label>Also applies to: 563-581
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/RequestModal/AdvancedRequester/index.tsx` around lines 308 - 319, The current complex render guard (the condition using data, selectedServer, is4k, serverData, selectedUser, and filteredUserData) returns null when there are no alternate servers/profiles/users and therefore prevents the new quota bypass toggle from rendering; update that guard so it also permits rendering when the quota bypass control should be shown — i.e., add a check for the quota bypass state/permission (the component prop/state that governs the new "quota bypass" toggle, e.g., bypassQuota / canBypassQuota / quotaBypassEnabled) so the component does not return null when that toggle should be visible; apply the same change to the corresponding guard around the other block (the one at the 563-581 region) so the bypass toggle is always reachable even if there is only one server/profile/folder/user.src/components/RequestModal/TvRequestModal.tsx (2)
289-320:⚠️ Potential issue | 🟠 MajorLet quota bypass unlock TV season selection too.
With partial requests enabled and no quota remaining,
toggleSeason/toggleAllSeasonsstill return early even after the admin enables bypass, so no seasons can be selected.🐛 Proposed fix
if ( quota?.tv.limit && currentlyRemaining <= 0 && + !requestOverrides?.ignoreQuota && !isSelectedSeason(seasonNumber) ) { return; } @@ if ( quota?.tv.limit && (quota?.tv.remaining ?? 0) < unrequestedSeasons.length && + !requestOverrides?.ignoreQuota ) { return; } @@ quota?.tv.remaining && quota.tv.limit && + !requestOverrides?.ignoreQuota && quota.tv.remaining < unrequestedSeasons.length ? 'opacity-50' : '' @@ mediaSeason || (quota?.tv.limit && + !requestOverrides?.ignoreQuota && currentlyRemaining <= 0 && !isSelectedSeason(season.seasonNumber)) ||Also applies to: 555-561, 637-646
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/RequestModal/TvRequestModal.tsx` around lines 289 - 320, The early-return quota checks in toggleSeason and toggleAllSeasons block selection even when the admin bypass is enabled; update those conditions to skip the return when the bypass flag is set (e.g. add !settings.currentSettings.quotaBypassEnabled to the guard). Concretely, in toggleSeason (the if with quota?.tv.limit && currentlyRemaining <= 0 && !isSelectedSeason(seasonNumber)) change it to also require the bypass is not enabled before returning, and in toggleAllSeasons (the if with quota?.tv.limit && (quota?.tv.remaining ?? 0) < unrequestedSeasons.length) do the same; apply the same pattern to the other analogous guards mentioned (around lines 555-561 and 637-646) so the admin quota bypass allows season selection.
443-460:⚠️ Potential issue | 🔴 CriticalFix the malformed
okDisabledexpression and compare against remaining quota.Line 454 continues the boolean expression without an operator, which causes a parse failure. While fixing it, use
quota.tv.remaining; comparing againstlimitlets over-quota full-series requests reach the server and fail there.🐛 Proposed fix
okDisabled={ editRequest ? false - : !settings.currentSettings.partialRequestsEnabled && - quota?.tv.limit && - unrequestedSeasons.length > quota.tv.limit && - !requestOverrides?.ignoreQuota - ? true - : getAllRequestedSeasons().length >= getAllSeasons().length || - (settings.currentSettings.partialRequestsEnabled && - selectedSeasons.length === 0) - quota?.tv.limit && - unrequestedSeasons.length > quota.tv.limit - ? true - : getAllRequestedSeasons().length >= getAllSeasons().length || - (settings.currentSettings.partialRequestsEnabled && - selectedSeasons.length === 0) + : getAllRequestedSeasons().length >= getAllSeasons().length || + (settings.currentSettings.partialRequestsEnabled && + selectedSeasons.length === 0) || + (!!quota?.tv.limit && + !requestOverrides?.ignoreQuota && + (settings.currentSettings.partialRequestsEnabled + ? selectedSeasons.length > (quota.tv.remaining ?? 0) + : unrequestedSeasons.length > (quota.tv.remaining ?? 0))) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/RequestModal/TvRequestModal.tsx` around lines 443 - 460, The okDisabled expression in TvRequestModal (the okDisabled prop near editRequest / partialRequestsEnabled) is malformed due to a missing operator and needs to be simplified and corrected to compare against remaining quota; rewrite the boolean so operators and parentheses are correct, replace any uses of quota?.tv.limit with quota?.tv.remaining when checking unrequestedSeasons length, and preserve existing short-circuits (editRequest => false, respect requestOverrides?.ignoreQuota) while keeping the other checks: full-series already requested (getAllRequestedSeasons().length >= getAllSeasons().length) or (partialRequestsEnabled && selectedSeasons.length === 0) should still disable OK.server/entity/User.ts (1)
315-364:⚠️ Potential issue | 🔴 CriticalFix the duplicate TV quota query block.
The code contains a syntax error: two consecutive
awaitstatements without a connecting operator (lines 339–359). Remove the duplicateawait requestRepositoryblock and add theignoreQuotapredicate totvQuotaUsedQuerywhile preserving the conditional date behavior (if (tvQuotaDays)).Proposed fix
const tvQuotaUsedQuery = requestRepository .createQueryBuilder('request') .leftJoin('request.requestedBy', 'requestedBy') .where('request.type = :requestType', { requestType: MediaType.TV, }) .andWhere('requestedBy.id = :userId', { userId: this.id, }) .andWhere('request.status != :declinedStatus', { declinedStatus: MediaRequestStatus.DECLINED, + }) + .andWhere('request.ignoreQuota = :ignoreQuota', { + ignoreQuota: false, }); if (tvQuotaDays) { tvQuotaUsedQuery.andWhere('request.createdAt > :date', { date: tvQuotaStartDate, }); } const tvQuotaUsed = tvQuotaLimit ? ( - await requestRepository - .createQueryBuilder('request') - .leftJoin('request.seasons', 'seasons') - .leftJoin('request.requestedBy', 'requestedBy') - .where('request.type = :requestType', { - requestType: MediaType.TV, - }) - .andWhere('requestedBy.id = :userId', { - userId: this.id, - }) - .andWhere('request.createdAt > :date', { - date: tvQuotaStartDate, - }) - .andWhere('request.status != :declinedStatus', { - declinedStatus: MediaRequestStatus.DECLINED, - }) - .andWhere('request.ignoreQuota = :ignoreQuota', { - ignoreQuota: false, - }) - await tvQuotaUsedQuery + await tvQuotaUsedQuery .addSelect((subQuery) => { return subQuery .select('COUNT(season.id)', 'seasonCount') .from(SeasonRequest, 'season') .leftJoin('season.request', 'parentRequest') .where('parentRequest.id = request.id'); }, 'seasonCount') .getMany()🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@server/entity/User.ts` around lines 315 - 364, There is a duplicated/invalid query block causing a syntax error; remove the extra awaited requestRepository query and instead add the ignoreQuota predicate to the existing tvQuotaUsedQuery, preserving the tvQuotaDays conditional date logic (use tvQuotaStartDate when tvQuotaDays is set) and ensure tvQuotaUsed still computes the sum of seasonCount from the select on tvQuotaUsedQuery (referencing tvQuotaUsedQuery, requestRepository, tvQuotaDays, tvQuotaStartDate, tvQuotaLimit, MediaRequest.seasonCount/seasonCount and the ignoreQuota = false predicate).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@server/entity/MediaRequest.ts`:
- Around line 116-130: The requestBody.ignoreQuota flag is trusted from the
client and must be sanitized: compute an effectiveIgnoreQuota boolean that is
true only if the user actually has the MANAGE_REQUESTS permission
(user.hasPermission([Permission.MANAGE_REQUESTS])) and requestBody.ignoreQuota
is true, otherwise set it to false; use this effectiveIgnoreQuota everywhere (in
the quota-enforcement branch that references canBypassQuota and in any
persistence code that stores ignoreQuota) so regular users cannot bypass quota
by sending ignoreQuota: true and managers cannot bypass when the toggle is off.
---
Outside diff comments:
In `@server/entity/User.ts`:
- Around line 315-364: There is a duplicated/invalid query block causing a
syntax error; remove the extra awaited requestRepository query and instead add
the ignoreQuota predicate to the existing tvQuotaUsedQuery, preserving the
tvQuotaDays conditional date logic (use tvQuotaStartDate when tvQuotaDays is
set) and ensure tvQuotaUsed still computes the sum of seasonCount from the
select on tvQuotaUsedQuery (referencing tvQuotaUsedQuery, requestRepository,
tvQuotaDays, tvQuotaStartDate, tvQuotaLimit,
MediaRequest.seasonCount/seasonCount and the ignoreQuota = false predicate).
In `@src/components/RequestModal/AdvancedRequester/index.tsx`:
- Around line 308-319: The current complex render guard (the condition using
data, selectedServer, is4k, serverData, selectedUser, and filteredUserData)
returns null when there are no alternate servers/profiles/users and therefore
prevents the new quota bypass toggle from rendering; update that guard so it
also permits rendering when the quota bypass control should be shown — i.e., add
a check for the quota bypass state/permission (the component prop/state that
governs the new "quota bypass" toggle, e.g., bypassQuota / canBypassQuota /
quotaBypassEnabled) so the component does not return null when that toggle
should be visible; apply the same change to the corresponding guard around the
other block (the one at the 563-581 region) so the bypass toggle is always
reachable even if there is only one server/profile/folder/user.
In `@src/components/RequestModal/TvRequestModal.tsx`:
- Around line 289-320: The early-return quota checks in toggleSeason and
toggleAllSeasons block selection even when the admin bypass is enabled; update
those conditions to skip the return when the bypass flag is set (e.g. add
!settings.currentSettings.quotaBypassEnabled to the guard). Concretely, in
toggleSeason (the if with quota?.tv.limit && currentlyRemaining <= 0 &&
!isSelectedSeason(seasonNumber)) change it to also require the bypass is not
enabled before returning, and in toggleAllSeasons (the if with quota?.tv.limit
&& (quota?.tv.remaining ?? 0) < unrequestedSeasons.length) do the same; apply
the same pattern to the other analogous guards mentioned (around lines 555-561
and 637-646) so the admin quota bypass allows season selection.
- Around line 443-460: The okDisabled expression in TvRequestModal (the
okDisabled prop near editRequest / partialRequestsEnabled) is malformed due to a
missing operator and needs to be simplified and corrected to compare against
remaining quota; rewrite the boolean so operators and parentheses are correct,
replace any uses of quota?.tv.limit with quota?.tv.remaining when checking
unrequestedSeasons length, and preserve existing short-circuits (editRequest =>
false, respect requestOverrides?.ignoreQuota) while keeping the other checks:
full-series already requested (getAllRequestedSeasons().length >=
getAllSeasons().length) or (partialRequestsEnabled && selectedSeasons.length ===
0) should still disable OK.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: a5d158d0-dd1e-4f06-8ee6-696e4bcddcde
📒 Files selected for processing (10)
seerr-api.ymlserver/entity/MediaRequest.tsserver/entity/User.tsserver/interfaces/api/requestInterfaces.tsserver/migration/postgres/1760028688313-AddIgnoreQuotaToMediaRequest.tsserver/migration/sqlite/1760028688313-AddIgnoreQuotaToMediaRequest.tssrc/components/RequestModal/AdvancedRequester/index.tsxsrc/components/RequestModal/MovieRequestModal.tsxsrc/components/RequestModal/TvRequestModal.tsxsrc/i18n/locale/en.json
gauthier-th
left a comment
There was a problem hiding this comment.
LGTM, but generated migrations are quite old, they should be re-generated before we merge this.
Yep yep, will do. This was just a rebase attempt from GH's web UI :) |
Signed-off-by: 0xsysr3ll <0xsysr3ll@pm.me>
Signed-off-by: 0xsysr3ll <0xsysr3ll@pm.me>
b29889e to
cdb7b1b
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/components/RequestModal/TvRequestModal.tsx (1)
443-454:⚠️ Potential issue | 🟠 MajorBypass still fails when partial requests are enabled.
Line 448 only relaxes quota gating in the non-partial branch. In partial mode,
toggleSeasonandtoggleAllSeasonsstill hard-block selection at quota limits, so admins cannot actually bypass quotas there.Suggested fix
+ const isIgnoringQuota = !!requestOverrides?.ignoreQuota; const toggleSeason = (seasonNumber: number): void => { @@ - if ( - quota?.tv.limit && - currentlyRemaining <= 0 && - !isSelectedSeason(seasonNumber) - ) { + if ( + quota?.tv.limit && + currentlyRemaining <= 0 && + !isSelectedSeason(seasonNumber) && + !isIgnoringQuota + ) { return; } @@ const toggleAllSeasons = (): void => { @@ - if ( - quota?.tv.limit && - (quota?.tv.remaining ?? 0) < unrequestedSeasons.length - ) { + if ( + quota?.tv.limit && + (quota?.tv.remaining ?? 0) < unrequestedSeasons.length && + !isIgnoringQuota + ) { return; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/RequestModal/TvRequestModal.tsx` around lines 443 - 454, The partial-request flow still enforces quota checks in selection functions even when a bypass is requested; update the quota checks to respect the bypass flag. Specifically, in the toggleSeason and toggleAllSeasons handlers add a short-circuit that skips the quota-limit blocking when requestOverrides?.ignoreQuota (or whichever bypass flag is used) is true (and keep existing editRequest behavior). Also adjust the okDisabled logic around partialRequestsEnabled to treat requestOverrides?.ignoreQuota as allowing selections beyond quota so the OK button reflects the bypass; reference toggleSeason, toggleAllSeasons, okDisabled, getAllRequestedSeasons, getAllSeasons and selectedSeasons when making these changes.src/components/RequestModal/AdvancedRequester/index.tsx (1)
308-320:⚠️ Potential issue | 🟡 MinorEarly return can hide the new "Bypass User Quota" toggle.
The bail-out at lines 308–320 only considers server/profile/folder/language/tags counts and the user-switcher list. It does not factor in the new
ignoreQuotatoggle, so an admin viewing a target with a single server, defaulted profiles/folders, and<2filtered users will get anulladvanced section even whenquota.{movie|tv}.limit > 0— meaning the bypass UI is unreachable in exactly the scenario it's most useful (admin requesting on behalf of a user who has hit their quota).Consider adding the bypass-visibility predicate to this guard, e.g.:
♻️ Proposed adjustment
+ const canShowIgnoreQuota = + currentHasPermission([Permission.MANAGE_REQUESTS]) && + !!quota && + ((type === 'movie' && (quota.movie.limit ?? 0) > 0) || + (type === 'tv' && (quota.tv.limit ?? 0) > 0)); + if ( (!data || selectedServer === null || (data.filter((server) => server.is4k === is4k).length < 2 && (!serverData || (serverData.profiles.length < 2 && serverData.rootFolders.length < 2 && (serverData.languageProfiles ?? []).length < 2 && !serverData.tags?.length)))) && - (!selectedUser || (filteredUserData ?? []).length < 2) + (!selectedUser || (filteredUserData ?? []).length < 2) && + !canShowIgnoreQuota ) { return null; }The same
canShowIgnoreQuotavalue can then be reused at line 563 instead of inlining the predicate.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/RequestModal/AdvancedRequester/index.tsx` around lines 308 - 320, Compute a canShowIgnoreQuota boolean that checks whether the current target has a non-zero quota (e.g., quota.movie.limit > 0 || quota.tv.limit > 0) and whether the ignoreQuota toggle should be exposed (use existing ignoreQuota / user/admin context as needed), then include canShowIgnoreQuota in the initial bail-out condition so the advanced section is not returned null when the bypass UI is needed; also replace the duplicated inline predicate at the later usage (around where the bypass toggle is rendered) with the same canShowIgnoreQuota variable so the visibility logic is consistent.
🧹 Nitpick comments (2)
src/components/RequestModal/AdvancedRequester/index.tsx (2)
250-276:ignoreQuotais not re-synced whendefaultOverrideschanges.This effect propagates updates to
defaultOverrides.{server,profile,folder,language,tags}into local state, butdefaultOverrides.ignoreQuotais only consumed in theuseStateinitializer at lines 107–109. If a parent recomputesdefaultOverrides(e.g., after an "edit request" hydration), the toggle will be stuck at its first value. For consistency with the other fields:♻️ Proposed adjustment
if (defaultOverrides && defaultOverrides.tags != null) { setSelectedTags(defaultOverrides.tags); } + + if (defaultOverrides && defaultOverrides.ignoreQuota != null) { + setIgnoreQuota(defaultOverrides.ignoreQuota); + } }, [ defaultOverrides?.server, defaultOverrides?.folder, defaultOverrides?.profile, defaultOverrides?.language, defaultOverrides?.tags, + defaultOverrides?.ignoreQuota, ]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/RequestModal/AdvancedRequester/index.tsx` around lines 250 - 276, The effect that syncs defaultOverrides into local state is missing the ignoreQuota field, so add logic to the useEffect to call setIgnoreQuota(defaultOverrides.ignoreQuota) when defaultOverrides.ignoreQuota is not null/undefined (mirroring the checks used for setSelectedServer/setSelectedProfile/etc.), and include defaultOverrides?.ignoreQuota in the effect dependency array so ignoreQuota is re-synced whenever defaultOverrides changes; this touches the useEffect block and the local state setter setIgnoreQuota (the current useState initializer for ignoreQuota at lines ~107–109).
42-44: Nit: i18n key casing inconsistent with the rest of the file.All sibling keys in this
defineMessagesblock use lowercase (advancedoptions,destinationserver,qualityprofile,requestas,languageprofile,selecttags,notagoptions). The two new keys use camelCase (ignoreQuotaTitle,ignoreQuotaDescription). Worth aligning toignorequotatitle/ignorequotadescription(or whatever convention the project standardizes on) so extracted message IDs stay uniform.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/RequestModal/AdvancedRequester/index.tsx` around lines 42 - 44, The new i18n keys in the defineMessages block use camelCase (ignoreQuotaTitle, ignoreQuotaDescription) while the file's convention is lowercase; rename these keys to match the existing pattern (e.g., ignorequotatitle and ignorequotadescription) in the defineMessages declaration and update any usages of ignoreQuotaTitle/ignoreQuotaDescription to the new lowercase keys so extracted message IDs remain consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@server/migration/sqlite/1777103565110-AddIgnoreQuotaToMediaRequest.ts`:
- Line 89: The down() migration in AddIgnoreQuotaToMediaRequest (method down)
recreates the media_request table without the ignoreQuota column, which will
silently drop ignoreQuota=true values on rollback; no code change is required,
but update the release notes/changelog to explicitly warn that downgrading after
admins use the quota bypass will lose the audit signal indicating which requests
bypassed quota so operators can make an informed decision before rolling back.
- Around line 7-60: This migration's up() and down() include duplicated rebuilds
of user_push_subscription (the DROP INDEX "IDX_03f7958328e311761b0de675fb" →
CREATE TABLE "temporary_user_push_subscription" → INSERT ... FROM
"user_push_subscription" → DROP TABLE "user_push_subscription" → ALTER TABLE ...
RENAME TO "user_push_subscription" → CREATE INDEX sequence) which are unrelated
to the intended change (adding ignoreQuota to media_request and the
temporary_media_request CREATE/INSERT/RENAME sequence); regenerate the migration
from a clean schema (matching develop) so that the up()/down() only modify
media_request (add ignoreQuota boolean and recreate its indexes) and remove all
duplicated temporary_user_push_subscription rebuild blocks, and also regenerate
the Postgres counterpart (the 1777103595541 migration) to confirm no similar
drift.
---
Outside diff comments:
In `@src/components/RequestModal/AdvancedRequester/index.tsx`:
- Around line 308-320: Compute a canShowIgnoreQuota boolean that checks whether
the current target has a non-zero quota (e.g., quota.movie.limit > 0 ||
quota.tv.limit > 0) and whether the ignoreQuota toggle should be exposed (use
existing ignoreQuota / user/admin context as needed), then include
canShowIgnoreQuota in the initial bail-out condition so the advanced section is
not returned null when the bypass UI is needed; also replace the duplicated
inline predicate at the later usage (around where the bypass toggle is rendered)
with the same canShowIgnoreQuota variable so the visibility logic is consistent.
In `@src/components/RequestModal/TvRequestModal.tsx`:
- Around line 443-454: The partial-request flow still enforces quota checks in
selection functions even when a bypass is requested; update the quota checks to
respect the bypass flag. Specifically, in the toggleSeason and toggleAllSeasons
handlers add a short-circuit that skips the quota-limit blocking when
requestOverrides?.ignoreQuota (or whichever bypass flag is used) is true (and
keep existing editRequest behavior). Also adjust the okDisabled logic around
partialRequestsEnabled to treat requestOverrides?.ignoreQuota as allowing
selections beyond quota so the OK button reflects the bypass; reference
toggleSeason, toggleAllSeasons, okDisabled, getAllRequestedSeasons,
getAllSeasons and selectedSeasons when making these changes.
---
Nitpick comments:
In `@src/components/RequestModal/AdvancedRequester/index.tsx`:
- Around line 250-276: The effect that syncs defaultOverrides into local state
is missing the ignoreQuota field, so add logic to the useEffect to call
setIgnoreQuota(defaultOverrides.ignoreQuota) when defaultOverrides.ignoreQuota
is not null/undefined (mirroring the checks used for
setSelectedServer/setSelectedProfile/etc.), and include
defaultOverrides?.ignoreQuota in the effect dependency array so ignoreQuota is
re-synced whenever defaultOverrides changes; this touches the useEffect block
and the local state setter setIgnoreQuota (the current useState initializer for
ignoreQuota at lines ~107–109).
- Around line 42-44: The new i18n keys in the defineMessages block use camelCase
(ignoreQuotaTitle, ignoreQuotaDescription) while the file's convention is
lowercase; rename these keys to match the existing pattern (e.g.,
ignorequotatitle and ignorequotadescription) in the defineMessages declaration
and update any usages of ignoreQuotaTitle/ignoreQuotaDescription to the new
lowercase keys so extracted message IDs remain consistent.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 4cf7fc58-9036-4ed4-b79d-cfe6bddad5db
📒 Files selected for processing (10)
seerr-api.ymlserver/entity/MediaRequest.tsserver/entity/User.tsserver/interfaces/api/requestInterfaces.tsserver/migration/postgres/1777103595541-AddIgnoreQuotaToMediaRequest.tsserver/migration/sqlite/1777103565110-AddIgnoreQuotaToMediaRequest.tssrc/components/RequestModal/AdvancedRequester/index.tsxsrc/components/RequestModal/MovieRequestModal.tsxsrc/components/RequestModal/TvRequestModal.tsxsrc/i18n/locale/en.json
✅ Files skipped from review due to trivial changes (2)
- server/interfaces/api/requestInterfaces.ts
- src/i18n/locale/en.json
🚧 Files skipped from review as they are similar to previous changes (1)
- server/entity/MediaRequest.ts
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/components/RequestModal/TvRequestModal.tsx (1)
91-96:⚠️ Potential issue | 🟡 MinorQuota fetch gate omits
MANAGE_REQUESTS, preventing target-user quota visibility forMANAGE_REQUESTS-only admins.The SWR fetch for the target user's quota only runs when
hasPermission(Permission.MANAGE_USERS), but both the user selector and "Bypass User Quota" toggle inAdvancedRequesterallowMANAGE_REQUESTSusers to operate. AMANAGE_REQUESTS-only admin who picks a different target user will getquota === undefined, which:
- Hides
QuotaDisplayfor that target.- Hides the "Bypass User Quota" toggle (even though they have permission to set it).
Same issue exists in
MovieRequestModal.tsxandCollectionRequestModal.tsx.Suggested adjustment
const { data: quota } = useSWR<QuotaResponse>( user && - (!requestOverrides?.user?.id || hasPermission(Permission.MANAGE_USERS)) + (!requestOverrides?.user?.id || + hasPermission([Permission.MANAGE_USERS, Permission.MANAGE_REQUESTS], { + type: 'or', + })) ? `/api/v1/user/${requestOverrides?.user?.id ?? user.id}/quota` : null );🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/RequestModal/TvRequestModal.tsx` around lines 91 - 96, The SWR quota fetch gate currently only allows fetching the target user's quota when hasPermission(Permission.MANAGE_USERS), which prevents MANAGE_REQUESTS-only admins from seeing or bypassing target-user quotas; update the conditional used in useSWR in TvRequestModal (and mirror the same change in MovieRequestModal and CollectionRequestModal) to also allow hasPermission(Permission.MANAGE_REQUESTS) when deciding whether to fetch `/api/v1/user/${requestOverrides?.user?.id ?? user.id}/quota`; this ensures QuotaDisplay and the "Bypass User Quota" toggle in AdvancedRequester are shown for users with MANAGE_REQUESTS permission when a different target user is selected.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@src/components/RequestModal/TvRequestModal.tsx`:
- Around line 91-96: The SWR quota fetch gate currently only allows fetching the
target user's quota when hasPermission(Permission.MANAGE_USERS), which prevents
MANAGE_REQUESTS-only admins from seeing or bypassing target-user quotas; update
the conditional used in useSWR in TvRequestModal (and mirror the same change in
MovieRequestModal and CollectionRequestModal) to also allow
hasPermission(Permission.MANAGE_REQUESTS) when deciding whether to fetch
`/api/v1/user/${requestOverrides?.user?.id ?? user.id}/quota`; this ensures
QuotaDisplay and the "Bypass User Quota" toggle in AdvancedRequester are shown
for users with MANAGE_REQUESTS permission when a different target user is
selected.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 1e2dedf2-c00d-4451-afc8-d53dba4ed68a
📒 Files selected for processing (2)
server/entity/MediaRequest.tssrc/components/RequestModal/TvRequestModal.tsx
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/components/RequestModal/TvRequestModal.tsx (1)
286-323:⚠️ Potential issue | 🟠 Major
ignoreQuotabypass is incomplete — season selection is still blocked by quota.
toggleSeason(Lines 293-299) andtoggleAllSeasons(Lines 318-323) short-circuit whencurrentlyRemaining <= 0orquota.tv.remaining < unrequestedSeasons.length, regardless ofrequestOverrides?.ignoreQuota. WithpartialRequestsEnabled, an admin who toggles "Bypass User Quota" on a user who is already over quota still cannot tick any additional seasons, so the bypass is effectively unreachable through the UI. Only theokDisabledclause at Line 451 was updated.Consider gating these blocks on
!requestOverrides?.ignoreQuotaas well (and similarly for the visualopacity-50checks at Lines 553-558 and 636-642 that signal "blocked").🔧 Suggested fix
const toggleSeason = (seasonNumber: number): void => { // If this season already has a pending request, don't allow it to be toggled if (getAllRequestedSeasons().includes(seasonNumber)) { return; } // If there are no more remaining requests available, block toggle if ( quota?.tv.limit && currentlyRemaining <= 0 && - !isSelectedSeason(seasonNumber) + !isSelectedSeason(seasonNumber) && + !requestOverrides?.ignoreQuota ) { return; } @@ const toggleAllSeasons = (): void => { // If the user has a quota and not enough requests for all seasons, block toggleAllSeasons if ( quota?.tv.limit && - (quota?.tv.remaining ?? 0) < unrequestedSeasons.length + (quota?.tv.remaining ?? 0) < unrequestedSeasons.length && + !requestOverrides?.ignoreQuota ) { return; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/RequestModal/TvRequestModal.tsx` around lines 286 - 323, toggleSeason and toggleAllSeasons are still blocked by quota checks even when requestOverrides?.ignoreQuota is true; update the short-circuit conditions to also allow toggling when requestOverrides?.ignoreQuota is set (i.e., only enforce currentlyRemaining <= 0 or quota?.tv.remaining < unrequestedSeasons.length when !requestOverrides?.ignoreQuota). Also update the UI blocking/opacity logic that uses "opacity-50" so it reflects requestOverrides?.ignoreQuota (remove or skip the faded/disabled styling when ignoreQuota is true) so admins can actually interact with seasons when bypass is enabled; reference toggleSeason, currentlyRemaining, quota?.tv.limit, toggleAllSeasons, quota?.tv.remaining, unrequestedSeasons, and the opacity-50 visual checks.src/components/RequestModal/CollectionRequestModal.tsx (1)
189-215:⚠️ Potential issue | 🟠 MajorAdd
quotaprop toAdvancedRequesterand forwardignoreQuotain collection requests.
CollectionRequestModaldoes not passquotatoAdvancedRequester(line 525) and does not forwardrequestOverrides?.ignoreQuotain the POST body, unlikeMovieRequestModalandTvRequestModal. This means the "Bypass User Quota" toggle will not appear for collection requests and admins cannot bypass quotas when requesting collections.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/RequestModal/CollectionRequestModal.tsx` around lines 189 - 215, CollectionRequestModal is not passing the quota prop to AdvancedRequester and is not forwarding requestOverrides?.ignoreQuota in the request body; update the AdvancedRequester usage in CollectionRequestModal to include the quota prop (same prop name used by MovieRequestModal/TvRequestModal) and modify sendRequest (overrideParams) to include ignoreQuota: requestOverrides.ignoreQuota so the POST body sent by sendRequest includes ignoreQuota alongside serverId/profileId/rootFolder/userId/tags; mirror the same shape and prop names used in MovieRequestModal and TvRequestModal to ensure the "Bypass User Quota" toggle appears and the flag is sent to the API.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@src/components/RequestModal/CollectionRequestModal.tsx`:
- Around line 189-215: CollectionRequestModal is not passing the quota prop to
AdvancedRequester and is not forwarding requestOverrides?.ignoreQuota in the
request body; update the AdvancedRequester usage in CollectionRequestModal to
include the quota prop (same prop name used by MovieRequestModal/TvRequestModal)
and modify sendRequest (overrideParams) to include ignoreQuota:
requestOverrides.ignoreQuota so the POST body sent by sendRequest includes
ignoreQuota alongside serverId/profileId/rootFolder/userId/tags; mirror the same
shape and prop names used in MovieRequestModal and TvRequestModal to ensure the
"Bypass User Quota" toggle appears and the flag is sent to the API.
In `@src/components/RequestModal/TvRequestModal.tsx`:
- Around line 286-323: toggleSeason and toggleAllSeasons are still blocked by
quota checks even when requestOverrides?.ignoreQuota is true; update the
short-circuit conditions to also allow toggling when
requestOverrides?.ignoreQuota is set (i.e., only enforce currentlyRemaining <= 0
or quota?.tv.remaining < unrequestedSeasons.length when
!requestOverrides?.ignoreQuota). Also update the UI blocking/opacity logic that
uses "opacity-50" so it reflects requestOverrides?.ignoreQuota (remove or skip
the faded/disabled styling when ignoreQuota is true) so admins can actually
interact with seasons when bypass is enabled; reference toggleSeason,
currentlyRemaining, quota?.tv.limit, toggleAllSeasons, quota?.tv.remaining,
unrequestedSeasons, and the opacity-50 visual checks.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 74ed28a5-5601-457a-ace8-584314a337c8
📒 Files selected for processing (3)
src/components/RequestModal/CollectionRequestModal.tsxsrc/components/RequestModal/MovieRequestModal.tsxsrc/components/RequestModal/TvRequestModal.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
- src/components/RequestModal/MovieRequestModal.tsx
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
src/components/RequestModal/AdvancedRequester/index.tsx (1)
42-44: Message key naming inconsistent with existing keys.All other keys in this
messagesobject use lowercase identifiers (advancedoptions,requestas,qualityprofile, …). The new keys are camelCase (ignoreQuotaTitle,ignoreQuotaDescription). Considerignorequotatitle/ignorequotadescription(orbypassquota*) for consistency. Note this only affects the source key; translated strings are unaffected.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/RequestModal/AdvancedRequester/index.tsx` around lines 42 - 44, The new message keys in the messages object use camelCase (ignoreQuotaTitle, ignoreQuotaDescription) which is inconsistent with existing lowercase keys; rename these keys to match the convention (e.g., ignorequotatitle and ignorequotadescription or bypassquota... as preferred) wherever they are defined and referenced in AdvancedRequester (search for ignoreQuotaTitle / ignoreQuotaDescription) so the messages object and all lookups use the new lowercase keys consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/components/RequestModal/AdvancedRequester/index.tsx`:
- Around line 107-109: The ignoreQuota state can remain true when its toggle
becomes hidden; add a useEffect that watches the visibility predicate (the
permission check + per-type limit > 0) and selectedUser and calls
setIgnoreQuota(false) whenever that predicate becomes false or selectedUser
changes; update the component where ignoreQuota and setIgnoreQuota are declared
(and similarly the other occurrence around lines 271-273) so the toggle cannot
leave stale true values in the POST body by resetting ignoreQuota to false
whenever visibility is lost or the user selection changes.
- Around line 573-583: The label's htmlFor="ignoreQuota" isn't connected to a
real control because SlideCheckbox lacks an id and proper ARIA handling; update
the JSX to pass an id (e.g., id="ignoreQuota") and any ARIA attributes
(aria-labelledby, aria-label) into SlideCheckbox, and modify the SlideCheckbox
component to accept and forward an id and arbitrary ARIA props to the underlying
focusable element, use the checked prop to set aria-checked (do not hardcode
false), and ensure the toggle uses an accessible event (onClick/onChange) that
toggles via setIgnoreQuota so the label correctly toggles the control and screen
readers see the real state.
---
Nitpick comments:
In `@src/components/RequestModal/AdvancedRequester/index.tsx`:
- Around line 42-44: The new message keys in the messages object use camelCase
(ignoreQuotaTitle, ignoreQuotaDescription) which is inconsistent with existing
lowercase keys; rename these keys to match the convention (e.g.,
ignorequotatitle and ignorequotadescription or bypassquota... as preferred)
wherever they are defined and referenced in AdvancedRequester (search for
ignoreQuotaTitle / ignoreQuotaDescription) so the messages object and all
lookups use the new lowercase keys consistently.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: e8eae48c-047d-40a6-851e-eaf3c86ed818
📒 Files selected for processing (3)
src/components/RequestModal/AdvancedRequester/index.tsxsrc/components/RequestModal/CollectionRequestModal.tsxsrc/components/RequestModal/TvRequestModal.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
- src/components/RequestModal/CollectionRequestModal.tsx
- src/components/RequestModal/TvRequestModal.tsx
| const [ignoreQuota, setIgnoreQuota] = useState<boolean>( | ||
| defaultOverrides?.ignoreQuota ?? false | ||
| ); |
There was a problem hiding this comment.
Stale ignoreQuota state when the toggle becomes hidden.
If ignoreQuota was previously toggled on and the toggle then becomes hidden (e.g., admin switches “Request As” to a user where limit is 0/undefined, or the target loses MANAGE_REQUESTS gating), the state remains true and is silently included in the POST body. The server still enforces MANAGE_REQUESTS, so this isn’t a security risk, but it can produce surprising request payloads. Consider resetting ignoreQuota to false whenever the visibility predicate (permission + per-type limit > 0) becomes false, or whenever selectedUser changes.
Also applies to: 271-273
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/RequestModal/AdvancedRequester/index.tsx` around lines 107 -
109, The ignoreQuota state can remain true when its toggle becomes hidden; add a
useEffect that watches the visibility predicate (the permission check + per-type
limit > 0) and selectedUser and calls setIgnoreQuota(false) whenever that
predicate becomes false or selectedUser changes; update the component where
ignoreQuota and setIgnoreQuota are declared (and similarly the other occurrence
around lines 271-273) so the toggle cannot leave stale true values in the POST
body by resetting ignoreQuota to false whenever visibility is lost or the user
selection changes.
Description
Note
Ported from sct/overseerr#4173 and slightly modified. CC @OwsleyJr
This PR adds the ability for users with
MANAGE_REQUESTSpermission to bypass quota restrictions when making requests on behalf of other users.The request will not count against the user's quota limits, allowing admins to make requests on behalf of users who may have exceeded their quota or for special circumstances.
How Has This Been Tested?
Screenshots / Logs (if applicable)
Checklist:
pnpm buildpnpm i18n:extractSummary by CodeRabbit