Move postman and web inside src
896
Cargo.lock
generated
15
Cargo.toml
@ -10,7 +10,7 @@ description = "Reminder Bot for Discord, now in Rust"
|
||||
poise = "0.6.1"
|
||||
dotenv = "0.15"
|
||||
tokio = { version = "1", features = ["process", "full"] }
|
||||
reqwest = "0.11"
|
||||
reqwest = { version = "0.12", features = ["json"] }
|
||||
lazy-regex = "3.1"
|
||||
regex = "1.10"
|
||||
log = "0.4"
|
||||
@ -29,12 +29,13 @@ sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "macros", "mysql",
|
||||
base64 = "0.21"
|
||||
secrecy = "0.8.0"
|
||||
futures = "0.3.30"
|
||||
|
||||
[dependencies.postman]
|
||||
path = "postman"
|
||||
|
||||
[dependencies.reminder_web]
|
||||
path = "web"
|
||||
prometheus = "0.13.3"
|
||||
rocket = { version = "0.5.0", features = ["tls", "secrets", "json"] }
|
||||
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"] }
|
||||
oauth2 = "4"
|
||||
csv = "1.2"
|
||||
axum = "0.7"
|
||||
|
||||
[dependencies.extract_derive]
|
||||
path = "extract_derive"
|
||||
|
22
Rocket.toml
@ -1,28 +1,28 @@
|
||||
[default]
|
||||
address = "0.0.0.0"
|
||||
port = 18920
|
||||
template_dir = "web/templates"
|
||||
template_dir = "templates"
|
||||
limits = { json = "10MiB" }
|
||||
|
||||
[debug]
|
||||
secret_key = "tR8krio5FXTnnyIZNiJDXPondz0kI1v6X6BXZcBGIRY="
|
||||
|
||||
[debug.tls]
|
||||
certs = "web/private/rsa_sha256_cert.pem"
|
||||
key = "web/private/rsa_sha256_key.pem"
|
||||
certs = "private/rsa_sha256_cert.pem"
|
||||
key = "private/rsa_sha256_key.pem"
|
||||
|
||||
[debug.rsa_sha256.tls]
|
||||
certs = "web/private/rsa_sha256_cert.pem"
|
||||
key = "web/private/rsa_sha256_key.pem"
|
||||
certs = "private/rsa_sha256_cert.pem"
|
||||
key = "private/rsa_sha256_key.pem"
|
||||
|
||||
[debug.ecdsa_nistp256_sha256.tls]
|
||||
certs = "web/private/ecdsa_nistp256_sha256_cert.pem"
|
||||
key = "web/private/ecdsa_nistp256_sha256_key_pkcs8.pem"
|
||||
certs = "private/ecdsa_nistp256_sha256_cert.pem"
|
||||
key = "private/ecdsa_nistp256_sha256_key_pkcs8.pem"
|
||||
|
||||
[debug.ecdsa_nistp384_sha384.tls]
|
||||
certs = "web/private/ecdsa_nistp384_sha384_cert.pem"
|
||||
key = "web/private/ecdsa_nistp384_sha384_key_pkcs8.pem"
|
||||
certs = "private/ecdsa_nistp384_sha384_cert.pem"
|
||||
key = "private/ecdsa_nistp384_sha384_key_pkcs8.pem"
|
||||
|
||||
[debug.ed25519.tls]
|
||||
certs = "web/private/ed25519_cert.pem"
|
||||
key = "eb/private/ed25519_key.pem"
|
||||
certs = "private/ed25519_cert.pem"
|
||||
key = "private/ed25519_key.pem"
|
||||
|
@ -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"] }
|
@ -1,4 +1,4 @@
|
||||
use chrono::NaiveDateTime;
|
||||
use chrono::DateTime;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
@ -24,10 +24,10 @@ impl Recordable for Options {
|
||||
let parsed = natural_parser(&until, &timezone.to_string()).await;
|
||||
|
||||
if let Some(timestamp) = parsed {
|
||||
match NaiveDateTime::from_timestamp_opt(timestamp, 0) {
|
||||
match DateTime::from_timestamp(timestamp, 0) {
|
||||
Some(dt) => {
|
||||
channel.paused = true;
|
||||
channel.paused_until = Some(dt);
|
||||
channel.paused_until = Some(dt.naive_utc());
|
||||
|
||||
channel.commit_changes(&ctx.data().database).await;
|
||||
|
||||
|
14
src/main.rs
@ -12,12 +12,15 @@ mod event_handlers;
|
||||
#[cfg(not(test))]
|
||||
mod hooks;
|
||||
mod interval_parser;
|
||||
mod metrics;
|
||||
#[cfg(not(test))]
|
||||
mod models;
|
||||
mod postman;
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
mod time_parser;
|
||||
mod utils;
|
||||
mod web;
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
@ -28,7 +31,7 @@ use std::{
|
||||
};
|
||||
|
||||
use chrono_tz::Tz;
|
||||
use log::{error, warn};
|
||||
use log::warn;
|
||||
use poise::serenity_prelude::{
|
||||
model::{
|
||||
gateway::GatewayIntents,
|
||||
@ -39,6 +42,7 @@ use poise::serenity_prelude::{
|
||||
use sqlx::{MySql, Pool};
|
||||
use tokio::sync::{broadcast, broadcast::Sender, RwLock};
|
||||
|
||||
use crate::metrics::init_metrics;
|
||||
#[cfg(test)]
|
||||
use crate::test::TestContext;
|
||||
#[cfg(not(test))]
|
||||
@ -206,6 +210,10 @@ async fn _main(tx: Sender<()>) -> Result<(), Box<dyn StdError + Send + Sync>> {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// Start metrics
|
||||
init_metrics();
|
||||
tokio::spawn(async { metrics::serve().await });
|
||||
|
||||
let database =
|
||||
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 {
|
||||
Ok(_) => {}
|
||||
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") {
|
||||
tokio::spawn(async move {
|
||||
reminder_web::initialize(kill_tx, ctx2, pool2).await.unwrap();
|
||||
web::initialize(kill_tx, ctx2, pool2).await.unwrap();
|
||||
});
|
||||
} else {
|
||||
warn!("Not running web");
|
||||
|
36
src/metrics.rs
Normal 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(®ISTRY.gather());
|
||||
|
||||
match res_custom {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
warn!("Error encoding metrics: {:?}", e);
|
||||
|
||||
String::new()
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use chrono::{Duration, NaiveDateTime, Utc};
|
||||
use chrono::{DateTime, NaiveDateTime, TimeDelta, Utc};
|
||||
use chrono_tz::Tz;
|
||||
use poise::serenity_prelude::{
|
||||
model::id::{ChannelId, GuildId, UserId},
|
||||
@ -73,7 +73,7 @@ impl ReminderBuilder {
|
||||
|
||||
match queried_time.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)
|
||||
} else {
|
||||
sqlx::query!(
|
||||
@ -165,7 +165,7 @@ impl<'a> MultiReminderBuilder<'a> {
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@ -173,7 +173,8 @@ impl<'a> MultiReminderBuilder<'a> {
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ use crate::{
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[allow(dead_code)]
|
||||
pub struct Reminder {
|
||||
pub id: u32,
|
||||
pub uid: String,
|
||||
|
@ -4,6 +4,7 @@ use sqlx::MySqlPool;
|
||||
pub struct Timer {
|
||||
pub name: String,
|
||||
pub start_time: DateTime<Utc>,
|
||||
#[allow(dead_code)]
|
||||
pub owner: u64,
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@ use crate::consts::LOCAL_TIMEZONE;
|
||||
|
||||
pub struct UserData {
|
||||
pub id: u32,
|
||||
#[allow(dead_code)]
|
||||
pub user: u64,
|
||||
pub dm_channel: u32,
|
||||
pub timezone: String,
|
||||
@ -22,7 +23,7 @@ impl UserData {
|
||||
|
||||
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
|
||||
)
|
||||
|
40
src/postman/metrics.rs
Normal 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(®ISTRY.gather());
|
||||
|
||||
match res_custom {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
warn!("Error encoding metrics: {:?}", e);
|
||||
|
||||
String::new()
|
||||
}
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@ mod sender;
|
||||
use std::env;
|
||||
|
||||
use log::{info, warn};
|
||||
use serenity::client::Context;
|
||||
use poise::serenity_prelude::client::Context;
|
||||
use sqlx::{Executor, MySql};
|
||||
use tokio::{
|
||||
sync::broadcast::Receiver,
|
@ -1,13 +1,11 @@
|
||||
use std::env;
|
||||
|
||||
use chrono::{DateTime, Days, Duration, Months};
|
||||
use chrono::{DateTime, Days, Months, TimeDelta};
|
||||
use chrono_tz::Tz;
|
||||
use lazy_static::lazy_static;
|
||||
use log::{error, info, warn};
|
||||
use num_integer::Integer;
|
||||
use regex::{Captures, Regex};
|
||||
use serde::Deserialize;
|
||||
use serenity::{
|
||||
use poise::serenity_prelude::{
|
||||
all::{CreateAttachment, CreateEmbedFooter},
|
||||
builder::{CreateEmbed, CreateEmbedAuthor, CreateMessage, ExecuteWebhook},
|
||||
http::{CacheHttp, Http, HttpError},
|
||||
@ -18,6 +16,8 @@ use serenity::{
|
||||
},
|
||||
Error, Result,
|
||||
};
|
||||
use regex::{Captures, Regex};
|
||||
use serde::Deserialize;
|
||||
use sqlx::{
|
||||
types::{
|
||||
chrono::{NaiveDateTime, Utc},
|
||||
@ -66,15 +66,15 @@ pub fn substitute(string: &str) -> String {
|
||||
let format = caps.name("format").map(|m| m.as_str());
|
||||
|
||||
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) => {
|
||||
let now = Utc::now().naive_utc();
|
||||
let now = Utc::now();
|
||||
|
||||
let difference = {
|
||||
if now < dt {
|
||||
dt - Utc::now().naive_utc()
|
||||
dt - Utc::now()
|
||||
} else {
|
||||
Utc::now().naive_utc() - dt
|
||||
Utc::now() - dt
|
||||
}
|
||||
};
|
||||
|
||||
@ -397,7 +397,13 @@ impl Reminder {
|
||||
}
|
||||
|
||||
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()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use rocket::serde::json::json;
|
||||
use rocket::{catch, serde::json::json};
|
||||
use rocket_dyn_templates::Template;
|
||||
|
||||
use crate::JsonValue;
|
||||
use crate::web::JsonValue;
|
||||
|
||||
#[catch(403)]
|
||||
pub(crate) async fn forbidden() -> Template {
|
@ -20,14 +20,14 @@ pub const DAY: usize = 24 * HOUR;
|
||||
|
||||
pub const CHARACTERS: &str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
|
||||
|
||||
use std::{collections::HashSet, env, iter::FromIterator};
|
||||
use std::{collections::HashSet, env};
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use serenity::builder::CreateAttachment;
|
||||
|
||||
lazy_static! {
|
||||
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",
|
||||
);
|
||||
pub static ref SUBSCRIPTION_ROLES: HashSet<u64> = HashSet::from_iter(
|
@ -20,6 +20,7 @@ impl Transaction<'_> {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub enum TransactionError {
|
||||
Error(sqlx::Error),
|
||||
Missing,
|
27
src/web/metrics.rs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,3 @@
|
||||
#[macro_use]
|
||||
extern crate rocket;
|
||||
|
||||
mod consts;
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
@ -11,24 +8,27 @@ mod routes;
|
||||
|
||||
use std::{env, path::Path};
|
||||
|
||||
use log::{error, info, warn};
|
||||
use oauth2::{basic::BasicClient, AuthUrl, ClientId, ClientSecret, RedirectUrl, TokenUrl};
|
||||
use rocket::{
|
||||
fs::FileServer,
|
||||
http::CookieJar,
|
||||
serde::json::{json, Value as JsonValue},
|
||||
tokio::sync::broadcast::Sender,
|
||||
};
|
||||
use rocket_dyn_templates::Template;
|
||||
use serenity::{
|
||||
use poise::serenity_prelude::{
|
||||
client::Context,
|
||||
http::CacheHttp,
|
||||
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 crate::{
|
||||
use crate::web::{
|
||||
consts::{CNC_GUILD, DISCORD_OAUTH_AUTHORIZE, DISCORD_OAUTH_TOKEN, SUBSCRIPTION_ROLES},
|
||||
metrics::{init_metrics, MetricProducer},
|
||||
metrics::MetricProducer,
|
||||
};
|
||||
|
||||
type Database = MySql;
|
||||
@ -68,9 +68,7 @@ pub async fn initialize(
|
||||
let reqwest_client = reqwest::Client::new();
|
||||
|
||||
let static_path =
|
||||
if Path::new("web/static").exists() { "web/static" } else { "/lib/reminder-rs/static" };
|
||||
|
||||
init_metrics();
|
||||
if Path::new("static").exists() { "static" } else { "/lib/reminder-rs/static" };
|
||||
|
||||
rocket::build()
|
||||
.attach(MetricProducer)
|
||||
@ -96,7 +94,6 @@ pub async fn initialize(
|
||||
routes![
|
||||
routes::cookies,
|
||||
routes::index,
|
||||
routes::metrics::metrics,
|
||||
routes::privacy,
|
||||
routes::report::report_error,
|
||||
routes::return_to_same_site,
|
||||
@ -154,7 +151,6 @@ pub async fn initialize(
|
||||
routes::dashboard::export::import_todos,
|
||||
],
|
||||
)
|
||||
.mount("/admin", routes![routes::admin::admin_dashboard_home, routes::admin::bot_data])
|
||||
.launch()
|
||||
.await?;
|
||||
|
@ -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 serenity::{
|
||||
client::Context,
|
||||
@ -8,7 +8,7 @@ use serenity::{
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{check_authorization, routes::JsonResult};
|
||||
use crate::web::{check_authorization, routes::JsonResult};
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct ChannelInfo {
|
@ -7,7 +7,7 @@ use std::env;
|
||||
|
||||
pub use channels::*;
|
||||
pub use reminders::*;
|
||||
use rocket::{http::CookieJar, serde::json::json, State};
|
||||
use rocket::{get, http::CookieJar, serde::json::json, State};
|
||||
pub use roles::*;
|
||||
use serenity::{
|
||||
client::Context,
|
||||
@ -15,7 +15,7 @@ use serenity::{
|
||||
};
|
||||
pub use templates::*;
|
||||
|
||||
use crate::{check_authorization, routes::JsonResult};
|
||||
use crate::web::{check_authorization, routes::JsonResult};
|
||||
|
||||
#[get("/api/guild/<id>")]
|
||||
pub async fn get_guild_info(id: u64, cookies: &CookieJar<'_>, ctx: &State<Context>) -> JsonResult {
|
@ -1,5 +1,8 @@
|
||||
use log::warn;
|
||||
use rocket::{
|
||||
get,
|
||||
http::CookieJar,
|
||||
patch, post,
|
||||
serde::json::{json, Json},
|
||||
State,
|
||||
};
|
||||
@ -9,7 +12,7 @@ use serenity::{
|
||||
};
|
||||
use sqlx::{MySql, Pool};
|
||||
|
||||
use crate::{
|
||||
use crate::web::{
|
||||
check_authorization, check_guild_subscription, check_subscription,
|
||||
consts::MIN_INTERVAL,
|
||||
guards::transaction::Transaction,
|
@ -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 serenity::client::Context;
|
||||
|
||||
use crate::{check_authorization, routes::JsonResult};
|
||||
use crate::web::{check_authorization, routes::JsonResult};
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct RoleInfo {
|
@ -1,12 +1,15 @@
|
||||
use log::warn;
|
||||
use rocket::{
|
||||
delete, get,
|
||||
http::CookieJar,
|
||||
post,
|
||||
serde::json::{json, Json},
|
||||
State,
|
||||
};
|
||||
use serenity::client::Context;
|
||||
use sqlx::{MySql, Pool};
|
||||
|
||||
use crate::{
|
||||
use crate::web::{
|
||||
check_authorization,
|
||||
consts::{
|
||||
MAX_CONTENT_LENGTH, MAX_EMBED_AUTHOR_LENGTH, MAX_EMBED_DESCRIPTION_LENGTH,
|
@ -1,14 +1,16 @@
|
||||
pub mod guild;
|
||||
pub mod user;
|
||||
|
||||
use log::warn;
|
||||
use rocket::{
|
||||
delete,
|
||||
http::CookieJar,
|
||||
serde::json::{json, Json},
|
||||
State,
|
||||
};
|
||||
use sqlx::{MySql, Pool};
|
||||
|
||||
use crate::routes::{dashboard::DeleteReminder, JsonResult};
|
||||
use crate::web::routes::{dashboard::DeleteReminder, JsonResult};
|
||||
|
||||
#[delete("/api/reminders", data = "<reminder>")]
|
||||
pub async fn delete_reminder(
|
@ -1,5 +1,7 @@
|
||||
use log::warn;
|
||||
use reqwest::Client;
|
||||
use rocket::{
|
||||
get,
|
||||
http::CookieJar,
|
||||
serde::json::{json, Value as JsonValue},
|
||||
State,
|
||||
@ -7,7 +9,7 @@ use rocket::{
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serenity::model::{id::GuildId, permissions::Permissions};
|
||||
|
||||
use crate::consts::DISCORD_API;
|
||||
use crate::web::consts::DISCORD_API;
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct GuildInfo {
|
@ -8,7 +8,9 @@ use chrono_tz::Tz;
|
||||
pub use guilds::*;
|
||||
pub use reminders::*;
|
||||
use rocket::{
|
||||
get,
|
||||
http::CookieJar,
|
||||
patch,
|
||||
serde::json::{json, Json, Value as JsonValue},
|
||||
State,
|
||||
};
|
@ -1,11 +1,12 @@
|
||||
use chrono::{naive::NaiveDateTime, Utc};
|
||||
use futures::TryFutureExt;
|
||||
use log::warn;
|
||||
use rocket::serde::json::json;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serenity::{client::Context, futures, model::id::UserId};
|
||||
use serenity::{client::Context, model::id::UserId};
|
||||
use sqlx::types::Json;
|
||||
|
||||
use crate::{
|
||||
use crate::web::{
|
||||
check_subscription,
|
||||
consts::{
|
||||
DAY, MAX_CONTENT_LENGTH, MAX_EMBED_AUTHOR_LENGTH, MAX_EMBED_DESCRIPTION_LENGTH,
|
||||
@ -15,10 +16,7 @@ use crate::{
|
||||
},
|
||||
guards::transaction::Transaction,
|
||||
routes::{
|
||||
dashboard::{
|
||||
create_database_channel, deserialize_optional_field, generate_uid, interval_default,
|
||||
name_default, Attachment, EmbedField, Unset,
|
||||
},
|
||||
dashboard::{create_database_channel, generate_uid, name_default, Attachment, EmbedField},
|
||||
JsonResult,
|
||||
},
|
||||
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>,
|
||||
}
|
@ -1,12 +1,15 @@
|
||||
use log::warn;
|
||||
use rocket::{
|
||||
get,
|
||||
http::CookieJar,
|
||||
patch, post,
|
||||
serde::json::{json, Json},
|
||||
State,
|
||||
};
|
||||
use serenity::{client::Context, model::id::UserId};
|
||||
use sqlx::{MySql, Pool};
|
||||
|
||||
use crate::{
|
||||
use crate::web::{
|
||||
check_subscription,
|
||||
guards::transaction::Transaction,
|
||||
routes::{
|
@ -1,7 +1,11 @@
|
||||
use base64::{prelude::BASE64_STANDARD, Engine};
|
||||
use csv::{QuoteStyle, WriterBuilder};
|
||||
use log::warn;
|
||||
use rocket::{
|
||||
get,
|
||||
http::CookieJar,
|
||||
serde::json::{json, serde_json, Json},
|
||||
put,
|
||||
serde::json::{json, Json},
|
||||
State,
|
||||
};
|
||||
use serenity::{
|
||||
@ -10,7 +14,7 @@ use serenity::{
|
||||
};
|
||||
use sqlx::{MySql, Pool};
|
||||
|
||||
use crate::{
|
||||
use crate::web::{
|
||||
check_authorization,
|
||||
guards::transaction::Transaction,
|
||||
routes::{
|
||||
@ -134,7 +138,7 @@ pub(crate) async fn import_reminders(
|
||||
let user_id =
|
||||
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) => {
|
||||
let mut reader = csv::Reader::from_reader(body.as_slice());
|
||||
let mut count = 0;
|
||||
@ -292,7 +296,7 @@ pub async fn import_todos(
|
||||
let channels_res = GuildId::new(id).channels(&ctx.inner()).await;
|
||||
|
||||
match channels_res {
|
||||
Ok(channels) => match base64::decode(&body.body) {
|
||||
Ok(channels) => match BASE64_STANDARD.decode(&body.body) {
|
||||
Ok(body) => {
|
||||
let mut reader = csv::Reader::from_reader(body.as_slice());
|
||||
|
@ -1,8 +1,12 @@
|
||||
use std::path::Path;
|
||||
|
||||
use base64::{prelude::BASE64_STANDARD, Engine};
|
||||
use chrono::{naive::NaiveDateTime, Utc};
|
||||
use log::warn;
|
||||
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 secrecy::ExposeSecret;
|
||||
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
||||
@ -14,7 +18,7 @@ use serenity::{
|
||||
};
|
||||
use sqlx::types::Json;
|
||||
|
||||
use crate::{
|
||||
use crate::web::{
|
||||
catchers::internal_server_error,
|
||||
check_guild_subscription, check_subscription,
|
||||
consts::{
|
||||
@ -63,7 +67,7 @@ impl<'de> Deserialize<'de> for Attachment {
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
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
|
||||
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,
|
||||
channel: ChannelId,
|
||||
transaction: &mut Transaction<'_>,
|
||||
) -> Result<u32, crate::Error> {
|
||||
) -> Result<u32, Error> {
|
||||
let row = sqlx::query!(
|
||||
"SELECT webhook_token, webhook_id FROM channels WHERE channel = ?",
|
||||
channel.get()
|
@ -5,13 +5,14 @@ use oauth2::{
|
||||
};
|
||||
use reqwest::Client;
|
||||
use rocket::{
|
||||
get,
|
||||
http::{private::cookie::Expiration, Cookie, CookieJar, SameSite},
|
||||
response::{Flash, Redirect},
|
||||
uri, State,
|
||||
};
|
||||
use serenity::model::user::User;
|
||||
|
||||
use crate::{consts::DISCORD_API, routes};
|
||||
use crate::web::{consts::DISCORD_API, routes};
|
||||
|
||||
#[get("/discord")]
|
||||
pub async fn discord_login(
|
@ -1,12 +1,10 @@
|
||||
pub mod admin;
|
||||
pub mod dashboard;
|
||||
pub mod login;
|
||||
pub mod metrics;
|
||||
pub mod report;
|
||||
|
||||
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;
|
||||
|
||||
pub type JsonResult = Result<JsonValue, JsonValue>;
|
@ -1,12 +1,14 @@
|
||||
use log::error;
|
||||
use rocket::{
|
||||
http::CookieJar,
|
||||
post,
|
||||
serde::{
|
||||
json::{json, Json},
|
||||
Deserialize,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::routes::JsonResult;
|
||||
use crate::web::routes::JsonResult;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ClientError {
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 7.2 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 7.2 KiB |
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 762 B After Width: | Height: | Size: 762 B |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 323 KiB After Width: | Height: | Size: 323 KiB |
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 81 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 65 KiB |
1
static/index.html
Symbolic link
@ -0,0 +1 @@
|
||||
/home/jude/reminder-bot/reminder-dashboard/dist/index.html
|
Before Width: | Height: | Size: 712 KiB After Width: | Height: | Size: 712 KiB |