From d026347897bfcdc86b38a05e5e5e2835c1b4a29c Mon Sep 17 00:00:00 2001 From: Max batleforc Date: Tue, 27 May 2025 19:22:12 +0200 Subject: [PATCH] feat: Setup Config question --- Cargo.lock | 1 + Readme.md | 3 +- apps/bot/src/bot/helper/mod.rs | 28 +++++ apps/bot/src/bot/trivia/config.rs | 149 +++++++++++++++++++++++ apps/bot/src/bot/trivia/mod.rs | 4 +- libs/clickhouse_pool/src/pool_manager.rs | 1 + libs/clickhouse_pool/src/traits.rs | 1 + libs/database/Cargo.toml | 1 + libs/database/src/guild.rs | 7 ++ libs/database/src/trivial.rs | 51 +++++++- libs/database/src/trivial_point.rs | 7 ++ libs/database/src/trivial_question.rs | 7 ++ libs/database/src/trivial_round.rs | 7 ++ 13 files changed, 259 insertions(+), 8 deletions(-) create mode 100644 apps/bot/src/bot/trivia/config.rs diff --git a/Cargo.lock b/Cargo.lock index eb9fa0d..0dc955d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -713,6 +713,7 @@ dependencies = [ "chrono", "clickhouse", "clickhouse_pool", + "poise", "serde", "serde_json", "serde_repr", diff --git a/Readme.md b/Readme.md index 6b734ae..bc9ac08 100644 --- a/Readme.md +++ b/Readme.md @@ -20,7 +20,7 @@ - [x] [ADMIN] trivial create : Creer une activité Trivial Daily - [x] [ADMIN] trivial list : List les activité -- [ ] [ADMIN] trivial config : Modification des paramétre de base d'un event +- [x] [ADMIN] trivial config : Modification des paramétre de base d'un event - [ ] [ADMIN] trivial add-question : Ajout de question + réponse ou X réponse - [ ] [ADMIN] trivial del-question : Suppression de question - [ ] [ADMIN] trivial load-question : Charge un fichier/lien de question @@ -29,6 +29,7 @@ - [ ] trivial score : Score d'une personne par défaut le sien - [ ] trivial top : Top X par défaut 3 - [ ] [ADMIN] trivial balance : Modifie le score d'une personne +/-/= +- [ ] [ADMIN] trivial status : Return trvial status or change trivial status #### TODO diff --git a/apps/bot/src/bot/helper/mod.rs b/apps/bot/src/bot/helper/mod.rs index 9fec1b4..cf29220 100644 --- a/apps/bot/src/bot/helper/mod.rs +++ b/apps/bot/src/bot/helper/mod.rs @@ -70,3 +70,31 @@ pub async fn is_user_admin_right(ctx: Context<'_>) -> Result { ); Ok(false) } + +// pub async fn autocomplete_channel_that_have_trivia( +// ctx: Context<'_>, +// _partial: &str, +// ) -> impl Iterator { +// let guild_id = match ctx.guild_id() { +// Some(id) => id.get(), +// None => { +// ctx.say("This command can only be used in a server.").await; +// return vec![]; +// } +// }; + +// let manager = ctx.data().datalake_config.clone(); +// let search_query = Trivial::build_select_query(Some(&format!("id = {}", guild_id)), None, None); +// let trivials = manager +// .execute_select_with_retry::(&search_query) +// .await; + +// if trivials.is_empty() { +// return vec![]; +// } + +// trivials +// .iter() +// .map(|c| ChannelId::from(c.channel_id).to_string()) +// .collect() +// } diff --git a/apps/bot/src/bot/trivia/config.rs b/apps/bot/src/bot/trivia/config.rs new file mode 100644 index 0000000..0d96705 --- /dev/null +++ b/apps/bot/src/bot/trivia/config.rs @@ -0,0 +1,149 @@ +use crate::bot::helper::is_user_admin_right; +use crate::bot::{Context, Error}; +use clickhouse_pool::traits::Model; +use database::trivial::{Trivial, TrivialRewardKind}; +use poise::serenity_prelude::{Channel, Role}; +use tracing::{debug, info, instrument}; + +/// Config an existing trivia game +#[instrument(name="trivia_config", skip(ctx), level = "info", fields(channel = ctx.channel_id().get(), guild = ?ctx.guild_id().unwrap().get()))] +#[poise::command( + prefix_command, + slash_command, + guild_only, + category = "Trivia", + check = "is_user_admin_right" +)] +pub async fn config( + ctx: Context<'_>, + #[description = "Channel of the trivia game, or current"] channel_id: Option, + #[description = "Name of the trivia game"] name: Option, + #[description = "Description of the trivia game"] description: Option, + #[description = "Whether to use random questions"] random_question: Option, + #[description = "Role to ping when a question is asked"] role_ping: Option, + #[description = "Whether to enable role ping"] role_ping_enabled: Option, + #[description = "Reward kind for the trivia game"] reward_kind: Option, + #[description = "Reward amount for the trivia game"] reward_amount: Option, + #[description = "Whether to send an ephemeral message when the answer is taken into account"] + taken_into_account: Option, +) -> Result<(), Error> { + let guild_id = match ctx.guild_id() { + Some(id) => id.get(), + None => { + ctx.say("This command can only be used in a server.") + .await?; + return Ok(()); + } + }; + let channel_id = match channel_id { + Some(c) => c.id().get(), + None => { + info!("No channel specified, using current channel."); + ctx.channel_id().get() + } + }; + let manager = ctx.data().datalake_config.clone(); + let seach_query = Trivial::build_select_query( + Some(&format!( + "channel_id = {} and guild_id = {}", + channel_id, guild_id + )), + None, + None, + ); + let mut trivia_exists: Trivial = match manager + .execute_select_with_retry::(&seach_query) + .await + { + Ok(trivia) => { + if trivia.is_empty() { + ctx.say("No trivia game found in this channel. Please create one first.") + .await?; + return Ok(()); + } + if trivia.len() > 1 { + ctx.say("Multiple trivia games found in this channel. Please contact an admin to resolve this.") + .await?; + return Ok(()); + } + trivia[0].clone() + } + Err(e) => { + ctx.say(format!("Error fetching trivia game: {}", e)) + .await?; + return Err(Error::from(e)); + } + }; + debug!("Found trivia game: {:?}", trivia_exists); + let mut inserter = match manager.get_insert::(Trivial::table_name()).await { + Ok(inserter) => inserter, + Err(e) => { + tracing::error!("Failed to create inserter for Trivial: {}", e); + ctx.say("Failed to update trivia game. Please try again later.") + .await?; + return Ok(()); + } + }; + trivia_exists.sign = -1; // Set sign to -1 to indicate an update + inserter.write(&trivia_exists.clone()).await?; + // Update the trivia game with provided options + if let Some(name) = name { + trivia_exists.name = name; + } + if let Some(description) = description { + trivia_exists.description = description; + } + if let Some(random_question) = random_question { + trivia_exists.random_question = random_question; + } + if let Some(role_ping) = role_ping { + trivia_exists.role_ping = role_ping.id.get(); + } + if let Some(role_ping_enabled) = role_ping_enabled { + trivia_exists.role_ping_enabled = role_ping_enabled; + } + if let Some(reward_kind) = reward_kind { + trivia_exists.reward_kind = reward_kind; + } + if let Some(reward_amount) = reward_amount { + trivia_exists.reward_amount = reward_amount; + } + if let Some(taken_into_account) = taken_into_account { + trivia_exists.taken_into_account = taken_into_account; + } + trivia_exists.updated_at = chrono::Utc::now(); + trivia_exists.updater_id = ctx.author().id.get(); + debug!("Updated trivia game: {:?}", trivia_exists); + // Set sign to 1 to indicate a successful update + trivia_exists.sign = 1; + // Write the updated trivia game back to the database + inserter.write(&trivia_exists).await?; + match inserter.end().await { + Ok(_) => { + info!( + "Trivia game '{}' updated successfully in channel <#{}>.", + trivia_exists.name, channel_id + ); + ctx.say(format!( + "Trivia game '{}' has been successfully updated.", + trivia_exists.name + )) + .await?; + } + Err(e) => { + tracing::error!("Failed to update trivia game: {}", e); + ctx.say("Failed to update trivia game. Please try again later.") + .await?; + return Ok(()); + } + } + + let optimize_querry = format!("OPTIMIZE TABLE {} FINAL", Trivial::table_name()); + if let Err(e) = manager.execute_with_retry(&optimize_querry).await { + tracing::error!("Failed to optimize trivia table: {}", e); + } else { + info!("Trivia table optimized successfully."); + } + + Ok(()) +} diff --git a/apps/bot/src/bot/trivia/mod.rs b/apps/bot/src/bot/trivia/mod.rs index af1340c..4a4e997 100644 --- a/apps/bot/src/bot/trivia/mod.rs +++ b/apps/bot/src/bot/trivia/mod.rs @@ -1,7 +1,9 @@ +pub mod config; pub mod create; pub mod list; use crate::bot::{Context, Error}; +use config::config; use create::create; use list::list; use tracing::instrument; @@ -12,7 +14,7 @@ use tracing::instrument; slash_command, prefix_command, category = "Trivia", - subcommands("create", "list"), + subcommands("create", "list", "config"), guild_only = true )] pub async fn trivia(ctx: Context<'_>) -> Result<(), Error> { diff --git a/libs/clickhouse_pool/src/pool_manager.rs b/libs/clickhouse_pool/src/pool_manager.rs index d852177..f8a575d 100644 --- a/libs/clickhouse_pool/src/pool_manager.rs +++ b/libs/clickhouse_pool/src/pool_manager.rs @@ -61,6 +61,7 @@ impl PoolManager { .get_connection() .await .unwrap() + .clone() .insert(table_name) } diff --git a/libs/clickhouse_pool/src/traits.rs b/libs/clickhouse_pool/src/traits.rs index be23202..a2c8863 100644 --- a/libs/clickhouse_pool/src/traits.rs +++ b/libs/clickhouse_pool/src/traits.rs @@ -2,6 +2,7 @@ pub trait Model { type T; fn create_table_sql() -> &'static str; + fn optimize_table_sql() -> &'static str; fn table_name() -> &'static str; fn column_names() -> Vec<&'static str>; fn to_row(&self) -> (Vec<&'static str>, Vec); diff --git a/libs/database/Cargo.toml b/libs/database/Cargo.toml index 2b35717..70cedad 100644 --- a/libs/database/Cargo.toml +++ b/libs/database/Cargo.toml @@ -11,6 +11,7 @@ clickhouse_pool = { path = "../clickhouse_pool" } clickhouse = { workspace = true } uuid = { workspace = true } chrono = { workspace = true } +poise = { workspace = true } serde_repr = "0.1.20" serde_json = { workspace = true } diff --git a/libs/database/src/guild.rs b/libs/database/src/guild.rs index b1830d0..8d75849 100644 --- a/libs/database/src/guild.rs +++ b/libs/database/src/guild.rs @@ -30,6 +30,13 @@ impl Model for Guild { "guild" } + fn optimize_table_sql() -> &'static str { + r#" + OPTIMIZE TABLE guild FINAL + "# + .trim() + } + fn create_table_sql() -> &'static str { r#" CREATE TABLE IF NOT EXISTS guild ( diff --git a/libs/database/src/trivial.rs b/libs/database/src/trivial.rs index a526db9..4a472df 100644 --- a/libs/database/src/trivial.rs +++ b/libs/database/src/trivial.rs @@ -5,20 +5,49 @@ use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use uuid::Uuid; -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize_repr, Deserialize_repr)] +#[derive( + Debug, + Clone, + Copy, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize_repr, + Deserialize_repr, + poise::ChoiceParameter, +)] #[repr(u8)] pub enum TrivialRewardKind { + #[name = "Only the first one"] OnlyTheFirstOne = 0, + #[name = "Top three"] TopThree = 1, + #[name = "Top five"] TopFive = 2, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize_repr, Deserialize_repr)] +#[derive( + Debug, + Clone, + Copy, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize_repr, + Deserialize_repr, + poise::ChoiceParameter, +)] #[repr(u8)] pub enum TrivialStatus { + #[name = "Init"] Init = 0, + #[name = "Started"] Started = 1, + #[name = "Finished"] Finished = 2, + #[name = "Paused"] Paused = 3, } @@ -48,6 +77,7 @@ pub struct Trivial { /// Whether or not the bot should send an ephemeral message to the user when their answer is taken into account. pub taken_into_account: bool, pub status: TrivialStatus, + pub sign: i8, } impl Default for Trivial { @@ -69,6 +99,7 @@ impl Default for Trivial { reward_amount: 3, taken_into_account: true, status: TrivialStatus::Init, + sign: 1, } } } @@ -100,11 +131,14 @@ impl Model for Trivial { fn table_name() -> &'static str { "trivial" } + fn optimize_table_sql() -> &'static str { + "OPTIMIZE TABLE trivial FINAL" + } fn create_table_sql() -> &'static str { r#" CREATE TABLE IF NOT EXISTS trivial ( - id UUID PRIMARY KEY, + id UUID, name String, description String, guild_id UInt64, @@ -119,9 +153,11 @@ impl Model for Trivial { reward_kind Enum8('OnlyTheFirstOne' = 0, 'TopThree' = 1, 'TopFive' = 2), reward_amount UInt64, taken_into_account Bool, - status Enum8('Init' = 0, 'Started' = 1, 'Finished' = 2, 'Paused' = 3) - ) ENGINE = MergeTree() - ORDER BY id + status Enum8('Init' = 0, 'Started' = 1, 'Finished' = 2, 'Paused' = 3), + sign Int8 + ) ENGINE = CollapsingMergeTree(sign) + PRIMARY KEY (guild_id, id) + ORDER BY (guild_id, id) "# } @@ -143,6 +179,7 @@ impl Model for Trivial { "reward_amount", "taken_into_account", "status", + "sign", ] } @@ -166,6 +203,7 @@ impl Model for Trivial { self.reward_amount.to_string(), self.taken_into_account.to_string(), format!("'{:?}'", serde_json::to_string(&self.status)), + self.sign.to_string(), ], ) } @@ -213,6 +251,7 @@ impl Model for Trivial { if let Some(offset) = offset { query.push_str(&format!(" OFFSET {}", offset)); } + //query.push_str(" FINAL"); query } } diff --git a/libs/database/src/trivial_point.rs b/libs/database/src/trivial_point.rs index 784034a..a371260 100644 --- a/libs/database/src/trivial_point.rs +++ b/libs/database/src/trivial_point.rs @@ -30,6 +30,13 @@ impl Model for TrivialPoint { "trivial_point" } + fn optimize_table_sql() -> &'static str { + r#" + OPTIMIZE TABLE trivial_point FINAL + "# + .trim() + } + fn create_table_sql() -> &'static str { r#" CREATE TABLE IF NOT EXISTS trivial_point ( diff --git a/libs/database/src/trivial_question.rs b/libs/database/src/trivial_question.rs index 3f2f103..4cedf5a 100644 --- a/libs/database/src/trivial_question.rs +++ b/libs/database/src/trivial_question.rs @@ -30,6 +30,13 @@ impl Model for TrivialQuestion { "trivial_question" } + fn optimize_table_sql() -> &'static str { + r#" + OPTIMIZE TABLE trivial_question FINAL + "# + .trim() + } + fn create_table_sql() -> &'static str { r#" CREATE TABLE IF NOT EXISTS trivial_question ( diff --git a/libs/database/src/trivial_round.rs b/libs/database/src/trivial_round.rs index b499071..5c2ed31 100644 --- a/libs/database/src/trivial_round.rs +++ b/libs/database/src/trivial_round.rs @@ -29,6 +29,13 @@ impl Model for TrivialRound { "trivial_round" } + fn optimize_table_sql() -> &'static str { + r#" + OPTIMIZE TABLE trivial_round FINAL + "# + .trim() + } + fn create_table_sql() -> &'static str { r#" CREATE TABLE IF NOT EXISTS trivial_round (