use chrono::{DateTime, Utc}; use clickhouse::Row; use clickhouse_pool::traits::Model; 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)] #[repr(u8)] pub enum TrivialRewardKind { OnlyTheFirstOne = 0, TopThree = 1, TopFive = 2, } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize_repr, Deserialize_repr)] #[repr(u8)] pub enum TrivialStatus { Init = 0, Started = 1, Finished = 2, Paused = 3, } #[derive(Debug, Clone, PartialEq, PartialOrd, Row, Serialize, Deserialize)] pub struct Trivial { #[serde(with = "clickhouse::serde::uuid")] pub id: Uuid, pub name: String, pub description: String, pub guild_id: u64, pub channel_id: u64, #[serde(with = "clickhouse::serde::chrono::datetime64::millis")] pub created_at: DateTime, #[serde(with = "clickhouse::serde::chrono::datetime64::millis")] pub updated_at: DateTime, pub creator_id: u64, pub updater_id: u64, pub random_question: bool, pub role_ping: u64, pub role_ping_enabled: bool, pub reward_kind: TrivialRewardKind, pub reward_amount: u64, /// 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, } impl Default for Trivial { fn default() -> Self { Self { id: Uuid::new_v4(), name: String::new(), description: String::new(), guild_id: 0, channel_id: 0, created_at: Utc::now(), updated_at: Utc::now(), creator_id: 0, updater_id: 0, random_question: true, role_ping: 0, role_ping_enabled: false, reward_kind: TrivialRewardKind::TopThree, reward_amount: 3, taken_into_account: true, status: TrivialStatus::Init, } } } impl Trivial { pub fn new( name: String, description: String, guild_id: u64, channel_id: u64, creator_id: u64, updater_id: u64, ) -> Self { Self { name, description, guild_id, channel_id, creator_id, updater_id, ..Default::default() } } } impl Model for Trivial { type T = Trivial; fn table_name() -> &'static str { "trivial" } fn create_table_sql() -> &'static str { r#" CREATE TABLE IF NOT EXISTS trivial ( id UUID PRIMARY KEY, name String, description String, guild_id UInt64, channel_id UInt64, created_at DateTime64(3, 'UTC'), updated_at DateTime64(3, 'UTC'), creator_id UInt64, updater_id UInt64, random_question Bool, role_ping UInt64, role_ping_enabled Bool, 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 "# } fn column_names() -> Vec<&'static str> { vec![ "id", "name", "description", "guild_id", "channel_id", "created_at", "updated_at", "creator_id", "updater_id", "random_question", "role_ping", "role_ping_enabled", "reward_kind", "reward_amount", "taken_into_account", "status", ] } fn to_row(&self) -> (Vec<&'static str>, Vec) { ( Self::column_names(), vec![ format!("'{}'", self.id), format!("'{}'", self.name), format!("'{}'", self.description), self.guild_id.to_string(), self.channel_id.to_string(), format!("'{}'", self.created_at.to_rfc3339()), format!("'{}'", self.updated_at.to_rfc3339()), self.creator_id.to_string(), self.updater_id.to_string(), self.random_question.to_string(), self.role_ping.to_string(), self.role_ping_enabled.to_string(), format!("'{:?}'", serde_json::to_string(&self.reward_kind)), self.reward_amount.to_string(), self.taken_into_account.to_string(), format!("'{:?}'", serde_json::to_string(&self.status)), ], ) } fn insert_query(&self) -> String { let (columns, values) = self.to_row(); let columns_str = columns.join(", "); let values_str = values.join(", "); format!( "INSERT INTO {} ({}) VALUES ({})", Self::table_name(), columns_str, values_str ) } fn batch_insert_query(items: &[Self::T]) -> String { let mut queries = Vec::new(); for item in items { let (columns, values) = item.to_row(); let columns_str = columns.join(", "); let values_str = values.join(", "); queries.push(format!( "INSERT INTO {} ({}) VALUES ({})", Self::table_name(), columns_str, values_str )); } queries.join("; ") } fn build_select_query( where_clause: Option<&str>, limit: Option, offset: Option, ) -> String { let mut query = format!("SELECT * FROM {}", Self::table_name()); if let Some(where_clause) = where_clause { query.push_str(&format!(" WHERE {}", where_clause)); } if let Some(limit) = limit { query.push_str(&format!(" LIMIT {}", limit)); } if let Some(offset) = offset { query.push_str(&format!(" OFFSET {}", offset)); } query } }