diff --git a/src/commands/offset.rs b/src/commands/offset.rs index 6618d41..593a593 100644 --- a/src/commands/offset.rs +++ b/src/commands/offset.rs @@ -31,31 +31,33 @@ impl Recordable for Options { .filter(|(_, channel)| channel.is_text_based()) .map(|(id, _)| id.get().to_string()) .collect::>() - .join(",") }) { - sqlx::query!( + let placeholder = vec!["?"; channels.len()].join(","); + let sql = format!( " - UPDATE reminders - INNER JOIN `channels` - ON `channels`.id = reminders.channel_id - SET reminders.`utc_time` = DATE_ADD(reminders.`utc_time`, INTERVAL ? SECOND) - WHERE FIND_IN_SET(channels.`channel`, ?) - ", - combined_time as i64, - channels - ) - .execute(&ctx.data().database) - .await - .unwrap(); + UPDATE reminders + INNER JOIN `channels` + ON `channels`.id = reminders.channel_id + SET reminders.`utc_time` = DATE_ADD(reminders.`utc_time`, INTERVAL ? SECOND) + WHERE channels.`channel` IN ({placeholder}) + " + ); + + let mut query = sqlx::query(&sql).bind(combined_time); + for channel in channels { + query = query.bind(channel); + } + + query.execute(&ctx.data().database).await.unwrap(); } else { sqlx::query!( " - UPDATE reminders - INNER JOIN `channels` - ON `channels`.id = reminders.channel_id - SET reminders.`utc_time` = reminders.`utc_time` + ? - WHERE channels.`channel` = ? - ", + UPDATE reminders + INNER JOIN `channels` + ON `channels`.id = reminders.channel_id + SET reminders.`utc_time` = reminders.`utc_time` + ? + WHERE channels.`channel` = ? + ", combined_time as i64, ctx.channel_id().get() ) diff --git a/src/component_models/mod.rs b/src/component_models/mod.rs index 8cc8a66..a1f1094 100644 --- a/src/component_models/mod.rs +++ b/src/component_models/mod.rs @@ -153,12 +153,12 @@ impl ComponentDataModel { ComponentDataModel::DelSelector(selector) => { if let ComponentInteractionDataKind::StringSelect { values } = &component.data.kind { - let placeholder = values.iter().map(|_| "?").collect::>().join(","); + let placeholder = vec!["?"; values.len()].join(","); let sql = format!( "UPDATE reminders SET `status` = 'deleted' WHERE id IN ({placeholder})" ); - let mut query = sqlx::query(&sql); + let mut query = sqlx::query(&sql); for id in values { query = query.bind(id); } @@ -269,17 +269,15 @@ impl ComponentDataModel { if let ComponentInteractionDataKind::StringSelect { values } = &component.data.kind { - let selected_id = values.join(","); + let placeholder = vec!["?"; values.len()].join(","); + let sql = format!("DELETE FROM todos WHERE id IN ({placeholder})"); - sqlx::query!( - " - DELETE FROM todos WHERE FIND_IN_SET(id, ?) - ", - selected_id - ) - .execute(&data.database) - .await - .unwrap(); + let mut query = sqlx::query(&sql); + for id in values { + query = query.bind(id); + } + + query.execute(&data.database).await.unwrap(); let values = if let Some(uid) = selector.user_id { sqlx::query!( diff --git a/src/models/reminder/mod.rs b/src/models/reminder/mod.rs index d1cf9e0..1319147 100644 --- a/src/models/reminder/mod.rs +++ b/src/models/reminder/mod.rs @@ -8,17 +8,6 @@ use std::{ hash::{Hash, Hasher}, }; -use chrono::{DateTime, NaiveDateTime, Utc}; -use chrono_tz::Tz; -use poise::{ - serenity_prelude::{ - model::id::{ChannelId, GuildId, UserId}, - ButtonStyle, Cache, ChannelType, CreateActionRow, CreateButton, CreateEmbed, ReactionType, - }, - CreateReply, -}; -use sqlx::Executor; - use crate::{ commands::look::{LookFlags, TimeDisplayType}, component_models::{ComponentDataModel, UndoReminder}, @@ -36,8 +25,18 @@ use crate::{ utils::{check_guild_subscription, check_subscription}, Context, Database, Error, }; +use chrono::{DateTime, NaiveDateTime, Utc}; +use chrono_tz::Tz; +use poise::{ + serenity_prelude::{ + model::id::{ChannelId, GuildId, UserId}, + ButtonStyle, Cache, ChannelType, CreateActionRow, CreateButton, CreateEmbed, ReactionType, + }, + CreateReply, +}; +use sqlx::{Executor, FromRow}; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, FromRow)] #[allow(dead_code)] pub struct Reminder { pub id: u32, @@ -140,7 +139,7 @@ impl Reminder { channel_id: C, flags: &LookFlags, ) -> Vec { - let enabled = if flags.show_disabled { "0,1" } else { "1" }; + let enabled = if flags.show_disabled { "0" } else { "1" }; let channel_id = channel_id.into(); sqlx::query_as_unchecked!( @@ -168,7 +167,7 @@ impl Reminder { WHERE `status` = 'pending' AND channels.channel = ? AND - FIND_IN_SET(reminders.enabled, ?) + reminders.enabled >= ? ORDER BY reminders.utc_time ", @@ -194,17 +193,16 @@ impl Reminder { .keys() .into_iter() .map(|k| k.get().to_string()) - .collect::>() - .join(","), + .collect::>(), ) } else { None }; match channel_query { - Some(channel_query) => { - sqlx::query_as_unchecked!( - Self, + Some(channels) => { + let placeholder = vec!["?"; channels.len()].join(","); + let sql = format!( " SELECT reminders.id, @@ -227,12 +225,16 @@ impl Reminder { channels.id = reminders.channel_id WHERE `status` = 'pending' AND - FIND_IN_SET(channels.channel, ?) - ", - channel_query - ) - .fetch_all(pool) - .await + channels.channel IN ({placeholder}) + " + ); + + let mut query = sqlx::query_as::(&sql); + for channel in channels { + query = query.bind(channel); + } + + query.fetch_all(pool).await } None => { sqlx::query_as_unchecked!( diff --git a/src/web/routes/dashboard/api/guild/reminders.rs b/src/web/routes/dashboard/api/guild/reminders.rs index a6ca5de..d0b17ca 100644 --- a/src/web/routes/dashboard/api/guild/reminders.rs +++ b/src/web/routes/dashboard/api/guild/reminders.rs @@ -72,55 +72,60 @@ pub async fn get_reminders( match channels_res { Ok(channels) => { - let channels = channels - .keys() - .into_iter() - .map(|k| k.get().to_string()) - .collect::>() - .join(","); + let channels = + channels.keys().into_iter().map(|k| k.get().to_string()).collect::>(); - sqlx::query_as_unchecked!( - GetReminder, + let placeholder = vec!["?"; channels.len()].join(","); + let sql = format!( " SELECT - reminders.attachment_name, - reminders.avatar, - channels.channel, - reminders.content, - reminders.embed_author, - reminders.embed_author_url, - reminders.embed_color, - reminders.embed_description, - reminders.embed_footer, - reminders.embed_footer_url, - reminders.embed_image_url, - reminders.embed_thumbnail_url, - reminders.embed_title, - IFNULL(reminders.embed_fields, '[]') AS embed_fields, - reminders.enabled, - reminders.expires, - reminders.interval_seconds, - reminders.interval_days, - reminders.interval_months, - reminders.name, - reminders.restartable, - reminders.tts, - reminders.uid, - reminders.username, - reminders.utc_time + reminders.attachment_name, + reminders.avatar, + channels.channel, + reminders.content, + reminders.embed_author, + reminders.embed_author_url, + reminders.embed_color, + reminders.embed_description, + reminders.embed_footer, + reminders.embed_footer_url, + reminders.embed_image_url, + reminders.embed_thumbnail_url, + reminders.embed_title, + IFNULL(reminders.embed_fields, '[]') AS embed_fields, + reminders.enabled, + reminders.expires, + reminders.interval_seconds, + reminders.interval_days, + reminders.interval_months, + reminders.name, + reminders.restartable, + reminders.tts, + reminders.uid, + reminders.username, + reminders.utc_time FROM reminders - INNER JOIN channels ON channels.id = reminders.channel_id - WHERE `status` = 'pending' AND FIND_IN_SET(channels.channel, ?)", - channels - ) - .fetch_all(pool.inner()) - .await - .map(|r| Ok(json!(r))) - .unwrap_or_else(|e| { - warn!("Failed to complete SQL query: {:?}", e); + INNER JOIN channels + ON channels.id = reminders.channel_id + WHERE `status` = 'pending' + AND channels.channel IN ({placeholder}) + " + ); - json_err!("Could not load reminders") - }) + let mut query = sqlx::query_as::(&sql); + for channel in channels { + query = query.bind(channel); + } + + query + .fetch_all(pool.inner()) + .await + .map(|reminders| Ok(json!(reminders))) + .unwrap_or_else(|e| { + warn!("Failed to complete SQL query: {:?}", e); + + json_err!("Could not load reminders") + }) } Err(e) => { warn!("Could not fetch channels from {}: {:?}", id, e); diff --git a/src/web/routes/dashboard/export.rs b/src/web/routes/dashboard/export.rs index 381d486..ba361a9 100644 --- a/src/web/routes/dashboard/export.rs +++ b/src/web/routes/dashboard/export.rs @@ -1,3 +1,14 @@ +use crate::web::{ + check_authorization, + guards::transaction::Transaction, + routes::{ + dashboard::{ + create_reminder, CreateReminder, ImportBody, ReminderCsv, ReminderTemplateCsv, TodoCsv, + }, + JsonResult, + }, +}; +use crate::Database; use base64::{prelude::BASE64_STANDARD, Engine}; use csv::{QuoteStyle, WriterBuilder}; use log::warn; @@ -14,17 +25,6 @@ use serenity::{ }; use sqlx::{MySql, Pool}; -use crate::web::{ - check_authorization, - guards::transaction::Transaction, - routes::{ - dashboard::{ - create_reminder, CreateReminder, ImportBody, ReminderCsv, ReminderTemplateCsv, TodoCsv, - }, - JsonResult, - }, -}; - #[get("/api/guild//export/reminders")] pub async fn export_reminders( id: u64, @@ -40,48 +40,47 @@ pub async fn export_reminders( match channels_res { Ok(channels) => { - let channels = channels - .keys() - .into_iter() - .map(|k| k.get().to_string()) - .collect::>() - .join(","); - - let result = sqlx::query_as_unchecked!( - ReminderCsv, - "SELECT - reminders.attachment, - reminders.attachment_name, - reminders.avatar, - CONCAT('#', channels.channel) AS channel, - reminders.content, - reminders.embed_author, - reminders.embed_author_url, - reminders.embed_color, - reminders.embed_description, - reminders.embed_footer, - reminders.embed_footer_url, - reminders.embed_image_url, - reminders.embed_thumbnail_url, - reminders.embed_title, - reminders.embed_fields, - reminders.enabled, - reminders.expires, - reminders.interval_seconds, - reminders.interval_days, - reminders.interval_months, - reminders.name, - reminders.restartable, - reminders.tts, - reminders.username, - reminders.utc_time + let channels = + channels.keys().into_iter().map(|k| k.get().to_string()).collect::>(); + let placeholder = vec!["?"; channels.len()].join(","); + let sql = format!( + " + SELECT + reminders.attachment, + reminders.attachment_name, + reminders.avatar, + CONCAT('#', channels.channel) AS channel, + reminders.content, + reminders.embed_author, + reminders.embed_author_url, + reminders.embed_color, + reminders.embed_description, + reminders.embed_footer, + reminders.embed_footer_url, + reminders.embed_image_url, + reminders.embed_thumbnail_url, + reminders.embed_title, + reminders.embed_fields, + reminders.enabled, + reminders.expires, + reminders.interval_seconds, + reminders.interval_days, + reminders.interval_months, + reminders.name, + reminders.restartable, + reminders.tts, + reminders.username, + reminders.utc_time FROM reminders - LEFT JOIN channels ON channels.id = reminders.channel_id - WHERE FIND_IN_SET(channels.channel, ?) AND status = 'pending'", - channels - ) - .fetch_all(pool.inner()) - .await; + LEFT JOIN channels + ON channels.id = reminders.channel_id + WHERE status = 'pending' + AND channels.channel IN ({placeholder}) + " + ); + + let result = + sqlx::query_as::(&sql).fetch_all(pool.inner()).await; match result { Ok(reminders) => { diff --git a/src/web/routes/dashboard/mod.rs b/src/web/routes/dashboard/mod.rs index 08c31e5..3aadbdd 100644 --- a/src/web/routes/dashboard/mod.rs +++ b/src/web/routes/dashboard/mod.rs @@ -17,6 +17,7 @@ use serenity::{ model::id::{ChannelId, GuildId, UserId}, }; use sqlx::types::Json; +use sqlx::FromRow; use crate::web::{ catchers::internal_server_error, @@ -177,7 +178,7 @@ pub struct CreateReminder { utc_time: NaiveDateTime, } -#[derive(Serialize)] +#[derive(Serialize, FromRow)] pub struct GetReminder { attachment_name: Option, avatar: Option, @@ -209,7 +210,7 @@ pub struct GetReminder { utc_time: NaiveDateTime, } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, FromRow)] pub struct ReminderCsv { attachment: Option, attachment_name: Option, diff --git a/systemd/reminder-rs.service b/systemd/reminder-rs.service index dc87139..5307eba 100644 --- a/systemd/reminder-rs.service +++ b/systemd/reminder-rs.service @@ -9,7 +9,7 @@ WorkingDirectory=/etc/reminder-rs Restart=always RestartSec=10 Environment="RUST_LOG=warn,rocket=info,reminder_rs=debug,postman=debug" -WatchdogSec=120 +WatchdogSec=300 [Install] WantedBy=multi-user.target