Skip to content

Add search event reason#546

Merged
Flaminel merged 26 commits into
mainfrom
add_search_event_reason
Apr 6, 2026
Merged

Add search event reason#546
Flaminel merged 26 commits into
mainfrom
add_search_event_reason

Conversation

@Flaminel

@Flaminel Flaminel commented Apr 5, 2026

Copy link
Copy Markdown
Contributor

Relates to #524

@Cleanuparr Cleanuparr deleted a comment from sourcery-ai Bot Apr 5, 2026
@qodo-code-review

Copy link
Copy Markdown

Review Summary by Qodo

Add search event reason tracking with normalized database schema and comprehensive integration tests

✨ Enhancement 🧪 Tests 🐞 Bug fix

Grey Divider

Walkthroughs

Description
• **Refactored search event publishing** to include structured search reasons (SeekerSearchReason
  enum) and per-item event tracking instead of batch events
• **Normalized database schema** for events by introducing SearchEventData entity with
  ItemTitle, SearchType, SearchReason, and GrabbedItems fields, replacing denormalized JSON
  storage
• **Added ArrInstanceId and DownloadClientId foreign keys** to AppEvent for proper relational
  tracking instead of storing instance/client metadata as strings
• **Refactored event publishing interfaces** - PublishSearchTriggered() now accepts individual
  item details with search reason; PublishSearchCompleted() accepts grabbed items list
• **Enhanced download client context tracking** by adding SetDownloadClientContext() helper method
  and applying it across all download client implementations
• **Updated API contracts** - SearchEventResponse now includes ArrInstanceId, ItemTitle, and
  SearchReason instead of InstanceName and item arrays
• **Comprehensive integration test suite** with new IntegrationTestFixture providing real services
  with mocked external boundaries, covering Seeker, Striker, QueueCleaner, DownloadCleaner, and
  MalwareBlocker jobs
• **Frontend enhancements** to display search reasons with severity badges and simplified grabbed
  items as string arrays
• **Fixed download client context setup** in Transmission service queue check method
• **Removed obsolete HTTP client provider tests** and refactored namespace imports across codebase
Diagram
flowchart LR
  A["Search Event<br/>Publishing"] -->|"per-item with<br/>reason"| B["SearchEventData<br/>Entity"]
  B -->|"structured<br/>storage"| C["Events DB<br/>Normalized"]
  D["Job Handlers<br/>Seeker/Striker/<br/>QueueCleaner"] -->|"publish with<br/>ArrInstanceId"| A
  E["Download Clients"] -->|"SetDownloadClientContext"| F["Context Provider"]
  F -->|"track client"| A
  G["API Controller"] -->|"query via<br/>ArrInstanceId"| C
  G -->|"return ItemTitle<br/>& SearchReason"| H["Frontend<br/>Display"]
  I["Integration Tests"] -->|"validate"| D
Loading

Grey Divider

File Changes

1. code/backend/Cleanuparr.Infrastructure.Tests/Features/Jobs/Integration/StrikerIntegrationTests.cs 🧪 Tests +503/-0

Add comprehensive Striker integration tests

• New comprehensive integration test suite for the Striker class with 10 test cases
• Tests cover strike creation, event publishing, notifications, and recurring item detection
• Validates strike data persistence, download item tracking, and JSON event data serialization
• Tests dry run mode, strike limit enforcement, and failed import reason handling

code/backend/Cleanuparr.Infrastructure.Tests/Features/Jobs/Integration/StrikerIntegrationTests.cs


2. code/backend/Cleanuparr.Infrastructure.Tests/Events/EventPublisherTests.cs 🧪 Tests +79/-90

Refactor EventPublisher tests with NSubstitute and search reason

• Migrated from Moq to NSubstitute for all mock objects
• Updated PublishSearchTriggered method calls to use new signature with itemTitle, searchType,
 and searchReason parameters
• Refactored search event data tests to use SearchEventData entity instead of JSON parsing
• Updated assertions to use NSubstitute syntax (Received(), Arg.Is(), etc.)

code/backend/Cleanuparr.Infrastructure.Tests/Events/EventPublisherTests.cs


3. code/backend/Cleanuparr.Infrastructure.Tests/Features/Jobs/Integration/DownloadCleanerIntegrationTests.cs 🧪 Tests +351/-0

Add DownloadCleaner integration tests

• New integration test suite for DownloadCleaner job with 3 test cases
• Tests validate exclusion of ARR-managed and ignored downloads from cleanup
• Tests verify event publishing for cleaned downloads and category changes with full property
 validation
• Includes JSON event data verification for download cleaned and category changed events

code/backend/Cleanuparr.Infrastructure.Tests/Features/Jobs/Integration/DownloadCleanerIntegrationTests.cs


View more (76)
4. code/backend/Cleanuparr.Persistence/Migrations/Events/20260401101043_AddSearchEventData.Designer.cs Database +461/-0

Add SearchEventData migration designer

• New EF Core migration designer file for SearchEventData entity
• Defines schema for search_event_data table with columns for item_title, search_type,
 search_reason, and grabbed_items
• Establishes one-to-one relationship with AppEvent via app_event_id foreign key
• Includes database indexes and cascade delete behavior configuration

code/backend/Cleanuparr.Persistence/Migrations/Events/20260401101043_AddSearchEventData.Designer.cs


5. code/backend/Cleanuparr.Infrastructure/Features/Jobs/Seeker.cs ✨ Enhancement +87/-58

Refactor Seeker to publish per-item search events with reasons

• Refactored search processing to publish individual events per item instead of batch events
• Introduced SeekerProcessResult and SeekerSearchCandidate classes to encapsulate search
 candidates with reasons
• Updated PublishSearchTriggered calls to include SeekerSearchReason parameter
• Modified ProcessRadarrAsync and ProcessSonarrAsync to return structured results with search
 reasons
• Added ArrInstanceId to context provider for event publishing

code/backend/Cleanuparr.Infrastructure/Features/Jobs/Seeker.cs


6. code/backend/Cleanuparr.Infrastructure.Tests/Features/Jobs/Integration/QueueCleanerIntegrationTests.cs 🧪 Tests +332/-0

Add QueueCleaner integration tests

• New integration test suite for QueueCleaner job with 4 test cases
• Tests validate stalled torrent removal, failed import handling, ignored download skipping, and
 private torrent behavior
• Verifies event publishing for marked deletion and queue item deletion with full property
 validation
• Tests notification publishing and search queue item creation for replacement searches

code/backend/Cleanuparr.Infrastructure.Tests/Features/Jobs/Integration/QueueCleanerIntegrationTests.cs


7. code/backend/Cleanuparr.Infrastructure.Tests/Features/Jobs/Integration/MalwareBlockerIntegrationTests.cs 🧪 Tests +268/-0

Add MalwareBlocker integration tests

• New integration test suite for MalwareBlocker job with 3 test cases
• Tests validate malware detection and removal, blocklist configuration checks, and private torrent
 handling
• Verifies event publishing for marked deletion and queue item deletion with full property
 validation
• Tests notification publishing and search queue item creation for replacement searches

code/backend/Cleanuparr.Infrastructure.Tests/Features/Jobs/Integration/MalwareBlockerIntegrationTests.cs


8. code/backend/Cleanuparr.Api.Tests/Features/Seeker/SearchStatsControllerTests.cs 🧪 Tests +59/-80

Refactor SearchStatsController tests for SearchEventData

