Skip to content

feat(permissions): add LIMIT_ONE_SEASON permission#2752

Open
dangerouslaser wants to merge 1 commit intoseerr-team:developfrom
dangerouslaser:feat/limit-one-season-permission
Open

feat(permissions): add LIMIT_ONE_SEASON permission#2752
dangerouslaser wants to merge 1 commit intoseerr-team:developfrom
dangerouslaser:feat/limit-one-season-permission

Conversation

@dangerouslaser
Copy link
Copy Markdown

@dangerouslaser dangerouslaser commented Mar 23, 2026

Summary

  • Adds a new LIMIT_ONE_SEASON user permission (bitmask 2^29) that restricts series requests to one season per request
  • When enabled, the request modal switches season toggles to single-select (radio button behavior) and hides the "select all" toggle
  • A warning banner informs the user that they can only request one season at a time
  • Backend enforces the limit server-side, rejecting multi-season requests with HTTP 403
  • Users with Manage Requests or Admin permissions are exempt
  • The permission appears as a standalone entry in the admin permissions UI, requiring either the Request or Request TV permission

Motivation

Addresses #1385. Users often request entire series and only watch the first season, wasting storage and bandwidth. This permission lets admins restrict users to one season at a time, which pairs well with an external prefetch service like Prefetcharr to automatically fetch additional seasons as users progress through what they requested.

Changes

File Change
server/lib/permissions.ts New LIMIT_ONE_SEASON enum value
server/entity/MediaRequest.ts SeasonLimitError class and enforcement in request()
server/routes/request.ts Map SeasonLimitError to 403 response
src/components/RequestModal/TvRequestModal.tsx Single-select toggle behavior, hide select-all, force partial request mode for limited users, warning banner
src/components/PermissionEdit/index.tsx Standalone permission entry in admin UI with i18n messages

How it works

  1. Admin enables "Limit One Season" on a user (or as a default permission)
  2. User opens the TV request modal and sees per-season toggles (even if partial requests are globally disabled)
  3. A warning banner reads: "You can only request one season at a time."
  4. Selecting a season deselects any previously selected season (radio button behavior)
  5. The select-all toggle is hidden
  6. Submitting sends only the single selected season to the API
  7. Backend validates and rejects if more than one season is included

Test plan

  • Enable the permission on a non-admin user and verify single-select behavior in the request modal
  • Verify the warning banner appears in the request modal for limited users
  • Verify the select-all toggle is hidden for limited users
  • Verify an admin user is unaffected by the permission
  • Send a direct API request with multiple seasons as a limited user and confirm 403 response
  • Verify the permission toggle appears as a standalone entry in the admin permissions UI
  • Verify existing request flows (movies, non-limited users, edit requests) are unaffected

Summary by CodeRabbit

  • New Features
    • New option to restrict TV-series requests to one season at a time, configurable by admins.
    • UI updates: warning message in the TV request modal, adjusted selection behavior (selecting a season replaces previous), and clearer button/disable states.
  • Bug Fixes / Validation
    • Frontend and backend now enforce the one-season-at-a-time constraint and prevent saving invalid multi-season requests.

@dangerouslaser dangerouslaser requested a review from a team as a code owner March 23, 2026 14:22
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 23, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 996cbc41-22db-44d5-81f2-d9de7abe9a9c

📥 Commits

Reviewing files that changed from the base of the PR and between 523d8d2 and ca988a6.

📒 Files selected for processing (5)
  • server/entity/MediaRequest.ts
  • server/lib/permissions.ts
  • server/routes/request.ts
  • src/components/PermissionEdit/index.tsx
  • src/components/RequestModal/TvRequestModal.tsx
✅ Files skipped from review due to trivial changes (1)
  • server/lib/permissions.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • server/entity/MediaRequest.ts
  • src/components/RequestModal/TvRequestModal.tsx

📝 Walkthrough

Walkthrough

Adds a new Permission.LIMIT_ONE_SEASON flag and enforces a single-season request constraint: backend throws a new SeasonLimitError when applicable; routes map it to HTTP 403; frontend disables/mutates multi-season selection and updates UI/messages accordingly.

