fix: preserve blocklist on media deletion & optimise watchlist-sync#2478
fix: preserve blocklist on media deletion & optimise watchlist-sync#2478fallenbagel wants to merge 2 commits intodevelopfrom
Conversation
…watchlist sync Blocklisted media was being re-requested via watchlist sync because deleting media from Sonarr/Radarr through the UI also deleted the Media entity and its associated Blocklist record via cascade. The media delete endpoint now preserves blocklisted Media entities by clearing service fields instead of removing the row. Additionally, the watchlist sync filter now excludes blocklisted items and skips media that already has an existing auto-request for the user, avoiding unnecessary API calls and database queries on every sync cycle. fix #2475
📝 WalkthroughWalkthroughAdds filtering to watchlist sync to avoid creating media requests for items the user already auto-requested, and changes DELETE /:id to retain BLOCKLISTED media records by nulling service-related fields instead of deleting the row. Changes
Sequence Diagram(s)sequenceDiagram
participant Plex as Plex Watchlist API
participant Job as Watchlist Sync Job
participant DB as Database
participant Creator as MediaRequest Creator
Plex->>Job: return watchlist items (tmdbId, mediaType)
Job->>DB: query MediaRequest where userId = X and tmdbId IN (watchlistTmdbIds)
DB-->>Job: existing auto-request set (mediaType:tmdbId)
Job->>Job: filter watchlist items excluding existing set and blocked/unavailable
Job->>Creator: create media request for each remaining item
Creator-->>DB: persist new MediaRequest (if created)
Creator-->>Job: return result (success/failure)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Poem
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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/lib/watchlistsync.ts`:
- Around line 73-84: existingAutoRequests mapping assumes every MediaRequest has
a loaded media relation and accessing r.media.tmdbId can throw; update the
mapping that builds autoRequestedTmdbIds to guard against missing media by
filtering or null-checking each request first (e.g., use
existingAutoRequests.filter(r => r.media).map(r => r.media.tmdbId) or
conditional access) so only valid tmdbId values are added; modify the logic
around requestRepository, existingAutoRequests, and autoRequestedTmdbIds to
safely handle requests with null/undefined media.
There was a problem hiding this comment.
Pull request overview
Fixes a watchlist-sync + media deletion edge case where deleting a Media row could cascade-delete its Blocklist entry, allowing future watchlist sync cycles to recreate and auto-approve requests that should remain blocked.
Changes:
- Updates the media delete endpoint to preserve blocklisted media by nulling service-related fields instead of deleting the row.
- Hardens watchlist sync filtering to skip blocklisted items and avoid repeated duplicate auto-request attempts.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
server/routes/media.ts |
Preserves blocklisted media on deletion by clearing service identifiers instead of removing the entity. |
server/lib/watchlistsync.ts |
Adds pre-filtering to skip blocklisted media and items with existing auto-requests before attempting request creation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…improve item filtering
| media.serviceId = null; | ||
| media.serviceId4k = null; | ||
| media.externalServiceId = null; | ||
| media.externalServiceId4k = null; | ||
| media.externalServiceSlug = null; | ||
| media.externalServiceSlug4k = null; | ||
| media.ratingKey = null; | ||
| media.ratingKey4k = null; | ||
| media.jellyfinMediaId = null; | ||
| media.jellyfinMediaId4k = null; |
There was a problem hiding this comment.
This feels a bit hardcoded. Don't we have another way of resetting values of the entity? Doing this seems to be a good way of having issues later when we'll add other fields to the entity.
There was a problem hiding this comment.
Or delete/recreate the entity with the blacklisted status instead?
There was a problem hiding this comment.
Or delete/recreate the entity with the blacklisted status instead?
The delete/recreate approach won't work because, deleting the Media would kill the Blocklist row before you can recreate.
One thing i could try is dynamically handling non-nullable columns to survive, and nullable ones to get cleared. Wdyt?
for (const column of mediaRepository.metadata.columns) {
if (column.isNullable && !column.isPrimary) {
updatePayload[column.propertyName] = null;
}
}
And then
await mediaRepository.update
There was a problem hiding this comment.
Yep, looks like more future-proof.
Description
When a user blocklists media and then removes it from Sonarr/Radarr via the Seerr interface, the Media entity and its associated Blocklist record were both destroyed due to the cascade delete on the relationship. On the next watchlist sync cycle, no record existed to check against, so a fresh request was created and auto-approved, completely bypassing the blocklist.
This PR fixes the media delete endpoint to detect blocklisted status and preserve the entity by nulling out service-related fields rather than removing the row entirely. It also improves the watchlist sync filtering to exclude blocklisted media as a safety net and to skip items where the user already has an existing auto-request, eliminating the repeated unnecessary api calls and database lookups that were happening every sync cycle just to throw a duplicate request error.
How Has This Been Tested?
Screenshots / Logs (if applicable)
Checklist:
pnpm buildpnpm i18n:extractSummary by CodeRabbit
Release Notes