• Refactored tests to use SearchEventData entity instead of parsing JSON from AppEvent.Data
• Updated test helper AddSearchEvent to create SearchEventData records with itemTitle,
 searchType, searchReason, and grabbedItems
• Simplified assertions by directly accessing SearchEventData properties instead of JSON parsing
• Updated filter tests to use ArrInstanceId instead of InstanceUrl for instance filtering

code/backend/Cleanuparr.Api.Tests/Features/Seeker/SearchStatsControllerTests.cs


9. code/backend/Cleanuparr.Infrastructure.Tests/Features/Jobs/QueueCleanerTests.cs 🧪 Tests +6/-7

Update QueueCleaner tests for instance type access

• Updated assertions in PublishQueueItemRemoveRequest tests to access InstanceType via
 r.Instance.ArrConfig.Type instead of r.InstanceType
• Applied changes across multiple test cases for Radarr, Lidarr, Readarr, and Whisparr instances

code/backend/Cleanuparr.Infrastructure.Tests/Features/Jobs/QueueCleanerTests.cs


10. code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/Transmission/TransmissionServiceQC.cs 🐞 Bug fix +1/-0

Add download client context setup in Transmission service

• Added call to SetDownloadClientContext() in ShouldRemoveFromArrQueueAsync method
• Ensures download client context is properly set before processing download checks

code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/Transmission/TransmissionServiceQC.cs


11. code/backend/Cleanuparr.Infrastructure.Tests/Features/Jobs/Integration/IntegrationTestFixture.cs 🧪 Tests +279/-0

Integration test fixture for job handler testing

• New comprehensive test fixture providing real services (EventPublisher, QueueItemRemover, Striker)
 with NSubstitute mocks at external boundaries
• Manages DataContext, EventsContext, MemoryCache, and FakeTimeProvider for integration testing
• Includes helper methods for configuring mock download services, ARR queue iterators, and capturing
 published messages
• Provides Reset() method to clean state between tests

code/backend/Cleanuparr.Infrastructure.Tests/Features/Jobs/Integration/IntegrationTestFixture.cs


12. code/backend/Cleanuparr.Infrastructure/Events/EventPublisher.cs ✨ Enhancement +46/-51

Refactor search event publishing with structured data

• Refactored PublishSearchTriggered() to accept itemTitle, searchType, and searchReason
 instead of multiple items and instance name
• Added transaction handling for atomic insertion of AppEvent and SearchEventData records
• Updated PublishSearchCompleted() to accept grabbedItems list instead of generic resultData
 object
• Simplified event data storage by moving search-specific data to SearchEventData table
• Added ArrInstanceId and DownloadClientId fields to event entity

code/backend/Cleanuparr.Infrastructure/Events/EventPublisher.cs


13. code/backend/Cleanuparr.Infrastructure.Tests/Features/Jobs/Integration/SeekerIntegrationTests.cs 🧪 Tests +221/-0

Integration tests for Seeker job execution

• New integration tests for Seeker job using IntegrationTestFixture
• Tests cover replacement search triggering, search disabled scenarios, and dry-run behavior
• Verifies SearchEventData creation with correct properties (ItemTitle, SearchType, SearchReason)
• Validates event publishing, notifications, and queue item dequeuing

code/backend/Cleanuparr.Infrastructure.Tests/Features/Jobs/Integration/SeekerIntegrationTests.cs


14. code/backend/Cleanuparr.Api/Features/Seeker/Controllers/SearchStatsController.cs ✨ Enhancement +38/-79

Update search events API to use structured data

• Updated GetEvents() to use ArrInstanceId instead of URL-based instance matching
• Added Include() for SearchEventData relationship
• Changed search filtering to query ItemTitle in SearchEventData instead of JSON Data field
• Refactored response mapping to use SearchEventData properties directly
• Removed JSON deserialization logic for event data

code/backend/Cleanuparr.Api/Features/Seeker/Controllers/SearchStatsController.cs


15. code/backend/Cleanuparr.Persistence/Migrations/Events/20260401101043_AddSearchEventData.cs ⚙️ Configuration changes +203/-0

Database migration for search event data normalization

• Creates new search_event_data table with ItemTitle, SearchType, SearchReason, and GrabbedItems
 columns
• Adds arr_instance_id and download_client_id columns to events table
• Migrates existing search event data from JSON Data column to SearchEventData table
• Removes denormalized columns (instance_type, instance_url, download_client_type,
 download_client_name)
• Includes data migration logic to backfill IDs from main database

code/backend/Cleanuparr.Persistence/Migrations/Events/20260401101043_AddSearchEventData.cs


16. code/backend/Cleanuparr.Persistence/Migrations/Events/EventsContextModelSnapshot.cs ⚙️ Configuration changes +70/-13

Update EF Core model snapshot for normalized schema

• Adds ArrInstanceId and DownloadClientId properties to AppEvent model
• Removes InstanceType, InstanceUrl, DownloadClientType, DownloadClientName properties from events
 table
• Adds SearchEventData entity configuration with one-to-one relationship to AppEvent
• Updates indexes to use ArrInstanceId and DownloadClientId instead of denormalized columns

code/backend/Cleanuparr.Persistence/Migrations/Events/EventsContextModelSnapshot.cs


17. code/backend/Cleanuparr.Infrastructure.Tests/Features/DownloadRemover/Consumers/DownloadRemoverConsumerTests.cs 🧪 Tests +10/-13

Update consumer tests for refactored request model

• Removed InstanceType property from QueueItemRemoveRequest, now derived from
 Instance.ArrConfig.Type
• Updated test assertions to access instance type via request.Instance.ArrConfig.Type
• Fixed import to use Cleanuparr.Domain.Entities.Arr instead of Data.Models.Arr
• Updated CreateArrInstance() helper to accept InstanceType parameter and set ArrConfig

code/backend/Cleanuparr.Infrastructure.Tests/Features/DownloadRemover/Consumers/DownloadRemoverConsumerTests.cs


18. code/backend/Cleanuparr.Infrastructure.Tests/Features/Jobs/SeekerTests.cs 🧪 Tests +7/-14

Update Seeker job unit tests for new API

• Updated mock setup for PublishSearchTriggered() to use new signature with itemTitle,
 searchType, and searchReason
• Updated all test verifications to match new method parameters
• Removed references to Data.Models.Arr namespace

code/backend/Cleanuparr.Infrastructure.Tests/Features/Jobs/SeekerTests.cs


19. code/backend/Cleanuparr.Infrastructure/Features/Jobs/QueueCleaner.cs ✨ Enhancement +13/-10

Add download client tracking to queue cleaner

• Added ArrInstanceId to context provider for event tracking
• Changed to use ExternalOrInternalUrl property instead of manual fallback logic
• Added foundInClient tracking to capture which download client found the item
• Pass downloadClient parameter to PublishQueueItemRemoveRequest()
• Removed instanceType parameter from method call (now derived from instance)

code/backend/Cleanuparr.Infrastructure/Features/Jobs/QueueCleaner.cs


20. code/backend/Cleanuparr.Infrastructure/Features/Jobs/SeekerCommandMonitor.cs ✨ Enhancement +11/-16

Simplify grabbed items tracking in command monitor

• Changed InspectDownloadQueueAsync() return type from object? to List<string>?
• Simplified grabbed items collection to store only titles instead of complex objects
• Updated logging to use simplified grabbed titles list

