api
This commit is contained in:
parent
95a2a71f25
commit
5d2465353a
8 changed files with 99 additions and 41 deletions
|
@ -3,9 +3,13 @@ use mongod::{
|
||||||
derive::{Model, Referencable},
|
derive::{Model, Referencable},
|
||||||
Model, Referencable, Reference, Validate,
|
Model, Referencable, Reference, Validate,
|
||||||
};
|
};
|
||||||
|
use mongodb::bson::doc;
|
||||||
use serde::{Deserialize, Serialize};
|
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)]
|
#[derive(Debug, Clone, Serialize, Deserialize, Model, Referencable)]
|
||||||
pub struct Album {
|
pub struct Album {
|
||||||
|
@ -28,6 +32,44 @@ impl Album {
|
||||||
a.insert().await.unwrap();
|
a.insert().await.unwrap();
|
||||||
a
|
a
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_tracks_of_album(album: &str) -> Vec<Track> {
|
||||||
|
Track::find(doc! { "album_id": album}, None).await.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_cover(&self) -> Option<String> {
|
||||||
|
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 {
|
impl Validate for Album {
|
||||||
|
|
|
@ -3,6 +3,9 @@ use mongod::{
|
||||||
Model, Validate,
|
Model, Validate,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
use crate::route::ToAPI;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, Model, Referencable)]
|
#[derive(Debug, Clone, Serialize, Deserialize, Model, Referencable)]
|
||||||
pub struct Artist {
|
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 {
|
impl Validate for Artist {
|
||||||
async fn validate(&self) -> Result<(), String> {
|
async fn validate(&self) -> Result<(), String> {
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -106,6 +106,7 @@ impl Libary {
|
||||||
.insert("meta".into(), metadata.0);
|
.insert("meta".into(), metadata.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if no title in metadata use file name
|
||||||
if entry.as_object().unwrap().get("title").is_none() {
|
if entry.as_object().unwrap().get("title").is_none() {
|
||||||
entry.as_object_mut().unwrap().insert(
|
entry.as_object_mut().unwrap().insert(
|
||||||
"title".into(),
|
"title".into(),
|
||||||
|
@ -125,10 +126,6 @@ impl Libary {
|
||||||
Artist::find(doc! {}, None).await.unwrap()
|
Artist::find(doc! {}, None).await.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_artist_by_id(&self, id: &str) -> Option<Artist> {
|
|
||||||
Artist::get(id).await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_albums_by_artist(&self, artist: &str) -> Vec<Album> {
|
pub async fn get_albums_by_artist(&self, artist: &str) -> Vec<Album> {
|
||||||
let artist = format!("artist::{artist}");
|
let artist = format!("artist::{artist}");
|
||||||
Album::find(doc! { "artist_id": artist}, None)
|
Album::find(doc! { "artist_id": artist}, None)
|
||||||
|
@ -140,10 +137,6 @@ impl Libary {
|
||||||
Album::get(album).await
|
Album::get(album).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_tracks_of_album(&self, album: &str) -> Vec<Track> {
|
|
||||||
Track::find(doc! { "album_id": album}, None).await.unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_track_by_id(&self, track_id: &str) -> Option<Track> {
|
pub async fn get_track_by_id(&self, track_id: &str) -> Option<Track> {
|
||||||
Track::get(track_id).await
|
Track::get(track_id).await
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,10 @@ use mongod::{
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
use crate::library::{album::Album, artist::Artist};
|
use crate::{
|
||||||
|
library::{album::Album, artist::Artist},
|
||||||
|
route::ToAPI,
|
||||||
|
};
|
||||||
|
|
||||||
use super::metadata::AudioMetadata;
|
use super::metadata::AudioMetadata;
|
||||||
|
|
||||||
|
@ -59,8 +62,10 @@ impl Track {
|
||||||
|
|
||||||
transcoded
|
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 {
|
let album_title = if let Some(album_ref) = &self.album_id {
|
||||||
album_ref
|
album_ref
|
||||||
.get_partial::<Album>(json!({"title": 1}))
|
.get_partial::<Album>(json!({"title": 1}))
|
||||||
|
|
|
@ -2,13 +2,16 @@ use std::cmp::Ordering;
|
||||||
|
|
||||||
use super::api_error;
|
use super::api_error;
|
||||||
use super::FallibleApiResponse;
|
use super::FallibleApiResponse;
|
||||||
|
use super::ToAPI;
|
||||||
use mongod::Referencable;
|
use mongod::Referencable;
|
||||||
use mongodb::bson::doc;
|
use mongodb::bson::doc;
|
||||||
use rocket::fs::NamedFile;
|
use rocket::fs::NamedFile;
|
||||||
use rocket::*;
|
use rocket::*;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
|
use crate::library::album::Album;
|
||||||
use crate::library::Libary;
|
use crate::library::Libary;
|
||||||
|
use crate::route::to_api;
|
||||||
|
|
||||||
#[get("/artist/<artist_id>/albums")]
|
#[get("/artist/<artist_id>/albums")]
|
||||||
pub async fn albums_route(artist_id: &str, lib: &State<Libary>) -> FallibleApiResponse {
|
pub async fn albums_route(artist_id: &str, lib: &State<Libary>) -> FallibleApiResponse {
|
||||||
|
@ -16,7 +19,7 @@ pub async fn albums_route(artist_id: &str, lib: &State<Libary>) -> FallibleApiRe
|
||||||
|
|
||||||
Ok(json!({
|
Ok(json!({
|
||||||
"artist": artist_id,
|
"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/<album_id>/cover")]
|
#[get("/album/<album_id>/cover")]
|
||||||
pub async fn album_cover_route(album_id: &str, lib: &State<Libary>) -> Option<NamedFile> {
|
pub async fn album_cover_route(album_id: &str, lib: &State<Libary>) -> Option<NamedFile> {
|
||||||
let album = lib.get_album_by_id(album_id).await?;
|
let album = lib.get_album_by_id(album_id).await?;
|
||||||
|
NamedFile::open(album.get_cover().await?).await.ok()
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/album/<album_id>")]
|
#[get("/album/<album_id>")]
|
||||||
|
@ -58,8 +44,7 @@ pub async fn album_route(album_id: &str, lib: &State<Libary>) -> FallibleApiResp
|
||||||
.await
|
.await
|
||||||
.ok_or_else(|| api_error("No album with that ID found"))?;
|
.ok_or_else(|| api_error("No album with that ID found"))?;
|
||||||
|
|
||||||
let mut tracks = lib
|
let mut tracks = Album::get_tracks_of_album(&format!("album::{}", album._id))
|
||||||
.get_tracks_of_album(&format!("album::{}", album._id))
|
|
||||||
.await
|
.await
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|x| {
|
.map(|x| {
|
||||||
|
@ -73,7 +58,7 @@ pub async fn album_route(album_id: &str, lib: &State<Libary>) -> FallibleApiResp
|
||||||
|
|
||||||
tracks.sort_by(sort_by_tracknumber);
|
tracks.sort_by(sort_by_tracknumber);
|
||||||
|
|
||||||
let mut album = serde_json::to_value(album).unwrap();
|
let mut album = album.api().await;
|
||||||
album
|
album
|
||||||
.as_object_mut()
|
.as_object_mut()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|
|
@ -1,23 +1,26 @@
|
||||||
use super::api_error;
|
use super::api_error;
|
||||||
|
use super::to_api;
|
||||||
use super::FallibleApiResponse;
|
use super::FallibleApiResponse;
|
||||||
|
use super::ToAPI;
|
||||||
|
use mongod::Model;
|
||||||
use mongodb::bson::doc;
|
use mongodb::bson::doc;
|
||||||
use rocket::*;
|
use rocket::*;
|
||||||
|
|
||||||
|
use crate::library::artist::Artist;
|
||||||
use crate::library::Libary;
|
use crate::library::Libary;
|
||||||
|
|
||||||
/// Get all artists
|
/// Get all artists
|
||||||
#[get("/artists")]
|
#[get("/artists")]
|
||||||
pub async fn artists_route(lib: &State<Libary>) -> FallibleApiResponse {
|
pub async fn artists_route(lib: &State<Libary>) -> FallibleApiResponse {
|
||||||
let artists = lib.get_artists().await;
|
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/<id>")]
|
#[get("/artist/<id>")]
|
||||||
pub async fn artist_route(id: &str, lib: &State<Libary>) -> FallibleApiResponse {
|
pub async fn artist_route(id: &str) -> FallibleApiResponse {
|
||||||
Ok(serde_json::to_value(
|
Ok(Artist::get(id)
|
||||||
&lib.get_artist_by_id(id)
|
.await
|
||||||
.await
|
.ok_or_else(|| api_error("No artist with that ID found"))?
|
||||||
.ok_or_else(|| api_error("No artist with that ID found"))?,
|
.api()
|
||||||
)
|
.await)
|
||||||
.unwrap())
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,8 @@ pub mod artist;
|
||||||
pub mod track;
|
pub mod track;
|
||||||
pub mod user;
|
pub mod user;
|
||||||
|
|
||||||
|
// todo : rework api
|
||||||
|
|
||||||
type ApiError = BadRequest<serde_json::Value>;
|
type ApiError = BadRequest<serde_json::Value>;
|
||||||
type FallibleApiResponse = Result<serde_json::Value, ApiError>;
|
type FallibleApiResponse = Result<serde_json::Value, ApiError>;
|
||||||
|
|
||||||
|
@ -14,3 +16,18 @@ pub fn api_error(msg: &str) -> ApiError {
|
||||||
"error": msg
|
"error": msg
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait ToAPI: Sized {
|
||||||
|
/// Generate public API JSON
|
||||||
|
fn api(&self) -> impl std::future::Future<Output = serde_json::Value>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn to_api(albums: &[impl ToAPI]) -> Vec<serde_json::Value> {
|
||||||
|
let mut ret = Vec::new();
|
||||||
|
|
||||||
|
for e in albums {
|
||||||
|
ret.push(e.api().await);
|
||||||
|
}
|
||||||
|
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use super::api_error;
|
use super::api_error;
|
||||||
use super::FallibleApiResponse;
|
use super::FallibleApiResponse;
|
||||||
|
use super::ToAPI;
|
||||||
use fs::NamedFile;
|
use fs::NamedFile;
|
||||||
use mongodb::bson::doc;
|
use mongodb::bson::doc;
|
||||||
use rocket::*;
|
use rocket::*;
|
||||||
|
|
Loading…
Reference in a new issue