- Create IEditorResource interface for common editor properties
- Create BaseMediaEditorController with common bulk edit/delete logic
- Extract tag handling (Add/Remove/Replace) to base class
- BookEditorController: 92 -> 36 lines (-56)
- AudiobookEditorController: 92 -> 36 lines (-56)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: admin <admin@ardentleatherworks.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Add data access and business logic layers for Music support:
- ArtistRepository/ArtistService for artist management
- AlbumRepository/AlbumService for album management
- TrackRepository/TrackService for track management
- MusicFileRepository for audio file management
Co-authored-by: admin <admin@ardentleatherworks.com>
Add foundation for Music support:
- Artist entity (parallel to Author)
- Album entity (parallel to Series)
- Track entity (extends MediaItem)
- MusicFile entity for audio files
- Migration 250 creates Artists, Albums, Tracks, MusicFiles tables
- Register all entities in TableMapping
Co-authored-by: admin <admin@ardentleatherworks.com>
Book and Audiobook now extend MediaItem instead of ModelBase,
eliminating duplicate properties and enabling cross-media operations.
- Remove duplicate MediaType, Monitored, Path, Tags, etc. from entities
- Implement abstract GetTitle() and GetYear() methods
- Add migration 249 (documentation only - no DB changes needed)
Co-authored-by: admin <admin@ardentleatherworks.com>
- Add BookStatistics and AudiobookStatistics services
- Create unified Dashboard with media type stats
- Add statistics to Book/Audiobook controllers
- Make dashboard the default landing page
Closes#6
Co-authored-by: admin <admin@ardentleatherworks.com>
* feat(monitoring): implement hierarchical monitoring for Author/Series/Book/Audiobook
- Add cascade logic: unmonitoring parent cascades to children
- Re-monitoring parent does not auto-monitor children (explicit control)
- EffectivelyMonitored computed from item AND all ancestors
- Database indexes for efficient cascade queries (migration 248)
- AuthorMonitoringChangedEvent and SeriesMonitoringChangedEvent
- EffectivelyMonitored field added to Book/Audiobook API resources
Closes#2🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor(monitoring): reduce code duplication in HierarchicalMonitoringService
- Extract common ancestor check to IsAncestorUnmonitored helper
- Consolidate monitoring context retrieval to GetMonitoringContext
- Create generic UnmonitorEntities helper for cascade operations
- Reduce code from 302 to 233 lines while preserving all functionality
* ci(sonar): exclude intentional structural duplication from CPD
* ci(codeql): exclude user-controlled-bypass for monitoring cascade logic
---------
Co-authored-by: admin <admin@ardentleatherworks.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* fix: address SonarCloud code quality issues
- Remove unused private fields from services and repositories
- Replace Object.assign with spread operator in Redux actions
- Use structuredClone instead of _.cloneDeep
- Add exception parameters to catch clause logging
- Use Number.parseInt instead of parseInt in detail pages
- Mark React component props as readonly
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* docs: update CHANGELOG with Phase 2 multi-media work
* fix: update labeler.yml for actions/labeler v6 format
---------
Co-authored-by: admin <admin@ardentleatherworks.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* feat(core): add AddAuthorService and AddSeriesService with validators
* feat(api): add Author and Series lookup controllers
* feat(ui): add Author and Series frontend index pages
* feat(ui): add Book and Audiobook search pages
* feat(ui): add Book and Audiobook detail pages
* feat(ui): add Author and Series detail pages
---------
Co-authored-by: admin <admin@ardentleatherworks.com>
* feat(api): add Book and Audiobook lookup and editor controllers
Adds search and bulk edit functionality for Books and Audiobooks:
- BookLookupController: search by ISBN, ISBN13, ASIN, ForeignId, title
- AudiobookLookupController: search by ISBN, ASIN, narrator, title
- BookEditorController: bulk update/delete books
- AudiobookEditorController: bulk update/delete audiobooks
- Editor validators and resources for both entity types
* feat(core): add AddBookService and AddAudiobookService
Adds services for adding new books and audiobooks with validation:
- AddBookService: handles book creation with path generation
- AddAudiobookService: handles audiobook creation with narrator-aware paths
- AddBookValidator: validates book additions
- AddAudiobookValidator: validates audiobook additions
* feat(core): add BookFile and AudiobookFile entities and repositories
Adds file tracking infrastructure for books and audiobooks:
- BookFile entity with format tracking
- AudiobookFile entity with audio metadata (duration, bitrate, etc.)
- BookFileRepository for book file queries
- AudiobookFileRepository for audiobook file queries
- Database migration for new tables
- Table mappings for new entities
* feat(metadata): add Book and Audiobook metadata provider interfaces
Adds metadata provider infrastructure for books and audiobooks:
- IProvideBookInfo: interface for book metadata lookups
- IProvideAudiobookInfo: interface for audiobook metadata lookups
- BookMetadata: model for book metadata from external sources
- AudiobookMetadata: model for audiobook metadata with narrator info
- BookInfoProxy: stub implementation (to be replaced with Goodreads, etc.)
- AudiobookInfoProxy: stub implementation (to be replaced with Audible, etc.)
* feat(api): add Author and Series repositories, services, and API controllers
* feat(ui): add Book and Audiobook Redux store and index pages
---------
Co-authored-by: admin <admin@ardentleatherworks.com>
Adds the complete data layer for Books and Audiobooks:
- BookRepository with query methods for ISBN, ASIN, Author, Series
- BookService with business logic and event publishing
- AudiobookRepository with narrator-aware query methods
- AudiobookService with duration and narrator support
- Domain events for both entity types
- REST API controllers with full CRUD operations
Co-authored-by: admin <admin@ardentleatherworks.com>
Add entities for books and audiobooks with:
- Book entity: title, ISBN, ASIN, publisher, author/series links
- Audiobook entity: title, narrator, duration, abridged flag, book link
- Database migration 246 for Books and Audiobooks tables
- Entity registrations in TableMapping
Note: Depends on PR #118 (Author/Series tables) for full FK support.
Co-authored-by: admin <admin@ardentleatherworks.com>
Add navigation sidebar entries for Books and Audiobooks with:
- New BOOK and AUDIOBOOK icons in props
- Books/Audiobooks sections in PageSidebar with Add New/Import children
- Routes for /books and /audiobooks paths
- Placeholder index pages for both media types
- Translation strings for Books/Audiobooks
Part of Phase 3 UI work for Issue #7.
Co-authored-by: admin <admin@ardentleatherworks.com>
* feat(db): add MediaType discriminator to Movies table
Adds foundation for multi-media support by:
- Adding MediaType column to Movies table (migration 244)
- Adding MediaType property to Movie entity, defaulting to Movie
Existing movies will have MediaType=1 (Movie) after migration.
This prepares for future Book and Audiobook media types.
Addresses Issue #1
* refactor(core): create MediaItem abstract base class
Extracts common properties from Movie into a new MediaItem base class:
- MediaType, Monitored, QualityProfileId
- Path, RootFolderPath, Added, Tags, LastSearchTime
Movie now inherits from MediaItem and implements abstract methods
GetTitle() and GetYear() while maintaining backward-compatible
Title/Year property accessors.
This prepares for Book and Audiobook entities that will share
the same base structure.
Addresses Issue #1
* feat(core): add Author and Series entities for hierarchical monitoring
Introduces hierarchical structure for books/audiobooks:
- Author entity: tracks authors with monitoring, quality profiles, paths
- Series entity: groups books/audiobooks by series, linked to Author
- MediaItem: adds AuthorId and SeriesId for hierarchy support
- Migration 245: creates Authors and Series tables, adds columns to Movies
This enables Author → Series → Item monitoring inheritance for
future book and audiobook support.
Addresses Issue #2
* feat(core): add generic IProvideMediaInfo interface
Introduces generic metadata provider interfaces:
- IProvideMediaInfo<T>: Base interface for all metadata providers
- GetByExternalId, GetById, GetBulkInfo
- GetTrending, GetPopular, GetChangedItems
- ISearchableMediaProvider<T>: Search capability interface
- SearchByTitle with optional year filtering
These interfaces establish the contract for future book and
audiobook metadata providers while maintaining compatibility
with the existing IProvideMovieInfo.
Addresses Issue #3
* feat(qualities): add book and audiobook quality definitions
Add quality source types and definitions for eBooks and audiobooks:
- EBOOK source: Unknown, EPUB, MOBI, AZW3, PDF, TXT
- AUDIOBOOK source: Unknown, MP3-128, MP3-320, M4B, FLAC
Quality definitions include appropriate size limits for each format.
New qualities auto-seed to database via QualityDefinitionService on startup.
Closes#4, Closes#5
---------
Co-authored-by: admin <admin@ardentleatherworks.com>
* feat(db): add MediaType discriminator to Movies table
Adds foundation for multi-media support by:
- Adding MediaType column to Movies table (migration 244)
- Adding MediaType property to Movie entity, defaulting to Movie
Existing movies will have MediaType=1 (Movie) after migration.
This prepares for future Book and Audiobook media types.
Addresses Issue #1
* refactor(core): create MediaItem abstract base class
Extracts common properties from Movie into a new MediaItem base class:
- MediaType, Monitored, QualityProfileId
- Path, RootFolderPath, Added, Tags, LastSearchTime
Movie now inherits from MediaItem and implements abstract methods
GetTitle() and GetYear() while maintaining backward-compatible
Title/Year property accessors.
This prepares for Book and Audiobook entities that will share
the same base structure.
Addresses Issue #1
* feat(core): add Author and Series entities for hierarchical monitoring
Introduces hierarchical structure for books/audiobooks:
- Author entity: tracks authors with monitoring, quality profiles, paths
- Series entity: groups books/audiobooks by series, linked to Author
- MediaItem: adds AuthorId and SeriesId for hierarchy support
- Migration 245: creates Authors and Series tables, adds columns to Movies
This enables Author → Series → Item monitoring inheritance for
future book and audiobook support.
Addresses Issue #2
* feat(core): add generic IProvideMediaInfo interface
Introduces generic metadata provider interfaces:
- IProvideMediaInfo<T>: Base interface for all metadata providers
- GetByExternalId, GetById, GetBulkInfo
- GetTrending, GetPopular, GetChangedItems
- ISearchableMediaProvider<T>: Search capability interface
- SearchByTitle with optional year filtering
These interfaces establish the contract for future book and
audiobook metadata providers while maintaining compatibility
with the existing IProvideMovieInfo.
Addresses Issue #3
---------
Co-authored-by: admin <admin@ardentleatherworks.com>
* feat(db): add MediaType discriminator to Movies table
Adds foundation for multi-media support by:
- Adding MediaType column to Movies table (migration 244)
- Adding MediaType property to Movie entity, defaulting to Movie
Existing movies will have MediaType=1 (Movie) after migration.
This prepares for future Book and Audiobook media types.
Addresses Issue #1
* refactor(core): create MediaItem abstract base class
Extracts common properties from Movie into a new MediaItem base class:
- MediaType, Monitored, QualityProfileId
- Path, RootFolderPath, Added, Tags, LastSearchTime
Movie now inherits from MediaItem and implements abstract methods
GetTitle() and GetYear() while maintaining backward-compatible
Title/Year property accessors.
This prepares for Book and Audiobook entities that will share
the same base structure.
Addresses Issue #1
* feat(core): add Author and Series entities for hierarchical monitoring
Introduces hierarchical structure for books/audiobooks:
- Author entity: tracks authors with monitoring, quality profiles, paths
- Series entity: groups books/audiobooks by series, linked to Author
- MediaItem: adds AuthorId and SeriesId for hierarchy support
- Migration 245: creates Authors and Series tables, adds columns to Movies
This enables Author → Series → Item monitoring inheritance for
future book and audiobook support.
Addresses Issue #2
---------
Co-authored-by: admin <admin@ardentleatherworks.com>
* feat(db): add MediaType discriminator to Movies table
Adds foundation for multi-media support by:
- Adding MediaType column to Movies table (migration 244)
- Adding MediaType property to Movie entity, defaulting to Movie
Existing movies will have MediaType=1 (Movie) after migration.
This prepares for future Book and Audiobook media types.
Addresses Issue #1
* refactor(core): create MediaItem abstract base class
Extracts common properties from Movie into a new MediaItem base class:
- MediaType, Monitored, QualityProfileId
- Path, RootFolderPath, Added, Tags, LastSearchTime
Movie now inherits from MediaItem and implements abstract methods
GetTitle() and GetYear() while maintaining backward-compatible
Title/Year property accessors.
This prepares for Book and Audiobook entities that will share
the same base structure.
Addresses Issue #1
---------
Co-authored-by: admin <admin@ardentleatherworks.com>
Adds foundation for multi-media support by:
- Adding MediaType column to Movies table (migration 244)
- Adding MediaType property to Movie entity, defaulting to Movie
Existing movies will have MediaType=1 (Movie) after migration.
This prepares for future Book and Audiobook media types.
Addresses Issue #1
Co-authored-by: admin <admin@ardentleatherworks.com>
* Initial plan
* Extract common notification helper methods to reduce duplication
- Create NotificationHelpers class with BytesToString, GetLinksString, and GetTitle methods
- Update Discord notification to use shared helper methods
- Remove duplicate helper methods from Discord.cs
- Reduces ~60 lines of duplicate code
Co-authored-by: cheir-mneme <176430037+cheir-mneme@users.noreply.github.com>
* Add common message helpers and update 11 notification providers
- Add GetMovieAddedMessage and GetHealthRestoredMessage to NotificationHelpers
- Update Discord, Gotify, Join, Mailgun, Prowl, PushBullet, Pushcut, Pushover, Pushsafer, Slack, and Telegram
- Replace duplicate message strings with shared helper methods
- Reduces ~22 lines of duplicate code across 11 providers
Co-authored-by: cheir-mneme <176430037+cheir-mneme@users.noreply.github.com>
* Update 5 more notification providers with common message helpers
- Update Apprise, Email, Ntfy, Simplepush, and Signal
- Standardize movie added and health restored messages
- Total of 16 providers now using shared helper methods
Co-authored-by: cheir-mneme <176430037+cheir-mneme@users.noreply.github.com>
* Address code review feedback
- Revert Apprise to use year in movie added message (preserve original behavior)
- Return empty string instead of null in GetLinksString and GetTitle helpers
- Improves null safety for consuming code
Co-authored-by: cheir-mneme <176430037+cheir-mneme@users.noreply.github.com>
* Add null checks to GetMovieAddedMessage and GetHealthRestoredMessage
- Prevent potential null reference exceptions
- Return empty strings when parameters are null
- Maintains consistency with other helper methods
Co-authored-by: cheir-mneme <176430037+cheir-mneme@users.noreply.github.com>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: cheir-mneme <176430037+cheir-mneme@users.noreply.github.com>
* Initial plan
* feat(ci): Add secret scanning with secretlint to pre-commit hooks
- Install secretlint and @secretlint/secretlint-rule-preset-recommend
- Configure secretlint with .secretlintrc.json
- Add secretlint to lint-staged configuration
- Update CONTRIBUTING.md to document secret scanning
- Resolves#55
Co-authored-by: cheir-mneme <176430037+cheir-mneme@users.noreply.github.com>
* feat(privacy): Disable telemetry and analytics by default
- Set SentryEnabled to false by default in SentryTarget
- Update English localization to clarify error reporting is opt-in
- Update README with detailed privacy information
- Machine fingerprinting already removed (returns "anonymous")
- Piwik analytics already removed
- AnalyticsEnabled defaults to false in config
This ensures no telemetry is sent without explicit user consent.
Resolves#8
Co-authored-by: cheir-mneme <176430037+cheir-mneme@users.noreply.github.com>
* refactor: Address code review feedback for pre-commit and telemetry changes
- Optimize secretlint to only scan relevant file types (not all files)
- Add ignoreFiles configuration to secretlint to exclude build artifacts
- Clarify comment in SentryTarget about reconfiguration location
Co-authored-by: cheir-mneme <176430037+cheir-mneme@users.noreply.github.com>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: cheir-mneme <176430037+cheir-mneme@users.noreply.github.com>
PerlRegexFactory: static Regex field should be readonly to
prevent accidental reassignment.
Closes#36
Co-authored-by: admin <admin@ardentleatherworks.com>
Convert blocking GetAwaiter().GetResult() to proper await pattern
in the API controller method.
Partial fix for #32
Co-authored-by: admin <admin@ardentleatherworks.com>
- Remove continue-on-error from integration tests
- Set fail-on-error: true on test reporter
Closes#56
Co-authored-by: admin <admin@ardentleatherworks.com>
Add guard clauses to prevent InvalidOperationException when
movieFiles list is empty in bulk update/delete operations
Co-authored-by: admin <admin@ardentleatherworks.com>
Replace .Single() with .SingleOrDefault() when reading Config element
from XML to prevent InvalidOperationException on malformed config files
Co-authored-by: admin <admin@ardentleatherworks.com>
- FileStationProxy: throw if no file info returned from API
- NzbVortex: return outputPath if no files in response
- RTorrent: use FirstOrDefault() for validation errors
Prevents InvalidOperationException on empty collections
Co-authored-by: admin <admin@ardentleatherworks.com>
- Update workflow triggers to use main instead of master
- Update CONTRIBUTING.md to reference main branch
- Aligns with documentation in CLAUDE.md
Closes#52
Note: Actual branch rename (master → main) must be done on GitHub.
Co-authored-by: admin <admin@ardentleatherworks.com>