diff --git a/src/library/album.rs b/src/library/album.rs index 52439b9..e0aeae8 100644 --- a/src/library/album.rs +++ b/src/library/album.rs @@ -3,9 +3,13 @@ use mongod::{ derive::{Model, Referencable}, Model, Referencable, Reference, Validate, }; +use mongodb::bson::doc; use serde::{Deserialize, Serialize}; +use serde_json::json; -use crate::library::artist::Artist; +use crate::{library::artist::Artist, route::ToAPI}; + +use super::track::Track; #[derive(Debug, Clone, Serialize, Deserialize, Model, Referencable)] pub struct Album { @@ -28,6 +32,44 @@ impl Album { a.insert().await.unwrap(); a } + + pub async fn get_tracks_of_album(album: &str) -> Vec { + Track::find(doc! { "album_id": album}, None).await.unwrap() + } + + pub async fn get_cover(&self) -> Option { + let track_path = Self::get_tracks_of_album(&format!("album::{}", self._id)) + .await + .first()? + .path + .clone(); + let track_path = std::path::Path::new(&track_path); + + for ext in ["png", "jpg", "jpeg", "avif"] { + let cover_file = track_path.parent()?.join(format!("cover.{ext}")); + + if cover_file.exists() { + return Some(cover_file.to_str().unwrap().to_string()); + } + } + + None + } +} + +impl ToAPI for Album { + async fn api(&self) -> serde_json::Value { + json!({ + "id": &self._id, + "title": &self.title, + "artist": self.artist_id.as_ref().map(|x| x.id()), + "cover_url": if self.get_cover().await.is_some() { + Some(format!("/album/{}/cover", self._id)) + } else { + None + } + }) + } } impl Validate for Album { diff --git a/src/library/artist.rs b/src/library/artist.rs index 4549e49..63a21fd 100644 --- a/src/library/artist.rs +++ b/src/library/artist.rs @@ -3,6 +3,9 @@ use mongod::{ Model, Validate, }; use serde::{Deserialize, Serialize}; +use serde_json::json; + +use crate::route::ToAPI; #[derive(Debug, Clone, Serialize, Deserialize, Model, Referencable)] pub struct Artist { @@ -21,6 +24,15 @@ impl Artist { } } +impl ToAPI for Artist { + async fn api(&self) -> serde_json::Value { + json!({ + "id": &self._id, + "name": &self.name + }) + } +} + impl Validate for Artist { async fn validate(&self) -> Result<(), String> { Ok(()) diff --git a/src/library/mod.rs b/src/library/mod.rs index 4be72a1..db55af6 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -106,6 +106,7 @@ impl Libary { .insert("meta".into(), metadata.0); } + // if no title in metadata use file name if entry.as_object().unwrap().get("title").is_none() { entry.as_object_mut().unwrap().insert( "title".into(), @@ -125,10 +126,6 @@ impl Libary { Artist::find(doc! {}, None).await.unwrap() } - pub async fn get_artist_by_id(&self, id: &str) -> Option { - Artist::get(id).await - } - pub async fn get_albums_by_artist(&self, artist: &str) -> Vec { let artist = format!("artist::{artist}"); Album::find(doc! { "artist_id": artist}, None) @@ -140,10 +137,6 @@ impl Libary { Album::get(album).await } - pub async fn get_tracks_of_album(&self, album: &str) -> Vec { - Track::find(doc! { "album_id": album}, None).await.unwrap() - } - pub async fn get_track_by_id(&self, track_id: &str) -> Option { Track::get(track_id).await } diff --git a/src/library/track.rs b/src/library/track.rs index 80fa6fe..24e758d 100644 --- a/src/library/track.rs +++ b/src/library/track.rs @@ -6,7 +6,10 @@ use mongod::{ use serde::{Deserialize, Serialize}; use serde_json::json; -use crate::library::{album::Album, artist::Artist}; +use crate::{ + library::{album::Album, artist::Artist}, + route::ToAPI, +}; use super::metadata::AudioMetadata; @@ -59,8 +62,10 @@ impl Track { transcoded } +} - pub async fn api(&self) -> serde_json::Value { +impl ToAPI for Track { + async fn api(&self) -> serde_json::Value { let album_title = if let Some(album_ref) = &self.album_id { album_ref .get_partial::(json!({"title": 1})) diff --git a/src/route/album.rs b/src/route/album.rs index 04ab986..014e9f8 100644 --- a/src/route/album.rs +++ b/src/route/album.rs @@ -2,13 +2,16 @@ use std::cmp::Ordering; use super::api_error; use super::FallibleApiResponse; +use super::ToAPI; use mongod::Referencable; use mongodb::bson::doc; use rocket::fs::NamedFile; use rocket::*; use serde_json::json; +use crate::library::album::Album; use crate::library::Libary; +use crate::route::to_api; #[get("/artist//albums")] pub async fn albums_route(artist_id: &str, lib: &State) -> FallibleApiResponse { @@ -16,7 +19,7 @@ pub async fn albums_route(artist_id: &str, lib: &State) -> FallibleApiRe Ok(json!({ "artist": artist_id, - "albums": &albums + "albums": to_api(&albums).await })) } @@ -31,24 +34,7 @@ fn sort_by_tracknumber(a: &serde_json::Value, b: &serde_json::Value) -> Ordering #[get("/album//cover")] pub async fn album_cover_route(album_id: &str, lib: &State) -> Option { let album = lib.get_album_by_id(album_id).await?; - - let track_path = lib - .get_tracks_of_album(&format!("album::{}", album._id)) - .await - .first()? - .path - .clone(); - let track_path = std::path::Path::new(&track_path); - - for ext in ["png", "jpg", "jpeg", "avif"] { - let cover_file = track_path.parent()?.join(format!("cover.{ext}")); - - if cover_file.exists() { - return NamedFile::open(cover_file).await.ok(); - } - } - - None + NamedFile::open(album.get_cover().await?).await.ok() } #[get("/album/")] @@ -58,8 +44,7 @@ pub async fn album_route(album_id: &str, lib: &State) -> FallibleApiResp .await .ok_or_else(|| api_error("No album with that ID found"))?; - let mut tracks = lib - .get_tracks_of_album(&format!("album::{}", album._id)) + let mut tracks = Album::get_tracks_of_album(&format!("album::{}", album._id)) .await .into_iter() .map(|x| { @@ -73,7 +58,7 @@ pub async fn album_route(album_id: &str, lib: &State) -> FallibleApiResp tracks.sort_by(sort_by_tracknumber); - let mut album = serde_json::to_value(album).unwrap(); + let mut album = album.api().await; album .as_object_mut() .unwrap() diff --git a/src/route/artist.rs b/src/route/artist.rs index 749e208..b50a567 100644 --- a/src/route/artist.rs +++ b/src/route/artist.rs @@ -1,23 +1,26 @@ use super::api_error; +use super::to_api; use super::FallibleApiResponse; +use super::ToAPI; +use mongod::Model; use mongodb::bson::doc; use rocket::*; +use crate::library::artist::Artist; use crate::library::Libary; /// Get all artists #[get("/artists")] pub async fn artists_route(lib: &State) -> FallibleApiResponse { let artists = lib.get_artists().await; - Ok(serde_json::to_value(&artists).unwrap()) + Ok(serde_json::to_value(&to_api(&artists).await).unwrap()) } #[get("/artist/")] -pub async fn artist_route(id: &str, lib: &State) -> FallibleApiResponse { - Ok(serde_json::to_value( - &lib.get_artist_by_id(id) - .await - .ok_or_else(|| api_error("No artist with that ID found"))?, - ) - .unwrap()) +pub async fn artist_route(id: &str) -> FallibleApiResponse { + Ok(Artist::get(id) + .await + .ok_or_else(|| api_error("No artist with that ID found"))? + .api() + .await) } diff --git a/src/route/mod.rs b/src/route/mod.rs index 604f308..f608d0b 100644 --- a/src/route/mod.rs +++ b/src/route/mod.rs @@ -6,6 +6,8 @@ pub mod artist; pub mod track; pub mod user; +// todo : rework api + type ApiError = BadRequest; type FallibleApiResponse = Result; @@ -14,3 +16,18 @@ pub fn api_error(msg: &str) -> ApiError { "error": msg })) } + +pub trait ToAPI: Sized { + /// Generate public API JSON + fn api(&self) -> impl std::future::Future; +} + +pub async fn to_api(albums: &[impl ToAPI]) -> Vec { + let mut ret = Vec::new(); + + for e in albums { + ret.push(e.api().await); + } + + ret +} diff --git a/src/route/track.rs b/src/route/track.rs index 8c73b35..35e7670 100644 --- a/src/route/track.rs +++ b/src/route/track.rs @@ -1,5 +1,6 @@ use super::api_error; use super::FallibleApiResponse; +use super::ToAPI; use fs::NamedFile; use mongodb::bson::doc; use rocket::*;