Extend user reminder API endpoints
This commit is contained in:
parent
8f4810b532
commit
3190738fc5
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -2359,7 +2359,7 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
|
||||
|
||||
[[package]]
|
||||
name = "reminder-rs"
|
||||
version = "1.7.1"
|
||||
version = "1.7.2"
|
||||
dependencies = [
|
||||
"base64 0.21.7",
|
||||
"chrono",
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "reminder-rs"
|
||||
version = "1.7.1"
|
||||
version = "1.7.2"
|
||||
authors = ["Jude Southworth <judesouthworth@pm.me>"]
|
||||
edition = "2021"
|
||||
license = "AGPL-3.0 only"
|
||||
|
@ -144,9 +144,9 @@ export const postGuildReminder = (guild: string) => ({
|
||||
axios.post(`/dashboard/api/guild/${guild}/reminders`, reminder).then((resp) => resp.data),
|
||||
});
|
||||
|
||||
export const deleteGuildReminder = (guild: string) => ({
|
||||
export const deleteReminder = () => ({
|
||||
mutationFn: (reminder: Reminder) =>
|
||||
axios.delete(`/dashboard/api/guild/${guild}/reminders`, {
|
||||
axios.delete(`/dashboard/api/reminders`, {
|
||||
data: {
|
||||
uid: reminder.uid,
|
||||
},
|
||||
@ -187,3 +187,7 @@ export const postUserReminder = () => ({
|
||||
mutationFn: (reminder: Reminder) =>
|
||||
axios.post(`/dashboard/api/user/reminders`, reminder).then((resp) => resp.data),
|
||||
});
|
||||
|
||||
export const patchUserReminder = () => ({
|
||||
mutationFn: (reminder: Reminder) => axios.patch(`/dashboard/api/user/reminders`, reminder),
|
||||
});
|
||||
|
@ -2,7 +2,6 @@ import { LoadTemplate } from "../LoadTemplate";
|
||||
import { useReminder } from "../ReminderContext";
|
||||
import { useMutation, useQueryClient } from "react-query";
|
||||
import { postGuildReminder, postGuildTemplate, postUserReminder } from "../../../api";
|
||||
import { useParams } from "wouter";
|
||||
import { useState } from "preact/hooks";
|
||||
import { ICON_FLASH_TIME } from "../../../consts";
|
||||
import { useFlash } from "../../App/FlashContext";
|
||||
@ -96,34 +95,36 @@ export const CreateButtonRow = () => {
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<div class="button-row-template">
|
||||
<div>
|
||||
<button
|
||||
class="button is-success is-outlined"
|
||||
onClick={() => {
|
||||
templateMutation.mutate(reminder);
|
||||
}}
|
||||
>
|
||||
<span>Create Template</span>{" "}
|
||||
{templateMutation.isLoading ? (
|
||||
<span class="icon">
|
||||
<i class="fas fa-spin fa-cog"></i>
|
||||
</span>
|
||||
) : templateRecentlyCreated ? (
|
||||
<span class="icon">
|
||||
<i class="fas fa-check"></i>
|
||||
</span>
|
||||
) : (
|
||||
<span class="icon">
|
||||
<i class="fas fa-file-spreadsheet"></i>
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
{guild && (
|
||||
<div class="button-row-template">
|
||||
<div>
|
||||
<button
|
||||
class="button is-success is-outlined"
|
||||
onClick={() => {
|
||||
templateMutation.mutate(reminder);
|
||||
}}
|
||||
>
|
||||
<span>Create Template</span>{" "}
|
||||
{templateMutation.isLoading ? (
|
||||
<span class="icon">
|
||||
<i class="fas fa-spin fa-cog"></i>
|
||||
</span>
|
||||
) : templateRecentlyCreated ? (
|
||||
<span class="icon">
|
||||
<i class="fas fa-check"></i>
|
||||
</span>
|
||||
) : (
|
||||
<span class="icon">
|
||||
<i class="fas fa-file-spreadsheet"></i>
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<LoadTemplate />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<LoadTemplate />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -2,9 +2,10 @@ import { useState } from "preact/hooks";
|
||||
import { Modal } from "../../Modal";
|
||||
import { useMutation, useQueryClient } from "react-query";
|
||||
import { useReminder } from "../ReminderContext";
|
||||
import { deleteGuildReminder } from "../../../api";
|
||||
import { deleteReminder } from "../../../api";
|
||||
import { useParams } from "wouter";
|
||||
import { useFlash } from "../../App/FlashContext";
|
||||
import { useGuild } from "../../App/useGuild";
|
||||
|
||||
export const DeleteButton = () => {
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
@ -26,20 +27,26 @@ export const DeleteButton = () => {
|
||||
|
||||
const DeleteModal = ({ setModalOpen }) => {
|
||||
const [reminder] = useReminder();
|
||||
const { guild } = useParams();
|
||||
const guild = useGuild();
|
||||
|
||||
const flash = useFlash();
|
||||
const queryClient = useQueryClient();
|
||||
const mutation = useMutation({
|
||||
...deleteGuildReminder(guild),
|
||||
...deleteReminder(),
|
||||
onSuccess: () => {
|
||||
flash({
|
||||
message: "Reminder deleted",
|
||||
type: "success",
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["GUILD_REMINDERS", guild],
|
||||
});
|
||||
if (guild) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["GUILD_REMINDERS", guild],
|
||||
});
|
||||
} else {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["USER_REMINDERS"],
|
||||
});
|
||||
}
|
||||
setModalOpen(false);
|
||||
},
|
||||
});
|
||||
|
@ -1,14 +1,14 @@
|
||||
import { useRef, useState } from "preact/hooks";
|
||||
import { useMutation, useQueryClient } from "react-query";
|
||||
import { patchGuildReminder } from "../../../api";
|
||||
import { useParams } from "wouter";
|
||||
import { patchGuildReminder, patchUserReminder } from "../../../api";
|
||||
import { ICON_FLASH_TIME } from "../../../consts";
|
||||
import { DeleteButton } from "./DeleteButton";
|
||||
import { useReminder } from "../ReminderContext";
|
||||
import { useFlash } from "../../App/FlashContext";
|
||||
import { useGuild } from "../../App/useGuild";
|
||||
|
||||
export const EditButtonRow = () => {
|
||||
const { guild } = useParams();
|
||||
const guild = useGuild();
|
||||
const [reminder, setReminder] = useReminder();
|
||||
|
||||
const [recentlySaved, setRecentlySaved] = useState(false);
|
||||
@ -18,11 +18,17 @@ export const EditButtonRow = () => {
|
||||
|
||||
const flash = useFlash();
|
||||
const mutation = useMutation({
|
||||
...patchGuildReminder(guild),
|
||||
...(guild ? patchGuildReminder(guild) : patchUserReminder()),
|
||||
onSuccess: (response) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["GUILD_REMINDERS", guild],
|
||||
});
|
||||
if (guild) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["GUILD_REMINDERS", guild],
|
||||
});
|
||||
} else {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["USER_REMINDERS"],
|
||||
});
|
||||
}
|
||||
|
||||
if (iconFlashTimeout.current !== null) {
|
||||
clearTimeout(iconFlashTimeout.current);
|
||||
|
@ -131,6 +131,7 @@ pub async fn initialize(
|
||||
routes![
|
||||
routes::dashboard::dashboard,
|
||||
routes::dashboard::dashboard_home,
|
||||
routes::dashboard::api::delete_reminder,
|
||||
routes::dashboard::api::user::get_user_info,
|
||||
routes::dashboard::api::user::update_user_info,
|
||||
routes::dashboard::api::user::get_user_guilds,
|
||||
@ -145,7 +146,6 @@ pub async fn initialize(
|
||||
routes::dashboard::api::guild::create_guild_reminder,
|
||||
routes::dashboard::api::guild::get_reminders,
|
||||
routes::dashboard::api::guild::edit_reminder,
|
||||
routes::dashboard::api::guild::delete_reminder,
|
||||
routes::dashboard::export::export_reminders,
|
||||
routes::dashboard::export::export_reminder_templates,
|
||||
routes::dashboard::export::export_todos,
|
||||
|
@ -14,9 +14,7 @@ use crate::{
|
||||
consts::MIN_INTERVAL,
|
||||
guards::transaction::Transaction,
|
||||
routes::{
|
||||
dashboard::{
|
||||
create_database_channel, create_reminder, DeleteReminder, PatchReminder, Reminder,
|
||||
},
|
||||
dashboard::{create_database_channel, create_reminder, PatchReminder, Reminder},
|
||||
JsonResult,
|
||||
},
|
||||
Database,
|
||||
@ -364,27 +362,3 @@ pub async fn edit_reminder(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[delete("/api/guild/<id>/reminders", data = "<reminder>")]
|
||||
pub async fn delete_reminder(
|
||||
cookies: &CookieJar<'_>,
|
||||
id: u64,
|
||||
reminder: Json<DeleteReminder>,
|
||||
ctx: &State<Context>,
|
||||
pool: &State<Pool<MySql>>,
|
||||
) -> JsonResult {
|
||||
check_authorization(cookies, ctx.inner(), id).await?;
|
||||
|
||||
match sqlx::query!("UPDATE reminders SET `status` = 'deleted' WHERE uid = ?", reminder.uid)
|
||||
.execute(pool.inner())
|
||||
.await
|
||||
{
|
||||
Ok(_) => Ok(json!({})),
|
||||
|
||||
Err(e) => {
|
||||
warn!("Error in `delete_reminder`: {:?}", e);
|
||||
|
||||
Err(json!({"error": "Could not delete reminder"}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,2 +1,41 @@
|
||||
pub mod guild;
|
||||
pub mod user;
|
||||
|
||||
use rocket::{
|
||||
http::CookieJar,
|
||||
serde::json::{json, Json},
|
||||
State,
|
||||
};
|
||||
use serenity::client::Context;
|
||||
use sqlx::{MySql, Pool};
|
||||
|
||||
use crate::routes::{dashboard::DeleteReminder, JsonResult};
|
||||
|
||||
#[delete("/api/reminders", data = "<reminder>")]
|
||||
pub async fn delete_reminder(
|
||||
cookies: &CookieJar<'_>,
|
||||
reminder: Json<DeleteReminder>,
|
||||
pool: &State<Pool<MySql>>,
|
||||
) -> JsonResult {
|
||||
match cookies.get_private("userid").map(|c| c.value().parse::<u64>().ok()).flatten() {
|
||||
Some(_) => {
|
||||
match sqlx::query!(
|
||||
"UPDATE reminders SET `status` = 'deleted' WHERE uid = ?",
|
||||
reminder.uid
|
||||
)
|
||||
.execute(pool.inner())
|
||||
.await
|
||||
{
|
||||
Ok(_) => Ok(json!({})),
|
||||
|
||||
Err(e) => {
|
||||
warn!("Error in `delete_reminder`: {:?}", e);
|
||||
|
||||
Err(json!({"error": "Could not delete reminder"}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None => Err(json!({"error": "User not authorized"})),
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,10 @@ use crate::{
|
||||
},
|
||||
guards::transaction::Transaction,
|
||||
routes::{
|
||||
dashboard::{create_database_channel, generate_uid, name_default, Attachment, EmbedField},
|
||||
dashboard::{
|
||||
create_database_channel, deserialize_optional_field, generate_uid, interval_default,
|
||||
name_default, Attachment, EmbedField, Unset,
|
||||
},
|
||||
JsonResult,
|
||||
},
|
||||
Error,
|
||||
@ -228,3 +231,60 @@ pub async fn create_reminder(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct PatchReminder {
|
||||
pub uid: String,
|
||||
#[serde(default)]
|
||||
#[serde(deserialize_with = "deserialize_optional_field")]
|
||||
pub attachment: Unset<Option<Attachment>>,
|
||||
#[serde(default)]
|
||||
#[serde(deserialize_with = "deserialize_optional_field")]
|
||||
pub attachment_name: Unset<Option<String>>,
|
||||
#[serde(default)]
|
||||
pub content: Unset<String>,
|
||||
#[serde(default)]
|
||||
pub embed_author: Unset<String>,
|
||||
#[serde(default)]
|
||||
#[serde(deserialize_with = "deserialize_optional_field")]
|
||||
pub embed_author_url: Unset<Option<String>>,
|
||||
#[serde(default)]
|
||||
pub embed_color: Unset<u32>,
|
||||
#[serde(default)]
|
||||
pub embed_description: Unset<String>,
|
||||
#[serde(default)]
|
||||
pub embed_footer: Unset<String>,
|
||||
#[serde(default)]
|
||||
#[serde(deserialize_with = "deserialize_optional_field")]
|
||||
pub embed_footer_url: Unset<Option<String>>,
|
||||
#[serde(default)]
|
||||
#[serde(deserialize_with = "deserialize_optional_field")]
|
||||
pub embed_image_url: Unset<Option<String>>,
|
||||
#[serde(default)]
|
||||
#[serde(deserialize_with = "deserialize_optional_field")]
|
||||
pub embed_thumbnail_url: Unset<Option<String>>,
|
||||
#[serde(default)]
|
||||
pub embed_title: Unset<String>,
|
||||
#[serde(default)]
|
||||
pub embed_fields: Unset<Json<Vec<EmbedField>>>,
|
||||
#[serde(default)]
|
||||
pub enabled: Unset<bool>,
|
||||
#[serde(default)]
|
||||
#[serde(deserialize_with = "deserialize_optional_field")]
|
||||
pub expires: Unset<Option<NaiveDateTime>>,
|
||||
#[serde(default = "interval_default")]
|
||||
#[serde(deserialize_with = "deserialize_optional_field")]
|
||||
pub interval_seconds: Unset<Option<u32>>,
|
||||
#[serde(default = "interval_default")]
|
||||
#[serde(deserialize_with = "deserialize_optional_field")]
|
||||
pub interval_days: Unset<Option<u32>>,
|
||||
#[serde(default = "interval_default")]
|
||||
#[serde(deserialize_with = "deserialize_optional_field")]
|
||||
pub interval_months: Unset<Option<u32>>,
|
||||
#[serde(default)]
|
||||
pub name: Unset<String>,
|
||||
#[serde(default)]
|
||||
pub tts: Unset<bool>,
|
||||
#[serde(default)]
|
||||
pub utc_time: Unset<NaiveDateTime>,
|
||||
}
|
||||
|
@ -7,11 +7,16 @@ use serenity::{client::Context, model::id::UserId};
|
||||
use sqlx::{MySql, Pool};
|
||||
|
||||
use crate::{
|
||||
check_subscription,
|
||||
guards::transaction::Transaction,
|
||||
routes::{
|
||||
dashboard::api::user::models::{create_reminder, Reminder},
|
||||
dashboard::{
|
||||
api::user::models::{create_reminder, Reminder},
|
||||
PatchReminder, MIN_INTERVAL,
|
||||
},
|
||||
JsonResult,
|
||||
},
|
||||
Database,
|
||||
};
|
||||
|
||||
#[post("/api/user/reminders", data = "<reminder>")]
|
||||
@ -103,3 +108,174 @@ pub async fn get_reminders(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[patch("/api/user/reminders", data = "<reminder>")]
|
||||
pub async fn edit_reminder(
|
||||
reminder: Json<PatchReminder>,
|
||||
ctx: &State<Context>,
|
||||
mut transaction: Transaction<'_>,
|
||||
pool: &State<Pool<Database>>,
|
||||
cookies: &CookieJar<'_>,
|
||||
) -> JsonResult {
|
||||
let user_id_cookie =
|
||||
cookies.get_private("userid").map(|c| c.value().parse::<u64>().ok()).flatten();
|
||||
|
||||
if user_id_cookie.is_none() {
|
||||
return Err(json!({"error": "User not authorized"}));
|
||||
}
|
||||
|
||||
let mut error = vec![];
|
||||
let user_id = user_id_cookie.unwrap();
|
||||
|
||||
if reminder.message_ok() {
|
||||
update_field!(transaction.executor(), error, reminder.[
|
||||
content,
|
||||
embed_author,
|
||||
embed_description,
|
||||
embed_footer,
|
||||
embed_title,
|
||||
embed_fields
|
||||
]);
|
||||
} else {
|
||||
error.push("Message exceeds limits.".to_string());
|
||||
}
|
||||
|
||||
update_field!(transaction.executor(), error, reminder.[
|
||||
attachment,
|
||||
attachment_name,
|
||||
embed_author_url,
|
||||
embed_color,
|
||||
embed_footer_url,
|
||||
embed_image_url,
|
||||
embed_thumbnail_url,
|
||||
enabled,
|
||||
expires,
|
||||
name,
|
||||
tts,
|
||||
utc_time
|
||||
]);
|
||||
|
||||
if reminder.interval_days.flatten().is_some()
|
||||
|| reminder.interval_months.flatten().is_some()
|
||||
|| reminder.interval_seconds.flatten().is_some()
|
||||
{
|
||||
if check_subscription(&ctx.inner(), user_id).await {
|
||||
let new_interval_length = match reminder.interval_days {
|
||||
Some(interval) => interval.unwrap_or(0),
|
||||
None => sqlx::query!(
|
||||
"SELECT interval_days AS days FROM reminders WHERE uid = ?",
|
||||
reminder.uid
|
||||
)
|
||||
.fetch_one(transaction.executor())
|
||||
.await
|
||||
.map_err(|e| {
|
||||
warn!("Error updating reminder interval: {:?}", e);
|
||||
json!({ "reminder": Option::<Reminder>::None, "errors": vec!["Unknown error"] })
|
||||
})?
|
||||
.days
|
||||
.unwrap_or(0),
|
||||
} * 86400 + match reminder.interval_months {
|
||||
Some(interval) => interval.unwrap_or(0),
|
||||
None => sqlx::query!(
|
||||
"SELECT interval_months AS months FROM reminders WHERE uid = ?",
|
||||
reminder.uid
|
||||
)
|
||||
.fetch_one(transaction.executor())
|
||||
.await
|
||||
.map_err(|e| {
|
||||
warn!("Error updating reminder interval: {:?}", e);
|
||||
json!({ "reminder": Option::<Reminder>::None, "errors": vec!["Unknown error"] })
|
||||
})?
|
||||
.months
|
||||
.unwrap_or(0),
|
||||
} * 2592000 + match reminder.interval_seconds {
|
||||
Some(interval) => interval.unwrap_or(0),
|
||||
None => sqlx::query!(
|
||||
"SELECT interval_seconds AS seconds FROM reminders WHERE uid = ?",
|
||||
reminder.uid
|
||||
)
|
||||
.fetch_one(transaction.executor())
|
||||
.await
|
||||
.map_err(|e| {
|
||||
warn!("Error updating reminder interval: {:?}", e);
|
||||
json!({ "reminder": Option::<Reminder>::None, "errors": vec!["Unknown error"] })
|
||||
})?
|
||||
.seconds
|
||||
.unwrap_or(0),
|
||||
};
|
||||
|
||||
if new_interval_length < *MIN_INTERVAL {
|
||||
error.push(String::from("New interval is too short."));
|
||||
} else {
|
||||
update_field!(transaction.executor(), error, reminder.[
|
||||
interval_days,
|
||||
interval_months,
|
||||
interval_seconds
|
||||
]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sqlx::query!(
|
||||
"
|
||||
UPDATE reminders
|
||||
SET interval_seconds = NULL, interval_days = NULL, interval_months = NULL
|
||||
WHERE uid = ?
|
||||
",
|
||||
reminder.uid
|
||||
)
|
||||
.execute(transaction.executor())
|
||||
.await
|
||||
.map_err(|e| {
|
||||
warn!("Error updating reminder interval: {:?}", e);
|
||||
json!({ "reminder": Option::<Reminder>::None, "errors": vec!["Unknown error"] })
|
||||
})?;
|
||||
}
|
||||
|
||||
if let Err(e) = transaction.commit().await {
|
||||
warn!("Couldn't commit transaction: {:?}", e);
|
||||
return json_err!("Couldn't commit transaction");
|
||||
}
|
||||
|
||||
match sqlx::query_as_unchecked!(
|
||||
Reminder,
|
||||
"
|
||||
SELECT reminders.attachment,
|
||||
reminders.attachment_name,
|
||||
reminders.content,
|
||||
reminders.embed_author,
|
||||
reminders.embed_author_url,
|
||||
reminders.embed_color,
|
||||
reminders.embed_description,
|
||||
reminders.embed_footer,
|
||||
reminders.embed_footer_url,
|
||||
reminders.embed_image_url,
|
||||
reminders.embed_thumbnail_url,
|
||||
reminders.embed_title,
|
||||
reminders.embed_fields,
|
||||
reminders.enabled,
|
||||
reminders.expires,
|
||||
reminders.interval_seconds,
|
||||
reminders.interval_days,
|
||||
reminders.interval_months,
|
||||
reminders.name,
|
||||
reminders.tts,
|
||||
reminders.uid,
|
||||
reminders.utc_time
|
||||
FROM reminders
|
||||
LEFT JOIN channels ON channels.id = reminders.channel_id
|
||||
WHERE uid = ?
|
||||
",
|
||||
reminder.uid
|
||||
)
|
||||
.fetch_one(pool.inner())
|
||||
.await
|
||||
{
|
||||
Ok(reminder) => Ok(json!({"reminder": reminder, "errors": error})),
|
||||
|
||||
Err(e) => {
|
||||
warn!("Error exiting `edit_reminder': {:?}", e);
|
||||
|
||||
Err(json!({"reminder": Option::<Reminder>::None, "errors": vec!["Unknown error"]}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user