* 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>
* fix(frontend): replace any types with proper TypeScript types
- AutoSuggestInput: use Data type from popper.js for modifier callback
- Tooltip: use Data type from popper.js for computeMaxSize callback
- OverlayScroller: use ComponentPropsWithoutRef<'div'> for renderView
- index.ts: use unknown[] instead of any[] for logError parameters
Improves type safety and removes eslint-disable comments.
Partially addresses #37
* fix(frontend): use ModifierFn type and string values for Popper styles
- Use ModifierFn type from popper.js for modifier callbacks
- Calculate bottom/right from offset properties (top+height, left+width)
- Convert numeric style values to strings with 'px' suffix
- Fix typo: 'botton' -> 'bottom' in AutoSuggestInput
---------
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>
- MoreInfo: point to Aletheia GitHub instead of Radarr resources
- UpdateChanges: link issue numbers to Aletheia repo
- Add "Upstream" translation key for Radarr reference link
Closes#53
Co-authored-by: admin <admin@ardentleatherworks.com>
* fix: SonarCloud null safety and struct comparison issues
- OsPath.cs: Remove ReferenceEquals checks on struct (always false)
- SkyHookProxy.cs: Add null-conditional operators for Credits.Cast/Crew
* fix: remaining React index-as-key issues and backend null safety
Frontend:
- Fix 8 remaining index-as-key violations using content-based keys
- ImportMovieSelectFolder.js: use errorMessage as key
- ImportMovieFooter.js: use errorMessage as key
- CustomFormat.js: use item.name as key
- AddSpecificationItem.js: use preset.name as key
- QualityProfileItems.js: use message as key
- QualityProfileFormatItems.js: use message as key
Backend (cherry-picked from batch-3):
- OsPath.cs: Remove ReferenceEquals on struct
- SkyHookProxy.cs: Add null-conditional for Credits
* refactor(notifications): consolidate GetPosterUrl to base class
* docs: add architectural decisions log
* fix(sonar): enable path traversal suppressions for media management app
---------
Co-authored-by: admin <admin@ardentleatherworks.com>