code/backend/Cleanuparr.Infrastructure/Features/Jobs/SeekerCommandMonitor.cs


21. code/backend/Cleanuparr.Infrastructure/Features/Jobs/MalwareBlocker.cs ✨ Enhancement +14/-11

Add download client tracking to malware blocker

• Added ArrInstanceId to context provider for event tracking
• Changed to use ExternalOrInternalUrl property instead of manual fallback logic
• Added foundInClient tracking to capture which download client found the item
• Pass downloadClient parameter to PublishQueueItemRemoveRequest()
• Removed instanceType parameter from method call

code/backend/Cleanuparr.Infrastructure/Features/Jobs/MalwareBlocker.cs


22. code/backend/Cleanuparr.Infrastructure/Features/Jobs/GenericHandler.cs ✨ Enhancement +14/-7

Refactor queue item removal request handling

• Removed instanceType parameter from PublishQueueItemRemoveRequest(), now derived from
 instance.ArrConfig.Type
• Added downloadClient optional parameter to method signature
• Updated both SeriesSearchItem and SearchItem request creation to remove InstanceType assignment
• Added context provider call for download client when provided
• Removed Data.Models.Arr import

code/backend/Cleanuparr.Infrastructure/Features/Jobs/GenericHandler.cs


23. code/backend/Cleanuparr.Persistence/Models/Events/AppEvent.cs ✨ Enhancement +24/-19

Normalize AppEvent model with foreign keys

• Replaced InstanceType, InstanceUrl, DownloadClientType, DownloadClientName with
 ArrInstanceId and DownloadClientId foreign keys
• Moved old properties to NotMapped attributes for backward compatibility with notifications
• Added SearchEventData navigation property for one-to-one relationship
• Updated indexes to use new ID columns

code/backend/Cleanuparr.Persistence/Models/Events/AppEvent.cs


24. code/backend/Cleanuparr.Infrastructure/Features/DownloadRemover/QueueItemRemover.cs ✨ Enhancement +12/-6

Update queue item remover for refactored request model

• Removed InstanceType from QueueItemRemoveRequest, now derived from
 request.Instance.ArrConfig.Type
• Added ArrInstanceId to context provider
• Changed to use ExternalOrInternalUrl property
• Added support for setting download client context when provided in request
• Updated error message to use request.Instance.ArrConfig.Type
• Removed Data.Models.Arr import

code/backend/Cleanuparr.Infrastructure/Features/DownloadRemover/QueueItemRemover.cs


25. code/backend/Cleanuparr.Infrastructure.Tests/Features/DownloadRemover/QueueItemRemoverTests.cs 🧪 Tests +2/-3

Update queue item remover tests

• Updated test to access instance type via request.Instance.ArrConfig.Type instead of
 request.InstanceType
• Fixed imports to use Cleanuparr.Domain.Entities.Arr instead of Data.Models.Arr

code/backend/Cleanuparr.Infrastructure.Tests/Features/DownloadRemover/QueueItemRemoverTests.cs


26. code/backend/Cleanuparr.Infrastructure/Features/Notifications/NotificationPublisher.cs ✨ Enhancement +7/-8

Update search triggered notification parameters

• Updated NotifySearchTriggered() signature to accept itemTitle, searchType, and
 searchReason instead of instance name and item list
• Simplified notification context building to use new parameters
• Updated notification data dictionary to include search type and reason

code/backend/Cleanuparr.Infrastructure/Features/Notifications/NotificationPublisher.cs


27. code/backend/Cleanuparr.Infrastructure/Features/Context/ContextProvider.cs ✨ Enhancement +11/-0

Add download client context helper method

• Added SetDownloadClient() method to set download client context properties atomically
• Added DownloadClientId and ArrInstanceId keys to Keys class
• Simplified context setting for download client operations

code/backend/Cleanuparr.Infrastructure/Features/Context/ContextProvider.cs


28. code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/DownloadService.cs ✨ Enhancement +6/-3

Add download client context helper

• Added SetDownloadClientContext() protected method to set download client context
• Refactored CleanDownloadsAsync() to use new helper method instead of manual context setting

code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/DownloadService.cs


29. code/backend/Cleanuparr.Infrastructure/Features/DownloadRemover/Models/QueueItemRemoveRequest.cs ✨ Enhancement +9/-8

Refactor queue item remove request model

• Removed InstanceType property (now derived from Instance.ArrConfig.Type)
• Added DownloadClient optional property to track which client found the item
• Fixed imports to use Cleanuparr.Domain.Entities.Arr instead of Data.Models.Arr

code/backend/Cleanuparr.Infrastructure/Features/DownloadRemover/Models/QueueItemRemoveRequest.cs


30. code/backend/Cleanuparr.Persistence/Models/Events/SearchEventData.cs ✨ Enhancement +32/-0

New SearchEventData entity for structured search data

• New entity class for storing structured search event data
• Contains ItemTitle, SearchType, SearchReason, and GrabbedItems properties
• One-to-one relationship with AppEvent via AppEventId foreign key

code/backend/Cleanuparr.Persistence/Models/Events/SearchEventData.cs


31. code/backend/Cleanuparr.Api/Features/Seeker/Contracts/Responses/SearchEventResponse.cs ✨ Enhancement +4/-4

Update search event response contract

• Replaced InstanceName and ItemCount with ArrInstanceId and ItemTitle
• Replaced Items array with single ItemTitle string
• Added SearchReason property
• Changed GrabbedItems from object? to List<string>

code/backend/Cleanuparr.Api/Features/Seeker/Contracts/Responses/SearchEventResponse.cs


32. code/backend/Cleanuparr.Infrastructure/Events/Interfaces/IEventPublisher.cs ✨ Enhancement +2/-2

Update event publisher interface signatures

• Updated PublishSearchTriggered() signature to accept itemTitle, searchType, and
 searchReason
• Updated PublishSearchCompleted() to accept grabbedItems list instead of generic resultData

code/backend/Cleanuparr.Infrastructure/Events/Interfaces/IEventPublisher.cs


33. code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/Transmission/TransmissionServiceDC.cs ✨ Enhancement +1/-3

Use download client context helper

• Refactored context setting to use SetDownloadClientContext() helper method

code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/Transmission/TransmissionServiceDC.cs


34. code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/UTorrent/UTorrentServiceDC.cs ✨ Enhancement +1/-3

Use download client context helper

• Refactored context setting to use SetDownloadClientContext() helper method

code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/UTorrent/UTorrentServiceDC.cs


35. code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/RTorrent/RTorrentServiceDC.cs ✨ Enhancement +1/-3

Use download client context helper

• Refactored context setting to use SetDownloadClientContext() helper method

code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/RTorrent/RTorrentServiceDC.cs


36. code/backend/Cleanuparr.Persistence/EventsContext.cs ✨ Enhancement +10/-0

Add SearchEventData to events context

• Added SearchEventData DbSet for new search event data entity
• Added model configuration for SearchEventData with one-to-one relationship to AppEvent

code/backend/Cleanuparr.Persistence/EventsContext.cs


37. code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/Deluge/DelugeServiceDC.cs ✨ Enhancement +1/-3

Use download client context helper

• Refactored context setting to use SetDownloadClientContext() helper method

code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/Deluge/DelugeServiceDC.cs


38. code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/Deluge/DelugeServiceCB.cs ✨ Enhancement +2/-1

Set download client context in block files check

