Move postman and web inside src

This commit is contained in:
jude 2024-03-24 20:23:16 +00:00
parent 075fde71df
commit 4a80d42f86
152 changed files with 833 additions and 779 deletions

896
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,7 @@ description = "Reminder Bot for Discord, now in Rust"
poise = "0.6.1" poise = "0.6.1"
dotenv = "0.15" dotenv = "0.15"
tokio = { version = "1", features = ["process", "full"] } tokio = { version = "1", features = ["process", "full"] }
reqwest = "0.11" reqwest = { version = "0.12", features = ["json"] }
lazy-regex = "3.1" lazy-regex = "3.1"
regex = "1.10" regex = "1.10"
log = "0.4" log = "0.4"
@ -29,12 +29,13 @@ sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "macros", "mysql",
base64 = "0.21" base64 = "0.21"
secrecy = "0.8.0" secrecy = "0.8.0"
futures = "0.3.30" futures = "0.3.30"
prometheus = "0.13.3"
[dependencies.postman] rocket = { version = "0.5.0", features = ["tls", "secrets", "json"] }
path = "postman" rocket_dyn_templates = { version = "0.1.0", features = ["tera"] }
serenity = { version = "0.12", default-features = false, features = ["builder", "cache", "client", "gateway", "http", "model", "utils", "rustls_backend"] }
[dependencies.reminder_web] oauth2 = "4"
path = "web" csv = "1.2"
axum = "0.7"
[dependencies.extract_derive] [dependencies.extract_derive]
path = "extract_derive" path = "extract_derive"

View File

@ -1,28 +1,28 @@
[default] [default]
address = "0.0.0.0" address = "0.0.0.0"
port = 18920 port = 18920
template_dir = "web/templates" template_dir = "templates"
limits = { json = "10MiB" } limits = { json = "10MiB" }
[debug] [debug]
secret_key = "tR8krio5FXTnnyIZNiJDXPondz0kI1v6X6BXZcBGIRY=" secret_key = "tR8krio5FXTnnyIZNiJDXPondz0kI1v6X6BXZcBGIRY="
[debug.tls] [debug.tls]
certs = "web/private/rsa_sha256_cert.pem" certs = "private/rsa_sha256_cert.pem"
key = "web/private/rsa_sha256_key.pem" key = "private/rsa_sha256_key.pem"
[debug.rsa_sha256.tls] [debug.rsa_sha256.tls]
certs = "web/private/rsa_sha256_cert.pem" certs = "private/rsa_sha256_cert.pem"
key = "web/private/rsa_sha256_key.pem" key = "private/rsa_sha256_key.pem"
[debug.ecdsa_nistp256_sha256.tls] [debug.ecdsa_nistp256_sha256.tls]
certs = "web/private/ecdsa_nistp256_sha256_cert.pem" certs = "private/ecdsa_nistp256_sha256_cert.pem"
key = "web/private/ecdsa_nistp256_sha256_key_pkcs8.pem" key = "private/ecdsa_nistp256_sha256_key_pkcs8.pem"
[debug.ecdsa_nistp384_sha384.tls] [debug.ecdsa_nistp384_sha384.tls]
certs = "web/private/ecdsa_nistp384_sha384_cert.pem" certs = "private/ecdsa_nistp384_sha384_cert.pem"
key = "web/private/ecdsa_nistp384_sha384_key_pkcs8.pem" key = "private/ecdsa_nistp384_sha384_key_pkcs8.pem"
[debug.ed25519.tls] [debug.ed25519.tls]
certs = "web/private/ed25519_cert.pem" certs = "private/ed25519_cert.pem"
key = "eb/private/ed25519_key.pem" key = "private/ed25519_key.pem"

View File

@ -1,16 +0,0 @@
[package]
name = "postman"
version = "0.1.0"
edition = "2021"
[dependencies]
tokio = { version = "1", features = ["process", "full"] }
regex = "1.9"
log = "0.4"
chrono = "0.4"
chrono-tz = { version = "0.8", features = ["serde"] }
lazy_static = "1.4"
num-integer = "0.1"
serde = "1.0"
sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "macros", "mysql", "bigdecimal", "chrono", "json"]}
serenity = { version = "0.12", default-features = false, features = ["builder", "cache", "client", "gateway", "http", "model", "utils", "rustls_backend"] }

View File

