use axum::{ extract::DefaultBodyLimit, extract::State, http::{ header::{self, *}, Method, StatusCode, Uri, }, response::{IntoResponse, Response}, routing::{get, post}, Json, Router, }; use mpd::Client; use serde::ser::{Serialize, SerializeStruct, Serializer}; use serde_json::json; use std::sync::Arc; use tower_http::cors::CorsLayer; use crate::Context; use crate::{ config, config::{Station, StationId, StationStatus}, }; pub fn routes(state: Arc) -> Router { let cors = CorsLayer::new() .allow_methods([Method::GET, Method::POST, Method::OPTIONS]) .allow_headers(vec![ORIGIN, AUTHORIZATION, ACCEPT, CONTENT_TYPE, COOKIE]) .allow_origin([ "http://localhost:54605".parse().unwrap(), "http://localhost:5173".parse().unwrap(), ]) .allow_credentials(true); Router::new() .route("/healthcheck", get(healthcheck)) .route("/stations", get(stations)) .route("/status", post(status)) .layer(cors) .fallback(fallback) .with_state(state) } #[derive(Debug, serde::Serialize)] pub enum Playback { Stopped, Playing, Paused, } #[derive(Debug, serde::Serialize)] pub struct SongInfo { pub artist: Option, pub title: Option, pub duration: Option, pub elapsed: Option, pub tags: Vec<(String, String)>, } #[derive(Debug, serde::Serialize)] pub struct StationInfo { pub id: StationId, pub name: String, pub url: Option, pub status: config::StationStatus, pub location: Option, pub genre: Option, pub playback: Option, } impl Default for StationInfo { fn default() -> Self { StationInfo { id: StationId(String::from("station")), name: String::from("Station"), url: None, status: config::StationStatus::Receive, location: None, genre: None, playback: None, } } } impl From for StationInfo { fn from(station: Station) -> Self { StationInfo { id: station.id, name: station.name, url: station.url, status: station.status, location: station.location, genre: station.genre, ..StationInfo::default() } } } pub async fn stations(State(state): State>) -> (StatusCode, Json>) { match &state.config.stations { Some(stations) => { let mut stations_info: Vec = Vec::with_capacity(stations.len()); for station in stations { stations_info.push(match station.status { StationStatus::Online | StationStatus::Offline => { StationInfo::from(station.clone()) } StationStatus::Receive => { let connection = Client::connect(format!("{}:{}", station.host, station.port)); if let Ok(mut client) = connection { let mut info = StationInfo::from(station.clone()); if let Ok(status) = client.status() { info.playback = match status.state { mpd::State::Play => Some(Playback::Playing), mpd::State::Stop => Some(Playback::Stopped), mpd::State::Pause => Some(Playback::Paused), }; } info } else { StationInfo::from(station.clone()) } } }) } (StatusCode::OK, Json(stations_info)) } None => (StatusCode::OK, Json(Vec::new())), } } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct GetSongInfo { pub id: StationId, } pub async fn status( State(state): State>, Json(body): Json, ) -> (StatusCode, Json>) { match &state.config.stations { Some(stations) => { for station in stations { if station.id.0.eq(&body.id.0) { let connection = Client::connect(format!("{}:{}", station.host, station.port)); if let Ok(mut client) = connection { if let Ok(Some(current)) = client.currentsong() { let mut info = SongInfo { artist: current.artist, title: current.title, duration: None, elapsed: None, tags: current.tags, }; if let Ok(status) = client.status() { if let Some(time) = status.time { info.duration = Some(time.1.as_secs()); info.elapsed = Some(time.0.as_secs()); } } return (StatusCode::OK, Json(Some(info))); } else { return (StatusCode::OK, Json(None)); } } else { return (StatusCode::OK, Json(None)); } } } (StatusCode::OK, Json(None)) } None => (StatusCode::OK, Json(None)), } } pub async fn healthcheck() -> impl IntoResponse { ( StatusCode::OK, Json(json!({ "status": StatusCode::OK.to_string(), })), ) } pub async fn fallback() -> impl IntoResponse { ( StatusCode::NOT_FOUND, Json(json!({ "status": StatusCode::NOT_FOUND.to_string(), })), ) }