• Added SetDownloadClientContext() call after finding download item

code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/Deluge/DelugeServiceCB.cs


39. code/backend/Cleanuparr.Infrastructure/Features/Seeker/SeekerSearchCandidate.cs ✨ Enhancement +23/-0

New search candidate record for seeker

• New record class representing a single item selected for proactive search
• Contains ItemId, Name, SeasonNumber, and Reason properties

code/backend/Cleanuparr.Infrastructure/Features/Seeker/SeekerSearchCandidate.cs


40. code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/UTorrent/UTorrentServiceCB.cs ✨ Enhancement +3/-2

Set download client context in block files check

• Added SetDownloadClientContext() call after finding download item

code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/UTorrent/UTorrentServiceCB.cs


41. code/backend/Cleanuparr.Persistence/Models/Configuration/Arr/ArrInstance.cs ✨ Enhancement +8/-0

Add computed URL property to ArrInstance

• Added ExternalOrInternalUrl computed property that returns ExternalUrl if set, otherwise Url
• Marked as NotMapped and JsonIgnore to avoid database persistence

code/backend/Cleanuparr.Persistence/Models/Configuration/Arr/ArrInstance.cs


42. code/backend/Cleanuparr.Api/Controllers/ManualEventsController.cs ✨ Enhancement +1/-3

Simplify manual events search filtering

• Removed search filtering on InstanceUrl and DownloadClientName fields
• Simplified search to only query Message and Data fields

code/backend/Cleanuparr.Api/Controllers/ManualEventsController.cs


43. code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/RTorrent/RTorrentServiceQC.cs ✨ Enhancement +1/-0

Set download client context in queue check

• Added SetDownloadClientContext() call after finding download item

code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/RTorrent/RTorrentServiceQC.cs


44. code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/RTorrent/RTorrentServiceCB.cs ✨ Enhancement +1/-0

Set download client context in block files check

• Added SetDownloadClientContext() call after finding download item

code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/RTorrent/RTorrentServiceCB.cs


45. code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/QBittorrent/QBitServiceCB.cs ✨ Enhancement +1/-0

Set download client context in block files check

• Added SetDownloadClientContext() call after finding download item

code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/QBittorrent/QBitServiceCB.cs


46. code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/Transmission/TransmissionServiceCB.cs ✨ Enhancement +2/-1

Set download client context in block files check

• Added SetDownloadClientContext() call after finding download item

code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/Transmission/TransmissionServiceCB.cs


47. code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/QBittorrent/QBitServiceQC.cs ✨ Enhancement +1/-0

Set download client context in queue check

• Added SetDownloadClientContext() call after finding download item

code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/QBittorrent/QBitServiceQC.cs


48. code/backend/Cleanuparr.Infrastructure/Features/Seeker/SeekerProcessResult.cs ✨ Enhancement +10/-0

New seeker process result record

• New record class representing result of processing an arr instance for proactive search candidates
• Contains Candidates list and AllLibraryIds list

code/backend/Cleanuparr.Infrastructure/Features/Seeker/SeekerProcessResult.cs


49. code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/UTorrent/UTorrentServiceQC.cs ✨ Enhancement +1/-0

Set download client context in queue check

• Added SetDownloadClientContext() call after finding download item

code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/UTorrent/UTorrentServiceQC.cs


50. code/backend/Cleanuparr.Infrastructure/Features/Notifications/INotificationPublisher.cs ✨ Enhancement +1/-1

Update notification publisher interface

• Updated NotifySearchTriggered() signature to accept itemTitle, searchType, and
 searchReason

code/backend/Cleanuparr.Infrastructure/Features/Notifications/INotificationPublisher.cs


51. code/backend/Cleanuparr.Infrastructure/Features/DownloadRemover/Consumers/DownloadRemoverConsumer.cs Miscellaneous +2/-2

Fix import namespace

• Fixed imports to use Cleanuparr.Domain.Entities.Arr instead of Data.Models.Arr

code/backend/Cleanuparr.Infrastructure/Features/DownloadRemover/Consumers/DownloadRemoverConsumer.cs


52. code/backend/Cleanuparr.Domain/Enums/SeekerSearchReason.cs ✨ Enhancement +12/-0

New SeekerSearchReason enum

• New enum defining reasons for search triggering (Missing, QualityCutoffNotMet,
 CustomFormatScoreBelowCutoff, Replacement)
• Includes JsonConverter attribute for serialization

code/backend/Cleanuparr.Domain/Enums/SeekerSearchReason.cs


53. code/backend/Cleanuparr.Infrastructure/Features/DownloadRemover/Interfaces/IQueueItemRemover.cs Miscellaneous +2/-2

Fix import namespace

• Fixed imports to use Cleanuparr.Domain.Entities.Arr instead of Data.Models.Arr

code/backend/Cleanuparr.Infrastructure/Features/DownloadRemover/Interfaces/IQueueItemRemover.cs


54. code/backend/Cleanuparr.Infrastructure.Tests/Features/Jobs/Integration/IntegrationTestCollection.cs 🧪 Tests +9/-0

New integration test collection definition

• New Xunit collection definition for integration tests
• Provides shared IntegrationTestFixture across tests in the collection

code/backend/Cleanuparr.Infrastructure.Tests/Features/Jobs/Integration/IntegrationTestCollection.cs


55. code/backend/Cleanuparr.Domain/Entities/Arr/SearchItem.cs Miscellaneous +1/-1

Fix SearchItem namespace

• Fixed namespace from Data.Models.Arr to Cleanuparr.Domain.Entities.Arr

code/backend/Cleanuparr.Domain/Entities/Arr/SearchItem.cs


56. code/frontend/src/app/features/seeker-stats/searches-tab/searches-tab.component.ts ✨ Enhancement +23/-3

Add search reason formatting to searches tab

• Added SeekerSearchReason import and enum
• Updated formatGrabbedItems() to work with string array instead of complex objects
• Added formatSearchReason() method to display human-readable reason labels
• Added searchReasonSeverity() method to map reasons to badge severity levels

code/frontend/src/app/features/seeker-stats/searches-tab/searches-tab.component.ts


57. code/frontend/src/app/core/models/search-stats.models.ts ✨ Enhancement +11/-4

Update search stats models with new structure

• Added SeekerSearchReason enum with Missing, QualityCutoffNotMet, CustomFormatScoreBelowCutoff,
 Replacement values
• Updated SearchEvent interface to use arrInstanceId, itemTitle, and searchReason instead of
 instanceName, items, itemCount
• Changed grabbedItems type from unknown[] to string[]

code/frontend/src/app/core/models/search-stats.models.ts


58. code/frontend/src/app/features/seeker-stats/searches-tab/searches-tab.component.html ✨ Enhancement +11/-7

Update searches tab template for new data structure

• Replaced item list display with single itemTitle field
• Added search reason badge display with formatting
• Moved item title to separate detail section below main row
• Updated grabbed items display to work with string array
• Added instance type badge display

code/frontend/src/app/features/seeker-stats/searches-tab/searches-tab.component.html


59. code/frontend/src/app/features/seeker-stats/upgrades-tab/upgrades-tab.component.html Formatting +3/-1

Reorganize upgrades tab layout

• Moved title element to separate detail section below main row

code/frontend/src/app/features/seeker-stats/upgrades-tab/upgrades-tab.component.html


60. code/frontend/src/app/features/seeker-stats/quality-tab/quality-tab.component.html Formatting +3/-1

