Compare commits
	
		
			11 Commits
		
	
	
		
			1.7.4
			...
			82dab53744
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 82dab53744 | |||
|  | 5f703e8538 | ||
|  | 2993505a47 | ||
|  | b225ad7e45 | ||
|  | ee89cb40c5 | ||
|  | b6b5e6d2b2 | ||
|  | adf29dca5d | ||
|  | ea3fe3f543 | ||
|  | 109cf16dbb | ||
|  | 6726ca0c2d | ||
| 38133be15d | 
							
								
								
									
										19
									
								
								migrations/20230812111348_orphan_reminders.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								migrations/20230812111348_orphan_reminders.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | |||||||
|  | -- Drop existing constraint | ||||||
|  | ALTER TABLE `reminders` DROP CONSTRAINT `reminders_ibfk_1`; | ||||||
|  |  | ||||||
|  | ALTER TABLE `reminders` MODIFY COLUMN `channel_id` INT UNSIGNED; | ||||||
|  | ALTER TABLE `reminders` ADD COLUMN `guild_id` INT UNSIGNED; | ||||||
|  |  | ||||||
|  | ALTER TABLE `reminders` | ||||||
|  |     ADD CONSTRAINT `guild_id_fk` | ||||||
|  |         FOREIGN KEY (`guild_id`) | ||||||
|  |         REFERENCES `guilds`(`id`) | ||||||
|  |         ON DELETE CASCADE; | ||||||
|  |  | ||||||
|  | ALTER TABLE `reminders` | ||||||
|  |     ADD CONSTRAINT `channel_id_fk` | ||||||
|  |         FOREIGN KEY (`channel_id`) | ||||||
|  |         REFERENCES `channels`(`id`) | ||||||
|  |         ON DELETE SET NULL; | ||||||
|  |  | ||||||
|  | UPDATE `reminders` SET `guild_id` = (SELECT guilds.`id` FROM `channels` INNER JOIN `guilds` ON channels.guild_id = guilds.id WHERE reminders.channel_id = channels.id); | ||||||
							
								
								
									
										4
									
								
								migrations/20230903131153_reminder_status_timing.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								migrations/20230903131153_reminder_status_timing.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | |||||||
