jude/orphan-reminders #1
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`;
|
@ -166,15 +166,21 @@ impl ComponentDataModel {
|
||||
.await;
|
||||
}
|
||||
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!(
|
||||
"UPDATE reminders SET `status` = 'deleted' WHERE FIND_IN_SET(id, ?)",
|
||||
selected_id
|
||||
)
|
||||
.execute(&data.database)
|
||||
.await
|
||||
.unwrap();
|
||||
Err(e) => {
|
||||
warn!("Error casting ID to integer: {:?}.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let reminders = Reminder::from_guild(
|
||||
&ctx,
|
||||
|
@ -304,10 +304,13 @@ WHERE
|
||||
&self,
|
||||
db: impl Executor<'_, Database = Database>,
|
||||
) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!("UPDATE reminders SET `status` = 'deleted' WHERE uid = ?", self.uid)
|
||||
.execute(db)
|
||||
.await
|
||||
.map(|_| ())
|
||||
sqlx::query!(
|
||||
"UPDATE reminders SET `status` = 'deleted', `status_change_time` = NOW() WHERE uid = ?",
|
||||
self.uid
|
||||
)
|
||||
.execute(db)
|
||||
.await
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
pub fn display_content(&self) -> &str {
|
||||
|
@ -166,7 +166,6 @@ pub async fn initialize(
|
||||
routes::dashboard::guild::get_reminders,
|
||||
routes::dashboard::guild::edit_reminder,
|
||||
routes::dashboard::guild::delete_reminder,
|
||||
routes::dashboard::guild::get_reminder_errors,
|
||||
routes::dashboard::export::export_reminders,
|
||||
routes::dashboard::export::export_reminder_templates,
|
||||
routes::dashboard::export::export_todos,
|
||||
|
@ -171,6 +171,8 @@ pub async fn import_reminders(
|
||||
uid: generate_uid(),
|
||||
username: record.username,
|
||||
utc_time: record.utc_time,
|
||||
status: "pending".to_string(),
|
||||
status_change_time: None,
|
||||
};
|
||||
|
||||
create_reminder(
|
||||
|
@ -26,7 +26,7 @@ use crate::{
|
||||
routes::{
|
||||
dashboard::{
|
||||
create_database_channel, create_reminder, template_name_default, DeleteReminder,
|
||||
DeleteReminderTemplate, PatchReminder, Reminder, ReminderError, ReminderTemplate,
|
||||
DeleteReminderTemplate, PatchReminder, Reminder, ReminderTemplate,
|
||||
},
|
||||
JsonResult,
|
||||
},
|
||||
@ -318,15 +318,18 @@ pub async fn create_guild_reminder(
|
||||
.await
|
||||
}
|
||||
|
||||
#[get("/api/guild/<id>/reminders")]
|
||||
#[get("/api/guild/<id>/reminders?<status>")]
|
||||
pub async fn get_reminders(
|
||||
id: u64,
|
||||
cookies: &CookieJar<'_>,
|
||||
serenity_context: &State<Context>,
|
||||
pool: &State<Pool<MySql>>,
|
||||
status: Option<String>,
|
||||
) -> JsonResult {
|
||||
check_authorization!(cookies, serenity_context.inner(), id);
|
||||
|
||||
let status = status.unwrap_or("pending".to_string());
|
||||
|
||||
sqlx::query_as_unchecked!(
|
||||
Reminder,
|
||||
"SELECT
|
||||
@ -355,10 +358,13 @@ pub async fn get_reminders(
|
||||
reminders.tts,
|
||||
reminders.uid,
|
||||
reminders.username,
|
||||
reminders.utc_time
|
||||
reminders.utc_time,
|
||||
reminders.status,
|
||||
reminders.status_change_time
|
||||
FROM reminders
|
||||
LEFT JOIN channels ON channels.id = reminders.channel_id
|
||||
WHERE `status` = 'pending' AND reminders.guild_id = (SELECT id FROM guilds WHERE guild = ?)",
|
||||
WHERE FIND_IN_SET(`status`, ?) AND reminders.guild_id = (SELECT id FROM guilds WHERE guild = ?)",
|
||||
status,
|
||||
id
|
||||
)
|
||||
.fetch_all(pool.inner())
|
||||
@ -567,7 +573,9 @@ pub async fn edit_reminder(
|
||||
reminders.tts,
|
||||
reminders.uid,
|
||||
reminders.username,
|
||||
reminders.utc_time
|
||||
reminders.utc_time,
|
||||
reminders.status,
|
||||
reminders.status_change_time
|
||||
FROM reminders
|
||||
LEFT JOIN channels ON channels.id = reminders.channel_id
|
||||
WHERE uid = ?",
|
||||
@ -591,9 +599,12 @@ pub async fn delete_reminder(
|
||||
reminder: Json<DeleteReminder>,
|
||||
pool: &State<Pool<MySql>>,
|
||||
) -> JsonResult {
|
||||
match sqlx::query!("UPDATE reminders SET `status` = 'deleted' WHERE uid = ?", reminder.uid)
|
||||
.execute(pool.inner())
|
||||
.await
|
||||
match sqlx::query!(
|
||||
"UPDATE reminders SET `status` = 'deleted', `status_change_time` = NOW() WHERE uid = ?",
|
||||
reminder.uid
|
||||
)
|
||||
.execute(pool.inner())
|
||||
.await
|
||||
{
|
||||
Ok(_) => Ok(json!({})),
|
||||
|
||||
@ -604,35 +615,3 @@ pub async fn delete_reminder(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/api/guild/<id>/errors")]
|
||||
pub async fn get_reminder_errors(
|
||||
id: u64,
|
||||
cookies: &CookieJar<'_>,
|
||||
serenity_context: &State<Context>,
|
||||
pool: &State<Pool<MySql>>,
|
||||
) -> JsonResult {
|
||||
check_authorization!(cookies, serenity_context.inner(), id);
|
||||
|
||||
sqlx::query_as_unchecked!(
|
||||
ReminderError,
|
||||
"SELECT
|
||||
reminders.status,
|
||||
reminders.utc_time,
|
||||
reminders.name,
|
||||
reminders.uid,
|
||||
reminders.channel_id AS channel
|
||||
FROM reminders
|
||||
LEFT JOIN channels ON channels.id = reminders.channel_id
|
||||
WHERE (`status` = 'failed' OR reminders.channel_id IS NULL) AND reminders.guild_id = (SELECT id FROM guilds WHERE guild = ?)",
|
||||
id
|
||||
)
|
||||
.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")
|
||||
})
|
||||
}
|
||||
|
@ -150,18 +150,8 @@ pub struct Reminder {
|
||||
uid: String,
|
||||
username: Option<String>,
|
||||
utc_time: NaiveDateTime,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct ReminderError {
|
||||
#[serde(with = "string")]
|
||||
channel: u64,
|
||||
status: String,
|
||||
#[serde(default = "name_default")]
|
||||
name: String,
|
||||
#[serde(default)]
|
||||
uid: String,
|
||||
utc_time: NaiveDateTime,
|
||||
status_change_time: Option<NaiveDateTime>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@ -574,7 +564,9 @@ pub async fn create_reminder(
|
||||
reminders.tts,
|
||||
reminders.uid,
|
||||
reminders.username,
|
||||
reminders.utc_time
|
||||
reminders.utc_time,
|
||||
reminders.status,
|
||||
reminders.status_change_time
|
||||
FROM reminders
|
||||
LEFT JOIN channels ON channels.id = reminders.channel_id
|
||||
WHERE uid = ?",
|
||||
|
@ -688,6 +688,44 @@ li.highlight {
|
||||
|
||||
/* 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.deleted {
|
||||
background-color: #e7e5e4;
|
||||
}
|
||||
|
||||
div.reminderError .errorIcon.success {
|
||||
background-color: #d9f99d;
|
||||
}
|
||||
|
||||
div.reminderError .errorIcon.errored {
|
||||
background-color: #fecaca;
|
||||
}
|
||||
|
||||
div.reminderError .errorHead .reminderName {
|
||||
font-size: 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
color: rgb(54, 54, 54);
|
||||
}
|
||||
|
||||
/* other stuff */
|
||||
|
||||
.half-rem {
|
||||
|
@ -57,7 +57,7 @@ function switch_pane(selector) {
|
||||
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) {
|
||||
@ -735,16 +735,19 @@ document.addEventListener("DOMContentLoaded", async () => {
|
||||
});
|
||||
}
|
||||
|
||||
const matches = window.location.href.match(/dashboard\/(\d+)/);
|
||||
const matches = window.location.href.match(
|
||||
/dashboard\/(\d+)(\/)?([a-zA-Z\-]+)?/
|
||||
);
|
||||
if (matches) {
|
||||
let id = matches[1];
|
||||
let kind = matches[3];
|
||||
let name = guildNames[id];
|
||||
|
||||
const event = new CustomEvent("guildSwitched", {
|
||||
detail: {
|
||||
guild_name: name,
|
||||
guild_id: id,
|
||||
pane: "guild",
|
||||
pane: kind,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -1,16 +1,18 @@
|
||||
function loadErrors() {
|
||||
return fetch(`/dashboard/api/guild/${guildId()}/errors`).then((response) =>
|
||||
response.json()
|
||||
);
|
||||
return fetch(
|
||||
`/dashboard/api/guild/${guildId()}/reminders?status=deleted,sent,error`
|
||||
).then((response) => response.json());
|
||||
}
|
||||
|
||||
document.addEventListener("paneLoad", (ev) => {
|
||||
if (ev.detail.pane !== "reminder-errors") {
|
||||
if (ev.detail.pane !== "errors") {
|
||||
return;
|
||||
}
|
||||
|
||||
// Load errors
|
||||
loadErrors().then((res) => {});
|
||||
loadErrors().then((res) => {
|
||||
console.log(res);
|
||||
});
|
||||
|
||||
$loader.classList.add("is-hidden");
|
||||
});
|
||||
|
@ -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>
|
||||
</div>
|
||||
</section>
|
||||
<section id="guild" class="is-hidden">
|
||||
<section data-name="reminders" class="is-hidden">
|
||||
{% include "reminder_dashboard/reminder_dashboard" %}
|
||||
</section>
|
||||
<section id="reminder-errors" class="is-hidden">
|
||||
<section data-name="errors" class="is-hidden">
|
||||
{% include "reminder_dashboard/reminder_errors" %}
|
||||
</section>
|
||||
<section id="guild-error" class="is-hidden">
|
||||
<section data-name="guild-error" class="is-hidden">
|
||||
{% include "reminder_dashboard/guild_error" %}
|
||||
</section>
|
||||
<section id="user-error" class="is-hidden">
|
||||
<section data-name="user-error" class="is-hidden">
|
||||
{% include "reminder_dashboard/user_error" %}
|
||||
</section>
|
||||
</div>
|
||||
@ -375,16 +375,16 @@
|
||||
|
||||
<template id="guildListEntry">
|
||||
<li>
|
||||
<a class="switch-pane" data-pane="guild" data-slug="reminders">
|
||||
<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>
|
||||
</a>
|
||||
<ul class="guild-submenu">
|
||||
<li>
|
||||
<a class="switch-pane" data-pane="guild" data-slug="reminders">
|
||||
<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="reminder-errors" data-slug="errors">
|
||||
<span class="icon"><i class="fas fa-exclamation-triangle"></i></span> Errors
|
||||
<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>
|
||||
@ -392,11 +392,11 @@
|
||||
</template>
|
||||
|
||||
<template id="guildReminder">
|
||||
{% include "reminder_dashboard/guild_reminder" %}
|
||||
{% include "reminder_dashboard/templates/guild_reminder" %}
|
||||
</template>
|
||||
|
||||
<template id="guildError">
|
||||
|
||||
<template id="reminderError">
|
||||
{% include "reminder_dashboard/templates/reminder_error" %}
|
||||
</template>
|
||||
|
||||
<script src="/static/js/iro.js"></script>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<strong>Create Reminder</strong>
|
||||
<div id="reminderCreator">
|
||||
{% set creating = true %}
|
||||
{% include "reminder_dashboard/guild_reminder" %}
|
||||
{% include "reminder_dashboard/templates/guild_reminder" %}
|
||||
{% set creating = false %}
|
||||
</div>
|
||||
<br>
|
||||
|
@ -1,5 +1,51 @@
|
||||
<div>
|
||||
<div class="reminderError">
|
||||
<div class="errorHead">
|
||||
<div class="errorIcon deleted">
|
||||
<span class="icon">
|
||||
<i class="fas fa-trash"></i>
|
||||
</span>
|
||||
</div>
|
||||
<div class="reminderName">
|
||||
Reminder
|
||||
</div>
|
||||
<div class="reminderTime">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="reminderError">
|
||||
<div class="errorHead">
|
||||
<div class="errorIcon errored">
|
||||
<span class="icon">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
</span>
|
||||
</div>
|
||||
<div class="reminderName">
|
||||
Reminder
|
||||
</div>
|
||||
<div class="reminderTime">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="reminderError">
|
||||
<div class="errorHead">
|
||||
<div class="errorIcon success">
|
||||
<span class="icon">
|
||||
<i class="fas fa-check"></i>
|
||||
</span>
|
||||
</div>
|
||||
<div class="reminderName">
|
||||
Reminder
|
||||
</div>
|
||||
<div class="reminderTime">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/static/js/reminder_errors.js"></script>
|
||||
|
@ -0,0 +1,13 @@
|
||||
<div class="reminderError">
|
||||
<div class="errorIcon">
|
||||
<span>
|
||||
<i class="fas fa-trash"></i>
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="reminderName"></span>
|
||||
</div>
|
||||
<div>
|
||||
<span></span>
|
||||
</div>
|
||||
</div>
|
Loading…
Reference in New Issue
Block a user