Skip to content

fix: preserve blocklist on media deletion & optimise watchlist-sync#2478

Open
fallenbagel wants to merge 2 commits intodevelopfrom
fallenbagel/fix/blocklist-watchlist-sync
Open

fix: preserve blocklist on media deletion & optimise watchlist-sync#2478
fallenbagel wants to merge 2 commits intodevelopfrom
fallenbagel/fix/blocklist-watchlist-sync

Conversation

@fallenbagel
Copy link
Collaborator

@fallenbagel fallenbagel commented Feb 17, 2026

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:

  • 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

Release Notes

  • Bug Fixes
    • Watchlist items you previously auto-requested are now excluded from unavailable-item checks, improving accuracy when syncing.
    • Deleting a blocklisted media item now preserves the record but clears external/service identifiers, instead of removing the record entirely.

…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
@fallenbagel fallenbagel requested a review from Copilot February 17, 2026 10:01
@fallenbagel fallenbagel requested a review from a team as a code owner February 17, 2026 10:01
@coderabbitai
Copy link

coderabbitai bot commented Feb 17, 2026

📝 Walkthrough

Walkthrough

Adds 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

Cohort / File(s) Summary
Watchlist Sync Auto-Request Filtering
server/lib/watchlistsync.ts
Queries MediaRequest for the current user's watchlist TMDB IDs, builds a set of "mediaType:tmdbId" identifiers, and filters watchlist items to exclude those already auto-requested before attempting request creation.
Media Deletion with Blocklist Preservation
server/routes/media.ts
Updates DELETE /:id handler to, when media.status is BLOCKLISTED, clear service-related fields (serviceId, serviceId4k, externalServiceId*, externalServiceSlug*, ratingKey*, jellyfinMediaId*) by setting them to null and saving the entity; otherwise proceeds with removal.

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)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 I hopped through lists both new and old,
Found echoes of requests already told.
I nudged the sync to skip what's done,
And kept blocklisted records, tidy and shun.
Hooray—less noise, more carrot cake fun! 🥕

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main objectives: preserving blocklists on media deletion and optimizing watchlist-sync filtering logic.
Linked Issues check ✅ Passed The PR addresses both requirements from issue #2475: preserves blocklisted media by nulling fields instead of deletion, and optimizes watchlist-sync to skip items with existing auto-requests and exclude blocklisted media.
Out of Scope Changes check ✅ Passed All changes are scoped to fixing the blocklist issue: media deletion endpoint and watchlist sync filtering. No unrelated modifications detected.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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

Copy link

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

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@fallenbagel fallenbagel changed the title fix(watchlist-sync): preserve blocklist on media deletion & optimise … fix: preserve blocklist on media deletion & optimise watchlist-sync Feb 17, 2026
Comment on lines +178 to +187
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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or delete/recreate the entity with the blacklisted status instead?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, looks like more future-proof.

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.

Media from watchlist with auto-approve isn't respected in blocklist

2 participants