feat: add artist sync flow and stub torrent client

- Add DownloadService to orchestrate metadata → indexer → torrent flow
- Add POST /api/sync/artist endpoint for syncing artist albums
- Add StubTorrentClient for testing (logs requests to file)
- Refactor TorrentConfig to tagged enum (client_type: qbittorrent|stub|none)
- Add POST /api/reload endpoint for hot config reload
- Add chrono dependency for timestamps
This commit is contained in:
Alexander
2026-04-28 21:40:11 +02:00
parent 925c7c3703
commit 3aaeade4d3
13 changed files with 697 additions and 37 deletions
+24
View File
@@ -1,5 +1,6 @@
mod indexer_controller;
mod metadata_controller;
mod sync_controller;
mod torrent_controller;
use axum::{
@@ -17,6 +18,7 @@ use crate::AppState;
pub fn routes(state: AppState) -> Router {
Router::new()
.route("/health", get(health))
.route("/reload", post(reload))
.route("/tracks", get(list_tracks))
.route("/tracks", post(create_track))
.route("/tracks/{id}", get(get_track))
@@ -26,6 +28,7 @@ pub fn routes(state: AppState) -> Router {
.nest("/indexers", indexer_controller::routes())
.nest("/torrents", torrent_controller::routes())
.nest("/metadata", metadata_controller::routes())
.nest("/sync", sync_controller::routes())
.with_state(state)
}
@@ -60,6 +63,27 @@ async fn health(State(state): State<AppState>) -> Json<Health> {
})
}
#[derive(serde::Serialize)]
struct ReloadResponse {
success: bool,
#[serde(skip_serializing_if = "Option::is_none")]
error: Option<String>,
}
async fn reload(State(state): State<AppState>) -> Json<ReloadResponse> {
let mut state = state.write().await;
match state.reload().await {
Ok(()) => Json(ReloadResponse {
success: true,
error: None,
}),
Err(e) => Json(ReloadResponse {
success: false,
error: Some(e),
}),
}
}
async fn list_tracks(State(state): State<AppState>) -> Json<Vec<Track>> {
let state = state.read().await;
Json(state.aggregator.get_all().to_vec())