From 81f0b9310fa078d18ed67dcbe81961ba2dcfd638 Mon Sep 17 00:00:00 2001 From: Ethan Girouard Date: Sun, 19 May 2024 12:44:47 -0400 Subject: [PATCH 01/10] Add DB table for song history --- .../2024-05-19-163229_add_song_history/down.sql | 2 ++ migrations/2024-05-19-163229_add_song_history/up.sql | 8 ++++++++ src/schema.rs | 12 ++++++++++++ 3 files changed, 22 insertions(+) create mode 100644 migrations/2024-05-19-163229_add_song_history/down.sql create mode 100644 migrations/2024-05-19-163229_add_song_history/up.sql diff --git a/migrations/2024-05-19-163229_add_song_history/down.sql b/migrations/2024-05-19-163229_add_song_history/down.sql new file mode 100644 index 0000000..4c1a657 --- /dev/null +++ b/migrations/2024-05-19-163229_add_song_history/down.sql @@ -0,0 +1,2 @@ +DROP INDEX song_history_user_id_idx; +DROP TABLE song_history; diff --git a/migrations/2024-05-19-163229_add_song_history/up.sql b/migrations/2024-05-19-163229_add_song_history/up.sql new file mode 100644 index 0000000..42ce503 --- /dev/null +++ b/migrations/2024-05-19-163229_add_song_history/up.sql @@ -0,0 +1,8 @@ +CREATE TABLE song_history ( + id SERIAL PRIMARY KEY UNIQUE NOT NULL, + user_id INTEGER REFERENCES users(id) ON DELETE CASCADE NOT NULL, + date TIMESTAMP NOT NULL DEFAULT NOW(), + song_id INTEGER REFERENCES songs(id) ON DELETE CASCADE NOT NULL +); + +CREATE INDEX song_history_user_id_idx ON song_history(user_id); diff --git a/src/schema.rs b/src/schema.rs index 97de324..986006f 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -30,6 +30,15 @@ diesel::table! { } } +diesel::table! { + song_history (id) { + id -> Int4, + user_id -> Int4, + date -> Timestamp, + song_id -> Int4, + } +} + diesel::table! { songs (id) { id -> Int4, @@ -57,6 +66,8 @@ diesel::joinable!(album_artists -> albums (album_id)); diesel::joinable!(album_artists -> artists (artist_id)); diesel::joinable!(song_artists -> artists (artist_id)); diesel::joinable!(song_artists -> songs (song_id)); +diesel::joinable!(song_history -> songs (song_id)); +diesel::joinable!(song_history -> users (user_id)); diesel::joinable!(songs -> albums (album_id)); diesel::allow_tables_to_appear_in_same_query!( @@ -64,6 +75,7 @@ diesel::allow_tables_to_appear_in_same_query!( albums, artists, song_artists, + song_history, songs, users, ); From cc11de4f3d47c07bbf854bf273d6636d1fc9fb2f Mon Sep 17 00:00:00 2001 From: Ethan Girouard Date: Fri, 10 May 2024 20:26:59 -0400 Subject: [PATCH 02/10] Add get_user auth function --- src/auth.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/auth.rs b/src/auth.rs index 2eda7cf..58ba6f4 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -120,3 +120,26 @@ pub async fn require_auth() -> Result<(), ServerFnError> { } }) } + +/// Get the current logged-in user +/// Returns a Result with the user if they are logged in +/// Returns an error if the user is not logged in, or if there is an error getting the user +/// Intended to be used in a route to get the current user: +/// ```rust +/// use leptos::*; +/// use libretunes::auth::get_user; +/// #[server(endpoint = "user_route")] +/// pub async fn user_route() -> Result<(), ServerFnError> { +/// let user = get_user().await?; +/// println!("Logged in as: {}", user.username); +/// // Do something with the user +/// Ok(()) +/// } +/// ``` +#[cfg(feature = "ssr")] +pub async fn get_user() -> Result { + let auth_session = extract::>().await + .map_err(|e| ServerFnError::::ServerError(format!("Error getting auth session: {}", e)))?; + + auth_session.user.ok_or(ServerFnError::::ServerError("User not logged in".to_string())) +} From 0c64865cd9e5f1c3c28a9eda9bac1fa51ef06413 Mon Sep 17 00:00:00 2001 From: Ethan Girouard Date: Fri, 10 May 2024 20:29:56 -0400 Subject: [PATCH 03/10] Add api module --- src/api/mod.rs | 0 src/lib.rs | 1 + 2 files changed, 1 insertion(+) create mode 100644 src/api/mod.rs diff --git a/src/api/mod.rs b/src/api/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/lib.rs b/src/lib.rs index e7949e3..86cac32 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,6 +15,7 @@ pub mod fileserv; pub mod error_template; pub mod upload; pub mod util; +pub mod api; use cfg_if::cfg_if; From 62def56a41f7ee69d49f4139e3c1c6ebdf064771 Mon Sep 17 00:00:00 2001 From: Ethan Girouard Date: Sun, 19 May 2024 15:43:58 -0400 Subject: [PATCH 04/10] Add model for history entry --- src/models.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/models.rs b/src/models.rs index 442713e..3076937 100644 --- a/src/models.rs +++ b/src/models.rs @@ -292,3 +292,20 @@ impl Song { Ok(my_artists) } } + +/// Model for a history entry +#[cfg_attr(feature = "ssr", derive(Queryable, Selectable, Insertable))] +#[cfg_attr(feature = "ssr", diesel(table_name = crate::schema::song_history))] +#[cfg_attr(feature = "ssr", diesel(check_for_backend(diesel::pg::Pg)))] +#[derive(Serialize, Deserialize)] +pub struct HistoryEntry { + /// A unique id for the history entry + #[cfg_attr(feature = "ssr", diesel(deserialize_as = i32))] + pub id: Option, + /// The id of the user who listened to the song + pub user_id: i32, + /// The date the song was listened to + pub date: SystemTime, + /// The id of the song that was listened to + pub song_id: i32, +} From ee70b1080e9b8f279d6d27f5f29877cd5163a906 Mon Sep 17 00:00:00 2001 From: Ethan Girouard Date: Sun, 19 May 2024 15:44:09 -0400 Subject: [PATCH 05/10] Add various history functions for users --- src/models.rs | 142 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) diff --git a/src/models.rs b/src/models.rs index 3076937..f0665ba 100644 --- a/src/models.rs +++ b/src/models.rs @@ -43,6 +43,148 @@ pub struct User { pub created_at: Option, } +impl User { + /// Get the history of songs listened to by this user from the database + /// + /// The returned history will be ordered by date in descending order, + /// and a limit of N will select the N most recent entries. + /// The `id` field of this user must be present (Some) to get history + /// + /// # Arguments + /// + /// * `limit` - An optional limit on the number of history entries to return + /// * `conn` - A mutable reference to a database connection + /// + /// # Returns + /// + /// * `Result, Box>` - + /// A result indicating success with a vector of history entries, or an error + /// + #[cfg(feature = "ssr")] + pub fn get_history(self: &Self, limit: Option, conn: &mut PgPooledConn) -> + Result, Box> { + use crate::schema::song_history::dsl::*; + + let my_id = self.id.ok_or("Artist id must be present (Some) to get history")?; + + let my_history = + if let Some(limit) = limit { + song_history + .filter(user_id.eq(my_id)) + .order(date.desc()) + .limit(limit) + .load(conn)? + } else { + song_history + .filter(user_id.eq(my_id)) + .load(conn)? + }; + + Ok(my_history) + } + + /// Get the history of songs listened to by this user from the database + /// + /// The returned history will be ordered by date in descending order, + /// and a limit of N will select the N most recent entries. + /// The `id` field of this user must be present (Some) to get history + /// + /// # Arguments + /// + /// * `limit` - An optional limit on the number of history entries to return + /// * `conn` - A mutable reference to a database connection + /// + /// # Returns + /// + /// * `Result, Box>` - + /// A result indicating success with a vector of listen dates and songs, or an error + /// + #[cfg(feature = "ssr")] + pub fn get_history_songs(self: &Self, limit: Option, conn: &mut PgPooledConn) -> + Result, Box> { + use crate::schema::songs::dsl::*; + use crate::schema::song_history::dsl::*; + + let my_id = self.id.ok_or("Artist id must be present (Some) to get history")?; + + let my_history = + if let Some(limit) = limit { + song_history + .inner_join(songs) + .filter(user_id.eq(my_id)) + .order(date.desc()) + .limit(limit) + .select((date, songs::all_columns())) + .load(conn)? + } else { + song_history + .inner_join(songs) + .filter(user_id.eq(my_id)) + .order(date.desc()) + .select((date, songs::all_columns())) + .load(conn)? + }; + + Ok(my_history) + } + + /// Add a song to this user's history in the database + /// + /// The date of the history entry will be the current time + /// The `id` field of this user must be present (Some) to add history + /// + /// # Arguments + /// + /// * `song_id` - The id of the song to add to this user's history + /// * `conn` - A mutable reference to a database connection + /// + /// # Returns + /// + /// * `Result<(), Box>` - A result indicating success with an empty value, or an error + /// + #[cfg(feature = "ssr")] + pub fn add_history(self: &Self, song_id: i32, conn: &mut PgPooledConn) -> Result<(), Box> { + use crate::schema::song_history; + + let my_id = self.id.ok_or("Artist id must be present (Some) to add history")?; + + diesel::insert_into(song_history::table) + .values((song_history::user_id.eq(my_id), song_history::song_id.eq(song_id))) + .execute(conn)?; + + Ok(()) + } + + /// Check if this user has listened to a song + /// + /// The `id` field of this user must be present (Some) to check history + /// + /// # Arguments + /// + /// * `song_id` - The id of the song to check if this user has listened to + /// * `conn` - A mutable reference to a database connection + /// + /// # Returns + /// + /// * `Result>` - A result indicating success with a boolean value, or an error + /// + #[cfg(feature = "ssr")] + pub fn has_listened_to(self: &Self, song_id: i32, conn: &mut PgPooledConn) -> Result> { + use crate::schema::song_history::{self, user_id}; + + let my_id = self.id.ok_or("Artist id must be present (Some) to check history")?; + + let has_listened = song_history::table + .filter(user_id.eq(my_id)) + .filter(song_history::song_id.eq(song_id)) + .first::(conn) + .optional()? + .is_some(); + + Ok(has_listened) + } +} + /// Model for an artist #[cfg_attr(feature = "ssr", derive(Queryable, Selectable, Insertable, Identifiable))] #[cfg_attr(feature = "ssr", diesel(table_name = crate::schema::artists))] From 4aa90cdefbb97626ef2098b24f18afb1268ed957 Mon Sep 17 00:00:00 2001 From: Ethan Girouard Date: Sun, 19 May 2024 16:02:39 -0400 Subject: [PATCH 06/10] Add API functions for getting/adding history --- src/api/history.rs | 44 ++++++++++++++++++++++++++++++++++++++++++++ src/api/mod.rs | 1 + 2 files changed, 45 insertions(+) create mode 100644 src/api/history.rs diff --git a/src/api/history.rs b/src/api/history.rs new file mode 100644 index 0000000..5f6cabb --- /dev/null +++ b/src/api/history.rs @@ -0,0 +1,44 @@ +use std::time::SystemTime; +use leptos::*; +use crate::models::HistoryEntry; +use crate::models::Song; + +use cfg_if::cfg_if; + +cfg_if! { + if #[cfg(feature = "ssr")] { + use leptos::server_fn::error::NoCustomError; + use crate::database::get_db_conn; + use crate::auth::get_user; + } +} + +/// Get the history of the current user. +#[server(endpoint = "history/get")] +pub async fn get_history(limit: Option) -> Result, ServerFnError> { + let user = get_user().await?; + let db_con = &mut get_db_conn(); + let history = user.get_history(limit, db_con) + .map_err(|e| ServerFnError::::ServerError(format!("Error getting history: {}", e)))?; + Ok(history) +} + +/// Get the listen dates and songs of the current user. +#[server(endpoint = "history/get_songs")] +pub async fn get_history_songs(limit: Option) -> Result, ServerFnError> { + let user = get_user().await?; + let db_con = &mut get_db_conn(); + let songs = user.get_history_songs(limit, db_con) + .map_err(|e| ServerFnError::::ServerError(format!("Error getting history songs: {}", e)))?; + Ok(songs) +} + +/// Add a song to the history of the current user. +#[server(endpoint = "history/add")] +pub async fn add_history(song_id: i32) -> Result<(), ServerFnError> { + let user = get_user().await?; + let db_con = &mut get_db_conn(); + user.add_history(song_id, db_con) + .map_err(|e| ServerFnError::::ServerError(format!("Error adding history: {}", e)))?; + Ok(()) +} diff --git a/src/api/mod.rs b/src/api/mod.rs index e69de29..2b44c34 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -0,0 +1 @@ +pub mod history; From e55656c0e6bb5dc6c4e007c78dd55d51584e3ca0 Mon Sep 17 00:00:00 2001 From: Ethan Girouard Date: Tue, 30 Jul 2024 22:29:13 -0400 Subject: [PATCH 07/10] Add leptos_use crate --- Cargo.lock | 132 +++++++++++++++++++++++++++++++++++++++++++++++++---- Cargo.toml | 2 + 2 files changed, 124 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1bc5c0a..0a6bd5b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -308,7 +308,7 @@ version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c878c71c2821aa2058722038a59a67583a4240524687c6028571c9b395ded61f" dependencies = [ - "darling", + "darling 0.14.4", "proc-macro2", "quote", "syn 1.0.109", @@ -377,6 +377,15 @@ dependencies = [ "half", ] +[[package]] +name = "codee" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af40247be877a1e3353fb406aa27ab3ef4bd3ff18cef91e75e667bfa3fde701d" +dependencies = [ + "thiserror", +] + [[package]] name = "collection_literals" version = "1.0.1" @@ -499,8 +508,18 @@ version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.14.4", + "darling_macro 0.14.4", +] + +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core 0.20.10", + "darling_macro 0.20.10", ] [[package]] @@ -513,21 +532,46 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "strsim", + "strsim 0.10.0", "syn 1.0.109", ] +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.11.1", + "syn 2.0.58", +] + [[package]] name = "darling_macro" version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" dependencies = [ - "darling_core", + "darling_core 0.14.4", "quote", "syn 1.0.109", ] +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core 0.20.10", + "quote", + "syn 2.0.58", +] + [[package]] name = "dashmap" version = "5.5.3" @@ -541,6 +585,18 @@ dependencies = [ "parking_lot_core", ] +[[package]] +name = "default-struct-builder" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fa90da96b8fd491f5754d1f7a731f73921e3b7aa0ce333c821a0e43666ac14" +dependencies = [ + "darling 0.20.10", + "proc-macro2", + "quote", + "syn 2.0.58", +] + [[package]] name = "deranged" version = "0.3.11" @@ -862,7 +918,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-sink", - "gloo-utils", + "gloo-utils 0.2.0 (git+https://github.com/rustwasm/gloo.git?rev=a823fab7ecc4068e9a28bd669da5eaf3f0a56380)", "http", "js-sys", "pin-project", @@ -874,6 +930,31 @@ dependencies = [ "web-sys", ] +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "gloo-utils" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa" +dependencies = [ + "js-sys", + "serde", + "serde_json", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "gloo-utils" version = "0.2.0" @@ -1338,6 +1419,30 @@ dependencies = [ "web-sys", ] +[[package]] +name = "leptos-use" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac79c02d0e2998569116aa36d26fd00bfa8cadbe8cb630eb771b4d1676412a16" +dependencies = [ + "async-trait", + "cfg-if", + "codee", + "cookie", + "default-struct-builder", + "futures-util", + "gloo-timers", + "gloo-utils 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "js-sys", + "lazy_static", + "leptos", + "paste", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "leptos_axum" version = "0.6.10" @@ -1587,6 +1692,7 @@ dependencies = [ "icondata", "lazy_static", "leptos", + "leptos-use", "leptos_axum", "leptos_icons", "leptos_meta", @@ -2477,6 +2583,12 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "subtle" version = "2.5.0" @@ -2580,18 +2692,18 @@ checksum = "384595c11a4e2969895cad5a8c4029115f5ab956a9e5ef4de79d11a426e5f20c" [[package]] name = "thiserror" -version = "1.0.58" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.58" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 953da57..b162619 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ multer = { version = "3.0.0", optional = true } log = { version = "0.4.21", optional = true } flexi_logger = { version = "0.28.0", optional = true, default-features = false } web-sys = "0.3.69" +leptos-use = "0.11.3" [patch.crates-io] gloo-net = { git = "https://github.com/rustwasm/gloo.git", rev = "a823fab7ecc4068e9a28bd669da5eaf3f0a56380" } @@ -74,6 +75,7 @@ ssr = [ "multer", "log", "flexi_logger", + "leptos-use/ssr", ] # Defines a size-optimized profile for the WASM bundle in release mode From c92d68dddf79fef9211e7e60686ee041f7ccab09 Mon Sep 17 00:00:00 2001 From: Ethan Girouard Date: Sat, 28 Sep 2024 20:46:27 -0400 Subject: [PATCH 08/10] Update leptos_use to 0.13.5 --- Cargo.lock | 72 ++++++++++++++++++++++++++++++++++++------------------ Cargo.toml | 2 +- 2 files changed, 49 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5ca54c0..d57331a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -379,9 +379,9 @@ dependencies = [ [[package]] name = "codee" -version = "0.1.2" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af40247be877a1e3353fb406aa27ab3ef4bd3ff18cef91e75e667bfa3fde701d" +checksum = "5d3ad3122b0001c7f140cf4d605ef9a9e2c24d96ab0b4fb4347b76de2425f445" dependencies = [ "thiserror", ] @@ -676,6 +676,17 @@ dependencies = [ "subtle", ] +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + [[package]] name = "dotenv" version = "0.15.0" @@ -918,7 +929,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-sink", - "gloo-utils 0.2.0 (git+https://github.com/rustwasm/gloo.git?rev=a823fab7ecc4068e9a28bd669da5eaf3f0a56380)", + "gloo-utils", "http", "js-sys", "pin-project", @@ -942,19 +953,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "gloo-utils" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa" -dependencies = [ - "js-sys", - "serde", - "serde_json", - "wasm-bindgen", - "web-sys", -] - [[package]] name = "gloo-utils" version = "0.2.0" @@ -1386,9 +1384,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" dependencies = [ "wasm-bindgen", ] @@ -1421,23 +1419,22 @@ dependencies = [ [[package]] name = "leptos-use" -version = "0.11.3" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac79c02d0e2998569116aa36d26fd00bfa8cadbe8cb630eb771b4d1676412a16" +checksum = "3ab8914bd0ff8ab5029521540a6e15292dcc05d0f1a791a3aa8cc31a94436bfb" dependencies = [ - "async-trait", "cfg-if", "codee", "cookie", "default-struct-builder", "futures-util", "gloo-timers", - "gloo-utils 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "js-sys", "lazy_static", "leptos", "paste", "thiserror", + "unic-langid", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -2741,6 +2738,15 @@ dependencies = [ "time-core", ] +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -3063,6 +3069,24 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "unic-langid" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23dd9d1e72a73b25e07123a80776aae3e7b0ec461ef94f9151eed6ec88005a44" +dependencies = [ + "unic-langid-impl", +] + +[[package]] +name = "unic-langid-impl" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a5422c1f65949306c99240b81de9f3f15929f5a8bfe05bb44b034cc8bf593e5" +dependencies = [ + "tinystr", +] + [[package]] name = "unicase" version = "2.7.0" @@ -3253,9 +3277,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.69" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/Cargo.toml b/Cargo.toml index c2500e6..cdd0855 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,7 +40,7 @@ multer = { version = "3.0.0", optional = true } log = { version = "0.4.21", optional = true } flexi_logger = { version = "0.28.0", optional = true, default-features = false } web-sys = "0.3.69" -leptos-use = "0.11.3" +leptos-use = "0.13.5" [patch.crates-io] gloo-net = { git = "https://github.com/rustwasm/gloo.git", rev = "a823fab7ecc4068e9a28bd669da5eaf3f0a56380" } From ec762693677e50c5512480d46a0135f41b05e551 Mon Sep 17 00:00:00 2001 From: Ethan Girouard Date: Sat, 28 Sep 2024 20:46:41 -0400 Subject: [PATCH 09/10] Fix duplicate api module definition --- src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index a96a600..7c48d1b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,7 +16,6 @@ pub mod error_template; pub mod api; pub mod upload; pub mod util; -pub mod api; use cfg_if::cfg_if; From 73910a087a804a36ca1ac5f37fc18a5fcde2d502 Mon Sep 17 00:00:00 2001 From: Ethan Girouard Date: Sun, 29 Sep 2024 14:04:51 -0400 Subject: [PATCH 10/10] Add playing songs to history --- src/playbar.rs | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/playbar.rs b/src/playbar.rs index 6ef6b7e..f0055e4 100644 --- a/src/playbar.rs +++ b/src/playbar.rs @@ -7,6 +7,7 @@ use leptos::html::{Audio, Div}; use leptos::leptos_dom::*; use leptos::*; use leptos_icons::*; +use leptos_use::{utils::Pausable, use_interval_fn}; /// Width and height of the forward/backward skip buttons const SKIP_BTN_SIZE: &str = "3.5em"; @@ -22,6 +23,9 @@ const MIN_SKIP_BACK_TIME: f64 = 5.0; /// How many seconds to skip forward/backward when the user presses the arrow keys const ARROW_KEY_SKIP_TIME: f64 = 5.0; +/// Threshold in seconds for considering when the user has listened to a song, for adding it to the history +const HISTORY_LISTEN_THRESHOLD: u64 = MIN_SKIP_BACK_TIME as u64; + // TODO Handle errors better, when getting audio HTML element and when playing/pausing audio /// Get the current time and duration of the current song, if available @@ -532,6 +536,39 @@ pub fn PlayBar(status: RwSignal) -> impl IntoView { }); }); + let current_song_id = create_memo(move |_| { + status.with(|status| { + status.queue.front().map(|song| song.id) + }) + }); + + // Track the last song that was added to the history to prevent duplicates + let last_history_song_id = create_rw_signal(None); + + let Pausable { + is_active: hist_timeout_pending, + resume: resume_hist_timeout, + pause: pause_hist_timeout, + .. + } = use_interval_fn(move || { + if last_history_song_id.get_untracked() == current_song_id.get_untracked() { + return; + } + + if let Some(current_song_id) = current_song_id.get_untracked() { + last_history_song_id.set(Some(current_song_id)); + + spawn_local(async move { + if let Err(e) = crate::api::history::add_history(current_song_id).await { + error!("Error adding song {} to history: {}", current_song_id, e); + } + }); + } + }, HISTORY_LISTEN_THRESHOLD * 1000); + + // Initially pause the timeout, since the audio starts off paused + pause_hist_timeout(); + let on_play = move |_| { log!("Audio playing"); status.update(|status| status.playing = true); @@ -540,6 +577,7 @@ pub fn PlayBar(status: RwSignal) -> impl IntoView { let on_pause = move |_| { log!("Audio paused"); status.update(|status| status.playing = false); + pause_hist_timeout(); }; let on_time_update = move |_| { @@ -557,6 +595,11 @@ pub fn PlayBar(status: RwSignal) -> impl IntoView { error!("Unable to update time: Audio element not available"); } }); + + // If time is updated, audio is playing, so make sure the history timeout is running + if !hist_timeout_pending.get_untracked() { + resume_hist_timeout(); + } }; let on_end = move |_| {