diff --git a/src/commands.rs b/src/commands.rs index da0119a..d72bb0b 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -4,7 +4,6 @@ use twilight_interactions::command::{CommandModel, CreateCommand}; use twilight_mention::{Mention, timestamp::{TimestampStyle, Timestamp}}; use twilight_model::{id::{Id, marker::{InteractionMarker, ChannelMarker, UserMarker}}, http::interaction::{InteractionResponse, InteractionResponseType, InteractionResponseData}, channel::message::MessageFlags}; - #[derive(CommandModel, CreateCommand)] #[command(name = "set_fact", desc = "Quietly save a fact")] pub struct SetFactCommand { @@ -31,24 +30,16 @@ pub async fn set_fact( command_data: SetFactCommand, pg_pool: &PgPool, ) -> Result { - let Ok(rows) = sqlx::query!(" -INSERT INTO facts (\"last_interaction_id\", \"channel_id\", \"author_id\", \"name\", \"value\") -VALUES ($1, $2, $3, $4, $5) -ON CONFLICT ON CONSTRAINT facts_origin_key DO UPDATE SET value = $5, version = facts.version + 1 - ", - interaction_id.to_string(), - channel_id.map(|cid| cid.to_string()), - author_id.to_string(), - command_data.fact_name, - command_data.fact_value, - ).execute(pg_pool).await.and_then(|rows| Ok(rows.rows_affected())) else { + let Ok(()) = crate::database::set_fact( + pg_pool, + interaction_id.to_string(), + channel_id.map(|cid| cid.to_string()), + author_id.to_string(), + command_data.fact_name.to_owned(), + command_data.fact_value.to_owned()).await else { return Err((StatusCode::INTERNAL_SERVER_ERROR, "Error saving fact.".to_string())); }; - if rows != 1 { - return Err((StatusCode::INTERNAL_SERVER_ERROR, "Error saving fact".to_string())); - } - Ok(InteractionResponse { kind: InteractionResponseType::ChannelMessageWithSource, data: Some(InteractionResponseData { @@ -62,62 +53,42 @@ ON CONFLICT ON CONSTRAINT facts_origin_key DO UPDATE SET value = $5, version = f }) } -struct FactResponse { - value: String, - version: i32, - created_at: time::OffsetDateTime, - updated_at: time::OffsetDateTime, -} - pub async fn get_fact( channel_id: Option>, author_id: Id, command_data: GetFactCommand, pg_pool: &PgPool, ) -> Result { - let Ok(facts) = sqlx::query_as!(FactResponse, - " -SELECT \"value\", \"version\", \"created_at\", \"updated_at\" -FROM facts -WHERE - channel_id IS NOT DISTINCT FROM $1 AND - author_id = $2 AND - name = $3 - ", channel_id.map(|cid| cid.to_string()), author_id.to_string(), command_data.fact_name).fetch_all(pg_pool).await else { - return Err((StatusCode::INTERNAL_SERVER_ERROR, "Querying facts failed".to_string())); - }; - if facts.len() == 0 { - return Ok(InteractionResponse { - kind: InteractionResponseType::ChannelMessageWithSource, - data: Some(InteractionResponseData { - content: Some(format!("Fact {0} in channel {1} by you, {2}, was not found.", - command_data.fact_name, - channel_id.map_or("".to_string(), |cid| cid.mention().to_string()), - author_id.mention().to_string(), - )), - flags: match command_data.public { Some(true) => None, _ => Some(MessageFlags::EPHEMERAL) }, - ..Default::default() - }), - }); - } - if facts.len() > 1 { - return Err((StatusCode::INTERNAL_SERVER_ERROR, "Too many facts found, wtf, impossible".to_string())); - } + let Ok(fact) = crate::database::get_fact( + pg_pool, + channel_id.map(|cid| cid.to_string()), + author_id.to_string(), + command_data.fact_name.to_owned(), + ).await else { + return Err((StatusCode::INTERNAL_SERVER_ERROR, "Error retrieving fact.".to_string())); + }; - let fact = &facts[0]; + let content = match fact { + None => format!("Fact {0} in channel {1} by you, {2}, was not found.", + command_data.fact_name, + channel_id.map_or("".to_string(), |cid| cid.mention().to_string()), + author_id.mention() + ), + Some(fact) => format!( + "Fact **{0}** was set to **{1}** by {2} at {3}, and was reset {4} times in total since {5}.", + command_data.fact_name, + fact.value, + author_id.mention(), + Timestamp::new(fact.updated_at.unix_timestamp().try_into().unwrap(), Some(TimestampStyle::RelativeTime)).mention(), + fact.version, + Timestamp::new(fact.created_at.unix_timestamp().try_into().unwrap(), Some(TimestampStyle::ShortDateTime)).mention(), + ), + }; Ok(InteractionResponse { kind: InteractionResponseType::ChannelMessageWithSource, data: Some(InteractionResponseData { - content: Some(format!( - "Fact **{0}** was set to **{1}** by {2} at {3}, and was reset {4} times in total since {5}.", - command_data.fact_name, - fact.value, - author_id.mention().to_string(), - Timestamp::new(fact.updated_at.unix_timestamp().try_into().unwrap(), Some(TimestampStyle::RelativeTime)).mention(), - fact.version, - Timestamp::new(fact.created_at.unix_timestamp().try_into().unwrap(), Some(TimestampStyle::ShortDateTime)).mention(), - )), + content: Some(content), flags: match command_data.public { Some(true) => None, _ => Some(MessageFlags::EPHEMERAL) }, ..Default::default() }), diff --git a/src/database.rs b/src/database.rs new file mode 100644 index 0000000..5b58917 --- /dev/null +++ b/src/database.rs @@ -0,0 +1,67 @@ +use sqlx::PgPool; + +pub async fn set_fact( + pg_pool: &PgPool, + interaction_id: String, + channel_id: Option, + author_id: String, + fact_name: String, + fact_value: String, +) -> Result<(), String> { + let Ok(rows) = sqlx::query!(" +INSERT INTO facts (\"last_interaction_id\", \"channel_id\", \"author_id\", \"name\", \"value\") +VALUES ($1, $2, $3, $4, $5) +ON CONFLICT ON CONSTRAINT facts_origin_key DO UPDATE SET value = $5, version = facts.version + 1 + ", + interaction_id.to_string(), + channel_id.map(|cid| cid.to_string()), + author_id.to_string(), + fact_name, + fact_value, + ).execute(pg_pool).await.map(|rows| rows.rows_affected()) else { + return Err("Error saving fact.".to_string()); + }; + + if rows != 1 { + return Err("Error saving fact".to_string()); + } + + Ok(()) +} + +#[derive(Clone)] +pub struct FactResponse { + pub value: String, + pub version: i32, + pub created_at: time::OffsetDateTime, + pub updated_at: time::OffsetDateTime, +} + +pub async fn get_fact( + pg_pool: &PgPool, + channel_id: Option, + author_id: String, + fact_name: String, +) -> Result, String>{ + let Ok(facts) = sqlx::query_as!(FactResponse, + " +SELECT \"value\", \"version\", \"created_at\", \"updated_at\" +FROM facts +WHERE + channel_id IS NOT DISTINCT FROM $1 AND + author_id = $2 AND + name = $3 + ", channel_id.map(|cid| cid.to_string()), author_id.to_string(), fact_name).fetch_all(pg_pool).await else { + return Err("Querying facts failed".to_string()); + }; + + if facts.is_empty() { + return Ok(None); + } + + if facts.len() > 1 { + return Err("Too many facts found, wtf, impossible".to_string()); + } + + Ok(Some(facts[0].clone())) +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 998b180..d4b3eee 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,6 +20,7 @@ use twilight_model::{ id::Id }; mod commands; +mod database; #[tokio::main] async fn main() -> anyhow::Result<()> { @@ -74,7 +75,7 @@ fn validate_request(headers: HeaderMap, body: String) -> Result