chore(monorepo): delta, january, quark
This commit is contained in:
13
crates/quark/src/util/manipulation.rs
Normal file
13
crates/quark/src/util/manipulation.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
/// Prefix keys on an arbitrary object
|
||||
pub fn prefix_keys<T: Serialize>(t: &T, prefix: &str) -> HashMap<String, serde_json::Value> {
|
||||
let v: String = serde_json::to_string(t).unwrap();
|
||||
let v: HashMap<String, serde_json::Value> = serde_json::from_str(&v).unwrap();
|
||||
v.into_iter()
|
||||
.filter(|(_k, v)| !v.is_null())
|
||||
.map(|(k, v)| (prefix.to_owned() + &k, v))
|
||||
.collect()
|
||||
}
|
||||
5
crates/quark/src/util/mod.rs
Normal file
5
crates/quark/src/util/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
pub mod manipulation;
|
||||
pub mod r#ref;
|
||||
pub mod result;
|
||||
pub mod value;
|
||||
pub mod variables;
|
||||
87
crates/quark/src/util/ref.rs
Normal file
87
crates/quark/src/util/ref.rs
Normal file
@@ -0,0 +1,87 @@
|
||||
use futures::future::join;
|
||||
use rocket::request::FromParam;
|
||||
use schemars::schema::{InstanceType, Schema, SchemaObject, SingleOrVec};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::models::{Bot, Channel, Invite, Member, Message, Server, ServerBan, User};
|
||||
use crate::presence::presence_is_online;
|
||||
use crate::{Database, Result};
|
||||
|
||||
/// Reference to some object in the database
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Ref {
|
||||
/// Id of object
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
impl Ref {
|
||||
/// Create a Ref from an unchecked string
|
||||
pub fn from_unchecked(id: String) -> Ref {
|
||||
Ref { id }
|
||||
}
|
||||
|
||||
/// Fetch user from Ref
|
||||
pub async fn as_user(&self, db: &Database) -> Result<User> {
|
||||
let (user, online) = join(db.fetch_user(&self.id), presence_is_online(&self.id)).await;
|
||||
let mut user = user?;
|
||||
user.online = Some(online);
|
||||
Ok(user)
|
||||
}
|
||||
|
||||
/// Fetch channel from Ref
|
||||
pub async fn as_channel(&self, db: &Database) -> Result<Channel> {
|
||||
db.fetch_channel(&self.id).await
|
||||
}
|
||||
|
||||
/// Fetch server from Ref
|
||||
pub async fn as_server(&self, db: &Database) -> Result<Server> {
|
||||
db.fetch_server(&self.id).await
|
||||
}
|
||||
|
||||
/// Fetch message from Ref
|
||||
pub async fn as_message(&self, db: &Database) -> Result<Message> {
|
||||
db.fetch_message(&self.id).await
|
||||
}
|
||||
|
||||
/// Fetch bot from Ref
|
||||
pub async fn as_bot(&self, db: &Database) -> Result<Bot> {
|
||||
db.fetch_bot(&self.id).await
|
||||
}
|
||||
|
||||
/// Fetch invite from Ref
|
||||
pub async fn as_invite(&self, db: &Database) -> Result<Invite> {
|
||||
Invite::find(db, &self.id).await
|
||||
}
|
||||
|
||||
/// Fetch member from Ref
|
||||
pub async fn as_member(&self, db: &Database, server: &str) -> Result<Member> {
|
||||
db.fetch_member(server, &self.id).await
|
||||
}
|
||||
|
||||
/// Fetch ban from Ref
|
||||
pub async fn as_ban(&self, db: &Database, server: &str) -> Result<ServerBan> {
|
||||
db.fetch_ban(server, &self.id).await
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r> FromParam<'r> for Ref {
|
||||
type Error = &'r str;
|
||||
|
||||
fn from_param(param: &'r str) -> Result<Self, Self::Error> {
|
||||
Ok(Ref::from_unchecked(param.into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl JsonSchema for Ref {
|
||||
fn schema_name() -> String {
|
||||
"Id".to_string()
|
||||
}
|
||||
|
||||
fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> Schema {
|
||||
Schema::Object(SchemaObject {
|
||||
instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
}
|
||||
264
crates/quark/src/util/result.rs
Normal file
264
crates/quark/src/util/result.rs
Normal file
@@ -0,0 +1,264 @@
|
||||
use okapi::openapi3::{self, RefOr, SchemaObject};
|
||||
use rocket::{
|
||||
http::{ContentType, Status},
|
||||
response::{self, Responder},
|
||||
serde::json::serde_json::json,
|
||||
Request, Response,
|
||||
};
|
||||
use schemars::schema::Schema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::Cursor;
|
||||
use validator::ValidationErrors;
|
||||
|
||||
use crate::{Permission, UserPermission};
|
||||
|
||||
/// Possible API Errors
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum Error {
|
||||
/// This error was not labeled :(
|
||||
LabelMe,
|
||||
|
||||
// ? Onboarding related errors.
|
||||
AlreadyOnboarded,
|
||||
|
||||
// ? User related errors.
|
||||
UsernameTaken,
|
||||
UnknownUser,
|
||||
AlreadyFriends,
|
||||
AlreadySentRequest,
|
||||
Blocked,
|
||||
BlockedByOther,
|
||||
NotFriends,
|
||||
|
||||
// ? Channel related errors.
|
||||
UnknownChannel,
|
||||
UnknownAttachment,
|
||||
UnknownMessage,
|
||||
CannotEditMessage,
|
||||
CannotJoinCall,
|
||||
TooManyAttachments,
|
||||
TooManyReplies,
|
||||
EmptyMessage,
|
||||
CannotRemoveYourself,
|
||||
GroupTooLarge {
|
||||
max: usize,
|
||||
},
|
||||
AlreadyInGroup,
|
||||
NotInGroup,
|
||||
|
||||
// ? Server related errors.
|
||||
UnknownServer,
|
||||
InvalidRole,
|
||||
Banned,
|
||||
TooManyServers {
|
||||
max: usize,
|
||||
},
|
||||
|
||||
// ? Bot related errors.
|
||||
ReachedMaximumBots,
|
||||
IsBot,
|
||||
BotIsPrivate,
|
||||
|
||||
// ? Permission errors.
|
||||
MissingPermission {
|
||||
permission: Permission,
|
||||
},
|
||||
MissingUserPermission {
|
||||
permission: UserPermission,
|
||||
},
|
||||
NotElevated,
|
||||
CannotGiveMissingPermissions,
|
||||
|
||||
// ? General errors.
|
||||
DatabaseError {
|
||||
operation: &'static str,
|
||||
with: &'static str,
|
||||
},
|
||||
InternalError,
|
||||
InvalidOperation,
|
||||
InvalidCredentials,
|
||||
InvalidSession,
|
||||
DuplicateNonce,
|
||||
VosoUnavailable,
|
||||
NotFound,
|
||||
NoEffect,
|
||||
FailedValidation {
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
error: ValidationErrors,
|
||||
},
|
||||
}
|
||||
|
||||
impl Error {
|
||||
/// Create a missing permission error from a given permission
|
||||
pub fn from_permission<T>(permission: Permission) -> Result<T> {
|
||||
Err(if let Permission::ViewChannel = permission {
|
||||
Error::NotFound
|
||||
} else {
|
||||
Error::MissingPermission { permission }
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a missing user permission error from a given user permission
|
||||
pub fn from_user_permission<T>(permission: UserPermission) -> Result<T> {
|
||||
Err(if let UserPermission::Access = permission {
|
||||
Error::NotFound
|
||||
} else {
|
||||
Error::MissingUserPermission { permission }
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a failed validation error from given validation errors
|
||||
pub fn from_invalid<T>(validation_error: ValidationErrors) -> Result<T> {
|
||||
Err(Error::FailedValidation {
|
||||
error: validation_error,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Empty 204 HTTP Response
|
||||
pub struct EmptyResponse;
|
||||
|
||||
/// Result type with custom Error
|
||||
pub type Result<T, E = Error> = std::result::Result<T, E>;
|
||||
|
||||
// ! FIXME: #[cfg]
|
||||
impl<'r> Responder<'r, 'static> for EmptyResponse {
|
||||
fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> {
|
||||
Response::build()
|
||||
.status(rocket::http::Status { code: 204 })
|
||||
.ok()
|
||||
}
|
||||
}
|
||||
|
||||
/// HTTP response builder for Error enum
|
||||
impl<'r> Responder<'r, 'static> for Error {
|
||||
fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> {
|
||||
let status = match self {
|
||||
Error::LabelMe => Status::InternalServerError,
|
||||
|
||||
Error::AlreadyOnboarded => Status::Forbidden,
|
||||
|
||||
Error::UnknownUser => Status::NotFound,
|
||||
Error::UsernameTaken => Status::Conflict,
|
||||
Error::AlreadyFriends => Status::Conflict,
|
||||
Error::AlreadySentRequest => Status::Conflict,
|
||||
Error::Blocked => Status::Conflict,
|
||||
Error::BlockedByOther => Status::Forbidden,
|
||||
Error::NotFriends => Status::Forbidden,
|
||||
|
||||
Error::UnknownChannel => Status::NotFound,
|
||||
Error::UnknownMessage => Status::NotFound,
|
||||
Error::UnknownAttachment => Status::BadRequest,
|
||||
Error::CannotEditMessage => Status::Forbidden,
|
||||
Error::CannotJoinCall => Status::BadRequest,
|
||||
Error::TooManyAttachments => Status::BadRequest,
|
||||
Error::TooManyReplies => Status::BadRequest,
|
||||
Error::EmptyMessage => Status::UnprocessableEntity,
|
||||
Error::CannotRemoveYourself => Status::BadRequest,
|
||||
Error::GroupTooLarge { .. } => Status::Forbidden,
|
||||
Error::AlreadyInGroup => Status::Conflict,
|
||||
Error::NotInGroup => Status::NotFound,
|
||||
|
||||
Error::UnknownServer => Status::NotFound,
|
||||
Error::InvalidRole => Status::NotFound,
|
||||
Error::Banned => Status::Forbidden,
|
||||
Error::TooManyServers { .. } => Status::Forbidden,
|
||||
|
||||
Error::ReachedMaximumBots => Status::BadRequest,
|
||||
Error::IsBot => Status::BadRequest,
|
||||
Error::BotIsPrivate => Status::Forbidden,
|
||||
|
||||
Error::MissingPermission { .. } => Status::Forbidden,
|
||||
Error::MissingUserPermission { .. } => Status::Forbidden,
|
||||
Error::NotElevated => Status::Forbidden,
|
||||
Error::CannotGiveMissingPermissions => Status::Forbidden,
|
||||
|
||||
Error::DatabaseError { .. } => Status::InternalServerError,
|
||||
Error::InternalError => Status::InternalServerError,
|
||||
Error::InvalidOperation => Status::BadRequest,
|
||||
Error::InvalidCredentials => Status::Unauthorized,
|
||||
Error::InvalidSession => Status::Unauthorized,
|
||||
Error::DuplicateNonce => Status::Conflict,
|
||||
Error::VosoUnavailable => Status::BadRequest,
|
||||
Error::NotFound => Status::NotFound,
|
||||
Error::NoEffect => Status::Ok,
|
||||
Error::FailedValidation { .. } => Status::BadRequest,
|
||||
};
|
||||
|
||||
// Serialize the error data structure into JSON.
|
||||
let string = json!(self).to_string();
|
||||
|
||||
// Build and send the request.
|
||||
Response::build()
|
||||
.sized_body(string.len(), Cursor::new(string))
|
||||
.header(ContentType::new("application", "json"))
|
||||
.status(status)
|
||||
.ok()
|
||||
}
|
||||
}
|
||||
|
||||
impl rocket_okapi::response::OpenApiResponderInner for Error {
|
||||
fn responses(
|
||||
gen: &mut rocket_okapi::gen::OpenApiGenerator,
|
||||
) -> std::result::Result<openapi3::Responses, rocket_okapi::OpenApiError> {
|
||||
let mut content = okapi::Map::new();
|
||||
|
||||
let settings = schemars::gen::SchemaSettings::default().with(|s| {
|
||||
s.option_nullable = true;
|
||||
s.option_add_null_type = false;
|
||||
s.definitions_path = "#/components/schemas/".to_string();
|
||||
});
|
||||
|
||||
let mut schema_generator = settings.into_generator();
|
||||
let schema = schema_generator.root_schema_for::<Error>();
|
||||
|
||||
let definitions = gen.schema_generator().definitions_mut();
|
||||
for (key, value) in schema.definitions {
|
||||
definitions.insert(key, value);
|
||||
}
|
||||
|
||||
definitions.insert("Error".to_string(), Schema::Object(schema.schema));
|
||||
|
||||
content.insert(
|
||||
"application/json".to_string(),
|
||||
openapi3::MediaType {
|
||||
schema: Some(SchemaObject {
|
||||
reference: Some("#/components/schemas/Error".to_string()),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
Ok(openapi3::Responses {
|
||||
default: Some(openapi3::RefOr::Object(openapi3::Response {
|
||||
content,
|
||||
description: "An error occurred.".to_string(),
|
||||
..Default::default()
|
||||
})),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl rocket_okapi::response::OpenApiResponderInner for EmptyResponse {
|
||||
fn responses(
|
||||
_gen: &mut rocket_okapi::gen::OpenApiGenerator,
|
||||
) -> std::result::Result<openapi3::Responses, rocket_okapi::OpenApiError> {
|
||||
let mut responses = okapi::Map::new();
|
||||
|
||||
responses.insert(
|
||||
"204".to_string(),
|
||||
RefOr::Object(openapi3::Response {
|
||||
description: "Success".to_string(),
|
||||
..Default::default()
|
||||
}),
|
||||
);
|
||||
|
||||
Ok(openapi3::Responses {
|
||||
responses,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
}
|
||||
38
crates/quark/src/util/value.rs
Normal file
38
crates/quark/src/util/value.rs
Normal file
@@ -0,0 +1,38 @@
|
||||
/// Owned, Ref or None Value
|
||||
#[derive(Clone)]
|
||||
pub enum Value<'a, T> {
|
||||
Owned(T),
|
||||
Ref(&'a T),
|
||||
None,
|
||||
}
|
||||
|
||||
impl<'a, T> Value<'a, T> {
|
||||
/// Check whether this Value exists
|
||||
pub fn has(&self) -> bool {
|
||||
!matches!(self, Self::None)
|
||||
}
|
||||
|
||||
/// Get this Value as an Option Ref
|
||||
pub fn get(&self) -> Option<&T> {
|
||||
match self {
|
||||
Self::Owned(t) => Some(t),
|
||||
Self::Ref(t) => Some(t),
|
||||
Self::None => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set owned value
|
||||
pub fn set(&mut self, t: T) {
|
||||
*self = Value::Owned(t);
|
||||
}
|
||||
|
||||
/// Set referential value
|
||||
pub fn set_ref(&mut self, t: &'a T) {
|
||||
*self = Value::Ref(t);
|
||||
}
|
||||
|
||||
/// Clear current value
|
||||
pub fn clear(&mut self) {
|
||||
*self = Value::None;
|
||||
}
|
||||
}
|
||||
98
crates/quark/src/util/variables/delta.rs
Normal file
98
crates/quark/src/util/variables/delta.rs
Normal file
@@ -0,0 +1,98 @@
|
||||
use std::env;
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
use log::warn;
|
||||
|
||||
lazy_static! {
|
||||
// Application Settings
|
||||
pub static ref PUBLIC_URL: String =
|
||||
env::var("REVOLT_PUBLIC_URL").expect("Missing REVOLT_PUBLIC_URL environment variable.");
|
||||
pub static ref APP_URL: String =
|
||||
env::var("REVOLT_APP_URL").expect("Missing REVOLT_APP_URL environment variable.");
|
||||
pub static ref EXTERNAL_WS_URL: String =
|
||||
env::var("REVOLT_EXTERNAL_WS_URL").expect("Missing REVOLT_EXTERNAL_WS_URL environment variable.");
|
||||
|
||||
pub static ref AUTUMN_URL: String =
|
||||
env::var("AUTUMN_PUBLIC_URL").unwrap_or_else(|_| "https://example.com".to_string());
|
||||
pub static ref JANUARY_URL: String =
|
||||
env::var("JANUARY_PUBLIC_URL").unwrap_or_else(|_| "https://example.com".to_string());
|
||||
pub static ref VOSO_URL: String =
|
||||
env::var("VOSO_PUBLIC_URL").unwrap_or_else(|_| "https://example.com".to_string());
|
||||
pub static ref VOSO_WS_HOST: String =
|
||||
env::var("VOSO_WS_HOST").unwrap_or_else(|_| "wss://example.com".to_string());
|
||||
pub static ref VOSO_MANAGE_TOKEN: String =
|
||||
env::var("VOSO_MANAGE_TOKEN").unwrap_or_else(|_| "0".to_string());
|
||||
|
||||
pub static ref HCAPTCHA_KEY: String =
|
||||
env::var("REVOLT_HCAPTCHA_KEY").unwrap_or_else(|_| "0x0000000000000000000000000000000000000000".to_string());
|
||||
pub static ref HCAPTCHA_SITEKEY: String =
|
||||
env::var("REVOLT_HCAPTCHA_SITEKEY").unwrap_or_else(|_| "10000000-ffff-ffff-ffff-000000000001".to_string());
|
||||
pub static ref VAPID_PRIVATE_KEY: String =
|
||||
env::var("REVOLT_VAPID_PRIVATE_KEY").expect("Missing REVOLT_VAPID_PRIVATE_KEY environment variable.");
|
||||
pub static ref VAPID_PUBLIC_KEY: String =
|
||||
env::var("REVOLT_VAPID_PUBLIC_KEY").expect("Missing REVOLT_VAPID_PUBLIC_KEY environment variable.");
|
||||
|
||||
// Application Flags
|
||||
pub static ref INVITE_ONLY: bool = env::var("REVOLT_INVITE_ONLY").map_or(false, |v| v == "1");
|
||||
pub static ref USE_EMAIL: bool = env::var("REVOLT_USE_EMAIL_VERIFICATION").map_or(
|
||||
env::var("REVOLT_SMTP_HOST").is_ok()
|
||||
&& env::var("REVOLT_SMTP_USERNAME").is_ok()
|
||||
&& env::var("REVOLT_SMTP_PASSWORD").is_ok()
|
||||
&& env::var("REVOLT_SMTP_FROM").is_ok(),
|
||||
|v| v == *"1"
|
||||
);
|
||||
pub static ref USE_HCAPTCHA: bool = env::var("REVOLT_HCAPTCHA_KEY").is_ok();
|
||||
pub static ref USE_AUTUMN: bool = env::var("AUTUMN_PUBLIC_URL").is_ok();
|
||||
pub static ref USE_JANUARY: bool = env::var("JANUARY_PUBLIC_URL").is_ok();
|
||||
pub static ref USE_VOSO: bool = env::var("VOSO_PUBLIC_URL").is_ok() && env::var("VOSO_MANAGE_TOKEN").is_ok();
|
||||
|
||||
// SMTP Settings
|
||||
pub static ref SMTP_HOST: String =
|
||||
env::var("REVOLT_SMTP_HOST").unwrap_or_else(|_| "".to_string());
|
||||
pub static ref SMTP_USERNAME: String =
|
||||
env::var("REVOLT_SMTP_USERNAME").unwrap_or_else(|_| "".to_string());
|
||||
pub static ref SMTP_PASSWORD: String =
|
||||
env::var("REVOLT_SMTP_PASSWORD").unwrap_or_else(|_| "".to_string());
|
||||
pub static ref SMTP_FROM: String = env::var("REVOLT_SMTP_FROM").unwrap_or_else(|_| "".to_string());
|
||||
|
||||
// Application Logic Settings
|
||||
pub static ref MAX_GROUP_SIZE: usize =
|
||||
env::var("REVOLT_MAX_GROUP_SIZE").unwrap_or_else(|_| "50".to_string()).parse().unwrap();
|
||||
pub static ref MAX_BOT_COUNT: usize =
|
||||
env::var("REVOLT_MAX_BOT_COUNT").unwrap_or_else(|_| "5".to_string()).parse().unwrap();
|
||||
pub static ref MAX_EMBED_COUNT: usize =
|
||||
env::var("REVOLT_MAX_EMBED_COUNT").unwrap_or_else(|_| "5".to_string()).parse().unwrap();
|
||||
pub static ref MAX_SERVER_COUNT: usize =
|
||||
env::var("REVOLT_MAX_SERVER_COUNT").unwrap_or_else(|_| "100".to_string()).parse().unwrap();
|
||||
pub static ref EARLY_ADOPTER_BADGE: i64 =
|
||||
env::var("REVOLT_EARLY_ADOPTER_BADGE").unwrap_or_else(|_| "0".to_string()).parse().unwrap();
|
||||
}
|
||||
|
||||
pub fn preflight_checks() {
|
||||
format!("url = {}", *APP_URL);
|
||||
format!("public = {}", *PUBLIC_URL);
|
||||
format!("external = {}", *EXTERNAL_WS_URL);
|
||||
|
||||
format!("privkey = {}", *VAPID_PRIVATE_KEY);
|
||||
format!("pubkey = {}", *VAPID_PUBLIC_KEY);
|
||||
|
||||
if !(*USE_EMAIL) {
|
||||
#[cfg(not(debug_assertions))]
|
||||
if !env::var("REVOLT_UNSAFE_NO_EMAIL").map_or(false, |v| v == *"1") {
|
||||
panic!("Running in production without email is not recommended, set REVOLT_UNSAFE_NO_EMAIL=1 to override.");
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
warn!("No SMTP settings specified! Remember to configure email.");
|
||||
}
|
||||
|
||||
if !(*USE_HCAPTCHA) {
|
||||
#[cfg(not(debug_assertions))]
|
||||
if !env::var("REVOLT_UNSAFE_NO_CAPTCHA").map_or(false, |v| v == *"1") {
|
||||
panic!("Running in production without CAPTCHA is not recommended, set REVOLT_UNSAFE_NO_CAPTCHA=1 to override.");
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
warn!("No Captcha key specified! Remember to add hCaptcha key.");
|
||||
}
|
||||
}
|
||||
1
crates/quark/src/util/variables/mod.rs
Normal file
1
crates/quark/src/util/variables/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod delta;
|
||||
Reference in New Issue
Block a user