Merge branch 'master' into webhooks

This commit is contained in:
Angelo Kontaxis
2023-04-01 22:52:11 +01:00
committed by GitHub
71 changed files with 1173 additions and 428 deletions

View File

@@ -1,6 +1,6 @@
[package]
name = "revolt-quark"
version = "0.5.9"
version = "0.5.17"
license = "AGPL-3.0-or-later"
edition = "2021"
@@ -29,7 +29,7 @@ default = [ "test" ]
serde = { version = "1", features = ["derive"] }
validator = { version = "0.14", features = ["derive"] }
iso8601-timestamp = { version = "0.1.8", features = ["schema", "bson"] }
optional_struct = { git = "https://github.com/insertish/OptionalStruct", rev = "e275d2726595474632485934aa0887fa52281f70" }
optional_struct = { git = "https://github.com/insertish/OptionalStruct", rev = "ee56427cee1f007839825d93d07fffd5a5e038c7" }
# Formats
bincode = "1.3.3"
@@ -67,8 +67,7 @@ impl_ops = "0.1.1"
num_enum = "0.5.6"
reqwest = "0.11.10"
bitfield = "0.13.2"
once_cell = "1.13.0"
lazy_static = "1.4.0"
once_cell = "1.17.1"
async-lock = "2.6.0"
lru = { version = "0.7.6", optional = true }

View File

