From eea94824cb1fcb92ffa4ee6f14a40a6ec2483e1e Mon Sep 17 00:00:00 2001 From: Vo Van Nghia Date: Thu, 28 Nov 2024 21:01:52 +0100 Subject: [PATCH 1/5] backend/orm: use language instead of option --- nghe-backend/src/error/mod.rs | 2 -- nghe-backend/src/file/audio/metadata.rs | 8 ++------ nghe-backend/src/orm/songs/mod.rs | 7 ++++++- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/nghe-backend/src/error/mod.rs b/nghe-backend/src/error/mod.rs index 1128e708..ea14989a 100644 --- a/nghe-backend/src/error/mod.rs +++ b/nghe-backend/src/error/mod.rs @@ -52,8 +52,6 @@ pub enum Error { CheckoutConnectionPool, #[error("Could not decrypt value from database")] DecryptDatabaseValue, - #[error("Language from database should not be null")] - LanguageFromDatabaseIsNull, #[error("Inconsistency encountered while querying database for scan process")] DatabaseScanQueryInconsistent, #[error("Invalid config format for key {0}")] diff --git a/nghe-backend/src/file/audio/metadata.rs b/nghe-backend/src/file/audio/metadata.rs index b45e7093..e80486ff 100644 --- a/nghe-backend/src/file/audio/metadata.rs +++ b/nghe-backend/src/file/audio/metadata.rs @@ -1,5 +1,3 @@ -use std::str::FromStr; - #[cfg(test)] use fake::{Dummy, Fake}; use isolang::Language; @@ -23,11 +21,9 @@ pub struct Song<'a> { #[map(~.try_into()?)] pub track_disc: position::TrackDisc, #[from(~.into_iter().map( - |language|Language::from_str(language.ok_or_else( - || Error::LanguageFromDatabaseIsNull)?.as_ref() - ).map_err(Error::from) + |language| language.as_str().parse().map_err(Error::from) ).try_collect()?)] - #[into(~.iter().map(|language| Some(language.to_639_3().into())).collect())] + #[into(~.iter().map(|language| language.to_639_3().into()).collect())] #[cfg_attr( test, dummy(expr = "((0..=7915), \ diff --git a/nghe-backend/src/orm/songs/mod.rs b/nghe-backend/src/orm/songs/mod.rs index a6fda5ef..7ba5ca75 100644 --- a/nghe-backend/src/orm/songs/mod.rs +++ b/nghe-backend/src/orm/songs/mod.rs @@ -2,9 +2,12 @@ use std::borrow::Cow; use std::str::FromStr; use diesel::deserialize::{self, FromSql}; +use diesel::dsl::sql; +use diesel::expression::SqlLiteral; use diesel::pg::PgValue; use diesel::prelude::*; use diesel::serialize::{self, Output, ToSql}; +use diesel::sql_types; use diesel::sql_types::Text; use diesel_derives::AsChangeset; use uuid::Uuid; @@ -34,7 +37,9 @@ pub struct Song<'a> { pub main: name_date_mbz::NameDateMbz<'a>, #[diesel(embed)] pub track_disc: position::TrackDisc, - pub languages: Vec>>, + #[diesel(select_expression = sql("songs.languages languages"))] + #[diesel(select_expression_type = SqlLiteral>)] + pub languages: Vec>, } #[derive(Debug, Queryable, Selectable, Insertable, AsChangeset)] From 9ade3614d31aace70b3b90b2c06e44b5dd195332 Mon Sep 17 00:00:00 2001 From: Vo Van Nghia Date: Thu, 28 Nov 2024 21:10:54 +0100 Subject: [PATCH 2/5] api/bookmarks: add api for save_playqueue --- nghe-api/src/bookmarks/mod.rs | 1 + nghe-api/src/bookmarks/save_playqueue.rs | 14 ++++++++++++++ nghe-api/src/lib.rs | 1 + 3 files changed, 16 insertions(+) create mode 100644 nghe-api/src/bookmarks/mod.rs create mode 100644 nghe-api/src/bookmarks/save_playqueue.rs diff --git a/nghe-api/src/bookmarks/mod.rs b/nghe-api/src/bookmarks/mod.rs new file mode 100644 index 00000000..1db44b00 --- /dev/null +++ b/nghe-api/src/bookmarks/mod.rs @@ -0,0 +1 @@ +pub mod save_playqueue; diff --git a/nghe-api/src/bookmarks/save_playqueue.rs b/nghe-api/src/bookmarks/save_playqueue.rs new file mode 100644 index 00000000..1c025af8 --- /dev/null +++ b/nghe-api/src/bookmarks/save_playqueue.rs @@ -0,0 +1,14 @@ +use nghe_proc_macro::api_derive; +use uuid::Uuid; + +#[api_derive] +#[endpoint(path = "savePlayQueue")] +pub struct Request { + #[serde(rename = "id")] + pub ids: Vec, + pub current: Option, + pub position: Option, +} + +#[api_derive] +pub struct Response; diff --git a/nghe-api/src/lib.rs b/nghe-api/src/lib.rs index 8367e0ef..7c448caa 100644 --- a/nghe-api/src/lib.rs +++ b/nghe-api/src/lib.rs @@ -2,6 +2,7 @@ #![feature(trait_alias)] pub mod auth; +pub mod bookmarks; pub mod browsing; pub mod common; pub mod constant; From 2ce7e710c4681b5de707a9b46897f05687b5442c Mon Sep 17 00:00:00 2001 From: Vo Van Nghia Date: Thu, 28 Nov 2024 21:47:10 +0100 Subject: [PATCH 3/5] backend/bookmarks: add handler for save_playqueue --- .../down.sql | 2 + .../up.sql | 10 ++++ nghe-backend/src/lib.rs | 1 + nghe-backend/src/orm/mod.rs | 1 + nghe-backend/src/orm/playqueues.rs | 46 +++++++++++++++++++ nghe-backend/src/route/bookmarks/mod.rs | 5 ++ .../src/route/bookmarks/save_playqueue.rs | 18 ++++++++ nghe-backend/src/route/mod.rs | 1 + nghe-backend/src/schema.rs | 14 ++++++ 9 files changed, 98 insertions(+) create mode 100644 nghe-backend/migrations/2024-11-28-201114_create_playqueues/down.sql create mode 100644 nghe-backend/migrations/2024-11-28-201114_create_playqueues/up.sql create mode 100644 nghe-backend/src/orm/playqueues.rs create mode 100644 nghe-backend/src/route/bookmarks/mod.rs create mode 100644 nghe-backend/src/route/bookmarks/save_playqueue.rs diff --git a/nghe-backend/migrations/2024-11-28-201114_create_playqueues/down.sql b/nghe-backend/migrations/2024-11-28-201114_create_playqueues/down.sql new file mode 100644 index 00000000..b4e2aaa2 --- /dev/null +++ b/nghe-backend/migrations/2024-11-28-201114_create_playqueues/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +drop table playqueues; diff --git a/nghe-backend/migrations/2024-11-28-201114_create_playqueues/up.sql b/nghe-backend/migrations/2024-11-28-201114_create_playqueues/up.sql new file mode 100644 index 00000000..bc281c8e --- /dev/null +++ b/nghe-backend/migrations/2024-11-28-201114_create_playqueues/up.sql @@ -0,0 +1,10 @@ +-- Your SQL goes here +create table playqueues ( + user_id uuid not null constraint playqueues_pkey primary key, + ids uuid [] not null check (array_position(ids, null) is null), + current uuid, + position bigint, + constraint playqueues_user_id_fkey foreign key ( + user_id + ) references users (id) on delete cascade +); diff --git a/nghe-backend/src/lib.rs b/nghe-backend/src/lib.rs index 00a2662d..83bc88ff 100644 --- a/nghe-backend/src/lib.rs +++ b/nghe-backend/src/lib.rs @@ -59,6 +59,7 @@ pub async fn build(config: config::Config) -> Router { cover_art: config.cover_art, }, )) + .merge(route::bookmarks::router()) .merge(route::browsing::router()) .merge(route::lists::router()) .merge(route::media_annotation::router()) diff --git a/nghe-backend/src/orm/mod.rs b/nghe-backend/src/orm/mod.rs index b3d961c6..a9e78eb4 100644 --- a/nghe-backend/src/orm/mod.rs +++ b/nghe-backend/src/orm/mod.rs @@ -13,6 +13,7 @@ pub mod playlist; pub mod playlists; pub mod playlists_songs; pub mod playlists_users; +pub mod playqueues; pub mod songs; pub mod songs_album_artists; pub mod songs_artists; diff --git a/nghe-backend/src/orm/playqueues.rs b/nghe-backend/src/orm/playqueues.rs new file mode 100644 index 00000000..a9a291c9 --- /dev/null +++ b/nghe-backend/src/orm/playqueues.rs @@ -0,0 +1,46 @@ +use diesel::dsl::sql; +use diesel::expression::SqlLiteral; +use diesel::prelude::*; +use diesel::sql_types; +use diesel_derives::AsChangeset; +use o2o::o2o; +use uuid::Uuid; + +pub use crate::schema::playqueues::{self, *}; +use crate::Error; + +#[derive(Debug, Queryable, Selectable, Insertable, AsChangeset, o2o)] +#[diesel(table_name = playqueues, check_for_backend(crate::orm::Type))] +#[diesel(treat_none_as_null = true)] +#[try_from_owned(nghe_api::bookmarks::save_playqueue::Request, Error)] +pub struct Data { + #[diesel(select_expression = sql("playqueues.ids ids"))] + #[diesel(select_expression_type = SqlLiteral>)] + pub ids: Vec, + pub current: Option, + #[from(~.map(i64::try_from).transpose()?)] + pub position: Option, +} + +mod upsert { + use diesel::ExpressionMethods; + use diesel_async::RunQueryDsl; + use uuid::Uuid; + + use super::{playqueues, Data}; + use crate::database::Database; + use crate::Error; + + impl crate::orm::upsert::Update for Data { + async fn update(&self, database: &Database, id: Uuid) -> Result<(), Error> { + diesel::insert_into(playqueues::table) + .values((playqueues::user_id.eq(id), self)) + .on_conflict(playqueues::user_id) + .do_update() + .set(self) + .execute(&mut database.get().await?) + .await?; + Ok(()) + } + } +} diff --git a/nghe-backend/src/route/bookmarks/mod.rs b/nghe-backend/src/route/bookmarks/mod.rs new file mode 100644 index 00000000..03811fd7 --- /dev/null +++ b/nghe-backend/src/route/bookmarks/mod.rs @@ -0,0 +1,5 @@ +mod save_playqueue; + +nghe_proc_macro::build_router! { + modules = [save_playqueue], +} diff --git a/nghe-backend/src/route/bookmarks/save_playqueue.rs b/nghe-backend/src/route/bookmarks/save_playqueue.rs new file mode 100644 index 00000000..93269c95 --- /dev/null +++ b/nghe-backend/src/route/bookmarks/save_playqueue.rs @@ -0,0 +1,18 @@ +pub use nghe_api::bookmarks::save_playqueue::{Request, Response}; +use nghe_proc_macro::handler; +use uuid::Uuid; + +use crate::database::Database; +use crate::orm::playqueues; +use crate::orm::upsert::Update; +use crate::Error; + +#[handler] +pub async fn handler( + database: &Database, + user_id: Uuid, + request: Request, +) -> Result { + playqueues::Data::try_from(request)?.update(database, user_id).await?; + Ok(Response) +} diff --git a/nghe-backend/src/route/mod.rs b/nghe-backend/src/route/mod.rs index 81fa69d6..a8494520 100644 --- a/nghe-backend/src/route/mod.rs +++ b/nghe-backend/src/route/mod.rs @@ -1,3 +1,4 @@ +pub mod bookmarks; pub mod browsing; pub mod lists; pub mod media_annotation; diff --git a/nghe-backend/src/schema.rs b/nghe-backend/src/schema.rs index 6039bf2f..540d0d03 100644 --- a/nghe-backend/src/schema.rs +++ b/nghe-backend/src/schema.rs @@ -167,6 +167,18 @@ diesel::table! { } } +diesel::table! { + use diesel::sql_types::*; + use diesel_full_text_search::*; + + playqueues (user_id) { + user_id -> Uuid, + ids -> Array>, + current -> Nullable, + position -> Nullable, + } +} + diesel::table! { use diesel::sql_types::*; use diesel_full_text_search::*; @@ -299,6 +311,7 @@ diesel::joinable!(playlists_songs -> playlists (playlist_id)); diesel::joinable!(playlists_songs -> songs (song_id)); diesel::joinable!(playlists_users -> playlists (playlist_id)); diesel::joinable!(playlists_users -> users (user_id)); +diesel::joinable!(playqueues -> users (user_id)); diesel::joinable!(scans -> music_folders (music_folder_id)); diesel::joinable!(songs -> albums (album_id)); diesel::joinable!(songs -> cover_arts (cover_art_id)); @@ -323,6 +336,7 @@ diesel::allow_tables_to_appear_in_same_query!( playlists, playlists_songs, playlists_users, + playqueues, scans, songs, songs_album_artists, From f2b50dddbca0c99ad4a2f7fba1d89fb45cd3798a Mon Sep 17 00:00:00 2001 From: Vo Van Nghia Date: Thu, 28 Nov 2024 22:14:31 +0100 Subject: [PATCH 4/5] backend/bookmarks: add api/handler for get_playqueue --- nghe-api/src/bookmarks/get_playqueue.rs | 23 +++++++++ nghe-api/src/bookmarks/mod.rs | 1 + .../src/route/bookmarks/get_playqueue.rs | 49 +++++++++++++++++++ nghe-backend/src/route/bookmarks/mod.rs | 3 +- 4 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 nghe-api/src/bookmarks/get_playqueue.rs create mode 100644 nghe-backend/src/route/bookmarks/get_playqueue.rs diff --git a/nghe-api/src/bookmarks/get_playqueue.rs b/nghe-api/src/bookmarks/get_playqueue.rs new file mode 100644 index 00000000..879ed159 --- /dev/null +++ b/nghe-api/src/bookmarks/get_playqueue.rs @@ -0,0 +1,23 @@ +use nghe_proc_macro::api_derive; +use uuid::Uuid; + +use crate::id3; + +#[api_derive] +#[endpoint(path = "getPlayQueue")] +pub struct Request {} + +#[api_derive] +#[derive(Default)] +pub struct Playqueue { + pub entry: Vec, + pub current: Option, + pub position: Option, +} + +#[api_derive] +#[derive(Default)] +pub struct Response { + #[serde(rename = "playQueue")] + pub playqueue: Playqueue, +} diff --git a/nghe-api/src/bookmarks/mod.rs b/nghe-api/src/bookmarks/mod.rs index 1db44b00..887c7fd1 100644 --- a/nghe-api/src/bookmarks/mod.rs +++ b/nghe-api/src/bookmarks/mod.rs @@ -1 +1,2 @@ +pub mod get_playqueue; pub mod save_playqueue; diff --git a/nghe-backend/src/route/bookmarks/get_playqueue.rs b/nghe-backend/src/route/bookmarks/get_playqueue.rs new file mode 100644 index 00000000..11db0861 --- /dev/null +++ b/nghe-backend/src/route/bookmarks/get_playqueue.rs @@ -0,0 +1,49 @@ +use diesel::{ExpressionMethods, OptionalExtension, QueryDsl, SelectableHelper}; +use diesel_async::RunQueryDsl; +use futures_lite::{stream, StreamExt as _}; +use nghe_api::bookmarks::get_playqueue::Playqueue; +pub use nghe_api::bookmarks::get_playqueue::{Request, Response}; +use nghe_proc_macro::handler; +use uuid::Uuid; + +use crate::database::Database; +use crate::orm::{id3, playqueues, songs}; +use crate::Error; + +#[handler] +pub async fn handler( + database: &Database, + user_id: Uuid, + request: Request, +) -> Result { + Ok( + if let Some(data) = playqueues::table + .filter(playqueues::user_id.eq(user_id)) + .select(playqueues::Data::as_select()) + .get_result(&mut database.get().await?) + .await + .optional()? + { + let entry = stream::iter(data.ids) + .then(async |id| { + id3::song::short::query::with_user_id(user_id) + .filter(songs::id.eq(id)) + .get_result(&mut database.get().await?) + .await? + .try_into() + }) + .try_collect() + .await?; + + Response { + playqueue: Playqueue { + entry, + current: data.current, + position: data.position.map(i64::try_into).transpose()?, + }, + } + } else { + Response::default() + }, + ) +} diff --git a/nghe-backend/src/route/bookmarks/mod.rs b/nghe-backend/src/route/bookmarks/mod.rs index 03811fd7..6abd079d 100644 --- a/nghe-backend/src/route/bookmarks/mod.rs +++ b/nghe-backend/src/route/bookmarks/mod.rs @@ -1,5 +1,6 @@ +mod get_playqueue; mod save_playqueue; nghe_proc_macro::build_router! { - modules = [save_playqueue], + modules = [get_playqueue, save_playqueue], } From 1df21a1d61dc86c7ac685c81954c308990db6040 Mon Sep 17 00:00:00 2001 From: Vo Van Nghia Date: Thu, 28 Nov 2024 22:30:04 +0100 Subject: [PATCH 5/5] add test for get_playqueue --- .../src/route/bookmarks/get_playqueue.rs | 48 +++++++++++++++++++ nghe-backend/src/route/bookmarks/mod.rs | 2 +- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/nghe-backend/src/route/bookmarks/get_playqueue.rs b/nghe-backend/src/route/bookmarks/get_playqueue.rs index 11db0861..0b59c1e4 100644 --- a/nghe-backend/src/route/bookmarks/get_playqueue.rs +++ b/nghe-backend/src/route/bookmarks/get_playqueue.rs @@ -47,3 +47,51 @@ pub async fn handler( }, ) } + +#[cfg(test)] +mod test { + use fake::Fake; + use rstest::rstest; + + use super::*; + use crate::route::bookmarks::save_playqueue; + use crate::test::{mock, Mock}; + + #[rstest] + #[tokio::test] + async fn test_handler( + #[future(awt)] + #[with(1, 0)] + mock: Mock, + #[values(true, false)] allow: bool, + ) { + mock.add_music_folder().allow(allow).call().await; + mock.add_music_folder().call().await; + + let mut music_folder_permission = mock.music_folder(0).await; + let mut music_folder = mock.music_folder(1).await; + + music_folder_permission.add_audio().n_song((2..4).fake()).call().await; + music_folder.add_audio().n_song((2..4).fake()).call().await; + + let song_ids: Vec<_> = music_folder_permission + .database + .keys() + .copied() + .chain(music_folder.database.keys().copied()) + .collect(); + + let user_id = mock.user_id(0).await; + + save_playqueue::handler( + mock.database(), + user_id, + save_playqueue::Request { ids: song_ids, position: None, current: None }, + ) + .await + .unwrap(); + + let playqueue = handler(mock.database(), user_id, Request {}).await; + assert_eq!(playqueue.is_ok(), allow); + } +} diff --git a/nghe-backend/src/route/bookmarks/mod.rs b/nghe-backend/src/route/bookmarks/mod.rs index 6abd079d..807e4eda 100644 --- a/nghe-backend/src/route/bookmarks/mod.rs +++ b/nghe-backend/src/route/bookmarks/mod.rs @@ -1,5 +1,5 @@ mod get_playqueue; -mod save_playqueue; +pub mod save_playqueue; nghe_proc_macro::build_router! { modules = [get_playqueue, save_playqueue],