chore(monorepo): delta, january, quark

This commit is contained in:
Paul Makles
2022-06-02 00:04:22 +01:00
parent 5d8432e267
commit 2ce610e1e7
232 changed files with 18094 additions and 554 deletions

View 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()
}

View File

@@ -0,0 +1,5 @@
pub mod manipulation;
pub mod r#ref;
pub mod result;
pub mod value;
pub mod variables;

View 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()
})
}
}

View 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()
})
}
}

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

View 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.");
}
}

View File

@@ -0,0 +1 @@
pub mod delta;