Reorganize quality tab layout

• Moved title element to separate detail section below main row

code/frontend/src/app/features/seeker-stats/quality-tab/quality-tab.component.html


61. code/frontend/src/app/features/seeker-stats/searches-tab/searches-tab.component.scss Formatting +6/-18

Update searches tab styling

• Updated __title styling with padding instead of flex layout
• Removed __meta styles for instance name display
• Updated responsive styles to hide instance badge on mobile
• Adjusted title padding for mobile layout

code/frontend/src/app/features/seeker-stats/searches-tab/searches-tab.component.scss


62. code/frontend/src/app/features/seeker-stats/quality-tab/quality-tab.component.scss Formatting +6/-7

Update quality tab styling

• Updated __title styling with padding and cursor pointer
• Adjusted responsive styles for title section

code/frontend/src/app/features/seeker-stats/quality-tab/quality-tab.component.scss


63. code/frontend/src/app/features/seeker-stats/upgrades-tab/upgrades-tab.component.scss Formatting +3/-10

Update upgrades tab styling

• Updated __title styling with padding
• Adjusted responsive styles for title section

code/frontend/src/app/features/seeker-stats/upgrades-tab/upgrades-tab.component.scss


64. code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/Deluge/DelugeServiceQC.cs ✨ Enhancement +1/-0

Set download client context in queue check

• Added SetDownloadClientContext() call after finding download item

code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/Deluge/DelugeServiceQC.cs


65. code/backend/Cleanuparr.Api/Controllers/EventsController.cs Additional files +0/-2

...

code/backend/Cleanuparr.Api/Controllers/EventsController.cs


66. code/backend/Cleanuparr.Api/DependencyInjection/MainDI.cs Additional files +0/-1

...

code/backend/Cleanuparr.Api/DependencyInjection/MainDI.cs


67. code/backend/Cleanuparr.Domain/Entities/Arr/SeriesSearchItem.cs Additional files +0/-1

...

code/backend/Cleanuparr.Domain/Entities/Arr/SeriesSearchItem.cs


68. code/backend/Cleanuparr.Infrastructure.Tests/Http/DynamicHttpClientProviderFixture.cs Additional files +0/-80

...

code/backend/Cleanuparr.Infrastructure.Tests/Http/DynamicHttpClientProviderFixture.cs


69. code/backend/Cleanuparr.Infrastructure.Tests/Http/DynamicHttpClientProviderTests.cs Additional files +0/-133

...

code/backend/Cleanuparr.Infrastructure.Tests/Http/DynamicHttpClientProviderTests.cs


70. code/backend/Cleanuparr.Infrastructure/Features/Arr/ArrClient.cs Additional files +0/-1

...

code/backend/Cleanuparr.Infrastructure/Features/Arr/ArrClient.cs


71. code/backend/Cleanuparr.Infrastructure/Features/Arr/Interfaces/IArrClient.cs Additional files +0/-1

...

code/backend/Cleanuparr.Infrastructure/Features/Arr/Interfaces/IArrClient.cs


72. code/backend/Cleanuparr.Infrastructure/Features/Arr/LidarrClient.cs Additional files +0/-1

...

code/backend/Cleanuparr.Infrastructure/Features/Arr/LidarrClient.cs


73. code/backend/Cleanuparr.Infrastructure/Features/Arr/RadarrClient.cs Additional files +0/-1

...

code/backend/Cleanuparr.Infrastructure/Features/Arr/RadarrClient.cs


74. code/backend/Cleanuparr.Infrastructure/Features/Arr/ReadarrClient.cs Additional files +0/-1

...

code/backend/Cleanuparr.Infrastructure/Features/Arr/ReadarrClient.cs


75. code/backend/Cleanuparr.Infrastructure/Features/Arr/SonarrClient.cs Additional files +0/-1

...

code/backend/Cleanuparr.Infrastructure/Features/Arr/SonarrClient.cs


76. code/backend/Cleanuparr.Infrastructure/Features/Arr/WhisparrV2Client.cs Additional files +0/-1

...

code/backend/Cleanuparr.Infrastructure/Features/Arr/WhisparrV2Client.cs


77. code/backend/Cleanuparr.Infrastructure/Features/Arr/WhisparrV3Client.cs Additional files +0/-1

...

code/backend/Cleanuparr.Infrastructure/Features/Arr/WhisparrV3Client.cs


78. code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/QBittorrent/QBitServiceDC.cs Additional files +1/-3

...

code/backend/Cleanuparr.Infrastructure/Features/DownloadClient/QBittorrent/QBitServiceDC.cs


79. code/backend/Cleanuparr.Persistence/Migrations/Events/20250614211246_InitialEvents.Designer.cs Additional files +0/-1

...

code/backend/Cleanuparr.Persistence/Migrations/Events/20250614211246_InitialEvents.Designer.cs


Grey Divider

Qodo Logo

@qodo-code-review

qodo-code-review Bot commented Apr 5, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (5) 📎 Requirement gaps (0) 🎨 UX Issues (0)

Grey Divider


Action required

1. LogInformation omits candidate.Reason📎 Requirement gap ≡ Correctness
Description
When Seeker triggers a proactive search, the info-level log message does not include the initiating
reason, making it ambiguous in logs. This reduces troubleshooting value and violates the requirement
to clearly log why a proactive search started.
Code

code/backend/Cleanuparr.Infrastructure/Features/Jobs/Seeker.cs[400]

+            _logger.LogInformation("Search triggered for {Item} | {InstanceUrl}", candidate.Name, arrInstance.Url);
Evidence
The compliance rule requires logs to include the proactive search reason; the updated log line only
prints item and instance URL, while the reason is available as candidate.Reason in the same block
but not logged.

Seeker logs include the reason why a proactive search started
code/backend/Cleanuparr.Infrastructure/Features/Jobs/Seeker.cs[397-400]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The proactive search log line does not include the initiating reason (missing/quality cutoff/CF cutoff/replacement), making logs ambiguous.
## Issue Context
`candidate.Reason` is already computed and passed into `PublishSearchTriggered(...)`, but the info-level log only prints `{Item}` and `{InstanceUrl}`.
## Fix Focus Areas
- code/backend/Cleanuparr.Infrastructure/Features/Jobs/Seeker.cs[397-400]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. SeekerSearchReason missing XML docs 📘 Rule violation ⚙ Maintainability
Description
The new public enum SeekerSearchReason is introduced without XML documentation comments. This
violates the requirement to document public backend API symbols.
Code

code/backend/Cleanuparr.Domain/Enums/SeekerSearchReason.cs[R5-12]

+[JsonConverter(typeof(JsonStringEnumConverter))]
+public enum SeekerSearchReason
+{
+    Missing,
+    QualityCutoffNotMet,
+    CustomFormatScoreBelowCutoff,
+    Replacement,
+}
Evidence
The rule requires /// XML docs with at least `` for public API symbols; the enum declaration has
no XML doc block above it.

Rule 225602: Require XML documentation comments for public backend APIs
code/backend/Cleanuparr.Domain/Enums/SeekerSearchReason.cs[5-12]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`SeekerSearchReason` is a new public enum but lacks required XML documentation comments.
## Issue Context
Compliance requires public backend API symbols to have `/// <summary>...</summary>` (and additional tags as applicable).
## Fix Focus Areas
- code/backend/Cleanuparr.Domain/Enums/SeekerSearchReason.cs[5-12]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. SearchEventData properties undocumented 📘 Rule violation ⚙ Maintainability
Description
The new public EF model SearchEventData has several public properties without XML documentation
comments. This violates the requirement to provide XML docs for public backend APIs and members.
Code

