mod indexer_controller; mod library_controller; mod metadata_controller; mod sync_controller; mod torrent_controller; use axum::{ extract::{Path, Query, State}, http::StatusCode, routing::{delete, get, post}, Json, Router, }; use serde::Deserialize; use uuid::Uuid; use crate::models::{CreateTrack, Track}; 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)) .route("/tracks/{id}", delete(delete_track)) .route("/tracks/search", get(search_tracks)) .route("/stats", get(get_stats)) .nest("/indexers", indexer_controller::routes()) .nest("/torrents", torrent_controller::routes()) .nest("/metadata", metadata_controller::routes()) .nest("/sync", sync_controller::routes()) .nest("/library", library_controller::routes()) .with_state(state) } #[derive(serde::Serialize)] struct Health { status: &'static str, services: ServiceStatus, } #[derive(serde::Serialize)] struct ServiceStatus { torrent: bool, metadata: bool, indexers: Vec, } async fn health(State(state): State) -> Json { let state = state.read().await; let indexers = state .indexer_service .list_indexers() .into_iter() .map(|i| i.name) .collect(); Json(Health { status: "ok", services: ServiceStatus { torrent: state.torrent_service.is_connected().await, metadata: state.metadata_service.is_connected(), indexers, }, }) } #[derive(serde::Serialize)] struct ReloadResponse { success: bool, #[serde(skip_serializing_if = "Option::is_none")] error: Option, } async fn reload(State(state): State) -> Json { 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) -> Json> { let state = state.read().await; Json(state.aggregator.get_all().to_vec()) } async fn create_track( State(state): State, Json(input): Json, ) -> (StatusCode, Json) { let mut state = state.write().await; let track = state.aggregator.add_track(input.into()); (StatusCode::CREATED, Json(track)) } async fn get_track( State(state): State, Path(id): Path, ) -> Result, StatusCode> { let state = state.read().await; state .aggregator .get_by_id(id) .cloned() .map(Json) .ok_or(StatusCode::NOT_FOUND) } async fn delete_track(State(state): State, Path(id): Path) -> StatusCode { let mut state = state.write().await; if state.aggregator.delete(id) { StatusCode::NO_CONTENT } else { StatusCode::NOT_FOUND } } #[derive(Deserialize)] struct SearchQuery { artist: String, } async fn search_tracks( State(state): State, Query(query): Query, ) -> Json> { let state = state.read().await; Json( state .aggregator .search_by_artist(&query.artist) .into_iter() .cloned() .collect(), ) } #[derive(serde::Serialize)] struct Stats { track_count: usize, total_duration_secs: u32, } async fn get_stats(State(state): State) -> Json { let state = state.read().await; Json(Stats { track_count: state.aggregator.get_all().len(), total_duration_secs: state.aggregator.total_duration(), }) }