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;
|
.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,
|
||||||
|
@ -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 {
|
||||||
|
@ -166,7 +166,6 @@ pub async fn initialize(
|
|||||||
routes::dashboard::guild::get_reminders,
|
routes::dashboard::guild::get_reminders,
|
||||||
routes::dashboard::guild::edit_reminder,
|
routes::dashboard::guild::edit_reminder,
|
||||||
routes::dashboard::guild::delete_reminder,
|
routes::dashboard::guild::delete_reminder,
|
||||||
routes::dashboard::guild::get_reminder_errors,
|
|
||||||
routes::dashboard::export::export_reminders,
|
routes::dashboard::export::export_reminders,
|
||||||
routes::dashboard::export::export_reminder_templates,
|
routes::dashboard::export::export_reminder_templates,
|
||||||
routes::dashboard::export::export_todos,
|
routes::dashboard::export::export_todos,
|
||||||
|
@ -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(
|
||||||
|
@ -26,7 +26,7 @@ use crate::{
|
|||||||
routes::{
|
routes::{
|
||||||
dashboard::{
|
dashboard::{
|
||||||
create_database_channel, create_reminder, template_name_default, DeleteReminder,
|
create_database_channel, create_reminder, template_name_default, DeleteReminder,
|
||||||
DeleteReminderTemplate, PatchReminder, Reminder, ReminderError, ReminderTemplate,
|
DeleteReminderTemplate, PatchReminder, Reminder, ReminderTemplate,
|
||||||
},
|
},
|
||||||
JsonResult,
|
JsonResult,
|
||||||
},
|
},
|
||||||
@ -318,15 +318,18 @@ 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<'_>,
|
||||||
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 status = status.unwrap_or("pending".to_string());
|
||||||
|
|
||||||
sqlx::query_as_unchecked!(
|
sqlx::query_as_unchecked!(
|
||||||
Reminder,
|
Reminder,
|
||||||
"SELECT
|
"SELECT
|
||||||
@ -355,10 +358,13 @@ pub async fn get_reminders(
|
|||||||
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 `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
|
id
|
||||||
)
|
)
|
||||||
.fetch_all(pool.inner())
|
.fetch_all(pool.inner())
|
||||||
@ -567,7 +573,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 = ?",
|
||||||
@ -591,9 +599,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!({})),
|
||||||
|
|
||||||
@ -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,
|
uid: String,
|
||||||
username: Option<String>,
|
username: Option<String>,
|
||||||
utc_time: NaiveDateTime,
|
utc_time: NaiveDateTime,
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
pub struct ReminderError {
|
|
||||||
#[serde(with = "string")]
|
|
||||||
channel: u64,
|
|
||||||
status: String,
|
status: String,
|
||||||
#[serde(default = "name_default")]
|
status_change_time: Option<NaiveDateTime>,
|
||||||
name: String,
|
|
||||||
#[serde(default)]
|
|
||||||
uid: String,
|
|
||||||
utc_time: NaiveDateTime,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
@ -574,7 +564,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 = ?",
|
||||||
|
@ -688,6 +688,44 @@ 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.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 */
|
/* other stuff */
|
||||||
|
|
||||||
.half-rem {
|
.half-rem {
|
||||||
|
@ -57,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) {
|
||||||
@ -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) {
|
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: "guild",
|
pane: kind,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,16 +1,18 @@
|
|||||||
function loadErrors() {
|
function loadErrors() {
|
||||||
return fetch(`/dashboard/api/guild/${guildId()}/errors`).then((response) =>
|
return fetch(
|
||||||
response.json()
|
`/dashboard/api/guild/${guildId()}/reminders?status=deleted,sent,error`
|
||||||
);
|
).then((response) => response.json());
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener("paneLoad", (ev) => {
|
document.addEventListener("paneLoad", (ev) => {
|
||||||
if (ev.detail.pane !== "reminder-errors") {
|
if (ev.detail.pane !== "errors") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load errors
|
// Load errors
|
||||||
loadErrors().then((res) => {});
|
loadErrors().then((res) => {
|
||||||
|
console.log(res);
|
||||||
|
});
|
||||||
|
|
||||||
$loader.classList.add("is-hidden");
|
$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>
|
<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,16 +375,16 @@
|
|||||||
|
|
||||||
<template id="guildListEntry">
|
<template id="guildListEntry">
|
||||||
<li>
|
<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>
|
<span class="icon"><i class="fas fa-map-pin"></i></span> <span class="guild-name">%guildname%</span>
|
||||||
</a>
|
</a>
|
||||||
<ul class="guild-submenu">
|
<ul class="guild-submenu">
|
||||||
<li>
|
<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
|
<span class="icon"><i class="fas fa-calendar-alt"></i></span> Reminders
|
||||||
</a>
|
</a>
|
||||||
<a class="switch-pane" data-pane="reminder-errors" data-slug="errors">
|
<a class="switch-pane" data-pane="errors" data-slug="errors">
|
||||||
<span class="icon"><i class="fas fa-exclamation-triangle"></i></span> Errors
|
<span class="icon"><i class="fas fa-file-alt"></i></span> Logs
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@ -392,11 +392,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template id="guildReminder">
|
<template id="guildReminder">
|
||||||
{% include "reminder_dashboard/guild_reminder" %}
|
{% include "reminder_dashboard/templates/guild_reminder" %}
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template id="guildError">
|
<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>
|
||||||
|
@ -1,5 +1,51 @@
|
|||||||
<div>
|
<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>
|
</div>
|
||||||
|
|
||||||
<script src="/static/js/reminder_errors.js"></script>
|
<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