code/backend/Cleanuparr.Persistence/Models/Events/SearchEventData.cs[R13-26]

+    [Key]
+    public Guid Id { get; set; } = Guid.CreateVersion7();
+
+    public Guid AppEventId { get; set; }
+
+    [JsonIgnore]
+    public AppEvent AppEvent { get; set; } = null!;
+
+    [MaxLength(500)]
+    public string ItemTitle { get; set; } = string.Empty;
+
+    public SeekerSearchType SearchType { get; set; }
+
+    public SeekerSearchReason SearchReason { get; set; }
Evidence
The rule requires XML docs for public properties; while the class has a ``, the public properties
Id, AppEventId, AppEvent, ItemTitle, SearchType, and SearchReason have no XML doc
blocks.

Rule 225602: Require XML documentation comments for public backend APIs
code/backend/Cleanuparr.Persistence/Models/Events/SearchEventData.cs[13-26]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
New public properties on `SearchEventData` are missing required XML documentation.
## Issue Context
Compliance requires XML docs on public properties/members, not only the class summary.
## Fix Focus Areas
- code/backend/Cleanuparr.Persistence/Models/Events/SearchEventData.cs[13-26]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (6)
4. AppEvent new properties undocumented 📘 Rule violation ⚙ Maintainability
Description
New public properties on AppEvent (including notification-only NotMapped properties) lack XML
documentation comments. This violates the requirement to document public backend API members.
Code

code/backend/Cleanuparr.Persistence/Models/Events/AppEvent.cs[R84-98]

+    public SearchEventData? SearchEventData { get; set; }
+
+    // Used only for notifications
+
+    [NotMapped]
+    public InstanceType? InstanceType { get; set; }
+
+    [NotMapped]
+    public string? InstanceUrl { get; set; }
+
+    [NotMapped]
+    public DownloadClientTypeName? DownloadClientType { get; set; }
+
+    [NotMapped]
+    public string? DownloadClientName { get; set; }
Evidence
The compliance rule requires XML docs for public properties; the newly added public properties in
this range have no /// documentation blocks.

Rule 225602: Require XML documentation comments for public backend APIs
code/backend/Cleanuparr.Persistence/Models/Events/AppEvent.cs[84-98]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
New public `AppEvent` properties were added without required XML documentation comments.
## Issue Context
This includes `SearchEventData` and notification-only `[NotMapped]` properties.
## Fix Focus Areas
- code/backend/Cleanuparr.Persistence/Models/Events/AppEvent.cs[84-98]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. SearchEventResponse fields undocumented 📘 Rule violation ⚙ Maintainability
Description
Modified public API contract SearchEventResponse includes new/changed public properties without
XML documentation comments. This violates the public backend API XML documentation requirement.
Code

code/backend/Cleanuparr.Api/Features/Seeker/Contracts/Responses/SearchEventResponse.cs[R9-16]

+    public Guid? ArrInstanceId { get; init; }
public string? InstanceType { get; init; }
-    public int ItemCount { get; init; }
-    public List<string> Items { get; init; } = [];
+    public string ItemTitle { get; init; } = string.Empty;
public SeekerSearchType SearchType { get; init; }
+    public SeekerSearchReason? SearchReason { get; init; }
public SearchCommandStatus? SearchStatus { get; init; }
public DateTime? CompletedAt { get; init; }
-    public object? GrabbedItems { get; init; }
+    public List<string> GrabbedItems { get; init; } = [];
Evidence
The rule requires XML docs for public properties; the response contract’s public properties in this
modified area have no /// XML documentation.

Rule 225602: Require XML documentation comments for public backend APIs
code/backend/Cleanuparr.Api/Features/Seeker/Contracts/Responses/SearchEventResponse.cs[9-16]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`SearchEventResponse` (public API contract) was modified but its public properties lack required XML documentation.
## Issue Context
Compliance expects `/// <summary>` and, where applicable, `<param>`/`<returns>` tags for public API surface.
## Fix Focus Areas
- code/backend/Cleanuparr.Api/Features/Seeker/Contracts/Responses/SearchEventResponse.cs[9-16]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. GetEvents missing `` docs 📘 Rule violation ⚙ Maintainability
Description
The modified controller action GetEvents has an XML ` but lacks required  and ` tags for its
public API surface. This violates the XML documentation requirement for public backend APIs.
Code

code/backend/Cleanuparr.Api/Features/Seeker/Controllers/SearchStatsController.cs[R119-123]

/// <summary>
-    /// Gets paginated search-triggered events with decoded data.
-    /// Supports optional text search across item names in event data.
+    /// Gets paginated search-triggered events
/// </summary>
[HttpGet("events")]
public async Task<IActionResult> GetEvents(
Evidence
The rule requires documenting public methods including parameters and return values; the modified
XML doc remains summary-only while the method has multiple parameters and returns IActionResult.

Rule 225602: Require XML documentation comments for public backend APIs
code/backend/Cleanuparr.Api/Features/Seeker/Controllers/SearchStatsController.cs[119-128]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`GetEvents` is a public backend API method and was modified, but its XML documentation lacks `<param>` and `<returns>` tags.
## Issue Context
Compliance requires documenting parameters and return values for public methods.
## Fix Focus Areas
- code/backend/Cleanuparr.Api/Features/Seeker/Controllers/SearchStatsController.cs[119-128]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. QueueItemRemoverTests uses Assert.*📘 Rule violation ⚙ Maintainability
Description
The modified unit test uses Assert.* assertions (and Moq-based setup) instead of Shouldly (and
NSubstitute). This violates the standard backend unit testing stack requirement.
Code

code/backend/Cleanuparr.Infrastructure.Tests/Features/DownloadRemover/QueueItemRemoverTests.cs[R374-377]

        () => _queueItemRemover.RemoveQueueItemAsync(request));
    Assert.Contains("might have already been deleted", exception.Message);
-        Assert.Contains(request.InstanceType.ToString(), exception.Message);
+        Assert.Contains(request.Instance.ArrConfig.Type.ToString(), exception.Message);
Evidence
The compliance checklist requires Shouldly assertions and NSubstitute mocks in backend unit tests;
the modified lines use Assert.Contains(...) and the surrounding test uses Moq patterns
(It.IsAny).

Rule 225592: Use xUnit with NSubstitute and Shouldly in backend unit tests
code/backend/Cleanuparr.Infrastructure.Tests/Features/DownloadRemover/QueueItemRemoverTests.cs[364-377]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A modified unit test uses `Assert.*` and Moq-style setup instead of the required Shouldly + NSubstitute stack.
## Issue Context
Touched tests should conform to the standard backend unit testing libraries.
## Fix Focus Areas
- code/backend/Cleanuparr.Infrastructure.Tests/Features/DownloadRemover/QueueItemRemoverTests.cs[364-377]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


8. Dropped columns still modeled🐞 Bug ≡ Correctness
Description
EventsContext’s reflection-based enum conversion loop recreates AppEvent’s [NotMapped] enums
(InstanceType/DownloadClientType) as EF properties, so the EF model still expects
instance_type/download_client_type columns. The migration drops those columns, so after upgrading,
inserts/queries against events can fail with missing-column errors.
Code