|  | ALTER TABLE reminders ADD COLUMN `status_change_time` DATETIME; | ||||||
|  |  | ||||||
|  | -- This is a best-guess as to the status change time. | ||||||
|  | UPDATE reminders SET `status_change_time` = `utc_time`; | ||||||
| @@ -310,6 +310,7 @@ WHERE | |||||||
|             reminders |             reminders | ||||||
|         WHERE |         WHERE | ||||||
|             reminders.`utc_time` <= NOW() AND |             reminders.`utc_time` <= NOW() AND | ||||||
|  |             reminders.`channel_id` IS NOT NULL AND | ||||||
|             `status` = 'pending' AND |             `status` = 'pending' AND | ||||||
|             ( |             ( | ||||||
|                 reminders.`interval_seconds` IS NOT NULL |                 reminders.`interval_seconds` IS NOT NULL | ||||||
| @@ -471,10 +472,17 @@ WHERE | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     async fn set_sent(&self, pool: impl Executor<'_, Database = Database> + Copy) { |     async fn set_sent(&self, pool: impl Executor<'_, Database = Database> + Copy) { | ||||||
|         sqlx::query!("UPDATE reminders SET `status` = 'sent' WHERE `id` = ?", self.id) |         sqlx::query!( | ||||||
|             .execute(pool) |             " | ||||||
|             .await |             UPDATE reminders | ||||||
|             .expect(&format!("Could not delete Reminder {}", self.id)); |             SET `status` = 'sent', `status_change_time` = NOW() | ||||||
|  |             WHERE `id` = ? | ||||||
|  |             ", | ||||||
|  |             self.id | ||||||
|  |         ) | ||||||
|  |         .execute(pool) | ||||||
|  |         .await | ||||||
|  |         .expect(&format!("Could not delete Reminder {}", self.id)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async fn set_failed( |     async fn set_failed( | ||||||
|   | |||||||
| @@ -166,15 +166,21 @@ impl ComponentDataModel { | |||||||
|                     .await; |                     .await; | ||||||
|             } |             } | ||||||
|             ComponentDataModel::DelSelector(selector) => { |             ComponentDataModel::DelSelector(selector) => { | ||||||
|                 let selected_id = component.data.values.join(","); |                 for id in &component.data.values { | ||||||
|  |                     match id.parse::<u32>() { | ||||||
|  |                         Ok(id) => { | ||||||
|  |                             if let Some(reminder) = Reminder::from_id(&data.database, id).await { | ||||||
|  |                                 reminder.delete(&data.database).await.unwrap(); | ||||||
|  |                             } else { | ||||||
|  |                                 warn!("Attempt to delete non-existent reminder"); | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |  | ||||||
|                 sqlx::query!( |                         Err(e) => { | ||||||
|                     "UPDATE reminders SET `status` = 'deleted' WHERE FIND_IN_SET(id, ?)", |                             warn!("Error casting ID to integer: {:?}.", e); | ||||||
|                     selected_id |                         } | ||||||
|                 ) |                     } | ||||||
|                 .execute(&data.database) |                 } | ||||||
|                 .await |  | ||||||
|                 .unwrap(); |  | ||||||
|  |  | ||||||
|                 let reminders = Reminder::from_guild( |                 let reminders = Reminder::from_guild( | ||||||
|                     &ctx, |                     &ctx, | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ pub struct ChannelData { | |||||||
|     pub webhook_id: Option<u64>, |     pub webhook_id: Option<u64>, | ||||||
|     pub webhook_token: Option<String>, |     pub webhook_token: Option<String>, | ||||||
|     pub paused: bool, |     pub paused: bool, | ||||||
|  |     pub db_guild_id: Option<u32>, | ||||||
|     pub paused_until: Option<NaiveDateTime>, |     pub paused_until: Option<NaiveDateTime>, | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -22,7 +23,11 @@ impl ChannelData { | |||||||
|  |  | ||||||
|         if let Ok(c) = sqlx::query_as_unchecked!( |         if let Ok(c) = sqlx::query_as_unchecked!( | ||||||
|             Self, |             Self, | ||||||
|             "SELECT id, name, nudge, blacklisted, webhook_id, webhook_token, paused, paused_until FROM channels WHERE channel = ?", |             " | ||||||
|  |             SELECT id, name, nudge, blacklisted, webhook_id, webhook_token, paused, paused_until, | ||||||
|  |                 guild_id AS db_guild_id | ||||||
|  |             FROM channels WHERE channel = ? | ||||||
|  |             ", | ||||||
|             channel_id |             channel_id | ||||||
|         ) |         ) | ||||||
|         .fetch_one(pool) |         .fetch_one(pool) | ||||||
| @@ -30,12 +35,18 @@ impl ChannelData { | |||||||
|         { |         { | ||||||
|             Ok(c) |             Ok(c) | ||||||
|         } else { |         } else { | ||||||
|             let props = channel.to_owned().guild().map(|g| (g.guild_id.as_u64().to_owned(), g.name)); |             let props = | ||||||
|  |                 channel.to_owned().guild().map(|g| (g.guild_id.as_u64().to_owned(), g.name)); | ||||||
|  |  | ||||||
|             let (guild_id, channel_name) = if let Some((a, b)) = props { (Some(a), Some(b)) } else { (None, None) }; |             let (guild_id, channel_name) = | ||||||
|  |                 if let Some((a, b)) = props { (Some(a), Some(b)) } else { (None, None) }; | ||||||
|  |  | ||||||
|             sqlx::query!( |             sqlx::query!( | ||||||
|                 "INSERT IGNORE INTO channels (channel, name, guild_id) VALUES (?, ?, (SELECT id FROM guilds WHERE guild = ?))", |                 " | ||||||
|  |                 INSERT IGNORE INTO channels | ||||||
|  |                 (channel, name, guild_id) | ||||||
|  |                 VALUES (?, ?, (SELECT id FROM guilds WHERE guild = ?)) | ||||||
|  |                 ", | ||||||
|                 channel_id, |                 channel_id, | ||||||
|                 channel_name, |                 channel_name, | ||||||
|                 guild_id |                 guild_id | ||||||
| @@ -46,7 +57,10 @@ impl ChannelData { | |||||||
|             Ok(sqlx::query_as_unchecked!( |             Ok(sqlx::query_as_unchecked!( | ||||||
|                 Self, |                 Self, | ||||||
|                 " |                 " | ||||||
| SELECT id, name, nudge, blacklisted, webhook_id, webhook_token, paused, paused_until FROM channels WHERE channel = ? |                 SELECT id, name, nudge, blacklisted, webhook_id, webhook_token, paused, | ||||||
|  |                     paused_until, guild_id AS db_guild_id | ||||||
|  |                 FROM channels | ||||||
|  |                 WHERE channel = ? | ||||||
|                 ", |                 ", | ||||||
|                 channel_id |                 channel_id | ||||||
|             ) |             ) | ||||||
| @@ -58,9 +72,10 @@ SELECT id, name, nudge, blacklisted, webhook_id, webhook_token, paused, paused_u | |||||||
|     pub async fn commit_changes(&self, pool: &MySqlPool) { |     pub async fn commit_changes(&self, pool: &MySqlPool) { | ||||||
|         sqlx::query!( |         sqlx::query!( | ||||||
|             " |             " | ||||||
| UPDATE channels SET name = ?, nudge = ?, blacklisted = ?, webhook_id = ?, webhook_token = ?, paused = ?, paused_until \ |             UPDATE channels | ||||||
|              = ? WHERE id = ? |             SET name = ?, nudge = ?, blacklisted = ?, webhook_id = ?, webhook_token = ?, | ||||||
|             ", |                 paused = ?, paused_until = ? | ||||||
|  |             WHERE id = ?", | ||||||
|             self.name, |             self.name, | ||||||
|             self.nudge, |             self.nudge, | ||||||
|             self.blacklisted, |             self.blacklisted, | ||||||
|   | |||||||
| @@ -51,6 +51,7 @@ pub struct ReminderBuilder { | |||||||
|     pool: MySqlPool, |     pool: MySqlPool, | ||||||
|     uid: String, |     uid: String, | ||||||
|     channel: u32, |     channel: u32, | ||||||
|  |     guild: Option<u32>, | ||||||
|     thread_id: Option<u64>, |     thread_id: Option<u64>, | ||||||
|     utc_time: NaiveDateTime, |     utc_time: NaiveDateTime, | ||||||
|     timezone: String, |     timezone: String, | ||||||
| @@ -86,6 +87,7 @@ impl ReminderBuilder { | |||||||
| INSERT INTO reminders ( | INSERT INTO reminders ( | ||||||
|     `uid`, |     `uid`, | ||||||
|     `channel_id`, |     `channel_id`, | ||||||
|  |     `guild_id`, | ||||||
|     `utc_time`, |     `utc_time`, | ||||||
|     `timezone`, |     `timezone`, | ||||||
|     `interval_seconds`, |     `interval_seconds`, | ||||||
| @@ -110,11 +112,13 @@ INSERT INTO reminders ( | |||||||
|     ?, |     ?, | ||||||
|     ?, |     ?, | ||||||
|     ?, |     ?, | ||||||
|  |     ?, | ||||||
|     ? |     ? | ||||||
| ) | ) | ||||||
|             ", |             ", | ||||||
|                         self.uid, |                         self.uid, | ||||||
|                         self.channel, |                         self.channel, | ||||||
|  |                         self.guild, | ||||||
|                         utc_time, |                         utc_time, | ||||||
|                         self.timezone, |                         self.timezone, | ||||||
|                         self.interval_seconds, |                         self.interval_seconds, | ||||||
| @@ -247,10 +251,10 @@ impl<'a> MultiReminderBuilder<'a> { | |||||||
|                                 { |                                 { | ||||||
|                                     Err(ReminderError::UserBlockedDm) |                                     Err(ReminderError::UserBlockedDm) | ||||||
|                                 } else { |                                 } else { | ||||||
|                                     Ok(user_data.dm_channel) |                                     Ok((user_data.dm_channel, None)) | ||||||
|                                 } |                                 } | ||||||
|                             } else { |                             } else { | ||||||
|                                 Ok(user_data.dm_channel) |                                 Ok((user_data.dm_channel, None)) | ||||||
|                             } |                             } | ||||||
|                         } else { |                         } else { | ||||||
|                             Err(ReminderError::InvalidTag) |                             Err(ReminderError::InvalidTag) | ||||||
| @@ -297,13 +301,13 @@ impl<'a> MultiReminderBuilder<'a> { | |||||||
|                                                 .commit_changes(&self.ctx.data().database) |                                                 .commit_changes(&self.ctx.data().database) | ||||||
|                                                 .await; |                                                 .await; | ||||||
|  |  | ||||||
|                                             Ok(channel_data.id) |                                             Ok((channel_data.id, channel_data.db_guild_id)) | ||||||
|                                         } |                                         } | ||||||
|  |  | ||||||
|                                         Err(e) => Err(ReminderError::DiscordError(e.to_string())), |                                         Err(e) => Err(ReminderError::DiscordError(e.to_string())), | ||||||
|                                     } |                                     } | ||||||
|                                 } else { |                                 } else { | ||||||
|                                     Ok(channel_data.id) |                                     Ok((channel_data.id, channel_data.db_guild_id)) | ||||||
|                                 } |                                 } | ||||||
|                             } |                             } | ||||||
|                         } else { |                         } else { | ||||||
| @@ -317,7 +321,8 @@ impl<'a> MultiReminderBuilder<'a> { | |||||||
|                         let builder = ReminderBuilder { |                         let builder = ReminderBuilder { | ||||||
|                             pool: self.ctx.data().database.clone(), |                             pool: self.ctx.data().database.clone(), | ||||||
|                             uid: generate_uid(), |                             uid: generate_uid(), | ||||||
|                             channel: c, |                             channel: c.0, | ||||||
|  |                             guild: c.1, | ||||||
|                             thread_id, |                             thread_id, | ||||||
|                             utc_time: self.utc_time, |                             utc_time: self.utc_time, | ||||||
|                             timezone: self.timezone.to_string(), |                             timezone: self.timezone.to_string(), | ||||||
|   | |||||||
| @@ -304,10 +304,13 @@ WHERE | |||||||
|         &self, |         &self, | ||||||
|         db: impl Executor<'_, Database = Database>, |         db: impl Executor<'_, Database = Database>, | ||||||
|     ) -> Result<(), sqlx::Error> { |     ) -> Result<(), sqlx::Error> { | ||||||
|         sqlx::query!("UPDATE reminders SET `status` = 'deleted' WHERE uid = ?", self.uid) |         sqlx::query!( | ||||||
|             .execute(db) |             "UPDATE reminders SET `status` = 'deleted', `status_change_time` = NOW() WHERE uid = ?", | ||||||
|             .await |             self.uid | ||||||
|             .map(|_| ()) |         ) | ||||||
|  |         .execute(db) | ||||||
|  |         .await | ||||||
|  |         .map(|_| ()) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn display_content(&self) -> &str { |     pub fn display_content(&self) -> &str { | ||||||
|   | |||||||
| @@ -150,7 +150,8 @@ pub async fn initialize( | |||||||
|         .mount( |         .mount( | ||||||
|             "/dashboard", |             "/dashboard", | ||||||
|             routes![ |             routes![ | ||||||
|                 routes::dashboard::dashboard, |                 routes::dashboard::dashboard_1, | ||||||
|  |                 routes::dashboard::dashboard_2, | ||||||
|                 routes::dashboard::dashboard_home, |                 routes::dashboard::dashboard_home, | ||||||
|                 routes::dashboard::user::get_user_info, |                 routes::dashboard::user::get_user_info, | ||||||
|                 routes::dashboard::user::update_user_info, |                 routes::dashboard::user::update_user_info, | ||||||
|   | |||||||
| @@ -145,7 +145,7 @@ pub async fn import_reminders( | |||||||
|                                     attachment: record.attachment, |                                     attachment: record.attachment, | ||||||
|                                     attachment_name: record.attachment_name, |                                     attachment_name: record.attachment_name, | ||||||
|                                     avatar: record.avatar, |                                     avatar: record.avatar, | ||||||
|                                     channel: channel_id, |                                     channel: Some(channel_id), | ||||||
|                                     content: record.content, |                                     content: record.content, | ||||||
|                                     embed_author: record.embed_author, |                                     embed_author: record.embed_author, | ||||||
|                                     embed_author_url: record.embed_author_url, |                                     embed_author_url: record.embed_author_url, | ||||||
| @@ -171,6 +171,8 @@ pub async fn import_reminders( | |||||||
|                                     uid: generate_uid(), |                                     uid: generate_uid(), | ||||||
|                                     username: record.username, |                                     username: record.username, | ||||||
|                                     utc_time: record.utc_time, |                                     utc_time: record.utc_time, | ||||||
|  |                                     status: "pending".to_string(), | ||||||
|  |                                     status_change_time: None, | ||||||
|                                 }; |                                 }; | ||||||
|  |  | ||||||
|                                 create_reminder( |                                 create_reminder( | ||||||
|   | |||||||
| @@ -318,76 +318,64 @@ pub async fn create_guild_reminder( | |||||||
|     .await |     .await | ||||||
| } | } | ||||||
|  |  | ||||||
| #[get("/api/guild/<id>/reminders")] | #[get("/api/guild/<id>/reminders?<status>")] | ||||||
| pub async fn get_reminders( | pub async fn get_reminders( | ||||||
|     id: u64, |     id: u64, | ||||||
|     cookies: &CookieJar<'_>, |     cookies: &CookieJar<'_>, | ||||||
|     ctx: &State<Context>, |  | ||||||
|     serenity_context: &State<Context>, |     serenity_context: &State<Context>, | ||||||
|     pool: &State<Pool<MySql>>, |     pool: &State<Pool<MySql>>, | ||||||
|  |     status: Option<String>, | ||||||
| ) -> JsonResult { | ) -> JsonResult { | ||||||
|     check_authorization!(cookies, serenity_context.inner(), id); |     check_authorization!(cookies, serenity_context.inner(), id); | ||||||
|  |  | ||||||
|     let channels_res = GuildId(id).channels(&ctx.inner()).await; |     let status = status.unwrap_or("pending".to_string()); | ||||||
|  |  | ||||||
|     match channels_res { |     sqlx::query_as_unchecked!( | ||||||
|         Ok(channels) => { |         Reminder, | ||||||
|             let channels = channels |         " | ||||||
|                 .keys() |         SELECT | ||||||
|                 .into_iter() |          reminders.attachment, | ||||||
|                 .map(|k| k.as_u64().to_string()) |          reminders.attachment_name, | ||||||
|                 .collect::<Vec<String>>() |          reminders.avatar, | ||||||
|                 .join(","); |          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.status, | ||||||
|  |          reminders.status_change_time | ||||||
|  |         FROM reminders | ||||||
|  |         LEFT JOIN channels ON channels.id = reminders.channel_id | ||||||
|  |         WHERE FIND_IN_SET(`status`, ?) AND reminders.guild_id = (SELECT id FROM guilds WHERE guild = ?)", | ||||||
|  |         status, | ||||||
|  |         id | ||||||
|  |     ) | ||||||
|  |     .fetch_all(pool.inner()) | ||||||
|  |     .await | ||||||
|  |     .map(|r| Ok(json!(r))) | ||||||
|  |     .unwrap_or_else(|e| { | ||||||
|  |         warn!("Failed to complete SQL query: {:?}", e); | ||||||
|  |  | ||||||
|             sqlx::query_as_unchecked!( |         json_err!("Could not load reminders") | ||||||
|                 Reminder, |     }) | ||||||
|                 "SELECT |  | ||||||
|                  reminders.attachment, |  | ||||||
|                  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 |  | ||||||
|                 LEFT 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); |  | ||||||
|  |  | ||||||
|                 json_err!("Could not load reminders") |  | ||||||
|             }) |  | ||||||
|         } |  | ||||||
|         Err(e) => { |  | ||||||
|             warn!("Could not fetch channels from {}: {:?}", id, e); |  | ||||||
|  |  | ||||||
|             Ok(json!([])) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  |  | ||||||
| #[patch("/api/guild/<id>/reminders", data = "<reminder>")] | #[patch("/api/guild/<id>/reminders", data = "<reminder>")] | ||||||
| @@ -586,7 +574,9 @@ pub async fn edit_reminder( | |||||||
|          reminders.tts, |          reminders.tts, | ||||||
|          reminders.uid, |          reminders.uid, | ||||||
|          reminders.username, |          reminders.username, | ||||||
|          reminders.utc_time |          reminders.utc_time, | ||||||
|  |          reminders.status, | ||||||
|  |          reminders.status_change_time | ||||||
|         FROM reminders |         FROM reminders | ||||||
|         LEFT JOIN channels ON channels.id = reminders.channel_id |         LEFT JOIN channels ON channels.id = reminders.channel_id | ||||||
|         WHERE uid = ?", |         WHERE uid = ?", | ||||||
| @@ -610,9 +600,12 @@ pub async fn delete_reminder( | |||||||
|     reminder: Json<DeleteReminder>, |     reminder: Json<DeleteReminder>, | ||||||
|     pool: &State<Pool<MySql>>, |     pool: &State<Pool<MySql>>, | ||||||
| ) -> JsonResult { | ) -> JsonResult { | ||||||
|     match sqlx::query!("UPDATE reminders SET `status` = 'deleted' WHERE uid = ?", reminder.uid) |     match sqlx::query!( | ||||||
|         .execute(pool.inner()) |         "UPDATE reminders SET `status` = 'deleted', `status_change_time` = NOW() WHERE uid = ?", | ||||||
|         .await |         reminder.uid | ||||||
|  |     ) | ||||||
|  |     .execute(pool.inner()) | ||||||
|  |     .await | ||||||
|     { |     { | ||||||
|         Ok(_) => Ok(json!({})), |         Ok(_) => Ok(json!({})), | ||||||
|  |  | ||||||
|   | |||||||
| @@ -124,8 +124,8 @@ pub struct Reminder { | |||||||
|     attachment: Option<Vec<u8>>, |     attachment: Option<Vec<u8>>, | ||||||
|     attachment_name: Option<String>, |     attachment_name: Option<String>, | ||||||
|     avatar: Option<String>, |     avatar: Option<String>, | ||||||
|     #[serde(with = "string")] |     #[serde(with = "string_opt")] | ||||||
|     channel: u64, |     channel: Option<u64>, | ||||||
|     content: String, |     content: String, | ||||||
|     embed_author: String, |     embed_author: String, | ||||||
|     embed_author_url: Option<String>, |     embed_author_url: Option<String>, | ||||||
| @@ -150,6 +150,8 @@ pub struct Reminder { | |||||||
|     uid: String, |     uid: String, | ||||||
|     username: Option<String>, |     username: Option<String>, | ||||||
|     utc_time: NaiveDateTime, |     utc_time: NaiveDateTime, | ||||||
|  |     status: String, | ||||||
|  |     status_change_time: Option<NaiveDateTime>, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Serialize, Deserialize)] | #[derive(Serialize, Deserialize)] | ||||||
| @@ -308,6 +310,34 @@ mod string { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | mod string_opt { | ||||||
|  |     use std::{fmt::Display, str::FromStr}; | ||||||
|  |  | ||||||
|  |     use serde::{de, Deserialize, Deserializer, Serializer}; | ||||||
|  |  | ||||||
|  |     pub fn serialize<T, S>(value: &Option<T>, serializer: S) -> Result<S::Ok, S::Error> | ||||||
|  |     where | ||||||
|  |         T: Display, | ||||||
|  |         S: Serializer, | ||||||
|  |     { | ||||||
|  |         match value { | ||||||
|  |             Some(value) => serializer.collect_str(value), | ||||||
|  |             None => serializer.serialize_none(), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn deserialize<'de, T, D>(deserializer: D) -> Result<Option<T>, D::Error> | ||||||
|  |     where | ||||||
|  |         T: FromStr, | ||||||
|  |         T::Err: Display, | ||||||
|  |         D: Deserializer<'de>, | ||||||
|  |     { | ||||||
|  |         Option::deserialize(deserializer)? | ||||||
|  |             .map(|d: String| d.parse().map_err(de::Error::custom)) | ||||||
|  |             .transpose() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| mod base64s { | mod base64s { | ||||||
|     use serde::{de, Deserialize, Deserializer, Serializer}; |     use serde::{de, Deserialize, Deserializer, Serializer}; | ||||||
|  |  | ||||||
| @@ -372,7 +402,7 @@ pub async fn create_reminder( | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     // validate channel |     // validate channel | ||||||
|     let channel = ChannelId(reminder.channel).to_channel_cached(&ctx); |     let channel = reminder.channel.map(|c| ChannelId(c).to_channel_cached(&ctx)).flatten(); | ||||||
|     let channel_exists = channel.is_some(); |     let channel_exists = channel.is_some(); | ||||||
|  |  | ||||||
|     let channel_matches_guild = |     let channel_matches_guild = | ||||||
| @@ -380,14 +410,14 @@ pub async fn create_reminder( | |||||||
|  |  | ||||||
|     if !channel_matches_guild || !channel_exists { |     if !channel_matches_guild || !channel_exists { | ||||||
|         warn!( |         warn!( | ||||||
|             "Error in `create_reminder`: channel {} not found for guild {} (channel exists: {})", |             "Error in `create_reminder`: channel {:?} not found for guild {} (channel exists: {})", | ||||||
|             reminder.channel, guild_id, channel_exists |             reminder.channel, guild_id, channel_exists | ||||||
|         ); |         ); | ||||||
|  |  | ||||||
|         return Err(json!({"error": "Channel not found"})); |         return Err(json!({"error": "Channel not found"})); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     let channel = create_database_channel(&ctx, ChannelId(reminder.channel), pool).await; |     let channel = create_database_channel(&ctx, ChannelId(reminder.channel.unwrap()), pool).await; | ||||||
|  |  | ||||||
|     if let Err(e) = channel { |     if let Err(e) = channel { | ||||||
|         warn!("`create_database_channel` returned an error code: {:?}", e); |         warn!("`create_database_channel` returned an error code: {:?}", e); | ||||||
| @@ -479,6 +509,7 @@ pub async fn create_reminder( | |||||||
|          attachment, |          attachment, | ||||||
|          attachment_name, |          attachment_name, | ||||||
|          channel_id, |          channel_id, | ||||||
|  |          guild_id, | ||||||
|          avatar, |          avatar, | ||||||
|          content, |          content, | ||||||
|          embed_author, |          embed_author, | ||||||
| @@ -501,11 +532,12 @@ pub async fn create_reminder( | |||||||
|          tts, |          tts, | ||||||
|          username, |          username, | ||||||
|          `utc_time` |          `utc_time` | ||||||
|         ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", |         ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", | ||||||
|         new_uid, |         new_uid, | ||||||
|         attachment_data, |         attachment_data, | ||||||
|         reminder.attachment_name, |         reminder.attachment_name, | ||||||
|         channel, |         channel, | ||||||
|  |         guild_id.0, | ||||||
|         reminder.avatar, |         reminder.avatar, | ||||||
|         reminder.content, |         reminder.content, | ||||||
|         reminder.embed_author, |         reminder.embed_author, | ||||||
| @@ -560,7 +592,9 @@ pub async fn create_reminder( | |||||||
|              reminders.tts, |              reminders.tts, | ||||||
|              reminders.uid, |              reminders.uid, | ||||||
|              reminders.username, |              reminders.username, | ||||||
|              reminders.utc_time |              reminders.utc_time, | ||||||
|  |              reminders.status, | ||||||
|  |              reminders.status_change_time | ||||||
|             FROM reminders |             FROM reminders | ||||||
|             LEFT JOIN channels ON channels.id = reminders.channel_id |             LEFT JOIN channels ON channels.id = reminders.channel_id | ||||||
|             WHERE uid = ?", |             WHERE uid = ?", | ||||||
| @@ -662,7 +696,17 @@ pub async fn dashboard_home(cookies: &CookieJar<'_>) -> Result<Template, Redirec | |||||||
| } | } | ||||||
|  |  | ||||||
| #[get("/<_>")] | #[get("/<_>")] | ||||||
| pub async fn dashboard(cookies: &CookieJar<'_>) -> Result<Template, Redirect> { | pub async fn dashboard_1(cookies: &CookieJar<'_>) -> Result<Template, Redirect> { | ||||||
|  |     if cookies.get_private("userid").is_some() { | ||||||
|  |         let map: HashMap<&str, String> = HashMap::new(); | ||||||
|  |         Ok(Template::render("dashboard", &map)) | ||||||
|  |     } else { | ||||||
|  |         Err(Redirect::to("/login/discord")) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[get("/<_>/<_>")] | ||||||
|  | pub async fn dashboard_2(cookies: &CookieJar<'_>) -> Result<Template, Redirect> { | ||||||
|     if cookies.get_private("userid").is_some() { |     if cookies.get_private("userid").is_some() { | ||||||
|         let map: HashMap<&str, String> = HashMap::new(); |         let map: HashMap<&str, String> = HashMap::new(); | ||||||
|         Ok(Template::render("dashboard", &map)) |         Ok(Template::render("dashboard", &map)) | ||||||
|   | |||||||
| @@ -291,10 +291,19 @@ div.dashboard-sidebar:not(.mobile-sidebar) { | |||||||
|     flex-direction: column; |     flex-direction: column; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | ul.guildList { | ||||||
|  |     flex-grow: 1; | ||||||
|  |     flex-shrink: 1; | ||||||
|  |     overflow: scroll; | ||||||
|  | } | ||||||
|  |  | ||||||
| div.dashboard-sidebar:not(.mobile-sidebar) .aside-footer { | div.dashboard-sidebar:not(.mobile-sidebar) .aside-footer { | ||||||
|     position: fixed; |     flex-shrink: 0; | ||||||
|     bottom: 0; |     flex-grow: 0; | ||||||
|     width: 226px; | } | ||||||
|  |  | ||||||
|  | div.dashboard-sidebar svg { | ||||||
|  |     flex-shrink: 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| div.mobile-sidebar { | div.mobile-sidebar { | ||||||
| @@ -679,6 +688,76 @@ li.highlight { | |||||||
|  |  | ||||||
| /* END */ | /* END */ | ||||||
|  |  | ||||||
|  | div.reminderError { | ||||||
|  |     margin: 10px; | ||||||
|  |     padding: 14px; | ||||||
|  |     background-color: #f5f5f5; | ||||||
|  |     border-radius: 8px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | div.reminderError .errorHead { | ||||||
|  |     display: flex; | ||||||
|  |     flex-direction: row; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | div.reminderError .errorIcon { | ||||||
|  |     padding: 8px; | ||||||
|  |     border-radius: 4px; | ||||||
|  |     margin-right: 12px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | div.reminderError .errorIcon .fas { | ||||||
|  |     display: none | ||||||
|  | } | ||||||
|  |  | ||||||
|  | div.reminderError[data-case="deleted"] .errorIcon { | ||||||
|  |     background-color: #e7e5e4; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | div.reminderError[data-case="failed"] .errorIcon { | ||||||
|  |     background-color: #fecaca; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | div.reminderError[data-case="sent"] .errorIcon { | ||||||
|  |     background-color: #d9f99d; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | div.reminderError[data-case="deleted"] .errorIcon .fas.fa-trash { | ||||||
|  |     display: block; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | div.reminderError[data-case="failed"] .errorIcon .fas.fa-exclamation-triangle { | ||||||
|  |     display: block; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | div.reminderError[data-case="sent"] .errorIcon .fas.fa-check { | ||||||
|  |     display: block; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | div.reminderError .errorHead .reminderName { | ||||||
|  |     font-size: 1rem; | ||||||
|  |     display: flex; | ||||||
|  |     flex-direction: column; | ||||||
|  |     justify-content: center; | ||||||
|  |     color: rgb(54, 54, 54); | ||||||
|  |     flex-grow: 1; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | div.reminderError .errorHead .reminderTime { | ||||||
|  |     font-size: 1rem; | ||||||
|  |     display: flex; | ||||||
|  |     flex-direction: column; | ||||||
|  |     flex-shrink: 1; | ||||||
|  |     justify-content: center; | ||||||
|  |     color: rgb(54, 54, 54); | ||||||
|  |     background-color: #ffffff; | ||||||
|  |     padding: 8px; | ||||||
|  |     border-radius: 4px; | ||||||
|  |     border-color: #e5e5e5; | ||||||
|  |     border-width: 1px; | ||||||
|  |     border-style: solid; | ||||||
|  | } | ||||||
|  |  | ||||||
| /* other stuff */ | /* other stuff */ | ||||||
|  |  | ||||||
| .half-rem { | .half-rem { | ||||||
| @@ -716,6 +795,18 @@ a.switch-pane { | |||||||
|     text-overflow: ellipsis; |     text-overflow: ellipsis; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | .guild-submenu { | ||||||
|  |     display: none; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .guild-submenu li { | ||||||
|  |     font-size: 0.8rem; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | a.switch-pane.is-active ~ .guild-submenu { | ||||||
|  |     display: block; | ||||||
|  | } | ||||||
|  |  | ||||||
| .feedback { | .feedback { | ||||||
|     background-color: #5865F2; |     background-color: #5865F2; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -18,6 +18,7 @@ const $downloader = document.querySelector("a#downloader"); | |||||||
| const $uploader = document.querySelector("input#uploader"); | const $uploader = document.querySelector("input#uploader"); | ||||||
|  |  | ||||||
| let channels = []; | let channels = []; | ||||||
|  | let reminderErrors = []; | ||||||
| let guildNames = {}; | let guildNames = {}; | ||||||
| let roles = []; | let roles = []; | ||||||
| let templates = {}; | let templates = {}; | ||||||
| @@ -33,7 +34,11 @@ let globalPatreon = false; | |||||||
| let guildPatreon = false; | let guildPatreon = false; | ||||||
|  |  | ||||||
| function guildId() { | function guildId() { | ||||||
|     return document.querySelector(".guildList a.is-active").dataset["guild"]; |     return document.querySelector("li > a.is-active").parentElement.dataset["guild"]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function guildName() { | ||||||
|  |     return guildNames[guildId()]; | ||||||
| } | } | ||||||
|  |  | ||||||
| function colorToInt(r, g, b) { | function colorToInt(r, g, b) { | ||||||
| @@ -52,7 +57,7 @@ function switch_pane(selector) { | |||||||
|         el.classList.add("is-hidden"); |         el.classList.add("is-hidden"); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     document.getElementById(selector).classList.remove("is-hidden"); |     document.querySelector(`*[data-name=${selector}]`).classList.remove("is-hidden"); | ||||||
| } | } | ||||||
|  |  | ||||||
| function update_select(sel) { | function update_select(sel) { | ||||||
| @@ -449,21 +454,19 @@ document.addEventListener("guildSwitched", async (e) => { | |||||||
|         .querySelectorAll(".patreon-only") |         .querySelectorAll(".patreon-only") | ||||||
|         .forEach((el) => el.classList.add("is-locked")); |         .forEach((el) => el.classList.add("is-locked")); | ||||||
|  |  | ||||||
|     let $anchor = document.querySelector( |     let $li = document.querySelector(`li[data-guild="${e.detail.guild_id}"]`); | ||||||
|         `.switch-pane[data-guild="${e.detail.guild_id}"]` |  | ||||||
|     ); |  | ||||||
|  |  | ||||||
|     let hasError = false; |     if ($li === null) { | ||||||
|  |  | ||||||
|     if ($anchor === null) { |  | ||||||
|         switch_pane("user-error"); |         switch_pane("user-error"); | ||||||
|         hasError = true; |  | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     switch_pane($anchor.dataset["pane"]); |     switch_pane(e.detail.pane); | ||||||
|     reset_guild_pane(); |     reset_guild_pane(); | ||||||
|     $anchor.classList.add("is-active"); |     $li.querySelector("li > a").classList.add("is-active"); | ||||||
|  |     $li.querySelectorAll(`*[data-pane="${e.detail.pane}"]`).forEach((el) => { | ||||||
|  |         el.classList.add("is-active"); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|     if (globalPatreon || (await fetch_patreon(e.detail.guild_id))) { |     if (globalPatreon || (await fetch_patreon(e.detail.guild_id))) { | ||||||
|         document |         document | ||||||
| @@ -471,15 +474,26 @@ document.addEventListener("guildSwitched", async (e) => { | |||||||
|             .forEach((el) => el.classList.remove("is-locked")); |             .forEach((el) => el.classList.remove("is-locked")); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     hasError = await fetch_channels(e.detail.guild_id); |     const event = new CustomEvent("paneLoad", { | ||||||
|  |         detail: { | ||||||
|  |             guild_id: e.detail.guild_id, | ||||||
|  |             pane: e.detail.pane, | ||||||
|  |         }, | ||||||
|  |     }); | ||||||
|  |     document.dispatchEvent(event); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | document.addEventListener("paneLoad", async (ev) => { | ||||||
|  |     const hasError = await fetch_channels(ev.detail.guild_id); | ||||||
|     if (!hasError) { |     if (!hasError) { | ||||||
|         fetch_roles(e.detail.guild_id); |         fetch_roles(ev.detail.guild_id); | ||||||
|         fetch_templates(e.detail.guild_id); |         fetch_templates(ev.detail.guild_id); | ||||||
|         fetch_reminders(e.detail.guild_id); |         fetch_reminders(ev.detail.guild_id); | ||||||
|  |  | ||||||
|         document.querySelectorAll("p.pageTitle").forEach((el) => { |         document.querySelectorAll("p.pageTitle").forEach((el) => { | ||||||
|             el.textContent = `${e.detail.guild_name} Reminders`; |             el.textContent = `${guildName()} Reminders`; | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         document.querySelectorAll("select.channel-selector").forEach((el) => { |         document.querySelectorAll("select.channel-selector").forEach((el) => { | ||||||
|             el.addEventListener("change", (e) => { |             el.addEventListener("change", (e) => { | ||||||
|                 update_select(e.target); |                 update_select(e.target); | ||||||
| @@ -684,36 +698,56 @@ document.addEventListener("DOMContentLoaded", async () => { | |||||||
|                             "%guildname%", |                             "%guildname%", | ||||||
|                             guild.name |                             guild.name | ||||||
|                         ); |                         ); | ||||||
|                         $anchor.dataset["guild"] = guild.id; |  | ||||||
|                         $anchor.dataset["name"] = guild.name; |                         $anchor.dataset["name"] = guild.name; | ||||||
|                         $anchor.href = `/dashboard/${guild.id}?name=${guild.name}`; |                         $anchor.href = `/dashboard/${guild.id}/reminders`; | ||||||
|  |  | ||||||
|                         $anchor.addEventListener("click", async (e) => { |                         const $li = $anchor.parentElement; | ||||||
|                             e.preventDefault(); |                         $li.dataset["guild"] = guild.id; | ||||||
|                             window.history.pushState({}, "", `/dashboard/${guild.id}`); |  | ||||||
|                             const event = new CustomEvent("guildSwitched", { |                         $li.querySelectorAll("a").forEach((el) => { | ||||||
|                                 detail: { |                             el.addEventListener("click", (e) => { | ||||||
|                                     guild_name: guild.name, |                                 const pane = el.dataset["pane"]; | ||||||
|                                     guild_id: guild.id, |                                 const slug = el.dataset["slug"]; | ||||||
|                                 }, |  | ||||||
|  |                                 if (pane !== undefined && slug !== undefined) { | ||||||
|  |                                     e.preventDefault(); | ||||||
|  |  | ||||||
|  |                                     switch_pane(pane); | ||||||
|  |  | ||||||
|  |                                     window.history.pushState( | ||||||
|  |                                         {}, | ||||||
|  |                                         "", | ||||||
|  |                                         `/dashboard/${guild.id}/${slug}` | ||||||
|  |                                     ); | ||||||
|  |                                     const event = new CustomEvent("guildSwitched", { | ||||||
|  |                                         detail: { | ||||||
|  |                                             guild_id: guild.id, | ||||||
|  |                                             pane, | ||||||
|  |                                         }, | ||||||
|  |                                     }); | ||||||
|  |  | ||||||
|  |                                     document.dispatchEvent(event); | ||||||
|  |                                 } | ||||||
|                             }); |                             }); | ||||||
|  |  | ||||||
|                             document.dispatchEvent(event); |  | ||||||
|                         }); |                         }); | ||||||
|  |  | ||||||
|                         element.append($clone); |                         element.append($clone); | ||||||
|                     }); |                     }); | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 const matches = window.location.href.match(/dashboard\/(\d+)/); |                 const matches = window.location.href.match( | ||||||
|  |                     /dashboard\/(\d+)(\/)?([a-zA-Z\-]+)?/ | ||||||
|  |                 ); | ||||||
|                 if (matches) { |                 if (matches) { | ||||||
|                     let id = matches[1]; |                     let id = matches[1]; | ||||||
|  |                     let kind = matches[3]; | ||||||
|                     let name = guildNames[id]; |                     let name = guildNames[id]; | ||||||
|  |  | ||||||
|                     const event = new CustomEvent("guildSwitched", { |                     const event = new CustomEvent("guildSwitched", { | ||||||
|                         detail: { |                         detail: { | ||||||
|                             guild_name: name, |                             guild_name: name, | ||||||
|                             guild_id: id, |                             guild_id: id, | ||||||
|  |                             pane: kind, | ||||||
|                         }, |                         }, | ||||||
|                     }); |                     }); | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										43
									
								
								web/static/js/reminder_errors.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								web/static/js/reminder_errors.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | |||||||
|  | function loadErrors() { | ||||||
|  |     return fetch( | ||||||
|  |         `/dashboard/api/guild/${guildId()}/reminders?status=deleted,sent,failed` | ||||||
|  |     ).then((response) => response.json()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | document.addEventListener("paneLoad", (ev) => { | ||||||
|  |     if (ev.detail.pane !== "errors") { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     document.querySelectorAll(".reminderError").forEach((el) => el.remove()); | ||||||
|  |  | ||||||
|  |     const template = document.getElementById("reminderError"); | ||||||
|  |     const container = document.getElementById("reminderLog"); | ||||||
|  |  | ||||||
|  |     loadErrors() | ||||||
|  |         .then((res) => { | ||||||
|  |             res = res | ||||||
|  |                 .filter((r) => r.status_change_time !== null) | ||||||
|  |                 .sort((a, b) => a.status_change_time < b.status_change_time); | ||||||
|  |  | ||||||
|  |             for (const reminder of res) { | ||||||
|  |                 const newRow = template.content.cloneNode(true); | ||||||
|  |  | ||||||
|  |                 newRow.querySelector(".reminderError").dataset["case"] = reminder.status; | ||||||
|  |  | ||||||
|  |                 const statusTime = new luxon.DateTime.fromISO( | ||||||
|  |                     reminder.status_change_time, | ||||||
|  |                     { zone: "UTC" } | ||||||
|  |                 ); | ||||||
|  |                 newRow.querySelector(".reminderName").textContent = reminder.name; | ||||||
|  |                 newRow.querySelector(".reminderTime").textContent = statusTime | ||||||
|  |                     .toLocal() | ||||||
|  |                     .toLocaleString(luxon.DateTime.DATETIME_MED); | ||||||
|  |  | ||||||
|  |                 container.appendChild(newRow); | ||||||
|  |             } | ||||||
|  |         }) | ||||||
|  |         .finally(() => { | ||||||
|  |             $loader.classList.add("is-hidden"); | ||||||
|  |         }); | ||||||
|  | }); | ||||||
| @@ -1,19 +0,0 @@ | |||||||
| let _reminderErrors = []; |  | ||||||
|  |  | ||||||
| const reminderErrors = () => { |  | ||||||
|     return _reminderErrors; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| const guildId = () => { |  | ||||||
|     let selected: HTMLElement = document.querySelector(".guildList a.is-active"); |  | ||||||
|     return selected.dataset["guild"]; |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| function loadErrors() { |  | ||||||
|     fetch(`/dashboard/api/guild/${guildId()}/errors`).then(response => response.json()) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| document.addEventListener('DOMContentLoaded', () => { |  | ||||||
|  |  | ||||||
| }) |  | ||||||
| @@ -332,16 +332,16 @@ | |||||||
|                 <p class="subtitle is-hidden-desktop">Press the <span class="icon"><i class="fal fa-bars"></i></span> to get started</p> |                 <p class="subtitle is-hidden-desktop">Press the <span class="icon"><i class="fal fa-bars"></i></span> to get started</p> | ||||||
|             </div> |             </div> | ||||||
|         </section> |         </section> | ||||||
|         <section id="guild" class="is-hidden"> |         <section data-name="reminders" class="is-hidden"> | ||||||
|             {% include "reminder_dashboard/reminder_dashboard" %} |             {% include "reminder_dashboard/reminder_dashboard" %} | ||||||
|         </section> |         </section> | ||||||
|         <section id="reminder-errors" class="is-hidden"> |         <section data-name="errors" class="is-hidden"> | ||||||
|             {% include "reminder_dashboard/reminder_errors" %} |             {% include "reminder_dashboard/reminder_errors" %} | ||||||
|         </section> |         </section> | ||||||
|         <section id="guild-error" class="is-hidden"> |         <section data-name="guild-error" class="is-hidden"> | ||||||
|             {% include "reminder_dashboard/guild_error" %} |             {% include "reminder_dashboard/guild_error" %} | ||||||
|         </section> |         </section> | ||||||
|         <section id="user-error" class="is-hidden"> |         <section data-name="user-error" class="is-hidden"> | ||||||
|             {% include "reminder_dashboard/user_error" %} |             {% include "reminder_dashboard/user_error" %} | ||||||
|         </section> |         </section> | ||||||
|     </div> |     </div> | ||||||
| @@ -375,14 +375,28 @@ | |||||||
|  |  | ||||||
| <template id="guildListEntry"> | <template id="guildListEntry"> | ||||||
|     <li> |     <li> | ||||||
|         <a class="switch-pane" data-pane="guild"> |         <a class="switch-pane" data-pane="reminders" data-slug="reminders"> | ||||||
|             <span class="icon"><i class="fas fa-map-pin"></i></span> <span class="guild-name">%guildname%</span> |             <span class="icon"><i class="fas fa-map-pin"></i></span> <span class="guild-name">%guildname%</span> | ||||||
|         </a> |         </a> | ||||||
|  |         <ul class="guild-submenu"> | ||||||
|  |             <li> | ||||||
|  |                 <a class="switch-pane" data-pane="reminders" data-slug="reminders"> | ||||||
|  |                     <span class="icon"><i class="fas fa-calendar-alt"></i></span> Reminders | ||||||
|  |                 </a> | ||||||
|  |                 <a class="switch-pane" data-pane="errors" data-slug="errors"> | ||||||
|  |                     <span class="icon"><i class="fas fa-file-alt"></i></span> Logs | ||||||
|  |                 </a> | ||||||
|  |             </li> | ||||||
|  |         </ul> | ||||||
|     </li> |     </li> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <template id="guildReminder"> | <template id="guildReminder"> | ||||||
|     {% include "reminder_dashboard/guild_reminder" %} |     {% include "reminder_dashboard/templates/guild_reminder" %} | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <template id="reminderError"> | ||||||
|  |     {% include "reminder_dashboard/templates/reminder_error" %} | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script src="/static/js/iro.js"></script> | <script src="/static/js/iro.js"></script> | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ | |||||||
|     <strong>Create Reminder</strong> |     <strong>Create Reminder</strong> | ||||||
|     <div id="reminderCreator"> |     <div id="reminderCreator"> | ||||||
|         {% set creating = true %} |         {% set creating = true %} | ||||||
|         {% include "reminder_dashboard/guild_reminder" %} |         {% include "reminder_dashboard/templates/guild_reminder" %} | ||||||
|         {% set creating = false %} |         {% set creating = false %} | ||||||
|     </div> |     </div> | ||||||
|     <br> |     <br> | ||||||
| @@ -46,6 +46,10 @@ | |||||||
|     <div id="guildReminders"> |     <div id="guildReminders"> | ||||||
|  |  | ||||||
|     </div> |     </div> | ||||||
|  |  | ||||||
|  |     <div id="guildErrors"> | ||||||
|  |  | ||||||
|  |     </div> | ||||||
| </div> | </div> | ||||||
|  |  | ||||||
| <script src="/static/js/sort.js"></script> | <script src="/static/js/sort.js"></script> | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| <div> | <div id="reminderLog"> | ||||||
|  |  | ||||||
| </div> | </div> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -0,0 +1,17 @@ | |||||||
|  | <div class="reminderError" data-case="success"> | ||||||
|  |     <div class="errorHead"> | ||||||
|  |         <div class="errorIcon"> | ||||||
|  |             <span class="icon"> | ||||||
|  |                 <i class="fas fa-trash"></i> | ||||||
|  |                 <i class="fas fa-check"></i> | ||||||
|  |                 <i class="fas fa-exclamation-triangle"></i> | ||||||
|  |             </span> | ||||||
|  |         </div> | ||||||
|  |         <div class="reminderName"> | ||||||
|  |             Reminder | ||||||
|  |         </div> | ||||||
|  |         <div class="reminderTime"> | ||||||
|  |  | ||||||
|  |         </div> | ||||||
|  |     </div> | ||||||
|  | </div> | ||||||
		Reference in New Issue
	
	Block a user