Changes

Cohort / File(s) Summary
Backend: permission & enforcement
server/lib/permissions.ts, server/entity/MediaRequest.ts, server/routes/request.ts
Added Permission.LIMIT_ONE_SEASON = 536870912; introduced exported SeasonLimitError; backend now throws SeasonLimitError when a user with LIMIT_ONE_SEASON (and without MANAGE_REQUESTS or ADMIN) requests >1 season. Route handlers map SeasonLimitError to HTTP 403.
Frontend: permission editor
src/components/PermissionEdit/index.tsx
Added i18n strings and a new permission entry (limit-one-season) in the permission list, tied to Permission.LIMIT_ONE_SEASON with requires constraint against request-related permissions.
Frontend: TV request modal
src/components/RequestModal/TvRequestModal.tsx
Computed isOneSeasonLimited and updated UI/logic: season toggling becomes mutually exclusive, submission payload sends selected seasons when limited, added alert/message, and adjusted submit gating and table filtering/visibility to respect single-season constraint.

Sequence Diagram

sequenceDiagram
    participant User as User (Limited)
    participant Modal as TV Request Modal
    participant API as POST /api/v1/request
    participant Server as MediaRequest Handler
    participant Resp as HTTP Response

    User->>Modal: select seasons (attempt >1)
    Modal->>Modal: check isOneSeasonLimited
    Modal->>Modal: adjust selection / block submit
    alt Modal allows submission (client-side)
        Modal->>API: POST /api/v1/request (seasons payload)
        API->>Server: validate request
        Server->>Server: check Permission.LIMIT_ONE_SEASON and management perms
        alt multiple seasons & limited
            Server->>Server: throw SeasonLimitError
            Server->>Resp: 403 Forbidden (error.message)
            Resp->>Modal: display error
        else valid single season
            Server->>Resp: 200 OK
            Resp->>Modal: success
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I nibble code and count each season,

One at a time is now the reason.
A gentle hop, a single pick,
Permissions guard each tiny trick,
Hooray—one season, neat and clean! 🥕

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main feature being added: a new LIMIT_ONE_SEASON permission. It matches the primary objective of the PR and uses proper semantic commit convention.

✏️ 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.

Copy link
Copy Markdown

@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: 2

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)

440-465: ⚠️ Potential issue | 🟡 Minor

Finish threading isOneSeasonLimited through the remaining quota-only branches.

The CTA state now treats limited users like partial-request mode, but the auto-approve alert and QuotaDisplay below still only key off partialRequestsEnabled. When partial requests are globally off, a limited user can still see over-limit/zero-remaining messaging based on the full unrequested season count even though selecting one season is valid.