code/backend/Cleanuparr.Persistence/Models/Events/AppEvent.cs[R84-98]

+    public SearchEventData? SearchEventData { get; set; }
+
+    // Used only for notifications
+
+    [NotMapped]
+    public InstanceType? InstanceType { get; set; }
+
+    [NotMapped]
+    public string? InstanceUrl { get; set; }
+
+    [NotMapped]
+    public DownloadClientTypeName? DownloadClientType { get; set; }
+
+    [NotMapped]
+    public string? DownloadClientName { get; set; }
Evidence
AppEvent marks InstanceType and DownloadClientType as [NotMapped], but EventsContext iterates all
CLR enum properties and calls Property(property.Name), which adds them back into the EF model as
properties. The EventsContextModelSnapshot still contains download_client_type and instance_type
columns, while the AddSearchEventData migration drops them—creating a schema/model mismatch that
will break runtime SQL after migration.

code/backend/Cleanuparr.Persistence/EventsContext.cs[96-116]
code/backend/Cleanuparr.Persistence/Models/Events/AppEvent.cs[84-98]
code/backend/Cleanuparr.Persistence/Migrations/Events/EventsContextModelSnapshot.cs[47-58]
code/backend/Cleanuparr.Persistence/Migrations/Events/20260401101043_AddSearchEventData.cs[118-141]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`EventsContext.OnModelCreating` uses reflection over CLR properties and calls `modelBuilder.Entity(...).Property(property.Name)` for every enum. This inadvertently adds EF properties for enums that are intentionally `[NotMapped]` on `AppEvent` (e.g., `InstanceType`, `DownloadClientType`).
Because the new migration drops the corresponding columns, the EF model/schema diverge and runtime queries/inserts against `events` can fail after upgrading.
## Issue Context
- `AppEvent` keeps several enum/string fields only for notifications and marks them `[NotMapped]`.
- The global “convert all enums to lowercase strings” loop ignores `[NotMapped]` and reintroduces them into the EF model.
- Migration `20260401101043_AddSearchEventData` drops the old columns.
## Fix Focus Areas
- code/backend/Cleanuparr.Persistence/EventsContext.cs[96-116]
- code/backend/Cleanuparr.Persistence/Models/Events/AppEvent.cs[84-98]
- code/backend/Cleanuparr.Persistence/Migrations/Events/20260401101043_AddSearchEventData.cs[118-141]
## Suggested fix approach
1. Change the enum conversion loop to iterate EF *model* properties (not CLR reflection), e.g. `foreach (var prop in entityType.GetProperties())` and apply converters only when `prop.ClrType` is an enum/nullable-enum.
2. Alternatively, explicitly skip `[NotMapped]` properties (via reflection attribute check) and/or hard-skip `InstanceType`/`DownloadClientType` names.
3. Regenerate the migration designer/snapshot so the post-migration model no longer includes `instance_type`/`download_client_type` if you intend to drop them (or, if you intend to keep them, remove the `DropColumn` operations).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


9. GrabbedItems backfill incompatible 🐞 Bug ☼ Reliability
Description
The migration backfills search_event_data.grabbed_items with
json_extract(e.data,'$.GrabbedItems'), but the new SearchEventData.GrabbedItems mapping is a
List<string> primitive collection. If legacy JSON stored grabbed items as objects (or any non-string
array), EF will fail to materialize SearchEventData and break SearchStats queries.
Code

code/backend/Cleanuparr.Persistence/Migrations/Events/20260401101043_AddSearchEventData.cs[R96-110]

+            // Step 4: Migrate search event data from JSON Data column
+            migrationBuilder.Sql("""
+                INSERT INTO search_event_data (id, app_event_id, item_title, search_type, search_reason, grabbed_items)
+                SELECT
+                    lower(hex(randomblob(4)) || '-' || hex(randomblob(2)) || '-' || hex(randomblob(2)) || '-' || hex(randomblob(2)) || '-' || hex(randomblob(6))),
+                    e.id,
+                    COALESCE(json_extract(e.data, '$.Items[0]'), 'Unknown'),
+                    COALESCE(LOWER(json_extract(e.data, '$.SearchType')), 'proactive'),
+                    'missing',
+                    COALESCE(json_extract(e.data, '$.GrabbedItems'), '[]')
+                FROM events e
+                WHERE e.event_type = 'searchtriggered'
+                  AND e.data IS NOT NULL
+                  AND e.data != '';
+                """);
Evidence
SearchEventData defines GrabbedItems as List, which EF maps as a primitive collection stored in a
single TEXT column (JSON array of strings). The migration writes whatever
json_extract(...'$.GrabbedItems') returns into that column without normalizing/validating to a
JSON string-array, so incompatible legacy shapes will cause JSON conversion/materialization failures
when including SearchEventData.

code/backend/Cleanuparr.Persistence/Models/Events/SearchEventData.cs[24-31]
code/backend/Cleanuparr.Persistence/Migrations/Events/20260401101043_AddSearchEventData.cs[96-110]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`search_event_data.grabbed_items` is mapped to `List<string>` but the migration backfills it using `json_extract(e.data,'$.GrabbedItems')` with no normalization. Any legacy rows where `$.GrabbedItems` is not a JSON array of strings can cause EF JSON conversion/materialization failures.
## Issue Context
- New model: `SearchEventData.GrabbedItems` is `List<string>`.
- Migration: copies raw JSON from legacy `events.data`.
## Fix Focus Areas
- code/backend/Cleanuparr.Persistence/Migrations/Events/20260401101043_AddSearchEventData.cs[96-110]
- code/backend/Cleanuparr.Persistence/Models/Events/SearchEventData.cs[24-31]
## Suggested fix approach
In the Step 4 insert, build a JSON array of strings explicitly.
- If legacy `$.GrabbedItems` is an array of objects, extract `$.Title`.
- If it’s already an array of strings, keep the string values.
- Otherwise, default to `'[]'`.
Example SQLite approach (sketch):
- Use `json_each(e.data, '$.GrabbedItems')` and `json_group_array(...)` to build a normalized array.
- Inside the aggregation: `CASE WHEN json_type(value)='object' THEN json_extract(value,'$.Title') ELSE value END`.
- Wrap with `COALESCE(..., '[]')` and ensure the result is valid JSON array text.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

10. Events lose source details🐞 Bug ≡ Correctness
Description
The migration drops instance_type/instance_url/download_client_type/download_client_name and
AppEvent now marks these fields [NotMapped], but EventsController returns AppEvent rows directly
from EventsContext. As a result, the Events UI’s “Source” section will be empty for events loaded
from the database unless the API re-hydrates these fields from ArrInstanceId/DownloadClientId.
Code

code/backend/Cleanuparr.Persistence/Migrations/Events/20260401101043_AddSearchEventData.cs[R127-141]

+            migrationBuilder.DropColumn(
+                name: "instance_type",
+                table: "events");
+
+            migrationBuilder.DropColumn(
+                name: "instance_url",
+                table: "events");
+
+            migrationBuilder.DropColumn(
+                name: "download_client_type",
+                table: "events");
+
+            migrationBuilder.DropColumn(
+                name: "download_client_name",
+                table: "events");
Evidence
AppEvent no longer persists source fields (they are [NotMapped]) and the migration removes the
underlying columns. EventsController fetches and returns AppEvent entities without any
join/mapping step to repopulate InstanceType/Url/DownloadClientType/Name, while the frontend renders
these fields in the Events page when present.

