finish postgres

This commit is contained in:
JMARyA 2024-10-04 14:38:35 +02:00
parent 7b7e1a4014
commit 08e24f63f4
Signed by: jmarya
GPG key ID: 901B2ADDF27C2263
16 changed files with 454 additions and 306 deletions

View file

@ -1,8 +1,7 @@
use super::api_error;
use super::FallibleApiResponse;
use mongod::vec_to_api;
use mongod::Model;
use mongodb::bson::doc;
use crate::get_pg;
use crate::route::vec_to_api;
use rocket::{get, State};
use serde_json::json;
@ -21,13 +20,11 @@ pub async fn clean_library(lib: &State<Libary>, u: User) -> FallibleApiResponse
#[get("/library/singles")]
pub async fn get_singles_route(u: User) -> FallibleApiResponse {
check_admin!(u);
let singles = Track::find(
doc! { "album_id": None::<String>, "artist_id": {"$ne": None::<String> }},
None,
None,
)
.await
.unwrap();
let singles: Vec<Track> =
sqlx::query_as("SELECT * FROM track WHERE album IS NULL AND artist IS NOT NULL")
.fetch_all(get_pg!())
.await
.unwrap();
Ok(json!(vec_to_api(&singles).await))
}

View file

@ -1,11 +1,8 @@
use std::cmp::Ordering;
use super::api_error;
use super::to_uuid;
use super::FallibleApiResponse;
use mongod::vec_to_api;
use mongod::Model;
use mongod::Referencable;
use mongodb::bson::doc;
use rocket::fs::NamedFile;
use rocket::{get, State};
use serde_json::json;
@ -14,13 +11,14 @@ use crate::cache::RouteCache;
use crate::library::album::Album;
use crate::library::track::Track;
use crate::library::Libary;
use crate::route::vec_to_api;
use crate::route::ToAPI;
use crate::use_api_cache;
use mongod::ToAPI;
#[get("/artist/<artist_id>/albums")]
pub async fn albums_route(artist_id: &str, lib: &State<Libary>) -> FallibleApiResponse {
let albums = lib.get_albums_by_artist(artist_id).await;
let singles = lib.get_singles_by_artist(artist_id).await;
let albums = lib.get_albums_by_artist(&to_uuid(artist_id)?).await;
let singles = lib.get_singles_by_artist(&to_uuid(artist_id)?).await;
Ok(json!({
"artist": artist_id,
@ -49,7 +47,7 @@ pub async fn album_cover_route(
NamedFile::open(
cache
.get_option("album_cover_route", album_id, || async {
let album = lib.get_album_by_id(album_id).await?;
let album = lib.get_album_by_id(&to_uuid(album_id).unwrap()).await?;
album.get_cover().await
})
.await?,
@ -60,18 +58,16 @@ pub async fn album_cover_route(
#[get("/albums/latest")]
pub async fn latest_albums_route(cache: &State<RouteCache>) -> FallibleApiResponse {
// todo : fix
use_api_cache!("albums", "latest", cache);
let albums = Album::find_all().await.unwrap();
let albums = Album::find_all().await;
let mut albums_tracks = vec![];
for album in &albums {
albums_tracks.push(
Track::find_one(doc! { "album_id": album.reference()})
.await
.unwrap(),
);
albums_tracks.push(Track::find_first_of_album(&album.id).await.unwrap());
}
let mut joined: Vec<(_, _)> = albums.into_iter().zip(albums_tracks).collect();
@ -100,11 +96,11 @@ pub async fn album_route(
use_api_cache!("album_route", album_id, cache);
let album = lib
.get_album_by_id(album_id)
.get_album_by_id(&to_uuid(album_id)?)
.await
.ok_or_else(|| api_error("No album with that ID found"))?;
let tracks = Album::get_tracks_of_album(&format!("album::{}", album._id)).await;
let tracks = Album::get_tracks_of_album(&album.id).await;
let mut tracks = vec_to_api(&tracks).await;

View file

@ -1,9 +1,9 @@
use super::api_error;
use super::to_uuid;
use super::vec_to_api;
use super::FallibleApiResponse;
use super::ToAPI;
use fs::NamedFile;
use mongod::vec_to_api;
use mongod::Model;
use mongod::ToAPI;
use mongodb::bson::doc;
use rocket::{fs, get, State};
@ -22,7 +22,7 @@ pub async fn artists_route(lib: &State<Libary>) -> FallibleApiResponse {
pub async fn artist_image_route(id: &str, cache: &State<RouteCache>) -> Option<NamedFile> {
let image = cache
.get_option("artist_image_route", id, || async {
Artist::get_image_of(id).await
Artist::get_image_of(&to_uuid(id).ok()?).await
})
.await;
@ -31,7 +31,7 @@ pub async fn artist_image_route(id: &str, cache: &State<RouteCache>) -> Option<N
#[get("/artist/<id>")]
pub async fn artist_route(id: &str) -> FallibleApiResponse {
Ok(Artist::get(id)
Ok(Artist::get(&to_uuid(id)?)
.await
.ok_or_else(|| api_error("No artist with that ID found"))?
.api()

View file

@ -1,8 +1,6 @@
use super::api_error;
use super::to_uuid;
use super::FallibleApiResponse;
use mongod::reference_of;
use mongod::Model;
use mongodb::bson::doc;
use rocket::post;
use rocket::serde::json::Json;
use serde::Deserialize;
@ -12,7 +10,6 @@ use crate::library::event::Event;
use crate::library::event::EventKind;
use crate::library::track::Track;
use crate::library::user::User;
use mongod::Referencable;
#[derive(Debug, Clone, Deserialize)]
pub struct EventJson {
@ -26,7 +23,10 @@ pub async fn event_report_route(report: Json<EventJson>, u: User) -> FallibleApi
Event::create(
report.kind.clone(),
&u,
reference_of!(Track, track).ok_or_else(|| api_error("Invalid track"))?,
Track::get(&to_uuid(&track)?)
.await
.ok_or_else(|| api_error("Invalid track"))?
.id,
)
.await;

View file

@ -1,3 +1,5 @@
use std::str::FromStr;
use rocket::{
get,
response::{status::BadRequest, Redirect},
@ -16,9 +18,34 @@ pub mod user;
// todo : rework api
/// A trait to generate a Model API representation in JSON format.
pub trait ToAPI: Sized {
/// Generate public API JSON
fn api(&self) -> impl std::future::Future<Output = serde_json::Value>;
}
/// Converts a slice of items implementing the `ToAPI` trait into a `Vec` of JSON values.
pub async fn vec_to_api(items: &[impl ToAPI]) -> Vec<serde_json::Value> {
let mut ret = Vec::with_capacity(items.len());
for e in items {
ret.push(e.api().await);
}
ret
}
pub fn to_uuid(id: &str) -> Result<uuid::Uuid, ApiError> {
uuid::Uuid::from_str(id).map_err(|_| no_uuid_error())
}
type ApiError = BadRequest<serde_json::Value>;
type FallibleApiResponse = Result<serde_json::Value, ApiError>;
pub fn no_uuid_error() -> ApiError {
api_error("No valid UUID")
}
pub fn api_error(msg: &str) -> ApiError {
BadRequest(json!({
"error": msg

View file

@ -1,9 +1,6 @@
use crate::get_pg;
use crate::library::track::Track;
use crate::library::user::User;
use mongod::reference_of;
use mongod::vec_to_api;
use mongod::Model;
use mongod::Referencable;
use mongodb::bson::doc;
use rocket::get;
use rocket::post;
@ -15,7 +12,8 @@ use crate::library::playlist::Visibility;
use crate::route::FallibleApiResponse;
use super::api_error;
use mongod::ToAPI;
use super::to_uuid;
use super::vec_to_api;
#[get("/playlists")]
pub async fn playlists_route(u: User) -> FallibleApiResponse {
@ -24,13 +22,11 @@ pub async fn playlists_route(u: User) -> FallibleApiResponse {
json!({"id": "recentlyAdded", "name": "Recently Added"}),
];
let own_playlists = Playlist::find(doc! { "owner": u.reference()}, None, None)
.await
.unwrap();
let own_playlists = Playlist::of(&u).await;
for playlist in own_playlists {
playlists.push(json!({
"id": playlist._id,
"id": playlist.id,
"name": playlist.title
}));
}
@ -39,9 +35,7 @@ pub async fn playlists_route(u: User) -> FallibleApiResponse {
}
pub async fn recently_added_playlist() -> FallibleApiResponse {
let tracks = Track::find(doc! {}, Some(90), Some(doc! { "date_added": -1 }))
.await
.unwrap();
let tracks = Track::find_recently_added().await;
Ok(json!(vec_to_api(&tracks).await))
}
@ -49,8 +43,8 @@ pub async fn recently_added_playlist() -> FallibleApiResponse {
pub async fn playlist_route(id: &str, u: User) -> FallibleApiResponse {
if id == "recents" {
return Ok(Playlist {
_id: "recents".to_string(),
owner: u.reference(),
id: "recents".to_string(),
owner: u.username,
title: "Recently Played".to_string(),
visibility: Visibility::Public,
tracks: vec![],
@ -61,8 +55,8 @@ pub async fn playlist_route(id: &str, u: User) -> FallibleApiResponse {
if id == "recentlyAdded" {
return Ok(Playlist {
_id: "recentlyAdded".to_string(),
owner: u.reference(),
id: "recentlyAdded".to_string(),
owner: u.username,
title: "Recently Added".to_string(),
visibility: Visibility::Public,
tracks: vec![],
@ -71,13 +65,11 @@ pub async fn playlist_route(id: &str, u: User) -> FallibleApiResponse {
.await);
}
let playlist = Playlist::get(id)
let playlist = Playlist::get(&to_uuid(id)?)
.await
.ok_or_else(|| api_error("No playlist with that ID found"))?;
if matches!(playlist.visibility, Visibility::Private)
&& u.username != playlist.owner.get::<User>().await.username
{
if matches!(playlist.visibility, Visibility::Private) && u.username != playlist.owner {
return Err(api_error("Forbidden"));
}
@ -95,22 +87,26 @@ pub async fn playlist_tracks_route(id: &str, u: User) -> FallibleApiResponse {
return recently_added_playlist().await;
}
let playlist = Playlist::get(id)
let playlist = Playlist::get(&to_uuid(id)?)
.await
.ok_or_else(|| api_error("No playlist with that ID found"))?;
if matches!(playlist.visibility, Visibility::Private)
&& u.username != playlist.owner.get::<User>().await.username
{
if matches!(playlist.visibility, Visibility::Private) && u.username != playlist.owner {
return Err(api_error("Forbidden"));
}
let mut tracks: Vec<Track> = vec![];
let mut tracks: Vec<uuid::Uuid> = vec![];
for track in playlist.tracks {
tracks.push(track.get().await);
tracks.push(track);
}
let tracks: Vec<Track> = sqlx::query_as("SELECT * FROM track WHERE id IN ANY($1)")
.bind(tracks)
.fetch_all(get_pg!())
.await
.unwrap();
Ok(json!(vec_to_api(&tracks).await))
}
@ -118,7 +114,7 @@ pub async fn playlist_tracks_route(id: &str, u: User) -> FallibleApiResponse {
pub struct PlaylistData {
pub title: Option<String>,
pub visibility: Option<Visibility>,
pub tracks: Vec<String>,
pub tracks: Vec<uuid::Uuid>,
}
#[post("/playlist", data = "<playlist>")]
@ -137,9 +133,8 @@ pub async fn playlist_add_route(playlist: Json<PlaylistData>, u: User) -> Fallib
)
.await
.ok_or_else(|| api_error("Failed to create playlist"))?;
playlist.insert().await.unwrap();
Ok(json!({"created": playlist._id}))
Ok(json!({"created": playlist.id}))
}
#[post("/playlist/<id>", data = "<edit>")]
@ -148,34 +143,36 @@ pub async fn playlist_edit_route(
edit: Json<PlaylistData>,
u: User,
) -> FallibleApiResponse {
let playlist = Playlist::get(id)
let playlist = Playlist::get(&to_uuid(id)?)
.await
.ok_or_else(|| api_error("No playlist with that ID found"))?;
if playlist.owner.id() != u._id {
if playlist.owner != u.username {
return Err(api_error("Forbidden"));
}
let mut tracks_ref = vec![];
for track in &edit.tracks {
tracks_ref
.push(reference_of!(Track, track).ok_or_else(|| api_error("Invalid tracks found"))?);
tracks_ref.push(
Track::get(track)
.await
.ok_or_else(|| api_error("Invalid tracks found"))?
.id,
);
}
let playlist_id = playlist._id.clone();
let playlist_id = playlist.id.clone();
let mut changed = playlist.change();
let new_title = edit.title.as_ref().unwrap_or(&playlist.title);
let new_vis = edit.visibility.as_ref().unwrap_or(&playlist.visibility);
if let Some(title) = &edit.title {
changed = changed.title(title);
}
if let Some(visibility) = &edit.visibility {
changed = changed.visibility(visibility.clone());
}
changed.tracks(tracks_ref).update().await.unwrap();
sqlx::query("UPDATE playlist SET title = $1, visibility = $2, tracks = $3 WHERE id = $4")
.bind(new_title)
.bind(new_vis)
.bind(tracks_ref)
.bind(&to_uuid(&playlist_id)?)
.fetch(get_pg!());
Ok(json!({"edited": playlist_id}))
}

View file

@ -1,9 +1,12 @@
use std::str::FromStr;
use super::api_error;
use super::no_uuid_error;
use super::to_uuid;
use super::FallibleApiResponse;
use super::ToAPI;
use crate::library::user::User;
use fs::NamedFile;
use mongod::ToAPI;
use mongodb::bson::doc;
use rocket::{fs, get, State};
use serde_json::json;
@ -13,7 +16,7 @@ use crate::library::Libary;
#[get("/track/<track_id>")]
pub async fn track_route(track_id: &str, lib: &State<Libary>) -> FallibleApiResponse {
Ok(lib
.get_track_by_id(track_id)
.get_track_by_id(&to_uuid(track_id)?)
.await
.ok_or_else(|| api_error("No track with that ID found"))?
.api()
@ -27,7 +30,7 @@ pub async fn track_reload_meta_route(
u: User,
) -> FallibleApiResponse {
check_admin!(u);
lib.reload_metadata(track_id)
lib.reload_metadata(&to_uuid(track_id)?)
.await
.map_err(|_| api_error("Error reloading metadata"))?;
Ok(json!({"ok": 1}))
@ -35,7 +38,9 @@ pub async fn track_reload_meta_route(
#[get("/track/<track_id>/audio")]
pub async fn track_audio_route(track_id: &str, lib: &State<Libary>) -> Option<NamedFile> {
let track = lib.get_track_by_id(track_id).await?;
let track = lib
.get_track_by_id(&uuid::Uuid::from_str(track_id).ok()?)
.await?;
NamedFile::open(std::path::Path::new(&track.path))
.await
.ok()
@ -43,12 +48,16 @@ pub async fn track_audio_route(track_id: &str, lib: &State<Libary>) -> Option<Na
#[get("/track/<track_id>/audio/opus128")]
pub async fn track_audio_opus128_route(track_id: &str, lib: &State<Libary>) -> Option<NamedFile> {
let track = lib.get_track_by_id(track_id).await?;
let track = lib
.get_track_by_id(&uuid::Uuid::from_str(track_id).ok()?)
.await?;
NamedFile::open(track.get_opus(128)?).await.ok()
}
#[get("/track/<track_id>/audio/aac128")]
pub async fn track_audio_aac128_route(track_id: &str, lib: &State<Libary>) -> Option<NamedFile> {
let track = lib.get_track_by_id(track_id).await?;
let track = lib
.get_track_by_id(&uuid::Uuid::from_str(track_id).ok()?)
.await?;
NamedFile::open(track.get_aac(128)?).await.ok()
}

View file

@ -1,9 +1,7 @@
use crate::get_pg;
use crate::library::user::Session;
use crate::library::user::User;
use mongod::vec_to_api;
use mongod::Model;
use mongod::ToAPI;
use mongodb::bson::doc;
use crate::route::vec_to_api;
use rocket::get;
use rocket::http::Status;
use rocket::outcome::Outcome;
@ -33,8 +31,7 @@ impl<'r> FromRequest<'r> for User {
async fn from_request(request: &'r Request<'_>) -> rocket::request::Outcome<Self, Self::Error> {
match request.headers().get_one("token") {
Some(key) => {
if let Some(session) = Session::find_one(doc! { "token": key}).await {
let user = session.user.get().await;
if let Some(user) = sqlx::query_as("SELECT * FROM user WHERE id = (SELECT user FROM user_session WHERE token = $1)").bind(key).fetch_optional(get_pg!()).await.unwrap() {
Outcome::Success(user)
} else {
Outcome::Error((Status::Unauthorized, ()))
@ -84,7 +81,7 @@ pub async fn passwd_route(passwd: Json<PasswdData>, u: User) -> FallibleApiRespo
pub async fn users_route(u: User) -> FallibleApiResponse {
check_admin!(u);
let users: Vec<_> = vec_to_api(&User::find_all().await.unwrap()).await;
let users: Vec<_> = vec_to_api(&User::find_all().await).await;
Ok(json!({"users": users}))
}
@ -101,5 +98,5 @@ pub async fn user_create_route(user: Json<LoginData>, u: User) -> FallibleApiRes
.await
.unwrap();
Ok(json!({"created": new_user._id}))
Ok(json!({"created": new_user.username}))
}