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"
|
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"
|
||||||
|
22
Rocket.toml
@ -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"
|
||||||
|
@ -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 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;
|
||||||
|
|
||||||
|
14
src/main.rs
@ -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
@ -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 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
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
@ -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 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,
|
@ -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()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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 {
|
@ -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(
|
@ -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
@ -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;
|
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?;
|
||||||
|
|
@ -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 {
|
@ -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 {
|
@ -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,
|
@ -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 {
|
@ -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,
|
@ -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(
|
@ -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 {
|
@ -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,
|
||||||
};
|
};
|
@ -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>,
|
|
||||||
}
|
|
@ -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::{
|
@ -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());
|
||||||
|
|
@ -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()
|
@ -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(
|
@ -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>;
|
@ -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 {
|
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 |