code/backend/Cleanuparr.Persistence/Models/Events/AppEvent.cs[86-98]
code/backend/Cleanuparr.Persistence/Migrations/Events/20260401101043_AddSearchEventData.cs[127-141]
code/backend/Cleanuparr.Api/Controllers/EventsController.cs[53-110]
code/frontend/src/app/features/events/events.component.html[122-136]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The PR drops persisted event source columns and marks the corresponding `AppEvent` properties `[NotMapped]`, but `EventsController` still returns `AppEvent` rows directly. This removes `instanceType/instanceUrl/downloadClientType/downloadClientName` from DB-loaded events, degrading the Events UI.
## Issue Context
- New persisted identifiers: `ArrInstanceId`, `DownloadClientId`.
- Legacy display fields are removed from the `events` table.
- Frontend Events page conditionally renders these source fields.
## Fix Focus Areas
- code/backend/Cleanuparr.Api/Controllers/EventsController.cs[53-110]
- code/backend/Cleanuparr.Persistence/Models/Events/AppEvent.cs[86-98]
- code/backend/Cleanuparr.Persistence/Migrations/Events/20260401101043_AddSearchEventData.cs[127-141]
## Suggested fix approach
Choose one:
1. **API re-hydration (recommended):** Inject `DataContext` into `EventsController`, fetch referenced Arr instances/download clients by `ArrInstanceId`/`DownloadClientId`, and populate the `[NotMapped]` display properties on the returned `AppEvent` list before returning.
2. **Keep denormalized columns:** Don’t drop the legacy columns and keep them mapped/persisted (undo `DropColumn` and avoid `[NotMapped]` for those properties). This is simpler but keeps duplication.
3. **Frontend change:** Update the Events UI/model to display IDs or fetch source data separately (more work and worse UX).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

@Flaminel

Flaminel commented Apr 5, 2026

Copy link
Copy Markdown
Contributor Author

@greptileai review

@greptile-apps

greptile-apps Bot commented Apr 5, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds a SeekerSearchReason enum and a new SearchEventData table to store structured per-item search events, replacing the old JSON blob approach in the data column. Seeker is also refactored to trigger searches individually per item so each gets its own event and command tracker.

  • The grabbed_items backfill in the migration copies old object-format JSON ([{Title, Status, Protocol}]) directly into a column that EF Core's PrimitiveCollection<string> expects to contain a plain string array, causing deserialization failures for any existing user with completed search events that captured grabbed items.
  • The model snapshot still maps the two columns the migration drops (instance_type, download_client_type), so the next dotnet ef migrations add will generate a migration attempting to re-drop non-existent columns and fail at runtime.

Confidence Score: 4/5

Not safe to merge as-is — two P1 migration issues will corrupt grabbed-item data for existing users and break future migration generation.

The feature logic and refactoring are well-executed, but the migration has a data-format mismatch for GrabbedItems backfill and the snapshot is inconsistent with the dropped columns, both of which have direct runtime consequences.

20260401101043_AddSearchEventData.cs (grabbed_items backfill SQL) and EventsContextModelSnapshot.cs (stale mapped columns)

Important Files Changed

Filename Overview
code/backend/Cleanuparr.Persistence/Migrations/Events/20260401101043_AddSearchEventData.cs Adds SearchEventData table and backfills data; GrabbedItems backfill stores incompatible object-format JSON for PrimitiveCollection
code/backend/Cleanuparr.Persistence/Migrations/Events/EventsContextModelSnapshot.cs Snapshot still maps InstanceType and DownloadClientType as persisted columns despite migration dropping them, breaking future migration generation
code/backend/Cleanuparr.Infrastructure/Features/Jobs/Seeker.cs Refactored to search items individually with per-item events and reasons; clean structural improvement
code/backend/Cleanuparr.Infrastructure/Events/EventPublisher.cs Replaces JSON blob with structured SearchEventData; adds transaction for atomic AppEvent+SearchEventData insert
code/backend/Cleanuparr.Persistence/Models/Events/AppEvent.cs Instance/client fields moved from DB columns to [NotMapped] for in-memory notification use; adds SearchEventData navigation
code/backend/Cleanuparr.Domain/Enums/SeekerSearchReason.cs New enum with correct JsonStringEnumConverter attribute; covers all expected search reason cases
code/backend/Cleanuparr.Infrastructure/Features/Seeker/SeekerSearchCandidate.cs Clean record type bundling ItemId, Name, SeasonNumber, and Reason for a search candidate
code/backend/Cleanuparr.Persistence/Models/Events/SearchEventData.cs New structured model for search event data; GrabbedItems uses PrimitiveCollection pattern correctly in new records
code/backend/Cleanuparr.Api/Features/Seeker/Controllers/SearchStatsController.cs Cleaned up to use structured SearchEventData; filters now use IDs instead of URL string matching
code/frontend/src/app/features/seeker-stats/searches-tab/searches-tab.component.ts Correctly updated for new API shape; adds searchReason severity/format helpers

Sequence Diagram

sequenceDiagram
    participant Seeker
    participant ArrClient
    participant EventPublisher
    participant DB as EventsDB
    participant SCM as SeekerCommandMonitor

    loop For each SeekerSearchCandidate
        Seeker->>ArrClient: SearchItemsAsync(arrInstance, [searchItem])
        ArrClient-->>Seeker: commandIds
        Seeker->>EventPublisher: PublishSearchTriggered(title, type, reason)
        EventPublisher->>DB: BEGIN TRANSACTION
        EventPublisher->>DB: INSERT AppEvent (SearchTriggered)
        EventPublisher->>DB: INSERT SearchEventData (title, type, reason)
        EventPublisher->>DB: COMMIT
        EventPublisher-->>Seeker: eventId
        Seeker->>DB: SaveCommandTracker(eventId, commandIds)
    end

    loop Poll command status
        SCM->>ArrClient: GetCommandAsync(commandId)
        ArrClient-->>SCM: commandStatus
    end

    SCM->>ArrClient: GetQueueAsync
    ArrClient-->>SCM: queueRecords (grabbed titles)
    SCM->>EventPublisher: PublishSearchCompleted(eventId, Completed, grabbedTitles)
    EventPublisher->>DB: UPDATE AppEvent.SearchStatus = Completed
    EventPublisher->>DB: UPDATE SearchEventData.GrabbedItems = [titles]
Loading

Reviews (1): Last reviewed commit: "improved Seeker process response" | Re-trigger Greptile

Comment thread code/backend/Cleanuparr.Api/Features/Seeker/Controllers/SearchStatsController.cs Outdated
Comment thread code/backend/Cleanuparr.Infrastructure/Features/Jobs/Seeker.cs Outdated
Comment thread code/backend/Cleanuparr.Domain/Enums/SeekerSearchReason.cs
Comment thread code/backend/Cleanuparr.Persistence/Models/Events/AppEvent.cs
Comment thread code/backend/Cleanuparr.Persistence/Models/Events/AppEvent.cs
@Flaminel Flaminel force-pushed the add_search_event_reason branch from 01c2d36 to fd41cb6 Compare April 5, 2026 20:36
@Flaminel Flaminel merged commit 80b46df into main Apr 6, 2026
12 checks passed
@Flaminel Flaminel deleted the add_search_event_reason branch April 6, 2026 06:59
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.

1 participant