Skip to content
Draft
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
d46b45a
feat(permissions): add removal request permissions
MrDWilson Apr 5, 2026
90de224
feat(media): add media removal requests
MrDWilson Apr 5, 2026
bd5c3f2
feat(sonarr): add season file removal and unmonitoring
MrDWilson Apr 5, 2026
b853456
feat(api): add removal request endpoints
MrDWilson Apr 5, 2026
9ba384e
feat(permissions): add removal request permissions to UI
MrDWilson Apr 5, 2026
e5d5281
feat(ui): add media removal request management
MrDWilson Apr 5, 2026
86f104f
feat(ui): add media and season removal request management
MrDWilson Apr 5, 2026
c896897
feat(ui): move removal requester info to tooltip
MrDWilson Apr 5, 2026
c4c92c3
feat(api): refine removal request logic and validation
MrDWilson Apr 5, 2026
f7ab2e2
feat(i18n): add translations for removal requests
MrDWilson Apr 5, 2026
eef4032
feat(removal): add 4k season support and refine request logic
MrDWilson Apr 5, 2026
cd19b83
feat(api): add requestedBy filter for removal requests
MrDWilson Apr 5, 2026
abca294
Merge branch 'develop' into feat/removal-request
MrDWilson Apr 5, 2026
f6cd337
feat(removal): improve request validation and data persistence
MrDWilson Apr 6, 2026
da4da62
feat(removal): include 4k status in request lookup, load media relations
MrDWilson Apr 6, 2026
3c6bdfb
feat(removal): implement multi-user aware media removal and partial t…
MrDWilson Apr 6, 2026
de780f8
Merge remote-tracking branch 'seerr-team/develop' into feat/removal-r…
MrDWilson May 30, 2026
53f6833
fix(removal): scope multi-user consent by 4K and season
MrDWilson May 30, 2026
fe8ed93
fix(removal): make Servarr removal idempotent and simplify create guard
MrDWilson May 30, 2026
0f88ac8
fix(removal): regenerate migrations against current develop
MrDWilson May 30, 2026
6871e0c
fix(removal): improve removal UI feedback, gating, and a11y
MrDWilson May 30, 2026
a14b501
docs(api): document removal-request schema and response codes
MrDWilson May 30, 2026
d095571
test(removal): cover removal-request routes and multi-user consent
MrDWilson May 30, 2026
d8a91b8
fix(removal): stop the season modal from freezing the manage pane
MrDWilson May 30, 2026
576802d
fix(removal): show users their own removal requests and unify the UI
MrDWilson May 30, 2026
503ec1c
fix(removal): count full-series requests in season consent
MrDWilson May 30, 2026
9fd6681
style(api): satisfy prettier for the removal-request filter enum
MrDWilson May 30, 2026
752f052
Merge branch 'develop' into feat/removal-request
MrDWilson Jun 2, 2026
08d0578
Merge branch 'develop' into feat/removal-request
MrDWilson Jun 5, 2026
25caecb
Merge branch 'develop' into feat/removal-request
MrDWilson Jun 10, 2026
e1f972a
Merge branch 'develop' into feat/removal-request
MrDWilson Jun 16, 2026
152aacb
Merge branch 'develop' into feat/removal-request
MrDWilson Jun 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
242 changes: 242 additions & 0 deletions seerr-api.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1274,6 +1274,53 @@ components:
required:
- id
- status
MediaRemovalRequest:
type: object
properties:
id:
type: number
example: 1
readOnly: true
status:
type: number
description: >-
Status of the removal request. 1 = PENDING, 2 = APPROVED,
3 = DECLINED, 4 = FAILED, 5 = PARTIALLY_REMOVED
example: 1
readOnly: true
is4k:
type: boolean
example: false
seasons:
type: array
nullable: true
description: Season numbers targeted by the removal request (TV only).
items:
type: number
reason:
type: string
nullable: true
media:
$ref: '#/components/schemas/MediaInfo'
requestedBy:
$ref: '#/components/schemas/User'
modifiedBy:
anyOf:
- $ref: '#/components/schemas/User'
- type: string
nullable: true
createdAt:
type: string
example: '2020-09-12T10:00:27.000Z'
readOnly: true
updatedAt:
type: string
example: '2020-09-12T10:00:27.000Z'
readOnly: true
required:
- id
- status
- is4k
MediaInfo:
type: object
properties:
Expand Down Expand Up @@ -6276,6 +6323,201 @@ paths:
type: string
title:
type: string
/removal-request:
get:
summary: Get all removal requests
description: |
Returns all removal requests if the user has the `ADMIN`, `MANAGE_REQUESTS`, or `REQUEST_VIEW` permissions. Otherwise, only the logged-in user's removal requests are returned.
tags:
- removal-request
parameters:
- in: query
name: take
schema:
type: number
nullable: true
maximum: 100
example: 20
- in: query
name: skip
schema:
type: number
nullable: true
example: 0
- in: query
name: filter
schema:
type: string
enum:
[
all,
pending,
approved,
declined,
failed,
partially-removed,
]
nullable: true
- in: query
name: requestedBy
description: Filter removal requests by the ID of the user who created them. Only available for users with `MANAGE_REQUESTS` or `REQUEST_VIEW` permissions.
schema:
type: number
nullable: true
responses:
'200':
description: Removal requests returned
content:
application/json:
schema:
type: object
properties:
pageInfo:
$ref: '#/components/schemas/PageInfo'
results:
type: array
items:
$ref: '#/components/schemas/MediaRemovalRequest'
post:
summary: Create a removal request
description: |
Creates a new removal request. Requires the `REQUEST_REMOVAL` permission.
tags:
- removal-request
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- mediaId
properties:
mediaId:
type: number
example: 1
is4k:
type: boolean
default: false
reason:
type: string
nullable: true
seasons:
type: array
description: Season numbers to request removal for. Only valid for TV media. Must be positive integers.
items:
type: number
minimum: 1
nullable: true
responses:
'201':
description: Removal request created
content:
application/json:
schema:
$ref: '#/components/schemas/MediaRemovalRequest'
'400':
description: Invalid input (e.g. seasons provided for a movie, invalid season numbers, or reason too long)
'401':
description: Not authenticated
'403':
description: User may only request removal of media they originally requested
'404':
description: Media not found
'409':
description: The user already has an active removal request for this media
/removal-request/{requestId}/approve:
post:
summary: Approve a removal request
description: |
Approves a removal request and executes the media removal. Requires the `MANAGE_REQUESTS` permission.
tags:
- removal-request
parameters:
- in: path
name: requestId
required: true
schema:
type: number
responses:
'200':
description: Removal request approved
content:
application/json:
schema:
$ref: '#/components/schemas/MediaRemovalRequest'
'400':
description: The request is not pending
'404':
description: Removal request not found
/removal-request/{requestId}/decline:
post:
summary: Decline a removal request
description: |
Declines a removal request. Requires the `MANAGE_REQUESTS` permission.
tags:
- removal-request
parameters:
- in: path
name: requestId
required: true
schema:
type: number
responses:
'200':
description: Removal request declined
content:
application/json:
schema:
$ref: '#/components/schemas/MediaRemovalRequest'
'400':
description: The request is not pending
'404':
description: Removal request not found
/removal-request/{requestId}/retry:
post:
summary: Retry a failed removal request
description: |
Retries a previously failed removal request. Requires the `MANAGE_REQUESTS` permission.
tags:
- removal-request
parameters:
- in: path
name: requestId
required: true
schema:
type: number
responses:
'200':
description: Removal request retried
content:
application/json:
schema:
$ref: '#/components/schemas/MediaRemovalRequest'
'400':
description: Only failed requests can be retried
'404':
description: Removal request not found
/removal-request/{requestId}:
delete:
summary: Delete a removal request
description: |
Deletes a removal request. The requester or a user with `MANAGE_REQUESTS` permission can delete.
tags:
- removal-request
parameters:
- in: path
name: requestId
required: true
schema:
type: number
responses:
'204':
description: Removal request deleted
'403':
description: User does not have permission to delete this removal request
'404':
description: Removal request not found
/request:
get:
summary: Get all requests
Expand Down
31 changes: 31 additions & 0 deletions server/api/servarr/radarr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,17 @@ class RadarrAPI extends ServarrBase<{ movieId: number }> {
public removeMovie = async (movieId: number): Promise<void> => {
try {
const { id, title } = await this.getMovieByTmdbId(movieId);
if (!id) {
// Movie is not in the Radarr library (e.g. already removed). Treat the
// desired end-state as reached so retries remain idempotent.
logger.info(
'[Radarr] Movie not present in library; nothing to remove',
{
tmdbId: movieId,
}
);
return;
}
await this.axios.delete(`/movie/${id}`, {
params: {
deleteFiles: true,
Expand All @@ -284,6 +295,26 @@ class RadarrAPI extends ServarrBase<{ movieId: number }> {
}
};

public removeTagFromMovie = async (
tmdbId: number,
tagId: number
): Promise<void> => {
try {
const movie = await this.getMovieByTmdbId(tmdbId);
const updatedTags = movie.tags.filter((t) => t !== tagId);
await this.axios.put(`/movie`, {
...movie,
tags: updatedTags,
});
Comment thread
MrDWilson marked this conversation as resolved.
logger.info(`[Radarr] Removed tag ${tagId} from movie ${movie.title}`);
} catch (e) {
throw new Error(
`[Radarr] Failed to remove tag from movie: ${e.message}`,
{ cause: e }
);
}
};

public clearCache = ({
tmdbId,
externalId,
Expand Down
Loading
Loading