From e8750388517c054b986ac31e7a1e0682fe689f25 Mon Sep 17 00:00:00 2001 From: jude Date: Wed, 16 Aug 2023 16:47:33 +0100 Subject: [PATCH] Favorite/unfavorite sounds --- .idea/dataSources.local.xml | 2 +- migrations/20230816145151_favorite_sounds.sql | 8 ++ dump-query.sh => scripts/dump-query.sh | 0 src/cmds/favorite.rs | 96 +++++++++++++++++ src/cmds/info.rs | 3 + src/cmds/mod.rs | 1 + src/cmds/search.rs | 19 ++++ src/main.rs | 8 ++ src/models/sound.rs | 102 ++++++++++++++++++ 9 files changed, 238 insertions(+), 1 deletion(-) create mode 100644 migrations/20230816145151_favorite_sounds.sql rename dump-query.sh => scripts/dump-query.sh (100%) create mode 100644 src/cmds/favorite.rs diff --git a/.idea/dataSources.local.xml b/.idea/dataSources.local.xml index 874735f..ac78098 100644 --- a/.idea/dataSources.local.xml +++ b/.idea/dataSources.local.xml @@ -1,6 +1,6 @@ - + master_key diff --git a/migrations/20230816145151_favorite_sounds.sql b/migrations/20230816145151_favorite_sounds.sql new file mode 100644 index 0000000..da04a4c --- /dev/null +++ b/migrations/20230816145151_favorite_sounds.sql @@ -0,0 +1,8 @@ +ALTER TABLE users ADD PRIMARY KEY (`user`); + +CREATE TABLE favorite_sounds ( + user_id BIGINT UNSIGNED NOT NULL, + sound_id INT UNSIGNED NOT NULL, + FOREIGN KEY (sound_id) REFERENCES `sounds`(`id`) ON DELETE CASCADE ON UPDATE CASCADE, + PRIMARY KEY (user_id, sound_id) +); diff --git a/dump-query.sh b/scripts/dump-query.sh similarity index 100% rename from dump-query.sh rename to scripts/dump-query.sh diff --git a/src/cmds/favorite.rs b/src/cmds/favorite.rs new file mode 100644 index 0000000..e36adcc --- /dev/null +++ b/src/cmds/favorite.rs @@ -0,0 +1,96 @@ +use log::warn; + +use crate::{cmds::autocomplete_sound, models::sound::SoundCtx, Context, Error}; + +#[poise::command(slash_command, rename = "favorites", guild_only = true)] +pub async fn favorites(_ctx: Context<'_>) -> Result<(), Error> { + Ok(()) +} + +/// Add a sound as a favorite +#[poise::command( + slash_command, + rename = "add", + category = "Favorites", + guild_only = true +)] +pub async fn add_favorite( + ctx: Context<'_>, + #[description = "Name or ID of sound to favorite"] + #[autocomplete = "autocomplete_sound"] + name: String, +) -> Result<(), Error> { + let sounds = ctx + .data() + .search_for_sound(&name, ctx.guild_id().unwrap(), ctx.author().id, true) + .await; + + match sounds { + Ok(sounds) => { + let sound = &sounds[0]; + + sound + .add_favorite(ctx.author().id, &ctx.data().database) + .await?; + ctx.say(format!( + "Sound {} (ID {}) added to favorites.", + sound.name, sound.id + )) + .await?; + + Ok(()) + } + + Err(e) => { + warn!("Couldn't fetch sounds: {:?}", e); + + ctx.say("Failed to find sound.").await?; + + Ok(()) + } + } +} + +/// Remove a sound from your favorites +#[poise::command( + slash_command, + rename = "remove", + category = "Favorites", + guild_only = true +)] +pub async fn remove_favorite( + ctx: Context<'_>, + #[description = "Name or ID of sound to favorite"] + #[autocomplete = "autocomplete_sound"] + name: String, +) -> Result<(), Error> { + let sounds = ctx + .data() + .search_for_sound(&name, ctx.guild_id().unwrap(), ctx.author().id, true) + .await; + + match sounds { + Ok(sounds) => { + let sound = &sounds[0]; + + sound + .remove_favorite(ctx.author().id, &ctx.data().database) + .await?; + ctx.say(format!( + "Sound {} (ID {}) removed from favorites.", + sound.name, sound.id + )) + .await?; + + Ok(()) + } + + Err(e) => { + warn!("Couldn't fetch sounds: {:?}", e); + + ctx.say("Failed to find sound.").await?; + + Ok(()) + } + } +} diff --git a/src/cmds/info.rs b/src/cmds/info.rs index 1a3d07c..311c95e 100644 --- a/src/cmds/info.rs +++ b/src/cmds/info.rs @@ -33,6 +33,9 @@ __Library Commands__ `/public` - Set a sound as public/private `/list server` - List sounds on this server `/list user` - List your sounds +`/favorites add` - Add a favorite +`/favorites remove` - Remove a favorite +`/list favorites` - List favorites __Search Commands__ `/search` - Search for public sounds by name diff --git a/src/cmds/mod.rs b/src/cmds/mod.rs index 3601bb6..59eae80 100644 --- a/src/cmds/mod.rs +++ b/src/cmds/mod.rs @@ -1,5 +1,6 @@ use crate::{models::sound::SoundCtx, Context}; +pub mod favorite; pub mod info; pub mod manage; pub mod play; diff --git a/src/cmds/search.rs b/src/cmds/search.rs index 5948a83..068dcd1 100644 --- a/src/cmds/search.rs +++ b/src/cmds/search.rs @@ -47,12 +47,14 @@ pub async fn list_sounds(_ctx: Context<'_>) -> Result<(), Error> { enum ListContext { User = 0, Guild = 1, + Favorite = 2, } impl ListContext { pub fn title(&self) -> &'static str { match self { ListContext::User => "Your sounds", + ListContext::Favorite => "Your favorite sounds", ListContext::Guild => "Server sounds", } } @@ -86,6 +88,20 @@ pub async fn list_user_sounds(ctx: Context<'_>) -> Result<(), Error> { Ok(()) } +/// Show all sounds you have uploaded +#[poise::command(slash_command, rename = "user", guild_only = true)] +pub async fn list_favorite_sounds(ctx: Context<'_>) -> Result<(), Error> { + let pager = SoundPager { + nonce: 0, + page: 0, + context: ListContext::Favorite, + }; + + pager.reply(ctx).await?; + + Ok(()) +} + #[derive(Serialize, Deserialize)] pub struct SoundPager { nonce: u64, @@ -102,6 +118,7 @@ impl SoundPager { ) -> Result, sqlx::Error> { match self.context { ListContext::User => data.user_sounds(user_id, Some(self.page)).await, + ListContext::Favorite => data.favorite_sounds(user_id, Some(self.page)).await, ListContext::Guild => data.guild_sounds(guild_id, Some(self.page)).await, } } @@ -205,6 +222,7 @@ impl SoundPager { let sounds = pager.get_page(data, user_id, guild_id).await?; let count = match pager.context { ListContext::User => data.count_user_sounds(user_id).await?, + ListContext::Favorite => data.count_favorite_sounds(user_id).await?, ListContext::Guild => data.count_guild_sounds(guild_id).await?, }; @@ -228,6 +246,7 @@ impl SoundPager { .await?; let count = match self.context { ListContext::User => ctx.data().count_user_sounds(ctx.author().id).await?, + ListContext::Favorite => ctx.data().count_favorite_sounds(ctx.author().id).await?, ListContext::Guild => { ctx.data() .count_guild_sounds(ctx.guild_id().unwrap()) diff --git a/src/main.rs b/src/main.rs index 13cc4f4..6dc6a25 100644 --- a/src/main.rs +++ b/src/main.rs @@ -92,9 +92,17 @@ async fn main() -> Result<(), Box> { subcommands: vec![ cmds::search::list_guild_sounds(), cmds::search::list_user_sounds(), + cmds::search::list_favorite_sounds(), ], ..cmds::search::list_sounds() }, + poise::Command { + subcommands: vec![ + cmds::favorite::add_favorite(), + cmds::favorite::remove_favorite(), + ], + ..cmds::favorite::favorites() + }, cmds::search::show_random_sounds(), cmds::search::search_sounds(), cmds::stop::stop_playing(), diff --git a/src/models/sound.rs b/src/models/sound.rs index 4ba210c..6a371c7 100644 --- a/src/models/sound.rs +++ b/src/models/sound.rs @@ -42,12 +42,21 @@ pub trait SoundCtx { user_id: U, page: Option, ) -> Result, sqlx::Error>; + async fn favorite_sounds + Send>( + &self, + user_id: U, + page: Option, + ) -> Result, sqlx::Error>; async fn guild_sounds + Send>( &self, guild_id: G, page: Option, ) -> Result, sqlx::Error>; async fn count_user_sounds + Send>(&self, user_id: U) -> Result; + async fn count_favorite_sounds + Send>( + &self, + user_id: U, + ) -> Result; async fn count_guild_sounds + Send>( &self, guild_id: G, @@ -219,6 +228,50 @@ SELECT name, id, public, server_id, uploader_id Ok(sounds) } + async fn favorite_sounds + Send>( + &self, + user_id: U, + page: Option, + ) -> Result, sqlx::Error> { + let sounds = match page { + Some(page) => { + sqlx::query_as_unchecked!( + Sound, + " +SELECT name, id, public, server_id, uploader_id + FROM sounds + INNER JOIN favorite_sounds f ON sounds.id = f.sound_id + WHERE f.user_id = ? + ORDER BY id DESC + LIMIT ?, ? + ", + user_id.into(), + page * 25, + (page + 1) * 25 + ) + .fetch_all(&self.database) + .await? + } + None => { + sqlx::query_as_unchecked!( + Sound, + " +SELECT name, id, public, server_id, uploader_id + FROM sounds + INNER JOIN favorite_sounds f ON sounds.id = f.sound_id + WHERE f.user_id = ? + ORDER BY id DESC + ", + user_id.into() + ) + .fetch_all(&self.database) + .await? + } + }; + + Ok(sounds) + } + async fn guild_sounds + Send>( &self, guild_id: G, @@ -272,6 +325,19 @@ SELECT name, id, public, server_id, uploader_id .count as u64) } + async fn count_favorite_sounds + Send>( + &self, + user_id: U, + ) -> Result { + Ok(sqlx::query!( + "SELECT COUNT(1) as count FROM favorite_sounds WHERE user_id = ?", + user_id.into() + ) + .fetch_one(&self.database) + .await? + .count as u64) + } + async fn count_guild_sounds + Send>( &self, guild_id: G, @@ -416,6 +482,42 @@ WHERE Ok(()) } + pub async fn add_favorite>( + &self, + user_id: U, + db_pool: impl Executor<'_, Database = Database>, + ) -> Result<(), Box> { + let user_id = user_id.into(); + + sqlx::query!( + "INSERT INTO favorite_sounds (user_id, sound_id) VALUES (?, ?)", + user_id, + self.id + ) + .execute(db_pool) + .await?; + + Ok(()) + } + + pub async fn remove_favorite>( + &self, + user_id: U, + db_pool: impl Executor<'_, Database = Database>, + ) -> Result<(), Box> { + let user_id = user_id.into(); + + sqlx::query!( + "DELETE FROM favorite_sounds WHERE user_id = ? AND sound_id = ?", + user_id, + self.id + ) + .execute(db_pool) + .await?; + + Ok(()) + } + pub async fn create_anon, U: Into>( name: &str, src_url: &str,