Compare commits
1 Commits
1d64c8bb79
...
jude/fix-o
Author | SHA1 | Date | |
---|---|---|---|
adb9c728f4 |
857
Cargo.lock
generated
857
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "reminder-rs"
|
name = "reminder-rs"
|
||||||
version = "1.6.50"
|
version = "1.6.48"
|
||||||
authors = ["Jude Southworth <judesouthworth@pm.me>"]
|
authors = ["Jude Southworth <judesouthworth@pm.me>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "AGPL-3.0 only"
|
license = "AGPL-3.0 only"
|
||||||
|
@ -445,9 +445,30 @@ WHERE
|
|||||||
};
|
};
|
||||||
|
|
||||||
error!("[Reminder {}] {}", self.id, message);
|
error!("[Reminder {}] {}", self.id, message);
|
||||||
|
|
||||||
|
if *LOG_TO_DATABASE {
|
||||||
|
sqlx::query!(
|
||||||
|
"INSERT INTO stat (type, reminder_id, message) VALUES ('reminder_failed', ?, ?)",
|
||||||
|
self.id,
|
||||||
|
message,
|
||||||
|
)
|
||||||
|
.execute(pool)
|
||||||
|
.await
|
||||||
|
.expect("Could not log error to database");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn log_success(&self, pool: impl Executor<'_, Database = Database> + Copy) {}
|
async fn log_success(&self, pool: impl Executor<'_, Database = Database> + Copy) {
|
||||||
|
if *LOG_TO_DATABASE {
|
||||||
|
sqlx::query!(
|
||||||
|
"INSERT INTO stat (type, reminder_id) VALUES ('reminder_sent', ?)",
|
||||||
|
self.id,
|
||||||
|
)
|
||||||
|
.execute(pool)
|
||||||
|
.await
|
||||||
|
.expect("Could not log success to database");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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!("UPDATE reminders SET `status` = 'sent' WHERE `id` = ?", self.id)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "reminder_web"
|
name = "reminder_web"
|
||||||
version = "0.1.4"
|
version = "0.1.2"
|
||||||
authors = ["jellywx <judesouthworth@pm.me>"]
|
authors = ["jellywx <judesouthworth@pm.me>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
@ -33,9 +33,11 @@ impl<'r> FromRequest<'r> for Transaction<'r> {
|
|||||||
match request.guard::<&State<Pool<Database>>>().await {
|
match request.guard::<&State<Pool<Database>>>().await {
|
||||||
Outcome::Success(pool) => match pool.begin().await {
|
Outcome::Success(pool) => match pool.begin().await {
|
||||||
Ok(transaction) => Outcome::Success(Transaction(transaction)),
|
Ok(transaction) => Outcome::Success(Transaction(transaction)),
|
||||||
Err(e) => Outcome::Error((Status::InternalServerError, TransactionError::Error(e))),
|
Err(e) => {
|
||||||
|
Outcome::Failure((Status::InternalServerError, TransactionError::Error(e)))
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Outcome::Error(e) => Outcome::Error((e.0, TransactionError::Missing)),
|
Outcome::Failure(e) => Outcome::Failure((e.0, TransactionError::Missing)),
|
||||||
Outcome::Forward(f) => Outcome::Forward(f),
|
Outcome::Forward(f) => Outcome::Forward(f),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -137,6 +137,38 @@ pub async fn bot_data(cookies: &CookieJar<'_>, pool: &State<Pool<MySql>>) -> Jso
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
let history = sqlx::query_as_unchecked!(
|
||||||
|
TimeFrame,
|
||||||
|
"SELECT
|
||||||
|
FROM_UNIXTIME(FLOOR(UNIX_TIMESTAMP(`utc_time`) / 86400) * 86400) AS `time_key`,
|
||||||
|
COUNT(1) AS `count`
|
||||||
|
FROM stat
|
||||||
|
WHERE
|
||||||
|
`utc_time` > DATE_SUB(NOW(), INTERVAL 31 DAY) AND
|
||||||
|
`type` = 'reminder_sent'
|
||||||
|
GROUP BY `time_key`
|
||||||
|
ORDER BY `time_key`"
|
||||||
|
)
|
||||||
|
.fetch_all(pool.inner())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let history_failed = sqlx::query_as_unchecked!(
|
||||||
|
TimeFrame,
|
||||||
|
"SELECT
|
||||||
|
FROM_UNIXTIME(FLOOR(UNIX_TIMESTAMP(`utc_time`) / 86400) * 86400) AS `time_key`,
|
||||||
|
COUNT(1) AS `count`
|
||||||
|
FROM stat
|
||||||
|
WHERE
|
||||||
|
`utc_time` > DATE_SUB(NOW(), INTERVAL 31 DAY) AND
|
||||||
|
`type` = 'reminder_failed'
|
||||||
|
GROUP BY `time_key`
|
||||||
|
ORDER BY `time_key`"
|
||||||
|
)
|
||||||
|
.fetch_all(pool.inner())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let interval_count = sqlx::query!(
|
let interval_count = sqlx::query!(
|
||||||
"SELECT COUNT(1) AS count
|
"SELECT COUNT(1) AS count
|
||||||
FROM reminders
|
FROM reminders
|
||||||
@ -174,6 +206,10 @@ pub async fn bot_data(cookies: &CookieJar<'_>, pool: &State<Pool<MySql>>) -> Jso
|
|||||||
"once": schedule_once_long,
|
"once": schedule_once_long,
|
||||||
"interval": schedule_interval_long,
|
"interval": schedule_interval_long,
|
||||||
},
|
},
|
||||||
|
"historyLong": {
|
||||||
|
"sent": history,
|
||||||
|
"failed": history_failed,
|
||||||
|
},
|
||||||
"count": {
|
"count": {
|
||||||
"reminders": reminder_count.count,
|
"reminders": reminder_count.count,
|
||||||
"intervals": interval_count.count,
|
"intervals": interval_count.count,
|
||||||
|
@ -4,7 +4,7 @@ use chrono::{naive::NaiveDateTime, Utc};
|
|||||||
use rand::{rngs::OsRng, seq::IteratorRandom};
|
use rand::{rngs::OsRng, seq::IteratorRandom};
|
||||||
use rocket::{http::CookieJar, response::Redirect, serde::json::json};
|
use rocket::{http::CookieJar, response::Redirect, serde::json::json};
|
||||||
use rocket_dyn_templates::Template;
|
use rocket_dyn_templates::Template;
|
||||||
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{Deserialize, Deserializer, Serialize};
|
||||||
use serenity::{
|
use serenity::{
|
||||||
client::Context,
|
client::Context,
|
||||||
http::Http,
|
http::Http,
|
||||||
@ -51,27 +51,12 @@ fn interval_default() -> Unset<Option<u32>> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(sqlx::Type)]
|
fn deserialize_optional_field<'de, T, D>(deserializer: D) -> Result<Option<Option<T>>, D::Error>
|
||||||
#[sqlx(transparent)]
|
where
|
||||||
struct Attachment(Vec<u8>);
|
D: Deserializer<'de>,
|
||||||
|
T: Deserialize<'de>,
|
||||||
impl<'de> Deserialize<'de> for Attachment {
|
{
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Attachment, D::Error>
|
Ok(Some(Option::deserialize(deserializer)?))
|
||||||
where
|
|
||||||
D: Deserializer<'de>,
|
|
||||||
{
|
|
||||||
let string = String::deserialize(deserializer)?;
|
|
||||||
Ok(Attachment(base64::decode(string).map_err(de::Error::custom)?))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Serialize for Attachment {
|
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: Serializer,
|
|
||||||
{
|
|
||||||
serializer.collect_str(&base64::encode(&self.0))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
@ -82,7 +67,7 @@ pub struct ReminderTemplate {
|
|||||||
guild_id: u32,
|
guild_id: u32,
|
||||||
#[serde(default = "template_name_default")]
|
#[serde(default = "template_name_default")]
|
||||||
name: String,
|
name: String,
|
||||||
attachment: Option<Attachment>,
|
attachment: Option<Vec<u8>>,
|
||||||
attachment_name: Option<String>,
|
attachment_name: Option<String>,
|
||||||
avatar: Option<String>,
|
avatar: Option<String>,
|
||||||
content: String,
|
content: String,
|
||||||
@ -107,7 +92,7 @@ pub struct ReminderTemplate {
|
|||||||
pub struct ReminderTemplateCsv {
|
pub struct ReminderTemplateCsv {
|
||||||
#[serde(default = "template_name_default")]
|
#[serde(default = "template_name_default")]
|
||||||
name: String,
|
name: String,
|
||||||
attachment: Option<Attachment>,
|
attachment: Option<Vec<u8>>,
|
||||||
attachment_name: Option<String>,
|
attachment_name: Option<String>,
|
||||||
avatar: Option<String>,
|
avatar: Option<String>,
|
||||||
content: String,
|
content: String,
|
||||||
@ -142,7 +127,8 @@ pub struct EmbedField {
|
|||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct Reminder {
|
pub struct Reminder {
|
||||||
attachment: Option<Attachment>,
|
#[serde(with = "base64s")]
|
||||||
|
attachment: Option<Vec<u8>>,
|
||||||
attachment_name: Option<String>,
|
attachment_name: Option<String>,
|
||||||
avatar: Option<String>,
|
avatar: Option<String>,
|
||||||
#[serde(with = "string")]
|
#[serde(with = "string")]
|
||||||
@ -175,7 +161,8 @@ pub struct Reminder {
|
|||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct ReminderCsv {
|
pub struct ReminderCsv {
|
||||||
attachment: Option<Attachment>,
|
#[serde(with = "base64s")]
|
||||||
|
attachment: Option<Vec<u8>>,
|
||||||
attachment_name: Option<String>,
|
attachment_name: Option<String>,
|
||||||
avatar: Option<String>,
|
avatar: Option<String>,
|
||||||
channel: String,
|
channel: String,
|
||||||
@ -208,7 +195,7 @@ pub struct PatchReminder {
|
|||||||
uid: String,
|
uid: String,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
#[serde(deserialize_with = "deserialize_optional_field")]
|
#[serde(deserialize_with = "deserialize_optional_field")]
|
||||||
attachment: Unset<Option<Attachment>>,
|
attachment: Unset<Option<String>>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
#[serde(deserialize_with = "deserialize_optional_field")]
|
#[serde(deserialize_with = "deserialize_optional_field")]
|
||||||
attachment_name: Unset<Option<String>>,
|
attachment_name: Unset<Option<String>>,
|
||||||
@ -304,14 +291,6 @@ pub fn generate_uid() -> String {
|
|||||||
.join("")
|
.join("")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deserialize_optional_field<'de, T, D>(deserializer: D) -> Result<Option<Option<T>>, D::Error>
|
|
||||||
where
|
|
||||||
D: Deserializer<'de>,
|
|
||||||
T: Deserialize<'de>,
|
|
||||||
{
|
|
||||||
Ok(Some(Option::deserialize(deserializer)?))
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://github.com/serde-rs/json/issues/329#issuecomment-305608405
|
// https://github.com/serde-rs/json/issues/329#issuecomment-305608405
|
||||||
mod string {
|
mod string {
|
||||||
use std::{fmt::Display, str::FromStr};
|
use std::{fmt::Display, str::FromStr};
|
||||||
@ -336,6 +315,29 @@ mod string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod base64s {
|
||||||
|
use serde::{de, Deserialize, Deserializer, Serializer};
|
||||||
|
|
||||||
|
pub fn serialize<S>(value: &Option<Vec<u8>>, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
if let Some(opt) = value {
|
||||||
|
serializer.collect_str(&base64::encode(opt))
|
||||||
|
} else {
|
||||||
|
serializer.serialize_none()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Vec<u8>>, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let string = Option::<String>::deserialize(deserializer)?;
|
||||||
|
Some(string.map(|b| base64::decode(b).map_err(de::Error::custom))).flatten().transpose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct DeleteReminder {
|
pub struct DeleteReminder {
|
||||||
uid: String,
|
uid: String,
|
||||||
|
@ -31,20 +31,22 @@ pub async fn discord_login(
|
|||||||
|
|
||||||
// store the pkce secret to verify the authorization later
|
// store the pkce secret to verify the authorization later
|
||||||
cookies.add_private(
|
cookies.add_private(
|
||||||
Cookie::build(("verify", pkce_verifier.secret().to_string()))
|
Cookie::build("verify", pkce_verifier.secret().to_string())
|
||||||
.http_only(true)
|
.http_only(true)
|
||||||
.path("/login")
|
.path("/login")
|
||||||
.same_site(SameSite::Lax)
|
.same_site(SameSite::Lax)
|
||||||
.expires(Expiration::Session),
|
.expires(Expiration::Session)
|
||||||
|
.finish(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// store the csrf token to verify no interference
|
// store the csrf token to verify no interference
|
||||||
cookies.add_private(
|
cookies.add_private(
|
||||||
Cookie::build(("csrf", csrf_token.secret().to_string()))
|
Cookie::build("csrf", csrf_token.secret().to_string())
|
||||||
.http_only(true)
|
.http_only(true)
|
||||||
.path("/login")
|
.path("/login")
|
||||||
.same_site(SameSite::Lax)
|
.same_site(SameSite::Lax)
|
||||||
.expires(Expiration::Session),
|
.expires(Expiration::Session)
|
||||||
|
.finish(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Redirect::to(auth_url.to_string())
|
Redirect::to(auth_url.to_string())
|
||||||
@ -52,9 +54,9 @@ pub async fn discord_login(
|
|||||||
|
|
||||||
#[get("/discord/logout")]
|
#[get("/discord/logout")]
|
||||||
pub async fn discord_logout(cookies: &CookieJar<'_>) -> Redirect {
|
pub async fn discord_logout(cookies: &CookieJar<'_>) -> Redirect {
|
||||||
cookies.remove_private(Cookie::from("username"));
|
cookies.remove_private(Cookie::named("username"));
|
||||||
cookies.remove_private(Cookie::from("userid"));
|
cookies.remove_private(Cookie::named("userid"));
|
||||||
cookies.remove_private(Cookie::from("access_token"));
|
cookies.remove_private(Cookie::named("access_token"));
|
||||||
|
|
||||||
Redirect::to(uri!(routes::index))
|
Redirect::to(uri!(routes::index))
|
||||||
}
|
}
|
||||||
@ -78,16 +80,17 @@ pub async fn discord_callback(
|
|||||||
.request_async(async_http_client)
|
.request_async(async_http_client)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
cookies.remove_private(Cookie::from("verify"));
|
cookies.remove_private(Cookie::named("verify"));
|
||||||
cookies.remove_private(Cookie::from("csrf"));
|
cookies.remove_private(Cookie::named("csrf"));
|
||||||
|
|
||||||
match token_result {
|
match token_result {
|
||||||
Ok(token) => {
|
Ok(token) => {
|
||||||
cookies.add_private(
|
cookies.add_private(
|
||||||
Cookie::build(("access_token", token.access_token().secret().to_string()))
|
Cookie::build("access_token", token.access_token().secret().to_string())
|
||||||
.secure(true)
|
.secure(true)
|
||||||
.http_only(true)
|
.http_only(true)
|
||||||
.path("/dashboard"),
|
.path("/dashboard")
|
||||||
|
.finish(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let request_res = reqwest_client
|
let request_res = reqwest_client
|
||||||
|
Reference in New Issue
Block a user