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"
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"

View File

@ -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"

View File

@ -1,41 +1,41 @@
server {
server_name www.reminder-bot.com;
server_name www.reminder-bot.com;
return 301 $scheme://reminder-bot.com$request_uri;
return 301 $scheme://reminder-bot.com$request_uri;
}
server {
listen 80;
server_name reminder-bot.com;
listen 80;
server_name reminder-bot.com;
return 301 https://reminder-bot.com$request_uri;
return 301 https://reminder-bot.com$request_uri;
}
server {
listen 443 ssl;
server_name reminder-bot.com;
listen 443 ssl;
server_name reminder-bot.com;
ssl_certificate /etc/letsencrypt/live/reminder-bot.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/reminder-bot.com/privkey.pem;
ssl_certificate /etc/letsencrypt/live/reminder-bot.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/reminder-bot.com/privkey.pem;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
location / {
proxy_pass http://localhost:18920;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location / {
proxy_pass http://localhost:18920;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /static {
alias /var/www/reminder-rs/static;
expires 30d;
}
location /static {
alias /var/www/reminder-rs/static;
expires 30d;
}
}

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 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;

View File

@ -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
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 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
}

View File

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

View File

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

View File

@ -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
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 log::{info, warn};
use serenity::client::Context;
use poise::serenity_prelude::client::Context;
use sqlx::{Executor, MySql};
use tokio::{
sync::broadcast::Receiver,

View File

@ -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()
});
}
}

View File

@ -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 {

View File

@ -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(

View File

@ -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
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;
#[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?;

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 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 {

View File

@ -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 {

View File

@ -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,

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 serenity::client::Context;
use crate::{check_authorization, routes::JsonResult};
use crate::web::{check_authorization, routes::JsonResult};
#[derive(Serialize)]
struct RoleInfo {

View File

@ -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,

View File

@ -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(

View File

@ -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 {

View File

@ -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,
};

View File

@ -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>,
}

View File

@ -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::{

View File

@ -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());

View File

@ -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()

View File

@ -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(

View File

@ -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>;

View File

@ -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 {

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