From 8066c80459c309964e1873c6af4a6dd8e2374688 Mon Sep 17 00:00:00 2001 From: Ethan Girouard Date: Fri, 16 Feb 2024 23:45:38 -0500 Subject: [PATCH] Add search functions --- src/lib.rs | 1 + src/models.rs | 3 ++ src/search.rs | 109 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 113 insertions(+) create mode 100644 src/search.rs diff --git a/src/lib.rs b/src/lib.rs index 5eaf272..1897b87 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,7 @@ pub mod playbar; pub mod database; pub mod models; pub mod users; +pub mod search; use cfg_if::cfg_if; cfg_if! { diff --git a/src/models.rs b/src/models.rs index 0c94672..7ad0cda 100644 --- a/src/models.rs +++ b/src/models.rs @@ -47,6 +47,7 @@ pub struct User { #[cfg_attr(feature = "ssr", derive(Queryable, Selectable, Insertable))] #[cfg_attr(feature = "ssr", diesel(table_name = crate::schema::artists))] #[cfg_attr(feature = "ssr", diesel(check_for_backend(diesel::pg::Pg)))] +#[derive(Serialize, Deserialize)] pub struct Artist { /// A unique id for the artist #[cfg_attr(feature = "ssr", diesel(deserialize_as = i32))] @@ -167,6 +168,7 @@ impl Artist { #[cfg_attr(feature = "ssr", derive(Queryable, Selectable, Insertable))] #[cfg_attr(feature = "ssr", diesel(table_name = crate::schema::albums))] #[cfg_attr(feature = "ssr", diesel(check_for_backend(diesel::pg::Pg)))] +#[derive(Serialize, Deserialize)] pub struct Album { /// A unique id for the album #[cfg_attr(feature = "ssr", diesel(deserialize_as = i32))] @@ -237,6 +239,7 @@ impl Album { #[cfg_attr(feature = "ssr", derive(Queryable, Selectable, Insertable))] #[cfg_attr(feature = "ssr", diesel(table_name = crate::schema::songs))] #[cfg_attr(feature = "ssr", diesel(check_for_backend(diesel::pg::Pg)))] +#[derive(Serialize, Deserialize)] pub struct Song { /// A unique id for the song #[cfg_attr(feature = "ssr", diesel(deserialize_as = i32))] diff --git a/src/search.rs b/src/search.rs new file mode 100644 index 0000000..3b24f08 --- /dev/null +++ b/src/search.rs @@ -0,0 +1,109 @@ +use leptos::*; +use crate::models::{Artist, Album, Song}; + +use cfg_if::cfg_if; + +cfg_if! { +if #[cfg(feature = "ssr")] { + use diesel::sql_types::*; + use diesel::*; + use diesel::pg::Pg; + use diesel::expression::AsExpression; + + use crate::database::get_db_conn; + + // Define pg_trgm operators + // Functions do not use indices for queries, so we need to use operators + diesel::infix_operator!(Similarity, " % ", backend: Pg); + diesel::infix_operator!(Distance, " <-> ", Float, backend: Pg); + + // Create functions to make use of the operators in queries + fn trgm_similar, U: AsExpression>(left: T, right: U) + -> Similarity { + Similarity::new(left.as_expression(), right.as_expression()) + } + + fn trgm_distance, U: AsExpression>(left: T, right: U) + -> Distance { + Distance::new(left.as_expression(), right.as_expression()) + } +} +} + +/// Search for albums by title +/// +/// # Arguments +/// `query` - The search query. This will be used to perform a fuzzy search on the album titles +/// `limit` - The maximum number of results to return +/// +/// # Returns +/// A Result containing a vector of albums if the search was successful, or an error if the search failed +#[server(endpoint = "search_albums")] +pub async fn search_albums(query: String, limit: i64) -> Result, ServerFnError> { + use crate::schema::albums::dsl::*; + + Ok(albums + .filter(trgm_similar(title, query.clone())) + .order_by(trgm_distance(title, query)) + .limit(limit) + .load(&mut get_db_conn())?) +} + +/// Search for artists by name +/// +/// # Arguments +/// `query` - The search query. This will be used to perform a fuzzy search on the artist names +/// `limit` - The maximum number of results to return +/// +/// # Returns +/// A Result containing a vector of artists if the search was successful, or an error if the search failed +#[server(endpoint = "search_artists")] +pub async fn search_artists(query: String, limit: i64) -> Result, ServerFnError> { + use crate::schema::artists::dsl::*; + + Ok(artists + .filter(trgm_similar(name, query.clone())) + .order_by(trgm_distance(name, query)) + .limit(limit) + .load(&mut get_db_conn())?) +} + +/// Search for songs by title +/// +/// # Arguments +/// `query` - The search query. This will be used to perform a fuzzy search on the song titles +/// `limit` - The maximum number of results to return +/// +/// # Returns +/// A Result containing a vector of songs if the search was successful, or an error if the search failed +#[server(endpoint = "search_songs")] +pub async fn search_songs(query: String, limit: i64) -> Result, ServerFnError> { + use crate::schema::songs::dsl::*; + + Ok(songs + .filter(trgm_similar(title, query.clone())) + .order_by(trgm_distance(title, query)) + .limit(limit) + .load(&mut get_db_conn())?) +} + +/// Search for songs, albums, and artists by title or name +/// +/// # Arguments +/// `query` - The search query. This will be used to perform a fuzzy search on the +/// song titles, album titles, and artist names +/// `limit` - The maximum number of results to return for each type +/// +/// # Returns +/// A Result containing a tuple of vectors of albums, artists, and songs if the search was successful, +#[server(endpoint = "search")] +pub async fn search(query: String, limit: i64) -> Result<(Vec, Vec, Vec), ServerFnError> { + let albums = search_albums(query.clone(), limit); + let artists = search_artists(query.clone(), limit); + let songs = search_songs(query.clone(), limit); + + use futures::join; + + let (albums, artists, songs) = join!(albums, artists, songs); + Ok((albums?, artists?, songs?)) +}