@@ -557,7 +557,7 @@ impl EventV1 {
/// Publish private event
pub async fn private(self, id: String) {
self.p(format!("{}!", id)).await;
self.p(format!("{id}!")).await;
}
/// Publish internal global event

View File

@@ -0,0 +1,10 @@
use crate::{models::stats::Stats, AbstractStats, Result};
use super::super::DummyDb;
#[async_trait]
impl AbstractStats for DummyDb {
async fn generate_stats(&self) -> Result<Stats> {
todo!()
}
}

View File

@@ -1,4 +1,4 @@
use crate::models::message::{AppendMessage, Message, MessageSort, PartialMessage};
use crate::models::message::{AppendMessage, Message, MessageQuery, PartialMessage};
use crate::{AbstractMessage, Result};
use super::super::DummyDb;
@@ -41,28 +41,8 @@ impl AbstractMessage for DummyDb {
Ok(())
}
async fn fetch_messages(
&self,
channel: &str,
_limit: Option<i64>,
_before: Option<String>,
_after: Option<String>,
_sort: Option<MessageSort>,
_nearby: Option<String>,
) -> Result<Vec<Message>> {
Ok(vec![self.fetch_message(channel).await.unwrap()])
}
async fn search_messages(
&self,
channel: &str,
_query: &str,
_limit: Option<i64>,
_before: Option<String>,
_after: Option<String>,
_sort: MessageSort,
) -> Result<Vec<Message>> {
Ok(vec![self.fetch_message(channel).await.unwrap()])
async fn fetch_messages(&self, _query: MessageQuery) -> Result<Vec<Message>> {
Ok(vec![])
}
/// Add a new reaction to a message

View File

@@ -2,6 +2,7 @@ use crate::AbstractDatabase;
pub mod admin {
pub mod migrations;
pub mod stats;
}
pub mod media {

View File

@@ -1,3 +1,4 @@
use crate::models::report::PartialReport;
use crate::models::Report;
use crate::{AbstractReport, Result};
@@ -9,4 +10,16 @@ impl AbstractReport for DummyDb {
info!("Insert {:?}", report);
Ok(())
}
async fn update_report(&self, _id: &str, _report: &PartialReport) -> Result<()> {
todo!()
}
async fn fetch_report(&self, _report_id: &str) -> Result<Report> {
todo!()
}
async fn fetch_reports(&self) -> Result<Vec<Report>> {
Ok(vec![])
}
}

View File

@@ -9,4 +9,8 @@ impl AbstractSnapshot for DummyDb {
info!("Insert {:?}", snapshot);
Ok(())
}
async fn fetch_snapshot(&self, _report_id: &str) -> Result<Snapshot> {
todo!()
}
}

View File

@@ -5,13 +5,11 @@ use crate::{
Database, Error, Result,
};
lazy_static! {
static ref ALPHABET: [char; 54] = [
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
'J', 'K', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
'e', 'f', 'g', 'h', 'j', 'k', 'm', 'n', 'p', 'q', 'r', 's', 't', 'v', 'w', 'x', 'y', 'z'
];
}
static ALPHABET: [char; 54] = [
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
'J', 'K', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
'e', 'f', 'g', 'h', 'j', 'k', 'm', 'n', 'p', 'q', 'r', 's', 't', 'v', 'w', 'x', 'y', 'z'
];
impl Invite {
/// Get the invite code for this invite
@@ -30,7 +28,7 @@ impl Invite {
/// Create a new invite from given information
pub async fn create(db: &Database, creator: &User, target: &Channel) -> Result<Invite> {
let code = nanoid!(8, &*ALPHABET);
let code = nanoid!(8, &ALPHABET);
let invite = match &target {
Channel::Group { id, .. } => Ok(Invite::Group {
code,

View File

@@ -385,7 +385,7 @@ impl SendableEmbed {
impl BulkMessageResponse {
pub async fn transform(
db: &Database,
channel: &Channel,
channel: Option<&Channel>,
messages: Vec<Message>,
include_users: Option<bool>,
) -> Result<BulkMessageResponse> {
@@ -394,7 +394,8 @@ impl BulkMessageResponse {
let users = User::fetch_foreign_users(db, &user_ids).await?;
Ok(match channel {
Channel::TextChannel { server, .. } | Channel::VoiceChannel { server, .. } => {
Some(Channel::TextChannel { server, .. })
| Some(Channel::VoiceChannel { server, .. }) => {
BulkMessageResponse::MessagesAndUsers {
messages,
users,

View File

@@ -1,4 +1,5 @@
use std::{collections::HashSet, str::FromStr};
use once_cell::sync::Lazy;
use ulid::Ulid;
@@ -8,13 +9,10 @@ use crate::{
Database, Result,
};
lazy_static! {
/// Permissible emojis
static ref PERMISSIBLE_EMOJIS: HashSet<String> = include_str!(crate::asset!("emojis.txt"))
.split('\n')
.map(|x| x.into())
.collect();
}
static PERMISSIBLE_EMOJIS: Lazy<HashSet<String>> = Lazy::new(|| include_str!(crate::asset!("emojis.txt"))
.split('\n')
.map(|x| x.into())
.collect());
impl Emoji {
/// Get parent id

View File

@@ -28,3 +28,7 @@ pub mod users {
pub mod user;
pub mod user_settings;
}
pub mod safety {
pub mod report;
}

View File

@@ -0,0 +1,9 @@
use crate::{models::report::PartialReport, models::Report, Database, Result};
impl Report {
/// Update report data
pub async fn update(&mut self, db: &Database, partial: PartialReport) -> Result<()> {
self.apply_options(partial.clone());
db.update_report(&self.id, &partial).await
}
}

View File

@@ -57,6 +57,14 @@ pub async fn create_database(db: &MongoDb) {
.await
.expect("Failed to create user_settings collection.");
db.create_collection("safety_reports", None)
.await
.expect("Failed to create safety_reports collection.");
db.create_collection("safety_snapshots", None)
.await
.expect("Failed to create safety_snapshots collection.");
db.create_collection("bots", None)
.await
.expect("Failed to create bots collection.");
@@ -103,18 +111,18 @@ pub async fn create_database(db: &MongoDb) {
},
"name": "content"
},
{
"key": {
"channel": 1_i32
},
"name": "channel"
},
{
"key": {
"channel": 1_i32,
"_id": 1_i32
},
"name": "channel_id_compound"
},
{
"key": {
"author": 1_i32
},
"name": "author"
}
]
},

View File

@@ -1,4 +1,4 @@
use std::time::Duration;
use std::{time::Duration, ops::BitXor};
use bson::{Bson, DateTime};
use futures::StreamExt;
@@ -16,7 +16,7 @@ struct MigrationInfo {
revision: i32,
}
pub const LATEST_REVISION: i32 = 18;
pub const LATEST_REVISION: i32 = 21;
pub async fn migrate_database(db: &MongoDb) {
let migrations = db.col::<Document>("migrations");
@@ -503,13 +503,8 @@ pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 {
update.insert(
"default_permissions",
(*DEFAULT_PERMISSION_SERVER
// Remove Send Message permission if it wasn't originally granted
^ (if has_send {
0
} else {
Permission::SendMessage as u64
})) as i64,
// Remove Send Message permission if it wasn't originally granted
DEFAULT_PERMISSION_SERVER.bitxor(if has_send { 0 } else { Permission::SendMessage as u64}) as i64,
);
if let Some(Bson::Document(mut roles)) = document.remove("roles") {
@@ -661,6 +656,103 @@ pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 {
.expect("Failed to update server members.");
}
if revision <= 18 {
info!("Running migration [revision 18 / 27-02-2022]: Create author index on messages. Drop plain channel index if exists.");
if db
.db()
.run_command(
doc! {
"dropIndexes": "messages",
"index": ["channel"]
},
None,
)
.await
.is_err()
{
info!("Failed to drop `messages.channel` index but this is ok since that means it's probably gone.");
}
db.db()
.run_command(
doc! {
"createIndexes": "messages",
"indexes": [
{
"key": {
"author": 1_i32,
},
"name": "author"
}
]
},
None,
)
.await
.expect("Failed to create messages author index.");
}
if revision <= 19 {
info!("Running migration [revision 19 / 27-02-2023]: Create report / snapshot collections, migrate to new model if applicable.");
// TODO: make these fail once production is migrated
if db
.db()
.create_collection("safety_reports", None)
.await
.is_err()
{
info!("Failed to create safety_reports collection but this is expected in production.");
}
if db
.db()
.create_collection("safety_snapshots", None)
.await
.is_err()
{
info!(
"Failed to create safety_snapshots collection but this is expected in production."
);
}
db.col::<Document>("safety_reports")
.update_many(
doc! {},
doc! {
"$set": {
"status": "Created"
}
},
None,
)
.await
.unwrap();
}
if revision <= 20 {
info!("Running migration [revision 20 / 28-02-2023]: Add index `snapshot.report_id`.");
db.db()
.run_command(
doc! {
"createIndexes": "safety_snapshots",
"indexes": [
{
"key": {
"report_id": 1_i32
},
"name": "report_id"
}
]
},
None,
)
.await
.expect("Failed to create safety snapshot index.");
}
// Need to migrate fields on attachments, change `user_id`, `object_id`, etc to `parent`.
// Reminder to update LATEST_REVISION when adding new migrations.

View File

@@ -0,0 +1,90 @@
use std::collections::HashMap;
use bson::{from_document, Document};
use futures::StreamExt;
use crate::{
models::stats::{Index, Stats},
AbstractStats, Error, Result,
};
use super::super::MongoDb;
#[async_trait]
impl AbstractStats for MongoDb {
async fn generate_stats(&self) -> Result<Stats> {
let mut indices = HashMap::new();
let mut coll_stats = HashMap::new();
let collection_names =
self.db()
.list_collection_names(None)
.await
.map_err(|_| Error::DatabaseError {
operation: "list_collection_names",
with: "database",
})?;
for collection in collection_names {
indices.insert(
collection.to_string(),
self.col::<Document>(&collection)
.aggregate(
vec![doc! {
"$indexStats": { }
}],
None,
)
.await
.map_err(|_| Error::DatabaseError {
operation: "aggregate",
with: "col",
})?
.filter_map(|s| async { s.ok() })
.collect::<Vec<Document>>()
.await
.into_iter()
.filter_map(|doc| from_document(doc).ok())
.collect::<Vec<Index>>(),
);
coll_stats.insert(
collection.to_string(),
self.col::<Document>(&collection)
.aggregate(
vec![doc! {
"$collStats": {
"latencyStats": {
"histograms": true
},
"storageStats": {},
"count": {},
"queryExecStats": {}
}
}],
None,
)
.await
.map_err(|_| Error::DatabaseError {
operation: "aggregate",
with: "col",
})?
.filter_map(|s| async { s.ok() })
.collect::<Vec<Document>>()
.await
.into_iter()
.filter_map(|doc| from_document(doc).ok())
.next()
.ok_or(Error::DatabaseError {
operation: "next aggregation",
with: "col",
})?,
);
}
Ok(Stats {
indices,
coll_stats,
})
}
}

View File

@@ -2,7 +2,9 @@ use bson::{to_bson, Document};
use futures::try_join;
use mongodb::options::FindOptions;
use crate::models::message::{AppendMessage, Message, MessageSort, PartialMessage};
use crate::models::message::{
AppendMessage, Message, MessageQuery, MessageSort, MessageTimePeriod, PartialMessage,
};
use crate::r#impl::mongo::DocumentId;
use crate::{AbstractMessage, Error, Result};
@@ -139,145 +141,138 @@ impl AbstractMessage for MongoDb {
.await
}
async fn fetch_messages(
&self,
channel: &str,
limit: Option<i64>,
before: Option<String>,
after: Option<String>,
sort: Option<MessageSort>,
nearby: Option<String>,
) -> Result<Vec<Message>> {
let limit = limit.unwrap_or(50);
Ok(if let Some(nearby) = nearby {
let (a, b) = try_join!(
self.find_with_options::<_, Message>(
COL,
async fn fetch_messages(&self, query: MessageQuery) -> Result<Vec<Message>> {
let mut filter = doc! {};
// 1. Apply message filters
if let Some(channel) = query.filter.channel {
filter.insert("channel", channel);
}
if let Some(author) = query.filter.author {
filter.insert("author", author);
}
let is_search_query = if let Some(query) = query.filter.query {
filter.insert(
"$text",
doc! {
"$search": query
},
);
true
} else {
false
};
// 2. Find query limit
let limit = query.limit.unwrap_or(50);
// 3. Apply message time period
match query.time_period {
MessageTimePeriod::Relative { nearby } => {
// 3.1. Prepare filters
let mut older_message_filter = filter.clone();
let mut newer_message_filter = filter;
older_message_filter.insert(
"_id",
doc! {
"channel": channel,
"_id": {
"$gte": &nearby
}
"$lt": &nearby
},
FindOptions::builder()
.limit(limit / 2 + 1)
.sort(doc! {
"_id": 1_i32
})
.build(),
),
self.find_with_options::<_, Message>(
COL,
);
newer_message_filter.insert(
"_id",
doc! {
"channel": channel,
"_id": {
"$lt": &nearby
}
"$gte": &nearby
},
);
// 3.2. Execute in both directions
let (a, b) = try_join!(
self.find_with_options::<_, Message>(
COL,
newer_message_filter,
FindOptions::builder()
.limit(limit / 2 + 1)
.sort(doc! {
"_id": 1_i32
})
.build(),
),
self.find_with_options::<_, Message>(
COL,
older_message_filter,
FindOptions::builder()
.limit(limit / 2)
.sort(doc! {
"_id": -1_i32
})
.build(),
)
)?;
Ok([a, b].concat())
}
MessageTimePeriod::Absolute {
before,
after,
sort,
} => {
// 3.1. Apply message ID filter
if let Some(doc) = match (before, after) {
(Some(before), Some(after)) => Some(doc! {
"$lt": before,
"$gt": after
}),
(Some(before), _) => Some(doc! {
"$lt": before
}),
(_, Some(after)) => Some(doc! {
"$gt": after
}),
_ => None,
} {
filter.insert("_id", doc);
}
// 3.2. Execute with given message sort
self.find_with_options(
COL,
filter,
FindOptions::builder()
.limit(limit / 2)
.sort(doc! {
"_id": -1_i32
.limit(limit)
.sort(match sort.unwrap_or(MessageSort::Latest) {
// Sort by relevance, fallback to latest
MessageSort::Relevance => {
if is_search_query {
doc! {
"score": {
"$meta": "textScore"
}
}
} else {
doc! {
"_id": -1_i32
}
}
}
// Sort by latest first
MessageSort::Latest => doc! {
"_id": -1_i32
},
// Sort by oldest first
MessageSort::Oldest => doc! {
"_id": 1_i32
},
})
.build(),
)
)?;
[a, b].concat()
} else {
let mut query = doc! { "channel": channel };
if let Some(before) = before {
query.insert("_id", doc! { "$lt": before });
.await
}
if let Some(after) = after {
query.insert("_id", doc! { "$gt": after });
}
let sort: i32 = if let MessageSort::Latest = sort.unwrap_or(MessageSort::Latest) {
-1
} else {
1
};
self.find_with_options::<_, Message>(
COL,
query,
FindOptions::builder()
.limit(limit)
.sort(doc! {
"_id": sort
})
.build(),
)
.await?
})
}
async fn search_messages(
&self,
channel: &str,
query: &str,
limit: Option<i64>,
before: Option<String>,
after: Option<String>,
sort: MessageSort,
) -> Result<Vec<Message>> {
let limit = limit.unwrap_or(50);
let mut filter = doc! {
"channel": channel,
"$text": {
"$search": query
}
};
if let Some(doc) = match (before, after) {
(Some(before), Some(after)) => Some(doc! {
"lt": before,
"gt": after
}),
(Some(before), _) => Some(doc! {
"lt": before
}),
(_, Some(after)) => Some(doc! {
"gt": after
}),
_ => None,
} {
filter.insert("_id", doc);
}
self.find_with_options(
COL,
filter,
FindOptions::builder()
.projection(if let MessageSort::Relevance = &sort {
doc! {
"score": {
"$meta": "textScore"
}
}
} else {
doc! {}
})
.limit(limit)
.sort(match &sort {
MessageSort::Relevance => doc! {
"score": {
"$meta": "textScore"
}
},
MessageSort::Latest => doc! {
"_id": -1_i32
},
MessageSort::Oldest => doc! {
"_id": 1_i32
},
})
.build(),
)
.await
}
/// Add a new reaction to a message

View File

@@ -37,7 +37,7 @@ impl AbstractAttachment for MongoDb {
parent_type: &str,
parent_id: &str,
) -> Result<File> {
let key = format!("{}_id", parent_type);
let key = format!("{parent_type}_id");
match self
.find_one::<File>(
COL,

View File

@@ -13,6 +13,7 @@ use crate::{util::manipulation::prefix_keys, AbstractDatabase, Error, Result};
pub mod admin {
pub mod migrations;
pub mod stats;
}
pub mod media {
@@ -90,17 +91,25 @@ impl MongoDb {
where
O: Into<Option<FindOptions>>,
{
Ok(self
.col::<T>(collection)
.find(projection, options)
.await
.map_err(|_| Error::DatabaseError {
let result = self.col::<T>(collection).find(projection, options).await;
Ok(if cfg!(debug_assertions) {
result.unwrap()
} else {
result.map_err(|_| Error::DatabaseError {
operation: "find",
with: collection,
})?
.filter_map(|s| async { s.ok() })
.collect::<Vec<T>>()
.await)
}
.filter_map(|s| async {
if cfg!(debug_assertions) {
// Hard fail on invalid documents
Some(s.unwrap())
} else {
s.ok()
}
})
.collect::<Vec<T>>()
.await)
}
async fn find<T: DeserializeOwned + Unpin + Send + Sync>(

View File

@@ -1,3 +1,4 @@
use crate::models::report::PartialReport;
use crate::models::Report;
use crate::{AbstractReport, Result};
@@ -10,4 +11,18 @@ impl AbstractReport for MongoDb {
async fn insert_report(&self, report: &Report) -> Result<()> {
self.insert_one(COL, report).await.map(|_| ())
}
async fn update_report(&self, id: &str, report: &PartialReport) -> Result<()> {
self.update_one_by_id(COL, id, report, vec![], None)
.await
.map(|_| ())
}
async fn fetch_report(&self, report_id: &str) -> Result<Report> {
self.find_one_by_id(COL, report_id).await
}
async fn fetch_reports(&self) -> Result<Vec<Report>> {
self.find(COL, doc! {}).await
}
}

View File

@@ -10,4 +10,14 @@ impl AbstractSnapshot for MongoDb {
async fn insert_snapshot(&self, snapshot: &Snapshot) -> Result<()> {
self.insert_one(COL, snapshot).await.map(|_| ())
}
async fn fetch_snapshot(&self, report_id: &str) -> Result<Snapshot> {
self.find_one(
COL,
doc! {
"report_id": report_id
},
)
.await
}
}

View File

@@ -1,6 +1,7 @@
use bson::Document;
use futures::StreamExt;
use mongodb::options::{Collation, CollationStrength, FindOneOptions, FindOptions};
use once_cell::sync::Lazy;
use crate::models::user::{FieldsUser, PartialUser, RelationshipStatus, User};
use crate::r#impl::mongo::IntoDocumentPath;
@@ -8,16 +9,14 @@ use crate::{AbstractUser, Error, Result};
use super::super::MongoDb;
lazy_static! {
static ref FIND_USERNAME_OPTIONS: FindOneOptions = FindOneOptions::builder()
.collation(
Collation::builder()
.locale("en")
.strength(CollationStrength::Secondary)
.build()
)
.build();
}
static FIND_USERNAME_OPTIONS: Lazy<FindOneOptions> = Lazy::new(|| FindOneOptions::builder()
.collation(
Collation::builder()
.locale("en")
.strength(CollationStrength::Secondary)
.build()
)
.build());
static COL: &str = "users";
@@ -265,7 +264,7 @@ impl AbstractUser for MongoDb {
[
{
"_id": target_id,
"status": format!("{:?}", relationship)
"status": format!("{relationship:?}")
}
]
]

View File

@@ -11,8 +11,6 @@ extern crate impl_ops;
#[macro_use]
extern crate optional_struct;
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate bitfield;
#[macro_use]
extern crate bson;

View File

@@ -0,0 +1,122 @@
use std::collections::HashMap;
use iso8601_timestamp::Timestamp;
use serde::{Deserialize, Serialize};
/// Index access information
#[derive(Serialize, Deserialize, JsonSchema, Debug)]
pub struct IndexAccess {
/// Operations since timestamp
ops: i32,
/// Timestamp at which data keeping begun
since: Timestamp,
}
/// Collection index
#[derive(Serialize, Deserialize, JsonSchema, Debug)]
pub struct Index {
/// Index name
name: String,
/// Access information
accesses: IndexAccess,
}
/// Histogram entry
#[derive(Serialize, Deserialize, JsonSchema, Debug)]
pub struct LatencyHistogramEntry {
/// Time
micros: i64,
/// Count
count: i64,
}
/// Collection latency stats
#[derive(Serialize, Deserialize, JsonSchema, Debug)]
pub struct LatencyStats {
/// Total operations
ops: i64,
/// Timestamp at which data keeping begun
latency: i64,
/// Histogram representation of latency data
histogram: Vec<LatencyHistogramEntry>,
}
/// Collection storage stats
#[derive(Serialize, Deserialize, JsonSchema, Debug)]
#[serde(rename_all = "camelCase")]
pub struct StorageStats {
/// Uncompressed data size
size: i64,
/// Data size on disk
storage_size: i64,
/// Total size of all indexes
total_index_size: i64,
/// Sum of storage size and total index size
total_size: i64,
/// Individual index sizes
index_sizes: HashMap<String, i64>,
/// Number of documents in collection
count: i64,
/// Average size of each document
avg_obj_size: i64,
}
/// Query collection scan stats
#[derive(Serialize, Deserialize, JsonSchema, Debug)]
#[serde(rename_all = "camelCase")]
pub struct CollectionScans {
/// Number of total collection scans
total: i64,
/// Number of total collection scans not using a tailable cursor
non_tailable: i64,
}
/// Collection query execution stats
#[derive(Serialize, Deserialize, JsonSchema, Debug)]
#[serde(rename_all = "camelCase")]
pub struct QueryExecStats {
/// Stats regarding collection scans
collection_scans: CollectionScans,
}
/// Collection stats
#[derive(Serialize, Deserialize, JsonSchema, Debug)]
#[serde(rename_all = "camelCase")]
pub struct CollectionStats {
/// Namespace
ns: String,
/// Local time
local_time: Timestamp,
/// Latency stats
latency_stats: HashMap<String, LatencyStats>,
/// Query exec stats
query_exec_stats: QueryExecStats,
/// Number of documents in collection
count: u64,
}
/// Server Stats
#[derive(Serialize, JsonSchema, Debug)]
pub struct Stats {
/// Index usage information
pub indices: HashMap<String, Vec<Index>>,
/// Collection stats
pub coll_stats: HashMap<String, CollectionStats>,
}

View File

@@ -160,10 +160,11 @@ pub struct Message {
/// # Message Sort
///
/// Sort used for retrieving messages
#[derive(Serialize, Deserialize, JsonSchema)]
#[derive(Serialize, Deserialize, JsonSchema, Debug, Default)]
#[cfg_attr(feature = "rocket_impl", derive(FromFormField))]
pub enum MessageSort {
/// Sort by the most relevant messages
#[default]
Relevance,
/// Sort by the newest messages first
Latest,
@@ -171,10 +172,54 @@ pub enum MessageSort {
Oldest,
}
impl Default for MessageSort {
fn default() -> MessageSort {
MessageSort::Relevance
}
/// # Message Time Period
///
/// Filter and sort messages by time
#[derive(Serialize, Deserialize, JsonSchema)]
#[serde(untagged)]
pub enum MessageTimePeriod {
Relative {
/// Message id to search around
///
/// Specifying 'nearby' ignores 'before', 'after' and 'sort'.
/// It will also take half of limit rounded as the limits to each side.
/// It also fetches the message ID specified.
nearby: String,
},
Absolute {
/// Message id before which messages should be fetched
before: Option<String>,
/// Message id after which messages should be fetched
after: Option<String>,
/// Message sort direction
sort: Option<MessageSort>,
},
}
/// # Message Filter
#[derive(Serialize, Deserialize, JsonSchema, Default)]
pub struct MessageFilter {
/// Parent channel ID
pub channel: Option<String>,
/// Message author ID
pub author: Option<String>,
/// Search query
pub query: Option<String>,
}
/// # Message Query
#[derive(Serialize, Deserialize, JsonSchema)]
pub struct MessageQuery {
/// Maximum number of messages to fetch
///
/// For fetching nearby messages, this is \`(limit + 1)\`.
pub limit: Option<i64>,
/// Filter to apply
#[serde(flatten)]
pub filter: MessageFilter,
/// Time period to fetch
#[serde(flatten)]
pub time_period: MessageTimePeriod,
}
/// # Bulk Message Response

View File

@@ -1,10 +1,11 @@
use serde::{Deserialize, Serialize};
/// Metadata associated with file
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)]
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, Default)]
#[serde(tag = "type")]
pub enum Metadata {
/// File is just a generic uncategorised file
#[default]
File,
/// File contains textual data and should be displayed as such
Text,
@@ -16,12 +17,6 @@ pub enum Metadata {
Audio,
}
impl Default for Metadata {
fn default() -> Metadata {
Metadata::File
}
}
/// Representation of a File on Revolt
/// Generated by Autumn
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, Default)]

View File

@@ -1,6 +1,7 @@
mod admin {
pub mod migrations;
pub mod simple;
pub mod stats;
}
mod media {

View File

@@ -44,6 +44,7 @@ pub enum UserReportReason {
Underage,
}
/// The content being reported
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)]
#[serde(tag = "type")]
pub enum ReportedContent {
@@ -70,8 +71,25 @@ pub enum ReportedContent {
},
}
/// User-generated platform moderation report.
/// Status of the report
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)]
#[serde(tag = "status")]
pub enum ReportStatus {
/// Report is waiting for triage / action
Created {},
/// Report was rejected
Rejected { rejection_reason: String },
/// Report was actioned and resolved
Resolved {},
}
/// User-generated platform moderation report.
#[derive(Serialize, Deserialize, JsonSchema, Debug, OptionalStruct, Clone)]
#[optional_derive(Serialize, Deserialize, JsonSchema, Debug, Default, Clone)]
#[optional_name = "PartialReport"]
#[opt_skip_serializing_none]
pub struct Report {
/// Unique Id
#[serde(rename = "_id")]
@@ -82,4 +100,11 @@ pub struct Report {
pub content: ReportedContent,
/// Additional report context
pub additional_context: String,
/// Status of the report
#[opt_passthrough]
#[serde(flatten)]
pub status: ReportStatus,
/// Additional notes included on the report
#[serde(default)]
pub notes: String,
}

View File

@@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize};
use crate::models::{Message, Server, User};
use crate::models::{Channel, Message, Server, User};
/// Enum to map into different models
/// that can be saved in a snapshot
@@ -9,11 +9,11 @@ use crate::models::{Message, Server, User};
pub enum SnapshotContent {
Message {
/// Context before the message
#[serde(rename = "_prior_context")]
#[serde(rename = "_prior_context", default)]
prior_context: Vec<Message>,
/// Context after the message
#[serde(rename = "_leading_context")]
#[serde(rename = "_leading_context", default)]
leading_context: Vec<Message>,
/// Message
@@ -35,3 +35,20 @@ pub struct Snapshot {
/// Snapshot of content
pub content: SnapshotContent,
}
/// Snapshot of some content with required data to render
#[derive(Serialize, JsonSchema, Debug)]
pub struct SnapshotWithContext {
/// Snapshot itself
#[serde(flatten)]
pub snapshot: Snapshot,
/// Users involved in snapshot
#[serde(rename = "_users", skip_serializing_if = "Vec::is_empty")]
pub users: Vec<User>,
/// Channels involved in snapshot
#[serde(rename = "_channels", skip_serializing_if = "Vec::is_empty")]
pub channels: Vec<Channel>,
/// Server involved in snapshot
#[serde(rename = "_server", skip_serializing_if = "Option::is_none")]
pub server: Option<Server>,
}

View File

@@ -118,7 +118,7 @@ pub struct Server {
#[serde(skip_serializing_if = "Option::is_none")]
pub banner: Option<File>,
/// Enum of server flags
/// Bitfield of server flags
#[serde(skip_serializing_if = "Option::is_none")]
pub flags: Option<i32>,

View File

@@ -1,6 +1,7 @@
use num_enum::TryFromPrimitive;
use serde::{Deserialize, Serialize};
use std::ops;
use once_cell::sync::Lazy;
use std::ops::{self, Add};
/// Permission value on Revolt
///
@@ -97,24 +98,18 @@ pub enum Permission {
impl_op_ex!(+ |a: &Permission, b: &Permission| -> u64 { *a as u64 | *b as u64 });
impl_op_ex_commutative!(+ |a: &u64, b: &Permission| -> u64 { *a | *b as u64 });
lazy_static! {
pub static ref ALLOW_IN_TIMEOUT: u64 = Permission::ViewChannel + Permission::ReadMessageHistory;
pub static ref DEFAULT_PERMISSION_VIEW_ONLY: u64 =
Permission::ViewChannel + Permission::ReadMessageHistory;
pub static ref DEFAULT_PERMISSION: u64 = *DEFAULT_PERMISSION_VIEW_ONLY
+ Permission::SendMessage
+ Permission::InviteOthers
+ Permission::SendEmbeds
+ Permission::UploadFiles
+ Permission::Connect
+ Permission::Speak;
pub static ref DEFAULT_PERMISSION_SAVED_MESSAGES: u64 = Permission::GrantAllSafe as u64;
pub static ref DEFAULT_PERMISSION_DIRECT_MESSAGE: u64 = *DEFAULT_PERMISSION
+ Permission::ManageChannel
+ Permission::React;
pub static ref DEFAULT_PERMISSION_SERVER: u64 =
*DEFAULT_PERMISSION + Permission::React + Permission::ChangeNickname + Permission::ChangeAvatar;
}
pub static ALLOW_IN_TIMEOUT: Lazy<u64> = Lazy::new(|| Permission::ViewChannel + Permission::ReadMessageHistory);
pub static DEFAULT_PERMISSION_VIEW_ONLY: Lazy<u64> = Lazy::new(|| Permission::ViewChannel + Permission::ReadMessageHistory);
pub static DEFAULT_PERMISSION: Lazy<u64> = Lazy::new(|| DEFAULT_PERMISSION_VIEW_ONLY.add(
Permission::SendMessage
+ Permission::InviteOthers
+ Permission::SendEmbeds
+ Permission::UploadFiles
+ Permission::Connect
+ Permission::Speak));
pub static DEFAULT_PERMISSION_SAVED_MESSAGES: u64 = Permission::GrantAllSafe as u64;
pub static DEFAULT_PERMISSION_DIRECT_MESSAGE: Lazy<u64> = Lazy::new(|| DEFAULT_PERMISSION.add(Permission::ManageChannel + Permission::React));
pub static DEFAULT_PERMISSION_SERVER: Lazy<u64> = Lazy::new(|| DEFAULT_PERMISSION.add(Permission::React + Permission::ChangeNickname + Permission::ChangeAvatar));
bitfield! {
#[derive(Default)]

View File

@@ -113,7 +113,7 @@ async fn calculate_channel_permission(
let value: PermissionValue = match channel {
Channel::SavedMessages { user, .. } => {
if user == &data.perspective.id {
(*DEFAULT_PERMISSION_SAVED_MESSAGES).into()
DEFAULT_PERMISSION_SAVED_MESSAGES.into()
} else {
0_u64.into()
}

View File

@@ -51,6 +51,10 @@ pub fn get_relationship(a: &User, b: &str) -> RelationshipStatus {
async fn calculate_permission(data: &mut PermissionCalculator<'_>, db: &crate::Database) -> u32 {
let user = data.user.get().unwrap();
if data.perspective.privileged {
return u32::MAX;
}
if data.perspective.id == user.id {
return u32::MAX;
}

View File

@@ -1,14 +1,14 @@
use std::env;
use serde::{Deserialize, Serialize};
use once_cell::sync::Lazy;
lazy_static! {
pub static ref REGION_ID: u16 = env::var("REGION_ID")
.unwrap_or_else(|_| "0".to_string())
.parse()
.unwrap();
pub static ref REGION_KEY: String = format!("region{}", &*REGION_ID);
}
pub static REGION_ID: Lazy<u16> = Lazy::new(|| env::var("REGION_ID")
.unwrap_or_else(|_| "0".to_string())
.parse()
.unwrap());
pub static REGION_KEY: Lazy<String> = Lazy::new(|| format!("region{}", &*REGION_ID));
/// Compact presence information for a user
#[derive(Serialize, Deserialize, Debug)]

View File

@@ -2,8 +2,8 @@
use crate::Database;
use deadqueue::limited::Queue;
use mongodb::bson::doc;
use std::{collections::HashMap, time::Duration};
use once_cell::sync::Lazy;
use super::DelayedTask;
@@ -38,9 +38,7 @@ struct Task {
event: AckEvent,
}
lazy_static! {
static ref Q: Queue<Data> = Queue::new(10_000);
}
static Q: Lazy<Queue<Data>> = Lazy::new(|| Queue::new(10_000));
/// Queue a new task for a worker
pub async fn queue(channel: String, user: String, event: AckEvent) {

View File

@@ -2,8 +2,8 @@
use crate::{models::channel::PartialChannel, Database};
use deadqueue::limited::Queue;
use mongodb::bson::doc;
use std::{collections::HashMap, time::Duration};
use once_cell::sync::Lazy;
use super::DelayedTask;
@@ -26,9 +26,7 @@ struct Task {
is_dm: bool,
}
lazy_static! {
static ref Q: Queue<Data> = Queue::new(10_000);
}
static Q: Lazy<Queue<Data>> = Lazy::new(|| Queue::new(10_000));
/// Queue a new task for a worker
pub async fn queue(channel: String, id: String, is_dm: bool) {

View File

@@ -9,6 +9,7 @@ use async_lock::Semaphore;
use async_std::task::spawn;
use deadqueue::limited::Queue;
use std::sync::Arc;
use once_cell::sync::Lazy;
/// Task information
#[derive(Debug)]
@@ -21,9 +22,8 @@ struct EmbedTask {
content: String,
}
lazy_static! {
static ref Q: Queue<EmbedTask> = Queue::new(10_000);
}
static Q: Lazy<Queue<EmbedTask>> = Lazy::new(|| Queue::new(10_000));
/// Queue a new task for a worker
pub async fn queue(channel: String, id: String, content: String) {

View File

@@ -1,8 +1,8 @@
use crate::bson::doc;
use crate::util::variables::delta::VAPID_PRIVATE_KEY;
use authifier::Database;
use deadqueue::limited::Queue;
use once_cell::sync::Lazy;
use web_push::{
ContentEncoding, SubscriptionInfo, SubscriptionKeys, VapidSignatureBuilder, WebPushClient,
WebPushMessageBuilder,
@@ -17,9 +17,8 @@ struct PushTask {
payload: String,
}
lazy_static! {
static ref Q: Queue<PushTask> = Queue::new(10_000);
}
static Q: Lazy<Queue<PushTask>> = Lazy::new(|| Queue::new(10_000));
/// Queue a new task for a worker
pub async fn queue(recipients: Vec<String>, payload: String) {

View File

@@ -0,0 +1,6 @@
use crate::{models::stats::Stats, Result};
#[async_trait]
pub trait AbstractStats: Sync + Send {
async fn generate_stats(&self) -> Result<Stats>;
}

View File

@@ -1,4 +1,4 @@
use crate::models::message::{AppendMessage, Message, MessageSort, PartialMessage};
use crate::models::message::{AppendMessage, Message, MessageQuery, PartialMessage};
use crate::Result;
#[async_trait]
@@ -21,27 +21,8 @@ pub trait AbstractMessage: Sync + Send {
/// Delete messages from a channel by their ids and corresponding channel id
async fn delete_messages(&self, channel: &str, ids: Vec<String>) -> Result<()>;
/// Fetch multiple messages
async fn fetch_messages(
&self,
channel: &str,
limit: Option<i64>,
before: Option<String>,
after: Option<String>,
sort: Option<MessageSort>,
nearby: Option<String>,
) -> Result<Vec<Message>>;
/// Search for messages
async fn search_messages(
&self,
channel: &str,
query: &str,
limit: Option<i64>,
before: Option<String>,
after: Option<String>,
sort: MessageSort,
) -> Result<Vec<Message>>;
/// Fetch multiple messages by given query
async fn fetch_messages(&self, query: MessageQuery) -> Result<Vec<Message>>;
/// Add a new reaction to a message
async fn add_reaction(&self, id: &str, emoji: &str, user: &str) -> Result<()>;

View File

@@ -1,5 +1,6 @@
mod admin {
pub mod migrations;
pub mod stats;
}
mod media {
@@ -33,6 +34,7 @@ mod safety {
}
pub use admin::migrations::AbstractMigrations;
pub use admin::stats::AbstractStats;
pub use media::attachment::AbstractAttachment;
pub use media::emoji::AbstractEmoji;
@@ -58,6 +60,7 @@ pub trait AbstractDatabase:
Sync
+ Send
+ AbstractMigrations
+ AbstractStats
+ AbstractAttachment
+ AbstractEmoji
+ AbstractChannel

View File

@@ -1,3 +1,4 @@
use crate::models::report::PartialReport;
use crate::models::Report;
use crate::Result;
@@ -5,4 +6,13 @@ use crate::Result;
pub trait AbstractReport: Sync + Send {
/// Insert a new report into the database
async fn insert_report(&self, report: &Report) -> Result<()>;
/// Update a given report with new information
async fn update_report(&self, id: &str, message: &PartialReport) -> Result<()>;
/// Fetch report
async fn fetch_report(&self, report_id: &str) -> Result<Report>;
/// Fetch reports
async fn fetch_reports(&self) -> Result<Vec<Report>>;
}

View File

@@ -5,4 +5,7 @@ use crate::Result;
pub trait AbstractSnapshot: Sync + Send {
/// Insert a new snapshot into the database
async fn insert_snapshot(&self, snapshot: &Snapshot) -> Result<()>;
/// Fetch a snapshot by a report's id
async fn fetch_snapshot(&self, report_id: &str) -> Result<Snapshot>;
}

View File

@@ -5,6 +5,7 @@ use linkify::{LinkFinder, LinkKind};
use regex::Regex;
use serde::{Deserialize, Serialize};
use std::{collections::HashSet, sync::Arc};
use once_cell::sync::Lazy;
use crate::{models::attachment::File, Error, Result};
@@ -175,6 +176,9 @@ pub enum Embed {
None,
}
static RE_CODE: Lazy<Regex> = Lazy::new(|| Regex::new("```(?:.|\n)+?```|`(?:.|\n)+?`").unwrap());
static RE_IGNORED: Lazy<Regex> = Lazy::new(|| Regex::new("(<http.+>)").unwrap());
impl Embed {
/// Generate embeds from given content
pub async fn generate(
@@ -183,10 +187,7 @@ impl Embed {
max_embeds: usize,
semaphore: Arc<Semaphore>,
) -> Result<Vec<Embed>> {
lazy_static! {
static ref RE_CODE: Regex = Regex::new("```(?:.|\n)+?```|`(?:.|\n)+?`").unwrap();
static ref RE_IGNORED: Regex = Regex::new("(<http.+>)").unwrap();
}
// Ignore code blocks.
let content = RE_CODE.replace_all(&content, "");

View File

@@ -83,6 +83,11 @@ impl Ref {
pub async fn as_webhook(&self, db: &Database) -> Result<Webhook> {
db.fetch_webhook(&self.id).await
}
/// Fetch report from Ref
pub async fn as_report(&self, db: &Database) -> Result<Report> {
db.fetch_report(&self.id).await
}
}
impl<'r> FromParam<'r> for Ref {

View File

@@ -75,6 +75,7 @@ pub enum Error {
permission: UserPermission,
},
NotElevated,
NotPrivileged,
CannotGiveMissingPermissions,
NotOwner,
@@ -174,6 +175,7 @@ impl<'r> Responder<'r, 'static> for Error {
Error::MissingPermission { .. } => Status::Forbidden,
Error::MissingUserPermission { .. } => Status::Forbidden,
Error::NotElevated => Status::Forbidden,
Error::NotPrivileged => Status::Forbidden,
Error::CannotGiveMissingPermissions => Status::Forbidden,
Error::NotOwner => Status::Forbidden,

View File

@@ -1,73 +1,50 @@
use std::env;
use once_cell::sync::Lazy;
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.");
// Application Settings
pub static PUBLIC_URL: Lazy<String> = Lazy::new(|| env::var("REVOLT_PUBLIC_URL").expect("Missing REVOLT_PUBLIC_URL environment variable."));
pub static APP_URL: Lazy<String> = Lazy::new(|| env::var("REVOLT_APP_URL").expect("Missing REVOLT_APP_URL environment variable."));
pub static EXTERNAL_WS_URL: Lazy<String> = Lazy::new(|| 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 JANUARY_CONCURRENT_CONNECTIONS: usize =
env::var("JANUARY_CONCURRENT_CONNECTIONS").map_or(50, |v| v.parse().unwrap());
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 AUTUMN_URL: Lazy<String> = Lazy::new(|| env::var("AUTUMN_PUBLIC_URL").unwrap_or_else(|_| "https://example.com".to_string()));
pub static JANUARY_URL: Lazy<String> = Lazy::new(|| env::var("JANUARY_PUBLIC_URL").unwrap_or_else(|_| "https://example.com".to_string()));
pub static JANUARY_CONCURRENT_CONNECTIONS: Lazy<usize> = Lazy::new(|| env::var("JANUARY_CONCURRENT_CONNECTIONS").map_or(50, |v| v.parse().unwrap()));
pub static VOSO_URL: Lazy<String> = Lazy::new(|| env::var("VOSO_PUBLIC_URL").unwrap_or_else(|_| "https://example.com".to_string()));
pub static VOSO_WS_HOST: Lazy<String> = Lazy::new(|| env::var("VOSO_WS_HOST").unwrap_or_else(|_| "wss://example.com".to_string()));
pub static VOSO_MANAGE_TOKEN: Lazy<String> = Lazy::new(|| 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.");
pub static ref AUTHIFIER_SHIELD_KEY: Option<String> =
env::var("REVOLT_AUTHIFIER_SHIELD_KEY").ok();
pub static HCAPTCHA_KEY: Lazy<String> = Lazy::new(|| env::var("REVOLT_HCAPTCHA_KEY").unwrap_or_else(|_| "0x0000000000000000000000000000000000000000".to_string()));
pub static HCAPTCHA_SITEKEY: Lazy<String> = Lazy::new(|| env::var("REVOLT_HCAPTCHA_SITEKEY").unwrap_or_else(|_| "10000000-ffff-ffff-ffff-000000000001".to_string()));
pub static VAPID_PRIVATE_KEY: Lazy<String> = Lazy::new(|| env::var("REVOLT_VAPID_PRIVATE_KEY").expect("Missing REVOLT_VAPID_PRIVATE_KEY environment variable."));
pub static VAPID_PUBLIC_KEY: Lazy<String> = Lazy::new(|| env::var("REVOLT_VAPID_PUBLIC_KEY").expect("Missing REVOLT_VAPID_PUBLIC_KEY environment variable."));
pub static AUTHIFIER_SHIELD_KEY: Lazy<Option<String>> = Lazy::new(|| env::var("REVOLT_AUTHIFIER_SHIELD_KEY").ok());
// 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();
// Application Flags
pub static INVITE_ONLY: Lazy<bool> = Lazy::new(|| env::var("REVOLT_INVITE_ONLY").map_or(false, |v| v == "1"));
pub static USE_EMAIL: Lazy<bool> = Lazy::new(|| 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 USE_HCAPTCHA: Lazy<bool> = Lazy::new(|| env::var("REVOLT_HCAPTCHA_KEY").is_ok());
pub static USE_AUTUMN: Lazy<bool> = Lazy::new(|| env::var("AUTUMN_PUBLIC_URL").is_ok());
pub static USE_JANUARY: Lazy<bool> = Lazy::new(|| env::var("JANUARY_PUBLIC_URL").is_ok());
pub static USE_VOSO: Lazy<bool> = Lazy::new(|| 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());
// SMTP Settings
pub static SMTP_HOST: Lazy<String> = Lazy::new(|| env::var("REVOLT_SMTP_HOST").unwrap_or_else(|_| "".to_string()));
pub static SMTP_USERNAME: Lazy<String> = Lazy::new(|| env::var("REVOLT_SMTP_USERNAME").unwrap_or_else(|_| "".to_string()));
pub static SMTP_PASSWORD: Lazy<String> = Lazy::new(|| env::var("REVOLT_SMTP_PASSWORD").unwrap_or_else(|_| "".to_string()));
pub static SMTP_FROM: Lazy<String> = Lazy::new(|| 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();
}
// Application Logic Settings
pub static MAX_GROUP_SIZE: Lazy<usize> = Lazy::new(|| env::var("REVOLT_MAX_GROUP_SIZE").unwrap_or_else(|_| "50".to_string()).parse().unwrap());
pub static MAX_BOT_COUNT: Lazy<usize> = Lazy::new(|| env::var("REVOLT_MAX_BOT_COUNT").unwrap_or_else(|_| "5".to_string()).parse().unwrap());
pub static MAX_EMBED_COUNT: Lazy<usize> = Lazy::new(|| env::var("REVOLT_MAX_EMBED_COUNT").unwrap_or_else(|_| "5".to_string()).parse().unwrap());
pub static MAX_SERVER_COUNT: Lazy<usize> = Lazy::new(|| env::var("REVOLT_MAX_SERVER_COUNT").unwrap_or_else(|_| "100".to_string()).parse().unwrap());
pub static EARLY_ADOPTER_BADGE: Lazy<i64> = Lazy::new(|| env::var("REVOLT_EARLY_ADOPTER_BADGE").unwrap_or_else(|_| "0".to_string()).parse().unwrap());
pub fn preflight_checks() {
format!("url = {}", *APP_URL);

View File

@@ -9,6 +9,7 @@ use rocket::request::{FromRequest, Outcome};
use schemars::schema::{InstanceType, SchemaObject, SingleOrVec};
use serde::{Deserialize, Serialize};
use validator::Validate;
use once_cell::sync::Lazy;
#[derive(Validate, Serialize, Deserialize)]
pub struct IdempotencyKey {
@@ -16,9 +17,7 @@ pub struct IdempotencyKey {
key: String,
}
lazy_static! {
static ref TOKEN_CACHE: Mutex<lru::LruCache<String, ()>> = Mutex::new(lru::LruCache::new(100));
}
static TOKEN_CACHE: Lazy<Mutex<lru::LruCache<String, ()>>> = Lazy::new(|| Mutex::new(lru::LruCache::new(100)));
impl IdempotencyKey {
// Backwards compatibility.

View File

@@ -22,17 +22,16 @@ use revolt_rocket_okapi::request::{OpenApiFromRequest, RequestHeaderInput};
use serde::Serialize;
use dashmap::DashMap;
use once_cell::sync::Lazy;
/// Ratelimit Bucket
#[derive(Clone, Copy)]
#[derive(Clone, Copy, Debug)]
struct Entry {
used: u8,
reset: u128,
}
lazy_static! {
static ref MAP: DashMap<u64, Entry> = DashMap::new();
}
static MAP: Lazy<DashMap<u64, Entry>> = Lazy::new(DashMap::new);
/// Get the current time from Unix Epoch as a Duration
fn now() -> Duration {
@@ -51,7 +50,7 @@ impl Entry {
}
/// Deduct one unit from the bucket and save
pub fn deduct(mut self) {
pub fn deduct(&mut self) {
let current_time = now().as_millis();
if current_time > self.reset {
self.used = 1;
@@ -193,7 +192,7 @@ impl Ratelimiter {
let key = key.finish();
let limit = resolve_bucket_limit(bucket);
let entry = Entry::from(key);
let mut entry = Entry::from(key);
let remaining = entry.get_remaining(limit);
if remaining > 0 {