feat: add indexer and torrent REST controllers with service layer

- Add config module for yaml config (database, indexers, torrent)
- Add indexer module with Torznab protocol support
- Add IndexerService and TorrentService for business logic
- Add REST controllers for indexer search and torrent management
- Add Docker Compose for PostgreSQL and Jackett
- Add ERD documentation for database schema
This commit is contained in:
Alexander
2026-04-28 18:53:50 +02:00
parent f77806ba46
commit 1aaaab4640
23 changed files with 1841 additions and 51 deletions
+274
View File
@@ -0,0 +1,274 @@
@startuml Music Aggregator ERD
skinparam linetype ortho
skinparam ranksep 60
skinparam nodesep 40
skinparam entity {
BackgroundColor White
BorderColor #333333
}
skinparam package {
BackgroundColor #FAFAFA
BorderColor #DDDDDD
}
title Music Aggregator - Database Structure
' ══════════════════════════════════════════════════════════════
' CORE MUSIC ENTITIES
' ══════════════════════════════════════════════════════════════
package "Core Music Entities" #E3F2FD {
entity "artist_metadata" {
* id : UUID <<PK>>
--
foreign_artist_id : TEXT <<UNIQUE>>
name : TEXT
sort_name : TEXT
disambiguation : TEXT
artist_type : TEXT
status : TEXT
overview : TEXT
images : JSONB
links : JSONB
genres : JSONB
--
created_at : TIMESTAMPTZ
updated_at : TIMESTAMPTZ
}
entity "artists" {
* id : UUID <<PK>>
--
metadata_id : UUID <<FK>>
quality_profile_id : UUID <<FK>>
metadata_profile_id : UUID <<FK>>
root_folder_id : UUID <<FK>>
--
path : TEXT
monitored : BOOLEAN
monitor_new_items : TEXT
--
last_info_sync : TIMESTAMPTZ
added_at : TIMESTAMPTZ
}
entity "albums" {
* id : UUID <<PK>>
--
artist_metadata_id : UUID <<FK>>
--
foreign_album_id : TEXT <<UNIQUE>>
title : TEXT
clean_title : TEXT
disambiguation : TEXT
overview : TEXT
album_type : TEXT
release_date : DATE
images : JSONB
genres : JSONB
--
monitored : BOOLEAN
added_at : TIMESTAMPTZ
}
entity "album_releases" {
* id : UUID <<PK>>
--
album_id : UUID <<FK>>
--
foreign_release_id : TEXT <<UNIQUE>>
title : TEXT
status : TEXT
duration_ms : INT
release_date : DATE
country : TEXT[]
label : TEXT[]
format : TEXT
track_count : INT
--
monitored : BOOLEAN
}
entity "tracks" {
* id : UUID <<PK>>
--
album_release_id : UUID <<FK>>
artist_metadata_id : UUID <<FK>>
track_file_id : UUID <<FK NULL>>
--
foreign_track_id : TEXT <<UNIQUE>>
title : TEXT
track_number : INT
disc_number : INT
duration_ms : INT
explicit : BOOLEAN
}
entity "track_files" {
* id : UUID <<PK>>
--
album_id : UUID <<FK>>
--
path : TEXT
relative_path : TEXT
size : BIGINT
--
file_hash : TEXT
audio_hash : TEXT
--
quality : JSONB
media_info : JSONB
--
scene_name : TEXT
release_group : TEXT
--
date_added : TIMESTAMPTZ
}
}
' ══════════════════════════════════════════════════════════════
' CONFIGURATION
' ══════════════════════════════════════════════════════════════
package "Configuration" #FFF3E0 {
entity "quality_profiles" {
* id : UUID <<PK>>
--
name : TEXT <<UNIQUE>>
cutoff : INT
items : JSONB
upgrade_allowed : BOOLEAN
}
entity "metadata_profiles" {
* id : UUID <<PK>>
--
name : TEXT <<UNIQUE>>
primary_album_types : JSONB
secondary_album_types : JSONB
release_statuses : JSONB
}
entity "root_folders" {
* id : UUID <<PK>>
--
name : TEXT
path : TEXT <<UNIQUE>>
default_quality_profile_id : UUID <<FK>>
default_metadata_profile_id : UUID <<FK>>
}
entity "indexers" {
* id : UUID <<PK>>
--
name : TEXT
implementation : TEXT
settings : JSONB
enable_rss : BOOLEAN
enable_search : BOOLEAN
priority : INT
}
entity "download_clients" {
* id : UUID <<PK>>
--
name : TEXT
implementation : TEXT
settings : JSONB
protocol : TEXT
priority : INT
enabled : BOOLEAN
}
}
' ══════════════════════════════════════════════════════════════
' DOWNLOAD TRACKING
' ══════════════════════════════════════════════════════════════
package "Download Tracking" #E8F5E9 {
entity "wanted_albums" {
* id : UUID <<PK>>
--
album_id : UUID <<FK>> <<UNIQUE>>
--
priority : INT
search_count : INT
last_searched_at : TIMESTAMPTZ
added_at : TIMESTAMPTZ
}
entity "download_queue" {
* id : UUID <<PK>>
--
artist_id : UUID <<FK>>
album_id : UUID <<FK>>
--
download_id : TEXT
title : TEXT
size : BIGINT
size_left : BIGINT
--
status : TEXT
progress : REAL
error_message : TEXT
--
protocol : TEXT
indexer : TEXT
download_client : TEXT
torrent_hash : TEXT
output_path : TEXT
--
added_at : TIMESTAMPTZ
completed_at : TIMESTAMPTZ
}
entity "blocklist" {
* id : UUID <<PK>>
--
artist_id : UUID <<FK>>
album_id : UUID <<FK>>
--
source_title : TEXT
quality : JSONB
size : BIGINT
protocol : TEXT
indexer : TEXT
message : TEXT
torrent_hash : TEXT
--
date : TIMESTAMPTZ
}
}
' ══════════════════════════════════════════════════════════════
' RELATIONSHIPS
' ══════════════════════════════════════════════════════════════
' Core music relationships
artist_metadata ||--|| artists : "has config"
artist_metadata ||--o{ albums : "released"
albums ||--o{ album_releases : "has releases"
album_releases ||--o{ tracks : "contains"
tracks }o--o| track_files : "stored in"
track_files }o--|| albums : "belongs to"
' Artist config relationships
artists }o--|| quality_profiles : "uses"
artists }o--o| metadata_profiles : "uses"
artists }o--o| root_folders : "stored in"
' Root folder defaults
root_folders }o--o| quality_profiles : "default"
root_folders }o--o| metadata_profiles : "default"
' Download tracking relationships
wanted_albums ||--|| albums : "targets"
download_queue }o--o| artists : "for"
download_queue }o--o| albums : "for"
blocklist }o--|| artists : "for"
blocklist }o--o| albums : "for"
@enduml
+362
View File
@@ -0,0 +1,362 @@
@startuml Lidarr-Style Music Aggregator ERD
skinparam linetype ortho
skinparam ranksep 60
skinparam nodesep 40
skinparam entity {
BackgroundColor White
BorderColor #333333
}
skinparam package {
BackgroundColor #FAFAFA
BorderColor #DDDDDD
}
title Music Aggregator - Lidarr-Style Database Structure
' ══════════════════════════════════════════════════════════════
' CORE MUSIC ENTITIES
' ══════════════════════════════════════════════════════════════
package "Core Music Entities" #E3F2FD {
entity "artist_metadata" {
* id : UUID <<PK>>
--
foreign_artist_id : TEXT <<UNIQUE>>
name : TEXT
sort_name : TEXT
disambiguation : TEXT
artist_type : TEXT
status : TEXT
overview : TEXT
images : JSONB
links : JSONB
genres : JSONB
ratings : JSONB
members : JSONB
--
created_at : TIMESTAMPTZ
updated_at : TIMESTAMPTZ
}
entity "artists" {
* id : UUID <<PK>>
--
metadata_id : UUID <<FK>>
quality_profile_id : UUID <<FK>>
metadata_profile_id : UUID <<FK>>
root_folder_id : UUID <<FK>>
--
path : TEXT
monitored : BOOLEAN
monitor_new_items : TEXT
--
last_info_sync : TIMESTAMPTZ
added_at : TIMESTAMPTZ
tags : INT[]
}
entity "albums" {
* id : UUID <<PK>>
--
artist_metadata_id : UUID <<FK>>
--
foreign_album_id : TEXT <<UNIQUE>>
title : TEXT
clean_title : TEXT
disambiguation : TEXT
overview : TEXT
album_type : TEXT
secondary_types : JSONB
release_date : DATE
images : JSONB
links : JSONB
genres : JSONB
ratings : JSONB
--
monitored : BOOLEAN
any_release_ok : BOOLEAN
last_search_time : TIMESTAMPTZ
added_at : TIMESTAMPTZ
}
entity "album_releases" {
* id : UUID <<PK>>
--
album_id : UUID <<FK>>
--
foreign_release_id : TEXT <<UNIQUE>>
title : TEXT
disambiguation : TEXT
status : TEXT
duration_ms : INT
release_date : DATE
country : TEXT[]
label : TEXT[]
media : JSONB
track_count : INT
--
monitored : BOOLEAN
}
entity "tracks" {
* id : UUID <<PK>>
--
album_release_id : UUID <<FK>>
artist_metadata_id : UUID <<FK>>
track_file_id : UUID <<FK NULL>>
--
foreign_track_id : TEXT <<UNIQUE>>
foreign_recording_id : TEXT
title : TEXT
track_number : INT
disc_number : INT
duration_ms : INT
explicit : BOOLEAN
ratings : JSONB
}
entity "track_files" {
* id : UUID <<PK>>
--
album_id : UUID <<FK>>
--
path : TEXT
relative_path : TEXT
size : BIGINT
quality : JSONB
media_info : JSONB
audio_tags : JSONB
--
scene_name : TEXT
release_group : TEXT
--
date_added : TIMESTAMPTZ
modified_at : TIMESTAMPTZ
}
}
' ══════════════════════════════════════════════════════════════
' CONFIGURATION
' ══════════════════════════════════════════════════════════════
package "Configuration" #FFF3E0 {
entity "quality_profiles" {
* id : UUID <<PK>>
--
name : TEXT <<UNIQUE>>
cutoff : INT
items : JSONB
upgrade_allowed : BOOLEAN
min_format_score : INT
cutoff_format_score : INT
}
entity "metadata_profiles" {
* id : UUID <<PK>>
--
name : TEXT <<UNIQUE>>
primary_album_types : JSONB
secondary_album_types : JSONB
release_statuses : JSONB
}
entity "root_folders" {
* id : UUID <<PK>>
--
name : TEXT
path : TEXT <<UNIQUE>>
default_quality_profile_id : UUID <<FK>>
default_metadata_profile_id : UUID <<FK>>
default_monitor_option : TEXT
default_tags : INT[]
}
entity "tags" {
* id : SERIAL <<PK>>
--
label : TEXT <<UNIQUE>>
}
entity "indexers" {
* id : UUID <<PK>>
--
name : TEXT
implementation : TEXT
settings : JSONB
enable_rss : BOOLEAN
enable_search : BOOLEAN
priority : INT
tags : INT[]
}
entity "download_clients" {
* id : UUID <<PK>>
--
name : TEXT
implementation : TEXT
settings : JSONB
protocol : TEXT
priority : INT
remove_completed : BOOLEAN
remove_failed : BOOLEAN
tags : INT[]
enabled : BOOLEAN
}
}
' ══════════════════════════════════════════════════════════════
' DOWNLOAD TRACKING
' ══════════════════════════════════════════════════════════════
package "Download Tracking" #E8F5E9 {
entity "download_queue" {
* id : UUID <<PK>>
--
artist_id : UUID <<FK>>
album_id : UUID <<FK>>
--
download_id : TEXT
title : TEXT
size : BIGINT
size_left : BIGINT
time_left : INTERVAL
estimated_completion : TIMESTAMPTZ
--
status : TEXT
state : TEXT
status_messages : JSONB
--
protocol : TEXT
indexer : TEXT
download_client : TEXT
output_path : TEXT
download_forced : BOOLEAN
--
added_at : TIMESTAMPTZ
}
entity "pending_releases" {
* id : UUID <<PK>>
--
artist_id : UUID <<FK>>
album_id : UUID <<FK>>
--
title : TEXT
release : JSONB
parsed_album_info : JSONB
reason : TEXT
additional_info : JSONB
--
added_at : TIMESTAMPTZ
}
entity "download_history" {
* id : UUID <<PK>>
--
artist_id : UUID <<FK>>
album_id : UUID <<FK>>
--
event_type : TEXT
download_id : TEXT
source_title : TEXT
protocol : TEXT
indexer_id : UUID
download_client_id : UUID
release : JSONB
data : JSONB
--
date : TIMESTAMPTZ
}
entity "blocklist" {
* id : UUID <<PK>>
--
artist_id : UUID <<FK>>
album_ids : UUID[]
--
source_title : TEXT
quality : JSONB
size : BIGINT
protocol : TEXT
indexer : TEXT
message : TEXT
torrent_hash : TEXT
--
published_date : TIMESTAMPTZ
date : TIMESTAMPTZ
}
}
' ══════════════════════════════════════════════════════════════
' HISTORY & TRACKING
' ══════════════════════════════════════════════════════════════
package "History & Tracking" #FCE4EC {
entity "history" {
* id : UUID <<PK>>
--
artist_id : UUID <<FK>>
album_id : UUID <<FK>>
track_id : UUID <<FK NULL>>
--
event_type : TEXT
source_title : TEXT
quality : JSONB
download_id : TEXT
data : JSONB
--
date : TIMESTAMPTZ
}
entity "wanted_albums" {
' View/materialized view for missing albums
* album_id : UUID <<FK>>
--
artist_id : UUID
title : TEXT
release_date : DATE
monitored : BOOLEAN
track_count : INT
track_file_count : INT
}
}
' ══════════════════════════════════════════════════════════════
' RELATIONSHIPS
' ══════════════════════════════════════════════════════════════
' Core music relationships
artist_metadata ||--|| artists : "has config"
artist_metadata ||--o{ albums : "released"
albums ||--o{ album_releases : "has releases"
album_releases ||--o{ tracks : "contains"
tracks }o--o| track_files : "stored in"
track_files }o--|| albums : "belongs to"
' Artist relationships
artists }o--|| quality_profiles : "uses"
artists }o--o| metadata_profiles : "uses"
artists }o--o| root_folders : "stored in"
' Configuration relationships
root_folders }o--o| quality_profiles : "default"
root_folders }o--o| metadata_profiles : "default"
' Download tracking relationships
download_queue }o--o| artists : "for"
download_queue }o--o| albums : "for"
pending_releases }o--|| artists : "for"
pending_releases }o--o| albums : "for"
download_history }o--|| artists : "for"
download_history }o--o| albums : "for"
blocklist }o--|| artists : "for"
' History relationships
history }o--|| artists : "for"
history }o--o| albums : "for"
history }o--o| tracks : "for"
@enduml
+93
View File
@@ -0,0 +1,93 @@
# Lidarr Database Research Summary
## Overview
Lidarr is a music collection manager that uses a **Release-based** system. Key design principles:
1. **Metadata Separation** - Artist metadata separated from configuration
2. **Release-Centric** - Works with releases, not loose tracks
3. **Monitoring Hierarchy** - Artist → Album → Release (only one release monitored per album)
4. **Quality Profiles** - Separate profiles for quality and metadata preferences
5. **Download Lifecycle** - Pending → Queue → Imported → History/Blocklist
## Core Entity Hierarchy
```
ArtistMetadata (1) ←→ (1) Artist (1) ←→ (N) Albums
(N) AlbumReleases (only 1 monitored)
(N) Tracks (N) ←→ (1) TrackFile
```
## Key Entities
### Music Entities
- **Artists** - Configuration (path, monitoring, profiles)
- **ArtistMetadata** - Metadata (name, images, genres, members)
- **Albums** - Album info with monitoring and search tracking
- **AlbumReleases** - Physical releases (CD, Vinyl, Digital) - only ONE monitored per album
- **Tracks** - Individual tracks linked to releases
- **TrackFiles** - Actual files on disk with quality info
### Configuration
- **QualityProfiles** - Acceptable formats and upgrade cutoff
- **MetadataProfiles** - Which album types to include (Studio, EP, Live, etc.)
- **RootFolders** - Storage locations with defaults
### Download Tracking
- **PendingReleases** - Delayed downloads (waiting for better quality)
- **DownloadHistory** - Download lifecycle events
- **Blocklist** - Failed/rejected releases (prevent re-download)
- **History** - Complete audit trail of all events
### System
- **Indexers** - Search sources (Newznab/Torznab)
- **DownloadClients** - Torrent/Usenet clients
- **ImportLists** - Auto-import from Spotify, Last.fm, etc.
- **Tags** - Categorization
## Monitoring States
### Artist Level
- `monitored` - Is artist being tracked
- `monitor_new_items` - Policy for new releases (All/Future/Missing/Existing/None)
### Album Level
- `monitored` - Should we look for this album
- `any_release_ok` - Auto-switch releases during import
### Release Level
- `monitored` - Is this the release we want (exactly ONE per album)
## Download States
```
TrackedDownloadState:
Downloading → ImportPending → Importing → Imported
→ Ignored
→ DownloadFailed → DownloadFailedPending
```
## History Event Types
- Grabbed - Download started
- TrackFileImported - File imported to library
- DownloadFailed - Download failed
- TrackFileDeleted - File removed
- TrackFileRenamed - File renamed
- TrackFileRetagged - Metadata updated
- AlbumImportIncomplete - Partial import
- DownloadIgnored - Download skipped
## Sources
- GitHub: Lidarr/Lidarr
- Key files:
- `src/NzbDrone.Core/Music/Model/Artist.cs`
- `src/NzbDrone.Core/Music/Model/Album.cs`
- `src/NzbDrone.Core/Music/Model/Release.cs` (AlbumRelease)
- `src/NzbDrone.Core/Music/Model/Track.cs`
- `src/NzbDrone.Core/MediaFiles/TrackFile.cs`
- `src/NzbDrone.Core/Datastore/Migration/001_initial_setup.cs`
- `src/NzbDrone.Core/Datastore/Migration/023_add_release_groups_etc.cs`