🔧 Suggested follow-up
       ) &&
         !(
           quota?.tv.limit &&
-          !settings.currentSettings.partialRequestsEnabled &&
+          !settings.currentSettings.partialRequestsEnabled &&
+          !isOneSeasonLimited &&
           unrequestedSeasons.length > (quota?.tv.remaining ?? 0)
         ) &&
         getAllRequestedSeasons().length < getAllSeasons().length &&
         !editRequest && (
@@
         <QuotaDisplay
           mediaType="tv"
           quota={quota?.tv}
           remaining={
-            !settings.currentSettings.partialRequestsEnabled &&
+            !settings.currentSettings.partialRequestsEnabled &&
+            !isOneSeasonLimited &&
             unrequestedSeasons.length > (quota?.tv.remaining ?? 0)
               ? 0
               : currentlyRemaining
           }
@@
           overLimit={
-            !settings.currentSettings.partialRequestsEnabled &&
+            !settings.currentSettings.partialRequestsEnabled &&
+            !isOneSeasonLimited &&
             unrequestedSeasons.length > (quota?.tv.remaining ?? 0)
               ? unrequestedSeasons.length
               : undefined
           }
🤖 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 440 - 465, The
UI still treats quota messaging and the QuotaDisplay/auto-approve alert as if
only settings.currentSettings.partialRequestsEnabled matters; thread
isOneSeasonLimited into those checks so limited users are treated like
partial-requests-enabled. Update all conditional checks that currently read
settings.currentSettings.partialRequestsEnabled (or its negation) in the
quota-only branches (including the auto-approve alert logic and the QuotaDisplay
rendering) to use (settings.currentSettings.partialRequestsEnabled ||
isOneSeasonLimited) (or its negation) and ensure unrequestedSeasons/quota
comparisons use the effective mode; keep references to isOneSeasonLimited,
quota, unrequestedSeasons, getAllRequestedSeasons, getAllSeasons,
selectedSeasons, QuotaDisplay and the auto-approve alert logic in your changes.
🤖 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 438-448: The LIMIT_ONE_SEASON check is applied to requestUser in
MediaRequest.request(), which diverges from the frontend (TvRequestModal) that
derives the flag from the logged-in user and is not enforced on edits (PUT
/api/v1/request/:id); extract and centralize this logic into a reusable
validator (e.g., MediaRequest.ensureOneSeasonLimit or
MediaRequest.validateSeasonLimit) that accepts the principal performing the
action (logged-in/acting user) and the request payload (finalSeasons), use
Permission.LIMIT_ONE_SEASON and throw SeasonLimitError when violated, then call
this validator from both MediaRequest.request() and the update handler for PUT
/api/v1/request/:id so on-behalf requests use the same principal and the same
403 behavior.

In `@src/components/PermissionEdit/index.tsx`:
- Around line 190-200: The UI toggle for the permission with id
'limit-one-season' (Permission.LIMIT_ONE_SEASON) only appears when the edited
user has Permission.REQUEST or Permission.REQUEST_TV; update its requires entry
so it also includes 4K-only request permissions (Permission.REQUEST_4K and
Permission.REQUEST_4K_TV) in the permissions array (keeping type: 'or') so users
who only have 4K request bits can be configured from the admin UI.

---

Outside diff comments:
In `@src/components/RequestModal/TvRequestModal.tsx`:
- Around line 440-465: The UI still treats quota messaging and the
QuotaDisplay/auto-approve alert as if only
settings.currentSettings.partialRequestsEnabled matters; thread
isOneSeasonLimited into those checks so limited users are treated like
partial-requests-enabled. Update all conditional checks that currently read
settings.currentSettings.partialRequestsEnabled (or its negation) in the
quota-only branches (including the auto-approve alert logic and the QuotaDisplay
rendering) to use (settings.currentSettings.partialRequestsEnabled ||
isOneSeasonLimited) (or its negation) and ensure unrequestedSeasons/quota
comparisons use the effective mode; keep references to isOneSeasonLimited,
quota, unrequestedSeasons, getAllRequestedSeasons, getAllSeasons,
selectedSeasons, QuotaDisplay and the auto-approve alert logic in your changes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9d98ff30-f512-4fd5-af74-81f00ac814b3

📥 Commits

Reviewing files that changed from the base of the PR and between 865396f and 523d8d2.

📒 Files selected for processing (5)
  • server/entity/MediaRequest.ts
  • server/lib/permissions.ts
  • server/routes/request.ts
  • src/components/PermissionEdit/index.tsx
  • src/components/RequestModal/TvRequestModal.tsx

Add a new user permission that restricts series requests to one season
at a time. When enabled, the request modal switches to single-select
mode for seasons, hides the select-all toggle, and the backend rejects
multi-season requests with a 403. Admins and users with Manage Requests
are exempt from the restriction.

This is intended to be paired with an external prefetch service (e.g.
Prefetcharr) that automatically requests additional seasons based on
user watch progress.
@dangerouslaser dangerouslaser force-pushed the feat/limit-one-season-permission branch from 523d8d2 to ca988a6 Compare March 23, 2026 14:46
@delagomme
Copy link
Copy Markdown

Thanks, small but impactful feature with a high cost-benefit ratio. This would also partially address #1351. It would be great to see this included in an upcoming release fairly soon, as many users are eagerly waiting for this 👍

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