@ -1,4 +1,4 @@
use chrono::NaiveDateTime; use chrono::DateTime;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::{
@ -24,10 +24,10 @@ impl Recordable for Options {
let parsed = natural_parser(&until, &timezone.to_string()).await; let parsed = natural_parser(&until, &timezone.to_string()).await;
if let Some(timestamp) = parsed { if let Some(timestamp) = parsed {
match NaiveDateTime::from_timestamp_opt(timestamp, 0) { match DateTime::from_timestamp(timestamp, 0) {
Some(dt) => { Some(dt) => {
channel.paused = true; channel.paused = true;
channel.paused_until = Some(dt); channel.paused_until = Some(dt.naive_utc());
channel.commit_changes(&ctx.data().database).await; channel.commit_changes(&ctx.data().database).await;

View File

@ -12,12 +12,15 @@ mod event_handlers;
#[cfg(not(test))] #[cfg(not(test))]
mod hooks; mod hooks;
mod interval_parser; mod interval_parser;
mod metrics;
#[cfg(not(test))] #[cfg(not(test))]
mod models; mod models;
mod postman;
#[cfg(test)] #[cfg(test)]
mod test; mod test;
mod time_parser; mod time_parser;
mod utils; mod utils;
mod web;
use std::{ use std::{
collections::HashMap, collections::HashMap,
@ -28,7 +31,7 @@ use std::{
}; };
use chrono_tz::Tz; use chrono_tz::Tz;
use log::{error, warn}; use log::warn;
use poise::serenity_prelude::{ use poise::serenity_prelude::{
model::{ model::{
gateway::GatewayIntents, gateway::GatewayIntents,
@ -39,6 +42,7 @@ use poise::serenity_prelude::{
use sqlx::{MySql, Pool}; use sqlx::{MySql, Pool};
use tokio::sync::{broadcast, broadcast::Sender, RwLock}; use tokio::sync::{broadcast, broadcast::Sender, RwLock};
use crate::metrics::init_metrics;
#[cfg(test)] #[cfg(test)]
use crate::test::TestContext; use crate::test::TestContext;
#[cfg(not(test))] #[cfg(not(test))]
@ -206,6 +210,10 @@ async fn _main(tx: Sender<()>) -> Result<(), Box<dyn StdError + Send + Sync>> {
..Default::default() ..Default::default()
}; };
// Start metrics
init_metrics();
tokio::spawn(async { metrics::serve().await });
let database = let database =
Pool::connect(&env::var("DATABASE_URL").expect("No database URL provided")).await.unwrap(); Pool::connect(&env::var("DATABASE_URL").expect("No database URL provided")).await.unwrap();
@ -249,7 +257,7 @@ async fn _main(tx: Sender<()>) -> Result<(), Box<dyn StdError + Send + Sync>> {
match postman::initialize(kill_recv, ctx1, &pool1).await { match postman::initialize(kill_recv, ctx1, &pool1).await {
Ok(_) => {} Ok(_) => {}
Err(e) => { Err(e) => {
error!("postman exiting: {}", e); panic!("postman exiting: {}", e);
} }
}; };
}); });
@ -259,7 +267,7 @@ async fn _main(tx: Sender<()>) -> Result<(), Box<dyn StdError + Send + Sync>> {
if !run_settings.contains("web") { if !run_settings.contains("web") {
tokio::spawn(async move { tokio::spawn(async move {
reminder_web::initialize(kill_tx, ctx2, pool2).await.unwrap(); web::initialize(kill_tx, ctx2, pool2).await.unwrap();
}); });
} else { } else {
warn!("Not running web"); warn!("Not running web");

36
src/metrics.rs Normal file
View File

@ -0,0 +1,36 @@
use axum::{routing::get, Router};
use lazy_static::lazy_static;
use log::warn;
use prometheus::{IntCounterVec, Opts, Registry};
lazy_static! {
pub static ref REGISTRY: Registry = Registry::new();
pub static ref REQUEST_COUNTER: IntCounterVec =
IntCounterVec::new(Opts::new("requests", "Requests"), &["method", "status", "route"])
.unwrap();
}
pub fn init_metrics() {
REGISTRY.register(Box::new(REQUEST_COUNTER.clone())).unwrap();
}
pub async fn serve() {
let app = Router::new().route("/metrics", get(metrics));
let listener = tokio::net::TcpListener::bind("localhost:31756").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
async fn metrics() -> String {
let encoder = prometheus::TextEncoder::new();
let res_custom = encoder.encode_to_string(&REGISTRY.gather());
match res_custom {
Ok(s) => s,
Err(e) => {
warn!("Error encoding metrics: {:?}", e);
String::new()
}
}
}

View File

@ -1,6 +1,6 @@
use std::collections::HashSet; use std::collections::HashSet;
use chrono::{Duration, NaiveDateTime, Utc}; use chrono::{DateTime, NaiveDateTime, TimeDelta, Utc};
use chrono_tz::Tz; use chrono_tz::Tz;
use poise::serenity_prelude::{ use poise::serenity_prelude::{
model::id::{ChannelId, GuildId, UserId}, model::id::{ChannelId, GuildId, UserId},
@ -73,7 +73,7 @@ impl ReminderBuilder {
match queried_time.utc_time { match queried_time.utc_time {
Some(utc_time) => { Some(utc_time) => {
if utc_time < (Utc::now() - Duration::seconds(60)).naive_local() { if utc_time < (Utc::now() - TimeDelta::try_minutes(1).unwrap()).naive_local() {
Err(ReminderError::PastTime) Err(ReminderError::PastTime)
} else { } else {
sqlx::query!( sqlx::query!(
@ -165,7 +165,7 @@ impl<'a> MultiReminderBuilder<'a> {
} }
pub fn time<T: Into<i64>>(mut self, time: T) -> Self { pub fn time<T: Into<i64>>(mut self, time: T) -> Self {
if let Some(utc_time) = NaiveDateTime::from_timestamp_opt(time.into(), 0) { if let Some(utc_time) = DateTime::from_timestamp(time.into(), 0).map(|d| d.naive_utc()) {
self.utc_time = utc_time; self.utc_time = utc_time;
} }
@ -173,7 +173,8 @@ impl<'a> MultiReminderBuilder<'a> {
} }
pub fn expires<T: Into<i64>>(mut self, time: Option<T>) -> Self { pub fn expires<T: Into<i64>>(mut self, time: Option<T>) -> Self {
self.expires = time.map(|t| NaiveDateTime::from_timestamp_opt(t.into(), 0)).flatten(); self.expires =
time.map(|t| DateTime::from_timestamp(t.into(), 0)).flatten().map(|d| d.naive_utc());
self self
} }

View File

@ -38,6 +38,7 @@ use crate::{
}; };
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct Reminder { pub struct Reminder {
pub id: u32, pub id: u32,
pub uid: String, pub uid: String,

View File

@ -4,6 +4,7 @@ use sqlx::MySqlPool;
pub struct Timer { pub struct Timer {
pub name: String, pub name: String,
pub start_time: DateTime<Utc>, pub start_time: DateTime<Utc>,
#[allow(dead_code)]
pub owner: u64, pub owner: u64,
} }

View File

@ -7,6 +7,7 @@ use crate::consts::LOCAL_TIMEZONE;
pub struct UserData { pub struct UserData {
pub id: u32, pub id: u32,
#[allow(dead_code)]
pub user: u64, pub user: u64,
pub dm_channel: u32, pub dm_channel: u32,
pub timezone: String, pub timezone: String,
@ -22,7 +23,7 @@ impl UserData {
match sqlx::query!( match sqlx::query!(
" "
SELECT IFNULL(timezone, 'UTC') AS timezone FROM users WHERE user = ? SELECT IFNULL(timezone, 'UTC') AS timezone FROM users WHERE user = ?
", ",
user_id user_id
) )

40
src/postman/metrics.rs Normal file
View File

@ -0,0 +1,40 @@
use axum::{routing::get, Router};
use lazy_static;
use log::warn;
use prometheus::{register_int_counter, IntCounter, Registry};
lazy_static! {
static ref REGISTRY: Registry = Registry::new();
pub static ref REMINDERS_SENT: IntCounter =
register_int_counter!("reminders_sent", "Number of reminders sent").unwrap();
pub static ref REMINDERS_FAILED: IntCounter =
register_int_counter!("reminders_failed", "Number of reminders failed").unwrap();
}
pub fn init_metrics() {
REGISTRY.register(Box::new(REMINDERS_SENT.clone())).unwrap();
REGISTRY.register(Box::new(REMINDERS_FAILED.clone())).unwrap();
}
pub async fn serve() {
let app = Router::new().route("/metrics", get(metrics));
let metrics_port = std::env("PROMETHEUS_PORT").unwrap();
let listener =
tokio::net::TcpListener::bind(format!("localhost:{}", metrics_port)).await.unwrap();
axum::serve(listener, app).await.unwrap();
}
async fn metrics() -> String {
let encoder = prometheus::TextEncoder::new();
let res_custom = encoder.encode_to_string(&REGISTRY.gather());
match res_custom {
Ok(s) => s,
Err(e) => {
warn!("Error encoding metrics: {:?}", e);
String::new()
}
}
}

View File

@ -3,7 +3,7 @@ mod sender;
use std::env; use std::env;
use log::{info, warn}; use log::{info, warn};
use serenity::client::Context; use poise::serenity_prelude::client::Context;
use sqlx::{Executor, MySql}; use sqlx::{Executor, MySql};
use tokio::{ use tokio::{
sync::broadcast::Receiver, sync::broadcast::Receiver,

View File

@ -1,13 +1,11 @@
use std::env; use std::env;
use chrono::{DateTime, Days, Duration, Months}; use chrono::{DateTime, Days, Months, TimeDelta};
use chrono_tz::Tz; use chrono_tz::Tz;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use log::{error, info, warn}; use log::{error, info, warn};
use num_integer::Integer; use num_integer::Integer;
use regex::{Captures, Regex}; use poise::serenity_prelude::{
use serde::Deserialize;
use serenity::{
all::{CreateAttachment, CreateEmbedFooter}, all::{CreateAttachment, CreateEmbedFooter},
builder::{CreateEmbed, CreateEmbedAuthor, CreateMessage, ExecuteWebhook}, builder::{CreateEmbed, CreateEmbedAuthor, CreateMessage, ExecuteWebhook},
http::{CacheHttp, Http, HttpError}, http::{CacheHttp, Http, HttpError},
@ -18,6 +16,8 @@ use serenity::{
}, },
Error, Result, Error, Result,
}; };
use regex::{Captures, Regex};
use serde::Deserialize;
use sqlx::{ use sqlx::{
types::{ types::{
chrono::{NaiveDateTime, Utc}, chrono::{NaiveDateTime, Utc},
@ -66,15 +66,15 @@ pub fn substitute(string: &str) -> String {
let format = caps.name("format").map(|m| m.as_str()); let format = caps.name("format").map(|m| m.as_str());
if let (Some(final_time), Some(format)) = (final_time, format) { if let (Some(final_time), Some(format)) = (final_time, format) {
match NaiveDateTime::from_timestamp_opt(final_time, 0) { match DateTime::from_timestamp(final_time, 0) {
Some(dt) => { Some(dt) => {
let now = Utc::now().naive_utc(); let now = Utc::now();
let difference = { let difference = {
if now < dt { if now < dt {
dt - Utc::now().naive_utc() dt - Utc::now()
} else { } else {
Utc::now().naive_utc() - dt Utc::now() - dt
} }
}; };
@ -397,7 +397,13 @@ impl Reminder {
} }
if let Some(interval) = self.interval_seconds { if let Some(interval) = self.interval_seconds {
updated_reminder_time += Duration::seconds(interval as i64); updated_reminder_time += TimeDelta::try_seconds(interval as i64)
.unwrap_or_else(|| {
warn!("{}: Could not add {} seconds to a reminder", self.id, interval);
fail_count += 1;
TimeDelta::zero()
});
} }
} }

View File

@ -1,9 +1,9 @@
use std::collections::HashMap; use std::collections::HashMap;
use rocket::serde::json::json; use rocket::{catch, serde::json::json};
use rocket_dyn_templates::Template; use rocket_dyn_templates::Template;
use crate::JsonValue; use crate::web::JsonValue;
#[catch(403)] #[catch(403)]
pub(crate) async fn forbidden() -> Template { pub(crate) async fn forbidden() -> Template {

View File

@ -20,14 +20,14 @@ pub const DAY: usize = 24 * HOUR;
pub const CHARACTERS: &str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"; pub const CHARACTERS: &str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
use std::{collections::HashSet, env, iter::FromIterator}; use std::{collections::HashSet, env};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use serenity::builder::CreateAttachment; use serenity::builder::CreateAttachment;
lazy_static! { lazy_static! {
pub static ref DEFAULT_AVATAR: CreateAttachment = CreateAttachment::bytes( pub static ref DEFAULT_AVATAR: CreateAttachment = CreateAttachment::bytes(
include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/../assets/webhook.jpg")) as &[u8], include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/webhook.jpg")) as &[u8],
"webhook.jpg", "webhook.jpg",
); );
pub static ref SUBSCRIPTION_ROLES: HashSet<u64> = HashSet::from_iter( pub static ref SUBSCRIPTION_ROLES: HashSet<u64> = HashSet::from_iter(

View File

@ -20,6 +20,7 @@ impl Transaction<'_> {
} }
#[derive(Debug)] #[derive(Debug)]
#[allow(dead_code)]
pub enum TransactionError { pub enum TransactionError {
Error(sqlx::Error), Error(sqlx::Error),
Missing, Missing,

27
src/web/metrics.rs Normal file
View File

@ -0,0 +1,27 @@
use rocket::{
fairing::{Fairing, Info, Kind},
Request, Response,
};
use crate::metrics::REQUEST_COUNTER;
pub struct MetricProducer;
#[rocket::async_trait]
impl Fairing for MetricProducer {
fn info(&self) -> Info {
Info { name: "Metrics fairing", kind: Kind::Response }
}
async fn on_response<'r>(&self, req: &'r Request<'_>, resp: &mut Response<'r>) {
if let Some(route) = req.route() {
REQUEST_COUNTER
.with_label_values(&[
req.method().as_str(),
&resp.status().code.to_string(),
&route.uri.to_string(),
])
.inc();
}
}
}

View File

@ -1,6 +1,3 @@
#[macro_use]
extern crate rocket;
mod consts; mod consts;
#[macro_use] #[macro_use]
mod macros; mod macros;
@ -11,24 +8,27 @@ mod routes;
use std::{env, path::Path}; use std::{env, path::Path};
use log::{error, info, warn};
use oauth2::{basic::BasicClient, AuthUrl, ClientId, ClientSecret, RedirectUrl, TokenUrl}; use oauth2::{basic::BasicClient, AuthUrl, ClientId, ClientSecret, RedirectUrl, TokenUrl};
use rocket::{ use poise::serenity_prelude::{
fs::FileServer,
http::CookieJar,
serde::json::{json, Value as JsonValue},
tokio::sync::broadcast::Sender,
};
use rocket_dyn_templates::Template;
use serenity::{
client::Context, client::Context,
http::CacheHttp, http::CacheHttp,
model::id::{GuildId, UserId}, model::id::{GuildId, UserId},
}; };
use rocket::{
catchers,
fs::FileServer,
http::CookieJar,
routes,
serde::json::{json, Value as JsonValue},
tokio::sync::broadcast::Sender,
};
use rocket_dyn_templates::Template;
use sqlx::{MySql, Pool}; use sqlx::{MySql, Pool};
use crate::{ use crate::web::{
consts::{CNC_GUILD, DISCORD_OAUTH_AUTHORIZE, DISCORD_OAUTH_TOKEN, SUBSCRIPTION_ROLES}, consts::{CNC_GUILD, DISCORD_OAUTH_AUTHORIZE, DISCORD_OAUTH_TOKEN, SUBSCRIPTION_ROLES},
metrics::{init_metrics, MetricProducer}, metrics::MetricProducer,
}; };
type Database = MySql; type Database = MySql;
@ -68,9 +68,7 @@ pub async fn initialize(
let reqwest_client = reqwest::Client::new(); let reqwest_client = reqwest::Client::new();
let static_path = let static_path =
if Path::new("web/static").exists() { "web/static" } else { "/lib/reminder-rs/static" }; if Path::new("static").exists() { "static" } else { "/lib/reminder-rs/static" };
init_metrics();
rocket::build() rocket::build()
.attach(MetricProducer) .attach(MetricProducer)
@ -96,7 +94,6 @@ pub async fn initialize(
routes![ routes![
routes::cookies, routes::cookies,
routes::index, routes::index,
routes::metrics::metrics,
routes::privacy, routes::privacy,
routes::report::report_error, routes::report::report_error,
routes::return_to_same_site, routes::return_to_same_site,
@ -154,7 +151,6 @@ pub async fn initialize(
routes::dashboard::export::import_todos, routes::dashboard::export::import_todos,
], ],
) )
.mount("/admin", routes![routes::admin::admin_dashboard_home, routes::admin::bot_data])
.launch() .launch()
.await?; .await?;

View File

@ -1,4 +1,4 @@
use rocket::{http::CookieJar, serde::json::json, State}; use rocket::{get, http::CookieJar, serde::json::json, State};
use serde::Serialize; use serde::Serialize;
use serenity::{ use serenity::{
client::Context, client::Context,
@ -8,7 +8,7 @@ use serenity::{
}, },
}; };
use crate::{check_authorization, routes::JsonResult}; use crate::web::{check_authorization, routes::JsonResult};
#[derive(Serialize)] #[derive(Serialize)]
struct ChannelInfo { struct ChannelInfo {

View File

@ -7,7 +7,7 @@ use std::env;
pub use channels::*; pub use channels::*;
pub use reminders::*; pub use reminders::*;
use rocket::{http::CookieJar, serde::json::json, State}; use rocket::{get, http::CookieJar, serde::json::json, State};
pub use roles::*; pub use roles::*;
use serenity::{ use serenity::{
client::Context, client::Context,
@ -15,7 +15,7 @@ use serenity::{
}; };
pub use templates::*; pub use templates::*;
use crate::{check_authorization, routes::JsonResult}; use crate::web::{check_authorization, routes::JsonResult};
#[get("/api/guild/<id>")] #[get("/api/guild/<id>")]
pub async fn get_guild_info(id: u64, cookies: &CookieJar<'_>, ctx: &State<Context>) -> JsonResult { pub async fn get_guild_info(id: u64, cookies: &CookieJar<'_>, ctx: &State<Context>) -> JsonResult {

View File

@ -1,5 +1,8 @@
use log::warn;
use rocket::{ use rocket::{
get,
http::CookieJar, http::CookieJar,
patch, post,
serde::json::{json, Json}, serde::json::{json, Json},
State, State,
}; };
@ -9,7 +12,7 @@ use serenity::{
}; };
use sqlx::{MySql, Pool}; use sqlx::{MySql, Pool};
use crate::{ use crate::web::{
check_authorization, check_guild_subscription, check_subscription, check_authorization, check_guild_subscription, check_subscription,
consts::MIN_INTERVAL, consts::MIN_INTERVAL,
guards::transaction::Transaction, guards::transaction::Transaction,

View File

@ -1,8 +1,9 @@
use rocket::{http::CookieJar, serde::json::json, State}; use log::warn;
use rocket::{get, http::CookieJar, serde::json::json, State};
use serde::Serialize; use serde::Serialize;
use serenity::client::Context; use serenity::client::Context;
use crate::{check_authorization, routes::JsonResult}; use crate::web::{check_authorization, routes::JsonResult};
#[derive(Serialize)] #[derive(Serialize)]
struct RoleInfo { struct RoleInfo {

View File

@ -1,12 +1,15 @@
use log::warn;
use rocket::{ use rocket::{
delete, get,
http::CookieJar, http::CookieJar,
post,
serde::json::{json, Json}, serde::json::{json, Json},
State, State,
}; };
use serenity::client::Context; use serenity::client::Context;
use sqlx::{MySql, Pool}; use sqlx::{MySql, Pool};
use crate::{ use crate::web::{
check_authorization, check_authorization,
consts::{ consts::{
MAX_CONTENT_LENGTH, MAX_EMBED_AUTHOR_LENGTH, MAX_EMBED_DESCRIPTION_LENGTH, MAX_CONTENT_LENGTH, MAX_EMBED_AUTHOR_LENGTH, MAX_EMBED_DESCRIPTION_LENGTH,

View File

@ -1,14 +1,16 @@
pub mod guild; pub mod guild;
pub mod user; pub mod user;
use log::warn;
use rocket::{ use rocket::{
delete,
http::CookieJar, http::CookieJar,
serde::json::{json, Json}, serde::json::{json, Json},
State, State,
}; };
use sqlx::{MySql, Pool}; use sqlx::{MySql, Pool};
use crate::routes::{dashboard::DeleteReminder, JsonResult}; use crate::web::routes::{dashboard::DeleteReminder, JsonResult};
#[delete("/api/reminders", data = "<reminder>")] #[delete("/api/reminders", data = "<reminder>")]
pub async fn delete_reminder( pub async fn delete_reminder(

View File

@ -1,5 +1,7 @@
use log::warn;
use reqwest::Client; use reqwest::Client;
use rocket::{ use rocket::{
get,
http::CookieJar, http::CookieJar,
serde::json::{json, Value as JsonValue}, serde::json::{json, Value as JsonValue},
State, State,
@ -7,7 +9,7 @@ use rocket::{
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serenity::model::{id::GuildId, permissions::Permissions}; use serenity::model::{id::GuildId, permissions::Permissions};
use crate::consts::DISCORD_API; use crate::web::consts::DISCORD_API;
#[derive(Serialize)] #[derive(Serialize)]
struct GuildInfo { struct GuildInfo {

View File

@ -8,7 +8,9 @@ use chrono_tz::Tz;
pub use guilds::*; pub use guilds::*;
pub use reminders::*; pub use reminders::*;
use rocket::{ use rocket::{
get,
http::CookieJar, http::CookieJar,
patch,
serde::json::{json, Json, Value as JsonValue}, serde::json::{json, Json, Value as JsonValue},
State, State,
}; };

View File

@ -1,11 +1,12 @@
use chrono::{naive::NaiveDateTime, Utc}; use chrono::{naive::NaiveDateTime, Utc};
use futures::TryFutureExt; use futures::TryFutureExt;
use log::warn;
use rocket::serde::json::json; use rocket::serde::json::json;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serenity::{client::Context, futures, model::id::UserId}; use serenity::{client::Context, model::id::UserId};
use sqlx::types::Json; use sqlx::types::Json;
use crate::{ use crate::web::{
check_subscription, check_subscription,
consts::{ consts::{
DAY, MAX_CONTENT_LENGTH, MAX_EMBED_AUTHOR_LENGTH, MAX_EMBED_DESCRIPTION_LENGTH, DAY, MAX_CONTENT_LENGTH, MAX_EMBED_AUTHOR_LENGTH, MAX_EMBED_DESCRIPTION_LENGTH,
@ -15,10 +16,7 @@ use crate::{
}, },
guards::transaction::Transaction, guards::transaction::Transaction,
routes::{ routes::{
dashboard::{ dashboard::{create_database_channel, generate_uid, name_default, Attachment, EmbedField},
create_database_channel, deserialize_optional_field, generate_uid, interval_default,
name_default, Attachment, EmbedField, Unset,
},
JsonResult, JsonResult,
}, },
Error, Error,
@ -231,60 +229,3 @@ 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>,
}

View File

@ -1,12 +1,15 @@
use log::warn;
use rocket::{ use rocket::{
get,
http::CookieJar, http::CookieJar,
patch, post,
serde::json::{json, Json}, serde::json::{json, Json},
State, State,
}; };
use serenity::{client::Context, model::id::UserId}; use serenity::{client::Context, model::id::UserId};
use sqlx::{MySql, Pool}; use sqlx::{MySql, Pool};
use crate::{ use crate::web::{
check_subscription, check_subscription,
guards::transaction::Transaction, guards::transaction::Transaction,
routes::{ routes::{

View File

@ -1,7 +1,11 @@
use base64::{prelude::BASE64_STANDARD, Engine};
use csv::{QuoteStyle, WriterBuilder}; use csv::{QuoteStyle, WriterBuilder};
use log::warn;
use rocket::{ use rocket::{
get,
http::CookieJar, http::CookieJar,
serde::json::{json, serde_json, Json}, put,
serde::json::{json, Json},
State, State,
}; };
use serenity::{ use serenity::{
@ -10,7 +14,7 @@ use serenity::{
}; };
use sqlx::{MySql, Pool}; use sqlx::{MySql, Pool};
use crate::{ use crate::web::{
check_authorization, check_authorization,
guards::transaction::Transaction, guards::transaction::Transaction,
routes::{ routes::{
@ -134,7 +138,7 @@ pub(crate) async fn import_reminders(
let user_id = let user_id =
cookies.get_private("userid").map(|c| c.value().parse::<u64>().ok()).flatten().unwrap(); cookies.get_private("userid").map(|c| c.value().parse::<u64>().ok()).flatten().unwrap();
match base64::decode(&body.body) { match BASE64_STANDARD.decode(&body.body) {
Ok(body) => { Ok(body) => {
let mut reader = csv::Reader::from_reader(body.as_slice()); let mut reader = csv::Reader::from_reader(body.as_slice());
let mut count = 0; let mut count = 0;
@ -292,7 +296,7 @@ pub async fn import_todos(
let channels_res = GuildId::new(id).channels(&ctx.inner()).await; let channels_res = GuildId::new(id).channels(&ctx.inner()).await;
match channels_res { match channels_res {
Ok(channels) => match base64::decode(&body.body) { Ok(channels) => match BASE64_STANDARD.decode(&body.body) {
Ok(body) => { Ok(body) => {
let mut reader = csv::Reader::from_reader(body.as_slice()); let mut reader = csv::Reader::from_reader(body.as_slice());

View File

@ -1,8 +1,12 @@
use std::path::Path; use std::path::Path;
use base64::{prelude::BASE64_STANDARD, Engine};
use chrono::{naive::NaiveDateTime, Utc}; use chrono::{naive::NaiveDateTime, Utc};
use log::warn;
use rand::{rngs::OsRng, seq::IteratorRandom}; use rand::{rngs::OsRng, seq::IteratorRandom};
use rocket::{fs::NamedFile, http::CookieJar, response::Redirect, serde::json::json}; use rocket::{
fs::NamedFile, get, http::CookieJar, response::Redirect, serde::json::json, Responder,
};
use rocket_dyn_templates::Template; use rocket_dyn_templates::Template;
use secrecy::ExposeSecret; use secrecy::ExposeSecret;
use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
@ -14,7 +18,7 @@ use serenity::{
}; };
use sqlx::types::Json; use sqlx::types::Json;
use crate::{ use crate::web::{
catchers::internal_server_error, catchers::internal_server_error,
check_guild_subscription, check_subscription, check_guild_subscription, check_subscription,
consts::{ consts::{
@ -63,7 +67,7 @@ impl<'de> Deserialize<'de> for Attachment {
D: Deserializer<'de>, D: Deserializer<'de>,
{ {
let string = String::deserialize(deserializer)?; let string = String::deserialize(deserializer)?;
Ok(Attachment(base64::decode(string).map_err(de::Error::custom)?)) Ok(Attachment(BASE64_STANDARD.decode(string).map_err(de::Error::custom)?))
} }
} }
@ -72,7 +76,7 @@ impl Serialize for Attachment {
where where
S: Serializer, S: Serializer,
{ {
serializer.collect_str(&base64::encode(&self.0)) serializer.collect_str(&BASE64_STANDARD.encode(&self.0))
} }
} }
@ -595,7 +599,7 @@ async fn create_database_channel(
ctx: impl CacheHttp, ctx: impl CacheHttp,
channel: ChannelId, channel: ChannelId,
transaction: &mut Transaction<'_>, transaction: &mut Transaction<'_>,
) -> Result<u32, crate::Error> { ) -> Result<u32, Error> {
let row = sqlx::query!( let row = sqlx::query!(
"SELECT webhook_token, webhook_id FROM channels WHERE channel = ?", "SELECT webhook_token, webhook_id FROM channels WHERE channel = ?",
channel.get() channel.get()

View File

@ -5,13 +5,14 @@ use oauth2::{
}; };
use reqwest::Client; use reqwest::Client;
use rocket::{ use rocket::{
get,
http::{private::cookie::Expiration, Cookie, CookieJar, SameSite}, http::{private::cookie::Expiration, Cookie, CookieJar, SameSite},
response::{Flash, Redirect}, response::{Flash, Redirect},
uri, State, uri, State,
}; };
use serenity::model::user::User; use serenity::model::user::User;
use crate::{consts::DISCORD_API, routes}; use crate::web::{consts::DISCORD_API, routes};
#[get("/discord")] #[get("/discord")]
pub async fn discord_login( pub async fn discord_login(

View File

@ -1,12 +1,10 @@
pub mod admin;
pub mod dashboard; pub mod dashboard;
pub mod login; pub mod login;
pub mod metrics;
pub mod report; pub mod report;
use std::collections::HashMap; use std::collections::HashMap;
use rocket::{request::FlashMessage, serde::json::Value as JsonValue}; use rocket::{get, request::FlashMessage, serde::json::Value as JsonValue};
use rocket_dyn_templates::Template; use rocket_dyn_templates::Template;
pub type JsonResult = Result<JsonValue, JsonValue>; pub type JsonResult = Result<JsonValue, JsonValue>;

View File

@ -1,12 +1,14 @@
use log::error;
use rocket::{ use rocket::{
http::CookieJar, http::CookieJar,
post,
serde::{ serde::{
json::{json, Json}, json::{json, Json},
Deserialize, Deserialize,
}, },
}; };
use crate::routes::JsonResult; use crate::web::routes::JsonResult;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct ClientError { pub struct ClientError {

View File

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

Before

Width:  |  Height:  |  Size: 762 B

After

Width:  |  Height:  |  Size: 762 B

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 323 KiB

After

Width:  |  Height:  |  Size: 323 KiB

View File

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 61 KiB

View File

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 81 KiB

View File

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

View File

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

View File

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View File

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

View File

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 65 KiB

1
static/index.html Symbolic link
View File

@ -0,0 +1 @@
/home/jude/reminder-bot/reminder-dashboard/dist/index.html

View File

Before

Width:  |  Height:  |  Size: 712 KiB

After

Width:  |  Height:  |  Size: 712 KiB

Some files were not shown because too many files have changed in this diff Show More