jude/orphan-reminders #1

Merged
jude merged 10 commits from jude/orphan-reminders into next 2023-09-16 17:09:34 +00:00
15 changed files with 172 additions and 85 deletions
Showing only changes of commit ee89cb40c5 - Show all commits

View 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`;

View File

@ -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,

View File

@ -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 {

View File

@ -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,

View File

@ -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(

View File

@ -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")
})
}

View File

@ -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 = ?",

View File

@ -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 {

View File

@ -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,
},
});

View File

@ -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");
});

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>