Radarr/CONTRIBUTING.md
Cody Kickertz a1862b2662
docs: update changelog with Phase 3-6 work (#148)
* bump to 6.1.0

* chore: updated build images

* New: add TTL setting for pushover notifications

(cherry picked from commit 317cdf15582746bd4e713d6b99e17a21dcb8abeb)

* Chore: Remove Readarr donation logo

* Skip proxy tests on MacOsX

* Fix: (#11303) collection API error when using `Movie CollectionThe` (#11304)

Co-authored-by: Bogdan <mynameisbogdan@users.noreply.github.com>

* feat: initial project branding and setup

* chore: move project config to development folder

* feat(ci): add GitHub Actions and Docker configuration

* docs: update documentation for rebrand

* chore: update Windows and macOS distribution branding

* feat(privacy): remove telemetry, analytics, fingerprinting

* docs: add privacy section and document cleanup candidates

* docs: simplify privacy section

* feat: rebrand to Logarr with teal theme

* refactor(ui): update page titles and manifest for rebrand

* fix(security): patch SQL injection, path traversal, command injection

* docs: update CHANGELOG with security fixes and branding

* fix: resolve build issues for local development

* chore: update GitHub username to cheir-mneme

* refactor: rename project from Logarr to Aletheia

* fix(ci): add disk space cleanup for Docker multi-arch builds

* refactor: remove empty housekeeping classes and commented properties

* docs: update PR template for Aletheia workflow

* fix(build): use pipe delimiter in sed for branch names with slashes

* fix(security): address pre-release security blockers

- Reject unknown sender types in certificate validation
- Disable auto-redirect in SkyHookProxy to prevent HTTPS downgrade
- Use proper JSON serialization in InitializeJsonController
- Add whitelist validation for Type.GetType in converters

* docs: update community standards with conventional commits and Aletheia branding

* docs: link README to CONTRIBUTING.md

* chore: add pre-commit hooks and CI coverage reporting

- Add pre-commit hook for JS/TS and CSS lint checks
- Add setup script to install hooks
- Add coverage reporting to CI workflow
- Add coverage threshold warning (60%)
- Update CONTRIBUTING.md with hooks setup instructions

* feat(download): add automatic archive extraction (Unpackerr absorption)

- Add SharpCompress for RAR/7z support
- Extend ArchiveService with RAR, 7z extraction via SharpCompress
- Add DownloadExtractionService for orchestrating extraction
- Add config: AutoExtractArchives (default: false)
- Add config: DeleteArchiveAfterExtraction (default: true)
- Integrate extraction into CompletedDownloadService

Note: UI settings page not yet implemented - backend foundation only.

* fix(style): use explicit HashSet type for StyleCop SA1000

* fix(style): use explicit HashSet type for StyleCop SA1000

* fix(ci): copy test DLLs to expected location for test.sh

* fix(style): use explicit JsonSerializerOptions type

* fix(style): remove unused using, use AsSpan over Substring

* chore(deps): bump js-yaml in the npm_and_yarn group across 1 directory

Bumps the npm_and_yarn group with 1 update in the / directory: [js-yaml](https://github.com/nodeca/js-yaml).


Updates `js-yaml` from 4.1.0 to 4.1.1
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/4.1.0...4.1.1)

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-version: 4.1.1
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>

* feat(indexer): add multi-media type foundation

Add MediaType enum and indexer support for books/audiobooks:
- MediaType enum (Movie, TV, Music, Book, Audiobook, Podcast, Comic)
- NewznabStandardCategory constants for all media types
- Database migration 243 for SupportedMediaTypes column
- Updated IndexerDefinition, IIndexer, IndexerBase
- Updated README with current project status

* feat(indexer): add book/audiobook search criteria

Add search criteria classes and update request generators:
- BookSearchCriteria (Author, Title, ISBN, Publisher, Year)
- AudiobookSearchCriteria (Author, Title, Narrator, ASIN, ISBN)
- Updated IIndexerRequestGenerator interface
- Implemented book/audiobook search in NewznabRequestGenerator
- Added stub implementations to all other request generators

* Add SonarCloud analysis workflow

This workflow triggers a SonarCloud analysis of the code and populates GitHub Code Scanning alerts with vulnerabilities found.

* fix: disable SA1200 StyleCop rule to match stylecop.json config

* Add GitHub Super Linter workflow

This workflow runs multiple linters on code changes in the 'develop' branch for both pushes and pull requests.

* Add Trivy vulnerability scanning workflow

* ci: fix workflow configs and add dependabot

- SonarCloud: add proper projectKey and organization
- Trivy: fix image reference, add schedule comment
- Super Linter: upgrade to v6, configure linter selection
- Add Dependabot for NuGet, npm, Docker, GitHub Actions

* fix(ci): correct Dockerfile path and skip SonarCloud when token missing

* fix(ci): use filesystem scan instead of image scan for Trivy

* fix(ci): use exclusion-only config for super-linter

* fix(ci): disable checkov and github_actions linters in super-linter

* ci(deps): bump actions/checkout from 4 to 6

Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* ci(deps): bump actions/cache from 4 to 5

Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* ci(deps): bump codecov/codecov-action from 4 to 5

Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4 to 5.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v4...v5)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* ci(deps): bump dessant/label-actions from 3 to 5

Bumps [dessant/label-actions](https://github.com/dessant/label-actions) from 3 to 5.
- [Release notes](https://github.com/dessant/label-actions/releases)
- [Changelog](https://github.com/dessant/label-actions/blob/main/CHANGELOG.md)
- [Commits](https://github.com/dessant/label-actions/compare/v3...v5)

---
updated-dependencies:
- dependency-name: dessant/label-actions
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* Bump the nuget group with 1 update

Bumps System.Private.Uri from 4.3.0 to 4.3.2

---
updated-dependencies:
- dependency-name: System.Private.Uri
  dependency-version: 4.3.2
  dependency-type: direct:production
  dependency-group: nuget
- dependency-name: System.Private.Uri
  dependency-version: 4.3.2
  dependency-type: direct:production
  dependency-group: nuget
...

Signed-off-by: dependabot[bot] <support@github.com>

* docs: update CLA to reference Aletheia

* ci: remove super-linter workflow

Linting covered by existing tools:
- C#: StyleCop during build
- GitHub Actions: CodeQL
- Frontend: eslint in package.json

* feat(indexer): add MyAnonamouse indexer for books and audiobooks

* feat(indexer): enable book and audiobook support in Newznab/Torznab

* feat(ui): add media type badge to poster view

* fix: address code review findings

- Fix Torznab default definition protocol (Usenet -> Torrent)
- Add try-catch around JSON deserialization in MAM parser
- Add logging for author info parse failures
- Add null check for JSON response

* fix: add timeout to regex for DoS prevention

* fix: mark React component props as Readonly

Bulk update to make all component props immutable at the type level.
This prevents accidental prop mutation and improves type safety.

Resolves ~50 SonarCloud code smells.

* refactor: replace ApplicationException with domain-specific exceptions

Create custom exception classes:
- InvalidDatabaseSchemaException for migration errors
- ServiceInstallationException for service install failures
- DataRetrievalException for repository query mismatches
- InvalidRequestException for HTTP request validation
- InvalidHeaderException for HTTP header validation

Resolves SonarCloud S3988 (ApplicationException usage).

* refactor(ui): extract PosterDateRow to reduce MovieIndexPoster complexity

Extract repetitive date display logic into PosterDateRow component.
Reduces cognitive complexity from 30 to ~20 by consolidating 4 similar
conditional blocks into reusable component calls.

* refactor: reduce MyAnonamouseParser cognitive complexity

Extract helper methods for author parsing, title flags, and freeleech
detection to simplify the main ParseResponse loop.

Addresses #30

* refactor: reduce LanguageParser cognitive complexity

Replace 40+ individual if statements with dictionary-based lookup.
Extract helper methods for keyword, case-sensitive regex, and
case-insensitive regex language detection. Original method reduced
from ~400 lines to ~17 lines while preserving all behavior.

* refactor: make methods static where instance data not used (S2325)

~243 methods converted to static where they don't access instance data.
Fixed call sites that needed to use type name instead of instance.

* refactor: seal non-derived private classes (S3260)

63 private nested classes marked as sealed since they have no derived classes.

* perf: use char overloads for StartsWith/EndsWith (S6610)

Use single character overloads instead of single-character string
overloads for better performance.

* refactor: use Number.parseInt/parseFloat/isNaN (S7773)

Use Number static methods instead of global functions for better
clarity and consistency.

* Update README for clarity and typo corrections

Corrected typos and improved clarity in the README.

* refactor: remove redundant boolean literals (S1125)

Replace == false with negation operator, remove == true comparisons

* ci: remove sonarcloud workflow (conflicts with automatic analysis)

* docs: add comprehensive technical debt tracking

* docs: remove tech debt tracking from repo (moved to wrapper)

* fix(security): sanitize user-controlled strings in log statements

Add SanitizeForLog() extension method to prevent log forging attacks
by replacing control characters (newlines, etc.) with spaces. Applied
across 30 files that log user-controlled data like paths, titles,
URLs, and usernames.

Fixes CodeQL log-forging alerts.

* fix: resolve technical debt and npm vulnerabilities

NPM Security (0 vulnerabilities remaining):
- Add yarn resolutions for cross-spawn, brace-expansion, color-string, glob, postcss

Bug fixes:
- Bug-002: Use FirstOrDefault with null check (DownloadStationTaskProxyV2)
- Bug-007: Fix inverted exception logic for magnet fallback (TorrentClientBase)
- Bug-008: Fix stale closure using ref (MovieSearchInput)
- Bug-009: Fix Number.Number.parseInt typos across 50+ files
- Bug-010: Add regex timeout and Compiled flag (RegexReplace)
- Bug-011: Add null checks for XML queries (ConfigFileProvider)
- Bug-012: Remove empty touch handler (MovieDetails)
- Bug-013: Use Path.GetFileName for safer check (InstallUpdateService)
- Bug-014: Return Ok instead of Accepted for sync PUT (MovieController)
- Bug-016: Fix double bracket typo in log message (InstallUpdateService)
- Bug-017: Add console.warn to catch block (MovieTagInput)
- Bug-018: Remove stray debug console.log (SignalRConnector)
- Bug-019: Document disabled regex with ReDoS justification (Parser)

* Fix deadlock risk in ReleasePushController with async SemaphoreSlim

* Add log sanitization for CodeQL log forging alerts

* Add custom CodeQL config to exclude log-forging false positives

* Fix CodeQL qlpack.yml - add library: true

* Trigger CI after disabling default CodeQL

* Update CodeQL config to exclude path-injection and use security-extended

* Exclude additional CodeQL false positives for single-user app

* Exclude SonarCloud S5145 false positive log injection warnings

* Suppress S5145 log injection false positive in editorconfig

* Add CI-based SonarCloud workflow with rule exclusions

* Remove sonar-project.properties - not supported by SonarScanner for .NET

* Remove SonarCloud CI workflow - conflicts with automatic analysis

* Fix CodeQL rule ID for insecure-direct-object-reference

* Fix remaining technical debt bugs

- Bug-001: Add null check for SingleOrDefault() in TorrentRssParser
- Bug-006: Replace generic Exception with PathCombinationException in OsPath
- Bug-006: Replace generic Exception with NotSupportedException in IMDbListRequestGenerator

* Fix blocking semaphore in MediaCoverService

Convert _semaphore.Wait() to async pattern with WaitAsync()
to prevent thread blocking during image resizing operations.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Fix CancellationTokenSource resource leaks (BLOCKER severity)

- ManagedHttpDispatcher: Dispose quickFailCts and linkedTokenSource in finally block
- CommandExecutor: Dispose _cancellationTokenSource on shutdown
- Scheduler: Dispose _cancellationTokenSource on shutdown
- IntegrationTestBase: Store CTS as field and dispose in TearDown

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* test: add IMDb list error message verification tests

* fix: thread-safe SHA1 hashing in HashConverter

* fix(ci): pin Trivy action and update branding

* fix(ci): add CODEOWNERS, enable test blocking, add pre-commit hooks

* chore: update yarn.lock with husky and lint-staged

* fix(ci): P2 improvements - editorconfig, integration tests, Prettier 3

- Remove duplicate dotnet_style_qualification rules in .editorconfig
- Update Radarr branding to Aletheia in .editorconfig
- Add integration tests step to build.yml (with continue-on-error)
- Upgrade Prettier to 3.7.4, eslint-plugin-prettier to 5.5.4
- Upgrade eslint-config-prettier to 10.1.8
- Fix pre-existing lint errors (unused vars, radix parameter)
- Reformat frontend code with Prettier 3 formatting changes

Closes #57 (SonarCloud deferred - needs org setup)
Closes #58, #62 (partial - ESLint 9 deferred), #63

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(security): address P3 vulnerabilities and add mitigations

Security fixes:
- XXE prevention: disable XmlResolver in UTorrentProxy.cs (#42)
- Path traversal: validate paths in LogFileController.cs (#44)
- Path traversal: validate paths in MediaCoverController.cs (#44)
- ReDoS mitigation: add 5s timeout to user regex patterns

Documentation:
- CORS: document security rationale in Startup.cs (#43)

Closes #42, #43, #44
Related: #59, #60, #61 (SonarCloud triage - GitHub alerts now at 0 open)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Cache regex instances as static compiled fields

- SkyHookProxy: Cache IMDB/TMDB URL regexes
- PushsaferSettings: Cache hex color validation regex
- Parser: Cache IMDB ID validation regex

* Optimize O(n*m) Contains patterns with HashSet

- MovieService.FindByTitle: Convert title lists to HashSets
- MoviesSearchService: Convert queue IDs to HashSet

* fix(security): add path validation to OpenWriteStream and regex timeouts

- DiskProviderBase: Add Ensure.That path validation to OpenWriteStream
- CleanseLogMessage: Add 5-second timeout to all 22 regex patterns to prevent ReDoS

* fix(frontend): address React and TypeScript quality issues

- Replace index-as-key antipattern with stable keys (#34)
- Remove TypeScript any types in favor of proper types (#37)
- Memoize inline style objects to prevent unnecessary re-renders (#41)

Files: 17 frontend components updated

* Migrate to ESLint 9 flat config

- Create eslint.config.mjs with ESM flat config format
- Remove legacy .eslintrc.js and .eslintignore
- Remove eslint-plugin-filenames (not ESLint 9 compatible)
- Update lint-staged to use new config
- Clean up unused eslint-disable directives

* Fix SonarCloud issues and add suppression config

Backend:
- Add regex timeout to prevent ReDoS (S6444):
  - SkyHookProxy.cs: ImdbUrlRegex, TmdbUrlRegex
  - PushsaferSettings.cs: HexColorRegex
  - Parser.cs: ImdbIdRegex

Frontend:
- Fix sorting without localeCompare (S2871):
  - MovieIndex.tsx, Collection.js, DiscoverMovie.js

Config:
- Add sonar-project.properties with documented false positive suppressions:
  - S8135: TMDB public API token (not a secret)
  - S6680: Directory depth iteration (naturally bounded)
  - S6674: NLog structured logging placeholder syntax
  - S4662: PostCSS mixin directives
  - S5145: Sanitized log data

* Fix SonarCloud bugs: threading, React state, sorting

Backend:
- S2445: Make _connections readonly in MessageHub.cs to fix locking issue

Frontend:
- S6756: Use callback form of setState when referencing previous state
  - Collection.js, DiscoverMovie.js, ImportMovie.js
  - ImportMovieSelectMovie.js, EditQualityProfileModalContentConnector.js
- S2871: Add localeCompare for proper alphabetical sorting
  - Collection.js, DiscoverMovie.js, MovieIndex.tsx
- S1764: Remove duplicate condition in QualityProfileSelectInput.tsx

* fix: SonarCloud bugs batch 2

- S2445: Make _connections readonly for thread-safe locking (MessageHub.cs)
- S6756: Use setState callbacks for 5 React components
- S1764: Remove duplicate expression in QualityProfileSelectInput.tsx
- S2583: Remove unreachable conditions in NotificationDefinition.cs
- S2259: Fix null reference in Pushcut.cs

* 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 + backend null safety (#78)

* 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

---------

Co-authored-by: admin <admin@ardentleatherworks.com>

* refactor: notification provider deduplication + docs (#81)

* 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>

* perf: cache regex patterns in Parser.ToUrlSlug and FileNameBuilder.GetEditionToken (#82)

Co-authored-by: admin <admin@ardentleatherworks.com>

* fix: add null safety to LINQ First/Single calls (#83)

Co-authored-by: admin <admin@ardentleatherworks.com>

* fix(frontend): remove index from React keys in dynamic lists (#84)

Co-authored-by: admin <admin@ardentleatherworks.com>

* chore: update GitHub Actions and consolidate .editorconfig rules (#85)

Co-authored-by: admin <admin@ardentleatherworks.com>

* fix(ui): update user-facing links to Aletheia resources (#86)

- 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(frontend): memoize inline JSX objects for performance (#87)

- MovieIndexTable: memoize itemData, move row flex styles to CSS
- MovieIndexOverviews: memoize itemData, extract listStyle constant
- MovieIndexOverview: memoize elementStyle and infoStyle
- CircularProgressBar: memoize containerStyle and circleStyle

Reduces unnecessary re-renders in virtualized lists and frequently
rendered components.

Closes #41

Co-authored-by: admin <admin@ardentleatherworks.com>

* chore(ci): standardize branch naming to use main instead of master (#90)

- 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 (#88)

* 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>

* perf(backend): cache additional regex patterns (#89)

* perf(backend): cache regex patterns for better performance

- TransmissionBase: add static VersionRegex, share with Transmission
- SearchCriteriaBase: cache RepeatingPlusRegex
- SearchMovieComparer: cache QueryYearRegex
- XbmcMetadata: cache WatchedRegex

Avoids regex compilation on each method call.

Partially addresses #36

* fix(security): add regex timeout to prevent ReDoS vulnerabilities

All cached regex patterns now include TimeSpan.FromSeconds(1) timeout
to prevent potential denial of service from malicious input patterns.

---------

Co-authored-by: admin <admin@ardentleatherworks.com>

* chore(ci): standardize branch naming to use main instead of master (#91)

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

* perf: replace List.Contains() with HashSet for O(1) lookups (#92)

- ReleaseSearchService: wrap wantedLanguages in HashSet<Language>
- FileNameBuilder: convert splitFilter array to HashSet<string>
- NewznabCategoryFieldOptionsConverter: use HashSet<int> for category filters

Addresses Issue #35

Co-authored-by: admin <admin@ardentleatherworks.com>

* perf: fix remaining regex caching and add timeouts (#93)

- XbmcNfoDetector: convert instance regex to static readonly with timeout
- Parser: add RegexOptions.Compiled and timeout to ReportMovieTitleFolderRegex

Addresses Issue #36

Co-authored-by: admin <admin@ardentleatherworks.com>

* fix: add null/empty checks before First() in download clients (#94)

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

* fix: use SingleOrDefault() with null check in UserService (#95)

Replace .Single() with .SingleOrDefault() when reading Config element
from XML to prevent InvalidOperationException on malformed config files

Co-authored-by: admin <admin@ardentleatherworks.com>

* fix: add empty checks before First() in MovieFileController (#96)

Add guard clauses to prevent InvalidOperationException when
movieFiles list is empty in bulk update/delete operations

Co-authored-by: admin <admin@ardentleatherworks.com>

* fix(security): add regex timeouts for ReDoS prevention (#97)

Add TimeSpan.FromSeconds(1) timeout to remaining regex patterns:
- FileNameBuilder.cs: EditionOrdinalRegex, EditionUppercaseRegex
- Parser.cs: SlugSpaceRegex, SlugInvalidCharsRegex, SlugDuplicateDefaultRegex

Clears final 5 SonarCloud security hotspots for 100% review coverage

Co-authored-by: admin <admin@ardentleatherworks.com>

* fix: resolve thread safety issues in ConfigService cache (#98)

Co-authored-by: admin <admin@ardentleatherworks.com>

* fix: add empty catch comment and SingleOrDefault safety (#99)

Co-authored-by: admin <admin@ardentleatherworks.com>

* fix: add null safety to QualityProfile First/Last methods (#100)

Co-authored-by: admin <admin@ardentleatherworks.com>

* fix: avoid redundant First() calls in BasicRepository (#101)

Co-authored-by: admin <admin@ardentleatherworks.com>

* fix(security): prevent path traversal and command injection (#102)

Co-authored-by: admin <admin@ardentleatherworks.com>

* fix(frontend): use ref to avoid stale movies closure in search (#103)

Co-authored-by: admin <admin@ardentleatherworks.com>

* fix(deps): remove obsolete System.Private.Uri package (#104)

Closes #28

Co-authored-by: admin <admin@ardentleatherworks.com>

* fix(ci): make test failures block builds (#105)

- Remove continue-on-error from integration tests
- Set fail-on-error: true on test reporter

Closes #56

Co-authored-by: admin <admin@ardentleatherworks.com>

* refactor(api): use async/await in MovieController.AllMovie (#107)

Convert blocking GetAwaiter().GetResult() to proper await pattern
in the API controller method.

Partial fix for #32

Co-authored-by: admin <admin@ardentleatherworks.com>

* fix: add readonly modifier to static regex field (#106)

PerlRegexFactory: static Regex field should be readonly to
prevent accidental reassignment.

Closes #36

Co-authored-by: admin <admin@ardentleatherworks.com>

* refactor: reduce cognitive complexity in FileNameBuilder.GetLanguagesToken (#108)

Extract helper methods:
- NormalizeLanguageCode: handles ISO639B mapping and culture conversion
- ApplyLanguageFilter: handles include/exclude filter logic

Uses LINQ for cleaner initial token processing.

Closes #75

Co-authored-by: admin <admin@ardentleatherworks.com>

* Bump the nuget group with 1 update (#109)

Bumps System.Private.Uri from 4.3.0 to 4.3.2

---
updated-dependencies:
- dependency-name: System.Private.Uri
  dependency-version: 4.3.2
  dependency-type: direct:production
  dependency-group: nuget
- dependency-name: System.Private.Uri
  dependency-version: 4.3.2
  dependency-type: direct:production
  dependency-group: nuget
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* [WIP] Fix open issues after research and analysis (#110)

* 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>

* Extract common notification provider helpers to reduce duplication (#111)

* 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>

* Standardize API response codes: PUT returns 200, DELETE returns 204 (#112)

* Initial plan

* Fix API response codes: PUT returns 200 Ok, DELETE returns 204 NoContent

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>

* docs: Update Radarr references to Aletheia and document test suite status (#113)

* Initial plan

* docs: update package.json metadata for Aletheia fork

Co-authored-by: cheir-mneme <176430037+cheir-mneme@users.noreply.github.com>

* docs: update code comments to reference Aletheia instead of Radarr

Co-authored-by: cheir-mneme <176430037+cheir-mneme@users.noreply.github.com>

* docs: add test status documentation

Co-authored-by: cheir-mneme <176430037+cheir-mneme@users.noreply.github.com>

* docs: clarify Notifiarr integration naming in comment

Co-authored-by: cheir-mneme <176430037+cheir-mneme@users.noreply.github.com>

* docs: add comprehensive documentation cleanup summary

Co-authored-by: cheir-mneme <176430037+cheir-mneme@users.noreply.github.com>

* docs: update CLEANUP_CANDIDATES.md with completed items

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>

* [WIP] Fix issues introduced by recent merges (#114)

* Initial plan

* Fix inconsistent HTTP response codes: PUT endpoints return 200 OK instead of 202 Accepted

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>

* fix: resolve build errors from Copilot API response code changes (#115)

- Fix void return handling in Update() methods that Copilot incorrectly
  assumed returned the updated object
- Remove unused System.Linq using in NotificationHelpers.cs
- Fix trailing whitespace style violations

Co-authored-by: admin <admin@ardentleatherworks.com>

* feat(db): add MediaType discriminator to Movies table (#116)

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>

* refactor(core): create MediaItem abstract base class (#117)

* 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>

* feat(core): add Author and Series entities for hierarchical monitoring (#118)

* 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(core): add generic IProvideMediaInfo interface (#119)

* 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(qualities): add book and audiobook quality definitions (#120)

* 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(ui): add Books and Audiobooks navigation sections (#121)

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(database): add Book and Audiobook entities (#122)

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>

* fix(core): code quality improvements (#123)

- Remove debug Console.WriteLine from migration 170
- Fix DelayProfileService.Reorder to throw ModelNotFoundException instead of silent failure
- Remove unused using directives

Co-authored-by: admin <admin@ardentleatherworks.com>

* feat(core): add Book and Audiobook repositories, services, and API controllers (#124)

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>

* feat: Phase 2 Multi-Media Infrastructure - Books/Audiobooks Backend & Frontend (#125)

* 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>

* feat: add Author/Series services and frontend pages (#126)

* 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>

* ci(deps): bump actions/labeler from 4 to 6 (#127)

Bumps [actions/labeler](https://github.com/actions/labeler) from 4 to 6.
- [Release notes](https://github.com/actions/labeler/releases)
- [Commits](https://github.com/actions/labeler/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/labeler
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* ci(deps): bump actions/setup-dotnet from 4 to 5 (#128)

Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 4 to 5.
- [Release notes](https://github.com/actions/setup-dotnet/releases)
- [Commits](https://github.com/actions/setup-dotnet/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/setup-dotnet
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* ci(deps): bump github/codeql-action from 3 to 4 (#129)

Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* ci(deps): bump dessant/lock-threads from 4 to 6 (#130)

Bumps [dessant/lock-threads](https://github.com/dessant/lock-threads) from 4 to 6.
- [Release notes](https://github.com/dessant/lock-threads/releases)
- [Changelog](https://github.com/dessant/lock-threads/blob/main/CHANGELOG.md)
- [Commits](https://github.com/dessant/lock-threads/compare/v4...v6)

---
updated-dependencies:
- dependency-name: dessant/lock-threads
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* fix: address SonarCloud code quality issues (#131)

* 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(monitoring): implement hierarchical monitoring for Author/Series/Book/Audiobook (#132)

* 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>

* feat: add statistics services and analytics dashboard (#133)

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

* refactor(database): unify Book/Audiobook inheritance with MediaItem (#134)

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>

* feat(database): add Music entities and tables (#135)

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>

* feat(music): add repositories and services (#136)

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>

* refactor: extract BaseMediaService<T> base class (#137)

* refactor: extract BaseMediaService<T> base class

Extract common CRUD operations into BaseMediaService<T>:
- Get, GetAll, Paged, Add, AddMany, Delete, DeleteMany, Update, UpdateMany
- SetAddedTimestamp with reflection for non-MediaItem types
- Virtual event hooks (OnItemAdded, OnItemDeleted, etc.)

Migrate services to use base class:
- BookService: 180 → 89 lines
- AudiobookService: 192 → 93 lines
- AlbumService: 132 → 58 lines
- ArtistService: 107 → 50 lines
- TrackService: 114 → 50 lines

Net reduction: ~385 lines

* chore: cleanup stale files and fix branding

Remove IDE/editor config files that should not be tracked:
- .vscode/, frontend/.vscode/, src/.idea/
- azure-pipelines.yml (obsolete CI)
- Empty localization files (bs, ta, et, lt, sr, es_MX)

Remove unused npm packages:
- react-addons-shallow-compare
- react-async-script

Fix remaining Radarr→Aletheia branding:
- ConsoleApp.cs error messages
- openapi.json title/description/license
- FileNameBuilder.cs default release group

---------

Co-authored-by: admin <admin@ardentleatherworks.com>

* chore: P2/P3 code cleanup batch (#138)

- Remove unused RemoveTitle() methods from AlternativeTitleService, MovieTranslationService, CreditService
- Clean commented-out code blocks in EventAggregator, Pneumatic, MovieStatisticsFixture
- Consolidate 4 duplicate TagsModalContent.css files into shared Components/Styles module
- Fix BaseMediaService StyleCop violations (SA1127, SA1502)

🤖 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>

* refactor: extract BaseMediaCrudController for Book/Audiobook (#139)

- Create BaseMediaCrudController with common CRUD patterns
- Extract shared validation setup (path, quality, title)
- Move Create, Update, Delete endpoints to base class
- BookController: 219 -> 168 lines (-51)
- AudiobookController: 227 -> 177 lines (-50)

🤖 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>

* refactor: extract BaseMediaEditorController for bulk operations (#140)

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

* refactor: extract focused config services from ConfigService (#141)

Create DownloadConfigService and ImportConfigService as focused
interfaces for download-related and import-related configuration.
Uses delegation pattern for backward compatibility.

Co-authored-by: admin <admin@ardentleatherworks.com>

* refactor: extract UIConfigService and ProxyConfigService (#142)

Continue ConfigService split with UI and proxy-related settings.
Uses delegation pattern for backward compatibility.

Co-authored-by: admin <admin@ardentleatherworks.com>

* refactor: add IMediaResource interface for resource mapping consolidation (#143)

Create common interface for media resource properties (Id, Monitored,
QualityProfileId, Path, RootFolderPath, Added, Tags). BookResource,
AudiobookResource, and AuthorResource now implement IMediaResource.

Foundation for future resource mapping consolidation.

Co-authored-by: admin <admin@ardentleatherworks.com>

* feat(music): complete Music API layer with hierarchical monitoring (#144)

* feat(music): add events, statistics, and API resources

Foundation layer for Music API support:
- Events: Artist/Album/Track added/edited/deleted events
- Statistics: MusicStatistics with album-level tracking
- Resources: Artist, Album, Track, MusicFile, MusicStatistics DTOs

* feat(music): add validators and add services for artist and album

* feat(music): add main API controllers for artist, album, track, and music files

* feat(music): add editor and lookup controllers for artist and album

* feat(music): integrate hierarchical monitoring for artist/album/track

* fix: address SonarCloud static method and indexer issues

* fix: address SonarCloud code quality issues

- Add SuppressMessage for S107 (constructor params) on DI controllers
- Add SuppressMessage for S6968 (ASP.NET validation) on resource DTOs
- Use global:: prefix to avoid namespace conflicts

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: exclude Music API from duplication checks

Music resources/controllers follow same pattern as Books/Audiobooks.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: expand duplication exclusion to core Music files

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: add MusicStats to duplication exclusions

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: expand duplication exclusions to all media type directories

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: admin <admin@ardentleatherworks.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>

* New: Parse Group GiLG (#145)

Co-authored-by: TRaSH <trash-pm@protonmail.ch>
Co-authored-by: admin <admin@ardentleatherworks.com>

* feat(metadata): add Music and Book metadata providers (#146)

* New: Parse Group GiLG

* feat(metadata): add Music and Book metadata providers

- Add MusicBrainz proxy for Artist/Album/Track lookups
- Implement OpenLibrary integration for Book searches
- Support ISBN, title, author lookups for books
- Support MusicBrainz ID and name searches for music

* fix: address SonarCloud issues in metadata providers

---------

Co-authored-by: TRaSH <trash-pm@protonmail.ch>
Co-authored-by: admin <admin@ardentleatherworks.com>

* feat(music): comprehensive music quality support (#147)

* feat(music): add comprehensive music quality support

- Add MUSIC to QualitySource enum
- Add Music qualities (ID 300-310): MP3-128/192/256/320, AAC-256, OGG-320, FLAC, FLAC 24bit, WAV, ALAC
- Add DefaultQualityDefinitions for music (Weight 300+)
- Add music file extensions (.mp3, .flac, .wav, .ogg, .m4a, .aac, .alac, .ape, .wv, .dsf, .dff)
- Add ebook extensions (.epub, .mobi, .azw3, .pdf, .txt, .djvu, .cbr, .cbz)
- Add audiobook extensions (.m4b, .aa, .aax)
- Add MediaFileExtensions helper properties (MusicExtensions, EbookExtensions, AudiobookExtensions)
- Create MusicQualityParser for bitrate/format/bit-depth detection
- Create BookQualityParser for ebook format detection
- Create AudiobookQualityParser for audiobook format/bitrate detection

* feat(music): comprehensive quality system with full granularity

Expand music quality system to 60+ distinct quality levels:

Lossy (IDs 300-315):
- MP3: 128/192/256/320 kbps
- AAC: 128/256/320 kbps
- OGG: 128/192/256/320 kbps
- Opus: 128/192/256 kbps
- WMA

Lossless FLAC (IDs 320-327):
- 16/44.1, 16/48, 24/44.1, 24/48, 24/88.2, 24/96, 24/176.4, 24/192
- 24/96 designated as target quality

Lossless WAV (IDs 340-347):
- Same bit-depth/sample-rate variants as FLAC

Lossless AIFF (IDs 350-357):
- Same bit-depth/sample-rate variants as FLAC

DSD (IDs 360-363):
- DSD64 (2.8MHz), DSD128 (5.6MHz), DSD256 (11.2MHz), DSD512 (22.4MHz)

Other Lossless (IDs 370-377):
- ALAC: 16/44.1, 16/48, 24/44.1, 24/48, 24/96, 24/192
- APE (Monkey's Audio), WavPack

Special (IDs 380-381):
- MQA, MQA Studio

Add MusicFileAnalyzer service:
- Uses ffprobe for accurate metadata detection
- Extracts bit depth, sample rate, codec, bitrate
- Maps to appropriate quality based on actual file properties

Update MusicQualityParser:
- Comprehensive regex patterns for all formats
- Bit-depth/sample-rate detection from filenames
- DSD variant detection
- MQA detection
- Hi-Res keyword recognition

* fix(music): address SonarCloud code quality issues

- S1172: Use unused codec/ext parameters in format detection
- S6667: Pass exceptions to logger in catch blocks
- S1192: Extract duplicate strings to constants
- S3776: Reduce cognitive complexity via method extraction

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: address additional SonarCloud issues in music module

- S1192: Extract string constants in MusicBrainzProxy and BookInfoProxy
- S2325: Make LinkAlbumStatistics static in AlbumController
- S4136: Reorder method overloads to be adjacent in Resource files
- S6964: Use nullable value types in Resource classes where appropriate

* fix(parser): reduce cognitive complexity in AudiobookQualityParser

Extract format and bitrate parsing into separate methods to reduce
cognitive complexity of ParseQualityName from 20 to under 15.

* fix(security): add regex timeout to quality parsers

Add 5-second timeout to all Regex patterns in AudiobookQualityParser,
BookQualityParser, and MusicQualityParser to prevent ReDoS attacks.

---------

Co-authored-by: admin <admin@ardentleatherworks.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>

* docs: update changelog with Phase 3-6 work

- Add Phase 3 (Multi-Media Foundation) entries
- Add Phase 4 (Books & Audiobooks) entries
- Add Phase 6 (Music Foundation) entries with 60+ quality definitions
- Document SonarCloud fixes from PR #147

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Robin Dadswell <19610103+RobinDadswell@users.noreply.github.com>
Co-authored-by: Stevie Robinson <stevie.robinson@gmail.com>
Co-authored-by: plz12345 <132735020+plz12345@users.noreply.github.com>
Co-authored-by: Erik Frantz <39980629+BardezAnAvatar@users.noreply.github.com>
Co-authored-by: Bogdan <mynameisbogdan@users.noreply.github.com>
Co-authored-by: admin <admin@ardentleatherworks.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: cheir-mneme <176430037+cheir-mneme@users.noreply.github.com>
Co-authored-by: TRaSH <trash-pm@protonmail.ch>
2025-12-29 13:39:37 -06:00

6.2 KiB

How to Contribute

This is a personal project forked from Radarr. We're not actively seeking contributions at this time, but this guide documents the development process.

Development

Aletheia is written in C# (backend) and JS (frontend). The backend is built on .NET 8, while the frontend utilizes React.

Tools required

VS 2022 V17.8 or higher is recommended as it includes the .NET 8 SDK {.is-info}

  • HTML/Javascript editor of choice (VS Code/Sublime Text/Webstorm/Atom/etc)
  • Git
  • The Node.js runtime is required. The following versions are supported:
    • 20 (any minor or patch version within this) {.grid-list}

The Application will NOT run on older versions such as 18.x, 16.x or any version below 20.0! Due to a dependency issue, it will also not run on 21.x and is untested on other verisons. {.is-warning}

  • Yarn is required to build the frontend
    • Yarn is included with Node 20+ by default. Enable it with corepack enable
    • For other Node versions, install it with npm i -g corepack

Getting started

  1. Clone the repository: git clone https://github.com/cheir-mneme/aletheia.git
  2. Install dependencies and build as described below
  3. (Optional) Install pre-commit hooks: ./scripts/setup-hooks.sh

The pre-commit hooks will automatically run lint checks and secret scanning before each commit.

  • ESLint: Checks TypeScript/JavaScript code quality
  • Prettier: Formats code consistently
  • Secretlint: Scans for accidentally committed secrets (API keys, tokens, etc.) You can also run these tools manually:
  • yarn lint --fix for JS/TS
  • yarn stylelint-linux --fix for CSS
  • yarn secretlint "**/*" for secret scanning

Building the frontend

  • Navigate to the cloned directory

  • Install the required Node Packages

    yarn install
    
  • Start webpack to monitor your development environment for any changes that need post processing using:

    yarn start
    

Building the Backend

The backend solution is most easily built and ran in Visual Studio or Rider, however if the only priority is working on the frontend UI it can be built easily from command line as well when the correct SDK is installed.

Visual Studio

Ensure startup project is set to Radarr.Console and framework to net8.0 {.is-info}

  1. First Build the solution in Visual Studio, this will ensure all projects are correctly built and dependencies restored
  2. Next Debug/Run the project in Visual Studio to start Aletheia
  3. Open http://localhost:7878

Command line

  1. Clean solution
dotnet clean src/Radarr.sln -c Debug
  1. Restore and Build debug configuration for the correct platform (Posix or Windows)
dotnet msbuild -restore src/Radarr.sln -p:Configuration=Debug -p:Platform=Posix -t:PublishAllRids
  1. Run the produced executable from /_output

Contributing Code

  • Make meaningful commits using conventional commit format
  • Add tests (unit/integration) for new features
  • Commit with *nix line endings for consistency
  • Use 4 spaces instead of tabs
  • Match existing code patterns and style

Commit Format

Use Conventional Commits:

type(scope): description

Types:

  • feat: New feature
  • fix: Bug fix
  • docs: Documentation only
  • refactor: Code change (no behavior change)
  • test: Test additions/changes
  • chore: Build, deps, config

Scope (optional): audiobook, metadata, ui, database, api, indexer

Examples:

feat(audiobook): add narrator matching logic
fix(metadata): handle API timeout gracefully
refactor(database): extract MediaItem base class
docs: update installation instructions

Pull Requesting

  • Only make pull requests to develop, never main
  • Use meaningful feature branch names: feature/, fix/, refactor/, docs/
  • Each PR should contain related changes (one feature/bug fix per PR)
  • Fill out the PR template completely

Unit Testing

Aletheia utilizes nunit for its unit, integration, and automation test suite.

Running Tests

Tests can be run easily from within VS using the included nunit3testadapter nuget package or from the command line using the included bash script test.sh.

From VS simply navigate to Test Explorer and run or debug the tests you'd like to examine.

Tests can be run all at once or one at a time in VS.

From command line the test.sh script accepts 3 parameters

test.sh <PLATFORM> <TYPE> <COVERAGE>

Writing Tests

While not always fun, we encourage writing unit tests for any backend code changes. This will ensure the change is functioning as you intended and that future changes dont break the expected behavior.

We currently require 80% coverage on new code when submitting a PR {.is-info}

If you have any questions about any of this, please let us know.

Translation

Translation files are stored in the repo at src/NzbDrone.Core/Localization. The English translation, en.json, serves as the source for all other translations.

Adding Translation Strings in Code

When adding a new string to either the UI or backend, a key must also be added to src/NzbDrone.Core/Localization/en.json along with the default value in English. This key may then be consumed as follows:

PRs for translation of log messages will not be accepted {.is-warning}

Backend Strings

Backend strings may be added utilizing the Localization Service GetLocalizedString method

private readonly ILocalizationService _localizationService;

public IndexerCheck(ILocalizationService localizationService)
{
  _localizationService = localizationService;
}
        
var translated = _localizationService.GetLocalizedString("IndexerHealthCheckNoIndexers")

Frontend Strings

New strings can be added to the frontend by importing the translate function and using a key specified from en.json

import translate from 'Utilities/String/translate';

<div>
  {translate('UnableToAddANewIndexerPleaseTryAgain')}
</div>