forked from jmug/stoatchat
Merge branch 'master' into webhooks
This commit is contained in:
14
Cargo.lock
generated
14
Cargo.lock
generated
@@ -2106,9 +2106,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.13.0"
|
version = "1.17.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1"
|
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "opaque-debug"
|
name = "opaque-debug"
|
||||||
@@ -2170,7 +2170,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "optional_struct"
|
name = "optional_struct"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
source = "git+https://github.com/insertish/OptionalStruct?rev=e275d2726595474632485934aa0887fa52281f70#e275d2726595474632485934aa0887fa52281f70"
|
source = "git+https://github.com/insertish/OptionalStruct?rev=ee56427cee1f007839825d93d07fffd5a5e038c7#ee56427cee1f007839825d93d07fffd5a5e038c7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote 0.3.15",
|
"quote 0.3.15",
|
||||||
"syn 0.11.11",
|
"syn 0.11.11",
|
||||||
@@ -2792,7 +2792,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "revolt-bonfire"
|
name = "revolt-bonfire"
|
||||||
version = "0.5.9"
|
version = "0.5.17"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-std",
|
"async-std",
|
||||||
"async-tungstenite",
|
"async-tungstenite",
|
||||||
@@ -2808,7 +2808,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "revolt-delta"
|
name = "revolt-delta"
|
||||||
version = "0.5.9"
|
version = "0.5.17"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-channel",
|
"async-channel",
|
||||||
"async-std",
|
"async-std",
|
||||||
@@ -2819,7 +2819,6 @@ dependencies = [
|
|||||||
"env_logger",
|
"env_logger",
|
||||||
"futures",
|
"futures",
|
||||||
"impl_ops",
|
"impl_ops",
|
||||||
"lazy_static",
|
|
||||||
"lettre",
|
"lettre",
|
||||||
"linkify 0.6.0",
|
"linkify 0.6.0",
|
||||||
"log",
|
"log",
|
||||||
@@ -2848,7 +2847,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "revolt-quark"
|
name = "revolt-quark"
|
||||||
version = "0.5.9"
|
version = "0.5.17"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-lock",
|
"async-lock",
|
||||||
"async-recursion",
|
"async-recursion",
|
||||||
@@ -2866,7 +2865,6 @@ dependencies = [
|
|||||||
"impl_ops",
|
"impl_ops",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"iso8601-timestamp",
|
"iso8601-timestamp",
|
||||||
"lazy_static",
|
|
||||||
"linkify 0.8.1",
|
"linkify 0.8.1",
|
||||||
"log",
|
"log",
|
||||||
"lru",
|
"lru",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "revolt-bonfire"
|
name = "revolt-bonfire"
|
||||||
version = "0.5.9"
|
version = "0.5.17"
|
||||||
license = "AGPL-3.0-or-later"
|
license = "AGPL-3.0-or-later"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "revolt-delta"
|
name = "revolt-delta"
|
||||||
version = "0.5.9"
|
version = "0.5.17"
|
||||||
license = "AGPL-3.0-or-later"
|
license = "AGPL-3.0-or-later"
|
||||||
authors = ["Paul Makles <paulmakles@gmail.com>"]
|
authors = ["Paul Makles <paulmakles@gmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
@@ -15,9 +15,8 @@ log = "0.4.11"
|
|||||||
dotenv = "0.15.0"
|
dotenv = "0.15.0"
|
||||||
dashmap = "5.2.0"
|
dashmap = "5.2.0"
|
||||||
linkify = "0.6.0"
|
linkify = "0.6.0"
|
||||||
once_cell = "1.4.1"
|
once_cell = "1.17.1"
|
||||||
env_logger = "0.7.1"
|
env_logger = "0.7.1"
|
||||||
lazy_static = "1.4.0"
|
|
||||||
|
|
||||||
# Lang. Utilities
|
# Lang. Utilities
|
||||||
regex = "1"
|
regex = "1"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ fn main() {
|
|||||||
.output()
|
.output()
|
||||||
{
|
{
|
||||||
if let Ok(git_origin) = String::from_utf8(output.stdout) {
|
if let Ok(git_origin) = String::from_utf8(output.stdout) {
|
||||||
println!("cargo:rustc-env=GIT_ORIGIN_URL={}", git_origin);
|
println!("cargo:rustc-env=GIT_ORIGIN_URL={git_origin}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ extern crate rocket;
|
|||||||
extern crate revolt_rocket_okapi;
|
extern crate revolt_rocket_okapi;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
#[macro_use]
|
|
||||||
extern crate lazy_static;
|
|
||||||
|
|
||||||
pub mod routes;
|
pub mod routes;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
|||||||
31
crates/delta/src/routes/admin/message_query.rs
Normal file
31
crates/delta/src/routes/admin/message_query.rs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
use revolt_quark::{
|
||||||
|
models::{
|
||||||
|
message::{BulkMessageResponse, MessageQuery},
|
||||||
|
User,
|
||||||
|
},
|
||||||
|
Db, Error, Result,
|
||||||
|
};
|
||||||
|
use rocket::serde::json::Json;
|
||||||
|
|
||||||
|
/// # Globally Fetch Messages
|
||||||
|
///
|
||||||
|
/// This is a privileged route to globally fetch messages.
|
||||||
|
#[openapi(tag = "Admin")]
|
||||||
|
#[post("/messages", data = "<data>")]
|
||||||
|
pub async fn message_query(
|
||||||
|
db: &Db,
|
||||||
|
user: User,
|
||||||
|
data: Json<MessageQuery>,
|
||||||
|
) -> Result<Json<BulkMessageResponse>> {
|
||||||
|
// Must be privileged for this route
|
||||||
|
if !user.privileged {
|
||||||
|
return Err(Error::NotPrivileged);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch data using query
|
||||||
|
let data = data.into_inner();
|
||||||
|
let messages = db.fetch_messages(data).await?;
|
||||||
|
BulkMessageResponse::transform(db, None, messages, Some(true))
|
||||||
|
.await
|
||||||
|
.map(Json)
|
||||||
|
}
|
||||||
9
crates/delta/src/routes/admin/mod.rs
Normal file
9
crates/delta/src/routes/admin/mod.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
use revolt_rocket_okapi::revolt_okapi::openapi3::OpenApi;
|
||||||
|
use rocket::Route;
|
||||||
|
|
||||||
|
mod message_query;
|
||||||
|
mod stats;
|
||||||
|
|
||||||
|
pub fn routes() -> (Vec<Route>, OpenApi) {
|
||||||
|
openapi_get_routes_spec![stats::stats, message_query::message_query]
|
||||||
|
}
|
||||||
13
crates/delta/src/routes/admin/stats.rs
Normal file
13
crates/delta/src/routes/admin/stats.rs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
use revolt_quark::models::stats::Stats;
|
||||||
|
use revolt_quark::{Db, Result};
|
||||||
|
|
||||||
|
use rocket::serde::json::Json;
|
||||||
|
|
||||||
|
/// # Query Stats
|
||||||
|
///
|
||||||
|
/// Fetch various technical statistics.
|
||||||
|
#[openapi(tag = "Admin")]
|
||||||
|
#[get("/stats")]
|
||||||
|
pub async fn stats(db: &Db) -> Result<Json<Stats>> {
|
||||||
|
Ok(Json(db.generate_stats().await?))
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
use revolt_quark::{
|
use revolt_quark::{
|
||||||
models::{Channel, User},
|
get_relationship,
|
||||||
|
models::{user::RelationshipStatus, Channel, User},
|
||||||
perms, Db, EmptyResponse, Error, Permission, Ref, Result,
|
perms, Db, EmptyResponse, Error, Permission, Ref, Result,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -22,6 +23,13 @@ pub async fn req(db: &Db, user: User, target: Ref, member: Ref) -> Result<EmptyR
|
|||||||
match &channel {
|
match &channel {
|
||||||
Channel::Group { .. } => {
|
Channel::Group { .. } => {
|
||||||
let member = member.as_user(db).await?;
|
let member = member.as_user(db).await?;
|
||||||
|
if !matches!(
|
||||||
|
get_relationship(&user, &member.id),
|
||||||
|
RelationshipStatus::Friend
|
||||||
|
) {
|
||||||
|
return Err(Error::NotFriends);
|
||||||
|
}
|
||||||
|
|
||||||
channel
|
channel
|
||||||
.add_user_to_group(db, &member.id, &user.id)
|
.add_user_to_group(db, &member.id, &user.id)
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
use revolt_quark::{
|
use revolt_quark::{
|
||||||
models::message::{PartialMessage, SendableEmbed},
|
models::message::{PartialMessage, SendableEmbed},
|
||||||
models::{Message, User},
|
models::{Message, User},
|
||||||
|
perms,
|
||||||
types::january::Embed,
|
types::january::Embed,
|
||||||
Db, Error, Ref, Result, Timestamp,
|
Db, Error, Permission, Ref, Result, Timestamp,
|
||||||
};
|
};
|
||||||
|
|
||||||
use rocket::serde::json::Json;
|
use rocket::serde::json::Json;
|
||||||
@@ -28,7 +29,7 @@ pub struct DataEditMessage {
|
|||||||
pub async fn req(
|
pub async fn req(
|
||||||
db: &Db,
|
db: &Db,
|
||||||
user: User,
|
user: User,
|
||||||
target: String,
|
target: Ref,
|
||||||
msg: Ref,
|
msg: Ref,
|
||||||
edit: Json<DataEditMessage>,
|
edit: Json<DataEditMessage>,
|
||||||
) -> Result<Json<Message>> {
|
) -> Result<Json<Message>> {
|
||||||
@@ -36,10 +37,17 @@ pub async fn req(
|
|||||||
edit.validate()
|
edit.validate()
|
||||||
.map_err(|error| Error::FailedValidation { error })?;
|
.map_err(|error| Error::FailedValidation { error })?;
|
||||||
|
|
||||||
|
// Ensure we have permissions to send a message
|
||||||
|
let channel = target.as_channel(db).await?;
|
||||||
|
let mut permissions = perms(&user).channel(&channel);
|
||||||
|
permissions
|
||||||
|
.throw_permission_and_view_channel(db, Permission::SendMessage)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Message::validate_sum(&edit.content, &edit.embeds)?;
|
Message::validate_sum(&edit.content, &edit.embeds)?;
|
||||||
|
|
||||||
let mut message = msg.as_message(db).await?;
|
let mut message = msg.as_message(db).await?;
|
||||||
if message.channel != target {
|
if message.channel != channel.id() {
|
||||||
return Err(Error::NotFound);
|
return Err(Error::NotFound);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
use revolt_quark::{
|
use revolt_quark::{
|
||||||
models::{
|
models::{
|
||||||
message::{BulkMessageResponse, MessageSort},
|
message::{
|
||||||
|
BulkMessageResponse, MessageFilter, MessageQuery, MessageSort, MessageTimePeriod,
|
||||||
|
},
|
||||||
User,
|
User,
|
||||||
},
|
},
|
||||||
perms, Db, Error, Permission, Ref, Result,
|
perms, Db, Error, Permission, Ref, Result,
|
||||||
@@ -68,14 +70,28 @@ pub async fn req(
|
|||||||
sort,
|
sort,
|
||||||
nearby,
|
nearby,
|
||||||
include_users,
|
include_users,
|
||||||
..
|
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
let messages = db
|
let messages = db
|
||||||
.fetch_messages(channel.id(), limit, before, after, sort, nearby)
|
.fetch_messages(MessageQuery {
|
||||||
|
filter: MessageFilter {
|
||||||
|
channel: Some(channel.id().to_string()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
time_period: if let Some(nearby) = nearby {
|
||||||
|
MessageTimePeriod::Relative { nearby }
|
||||||
|
} else {
|
||||||
|
MessageTimePeriod::Absolute {
|
||||||
|
before,
|
||||||
|
after,
|
||||||
|
sort,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
limit,
|
||||||
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
BulkMessageResponse::transform(db, &channel, messages, include_users)
|
BulkMessageResponse::transform(db, Some(&channel), messages, include_users)
|
||||||
.await
|
.await
|
||||||
.map(Json)
|
.map(Json)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
use revolt_quark::{
|
use revolt_quark::{
|
||||||
models::{
|
models::{
|
||||||
message::{BulkMessageResponse, MessageSort},
|
message::{
|
||||||
|
BulkMessageResponse, MessageFilter, MessageQuery, MessageSort, MessageTimePeriod,
|
||||||
|
},
|
||||||
User,
|
User,
|
||||||
},
|
},
|
||||||
perms, Db, Error, Permission, Ref, Result,
|
perms, Db, Error, Permission, Ref, Result,
|
||||||
@@ -30,7 +32,7 @@ pub struct OptionsMessageSearch {
|
|||||||
after: Option<String>,
|
after: Option<String>,
|
||||||
/// Message sort direction
|
/// Message sort direction
|
||||||
///
|
///
|
||||||
/// By default, it will be sorted by relevance.
|
/// By default, it will be sorted by latest.
|
||||||
#[serde(default = "MessageSort::default")]
|
#[serde(default = "MessageSort::default")]
|
||||||
sort: MessageSort,
|
sort: MessageSort,
|
||||||
/// Whether to include user (and member, if server channel) objects
|
/// Whether to include user (and member, if server channel) objects
|
||||||
@@ -73,10 +75,22 @@ pub async fn req(
|
|||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
let messages = db
|
let messages = db
|
||||||
.search_messages(channel.id(), &query, limit, before, after, sort)
|
.fetch_messages(MessageQuery {
|
||||||
|
filter: MessageFilter {
|
||||||
|
channel: Some(channel.id().to_string()),
|
||||||
|
query: Some(query),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
time_period: MessageTimePeriod::Absolute {
|
||||||
|
before,
|
||||||
|
after,
|
||||||
|
sort: Some(sort),
|
||||||
|
},
|
||||||
|
limit,
|
||||||
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
BulkMessageResponse::transform(db, &channel, messages, include_users)
|
BulkMessageResponse::transform(db, Some(&channel), messages, include_users)
|
||||||
.await
|
.await
|
||||||
.map(Json)
|
.map(Json)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use revolt_quark::{
|
|||||||
|
|
||||||
use rocket::serde::json::Json;
|
use rocket::serde::json::Json;
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
/// # Send Message
|
/// # Send Message
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ pub use rocket::http::Status;
|
|||||||
pub use rocket::response::Redirect;
|
pub use rocket::response::Redirect;
|
||||||
use rocket::{Build, Rocket};
|
use rocket::{Build, Rocket};
|
||||||
|
|
||||||
|
mod admin;
|
||||||
mod bots;
|
mod bots;
|
||||||
mod channels;
|
mod channels;
|
||||||
mod customisation;
|
mod customisation;
|
||||||
@@ -23,6 +24,7 @@ pub fn mount(mut rocket: Rocket<Build>) -> Rocket<Build> {
|
|||||||
rocket, "/".to_owned(), settings,
|
rocket, "/".to_owned(), settings,
|
||||||
"/" => (vec![], custom_openapi_spec()),
|
"/" => (vec![], custom_openapi_spec()),
|
||||||
"" => openapi_get_routes_spec![root::root, root::ping],
|
"" => openapi_get_routes_spec![root::root, root::ping],
|
||||||
|
"/admin" => admin::routes(),
|
||||||
"/users" => users::routes(),
|
"/users" => users::routes(),
|
||||||
"/bots" => bots::routes(),
|
"/bots" => bots::routes(),
|
||||||
"/channels" => channels::routes(),
|
"/channels" => channels::routes(),
|
||||||
@@ -111,8 +113,9 @@ fn custom_openapi_spec() -> OpenApi {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Platform Moderation",
|
"name": "Platform Administration",
|
||||||
"tags": [
|
"tags": [
|
||||||
|
"Admin",
|
||||||
"User Safety"
|
"User Safety"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
56
crates/delta/src/routes/safety/edit_report.rs
Normal file
56
crates/delta/src/routes/safety/edit_report.rs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
use revolt_quark::{
|
||||||
|
models::{
|
||||||
|
report::{PartialReport, ReportStatus},
|
||||||
|
Report, User,
|
||||||
|
},
|
||||||
|
Db, Error, Ref, Result,
|
||||||
|
};
|
||||||
|
use rocket::serde::json::Json;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use validator::Validate;
|
||||||
|
|
||||||
|
/// # Report Data
|
||||||
|
#[derive(Validate, Deserialize, JsonSchema)]
|
||||||
|
pub struct DataEditReport {
|
||||||
|
/// New report status
|
||||||
|
status: Option<ReportStatus>,
|
||||||
|
/// Report notes
|
||||||
|
notes: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Edit Report
|
||||||
|
///
|
||||||
|
/// Edit a report.
|
||||||
|
#[openapi(tag = "User Safety")]
|
||||||
|
#[patch("/reports/<report>", data = "<edit>")]
|
||||||
|
pub async fn edit_report(
|
||||||
|
db: &Db,
|
||||||
|
user: User,
|
||||||
|
report: Ref,
|
||||||
|
edit: Json<DataEditReport>,
|
||||||
|
) -> Result<Json<Report>> {
|
||||||
|
// Must be privileged for this route
|
||||||
|
if !user.privileged {
|
||||||
|
return Err(Error::NotPrivileged);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate data
|
||||||
|
let edit = edit.into_inner();
|
||||||
|
edit.validate()
|
||||||
|
.map_err(|error| Error::FailedValidation { error })?;
|
||||||
|
|
||||||
|
// Create and apply update to report
|
||||||
|
let mut report = report.as_report(db).await?;
|
||||||
|
report
|
||||||
|
.update(
|
||||||
|
db,
|
||||||
|
PartialReport {
|
||||||
|
status: edit.status,
|
||||||
|
notes: edit.notes,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(Json(report))
|
||||||
|
}
|
||||||
17
crates/delta/src/routes/safety/fetch_report.rs
Normal file
17
crates/delta/src/routes/safety/fetch_report.rs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
use revolt_quark::models::{Report, User};
|
||||||
|
use revolt_quark::{Db, Error, Result};
|
||||||
|
use rocket::serde::json::Json;
|
||||||
|
|
||||||
|
/// # Fetch Report
|
||||||
|
///
|
||||||
|
/// Fetch a report by its ID
|
||||||
|
#[openapi(tag = "User Safety")]
|
||||||
|
#[get("/report/<id>")]
|
||||||
|
pub async fn fetch_report(db: &Db, user: User, id: String) -> Result<Json<Report>> {
|
||||||
|
// Must be privileged for this route
|
||||||
|
if !user.privileged {
|
||||||
|
return Err(Error::NotPrivileged);
|
||||||
|
}
|
||||||
|
|
||||||
|
db.fetch_report(&id).await.map(Json)
|
||||||
|
}
|
||||||
17
crates/delta/src/routes/safety/fetch_reports.rs
Normal file
17
crates/delta/src/routes/safety/fetch_reports.rs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
use revolt_quark::models::{Report, User};
|
||||||
|
use revolt_quark::{Db, Error, Result};
|
||||||
|
use rocket::serde::json::Json;
|
||||||
|
|
||||||
|
/// # Fetch Reports
|
||||||
|
///
|
||||||
|
/// Fetch all available reports
|
||||||
|
#[openapi(tag = "User Safety")]
|
||||||
|
#[get("/reports")]
|
||||||
|
pub async fn fetch_reports(db: &Db, user: User) -> Result<Json<Vec<Report>>> {
|
||||||
|
// Must be privileged for this route
|
||||||
|
if !user.privileged {
|
||||||
|
return Err(Error::NotPrivileged);
|
||||||
|
}
|
||||||
|
|
||||||
|
db.fetch_reports().await.map(Json)
|
||||||
|
}
|
||||||
82
crates/delta/src/routes/safety/fetch_snapshot.rs
Normal file
82
crates/delta/src/routes/safety/fetch_snapshot.rs
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use revolt_quark::models::snapshot::{SnapshotContent, SnapshotWithContext};
|
||||||
|
use revolt_quark::models::{Channel, User};
|
||||||
|
use revolt_quark::{Db, Error, Result};
|
||||||
|
use rocket::serde::json::Json;
|
||||||
|
|
||||||
|
/// # Fetch Snapshot
|
||||||
|
///
|
||||||
|
/// Fetch a snapshot for a given report
|
||||||
|
#[openapi(tag = "User Safety")]
|
||||||
|
#[get("/snapshot/<report_id>")]
|
||||||
|
pub async fn fetch_snapshot(
|
||||||
|
db: &Db,
|
||||||
|
user: User,
|
||||||
|
report_id: String,
|
||||||
|
) -> Result<Json<SnapshotWithContext>> {
|
||||||
|
// Must be privileged for this route
|
||||||
|
if !user.privileged {
|
||||||
|
return Err(Error::NotPrivileged);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch snapshot
|
||||||
|
let snapshot = db.fetch_snapshot(&report_id).await?;
|
||||||
|
|
||||||
|
// Resolve and fetch IDs of associated content
|
||||||
|
let mut user_ids: HashSet<&str> = HashSet::new();
|
||||||
|
let mut channel_ids: HashSet<&str> = HashSet::new();
|
||||||
|
|
||||||
|
match &snapshot.content {
|
||||||
|
SnapshotContent::Message {
|
||||||
|
prior_context,
|
||||||
|
leading_context,
|
||||||
|
message,
|
||||||
|
} => {
|
||||||
|
for msg in prior_context {
|
||||||
|
user_ids.insert(&msg.author);
|
||||||
|
}
|
||||||
|
|
||||||
|
for msg in leading_context {
|
||||||
|
user_ids.insert(&msg.author);
|
||||||
|
}
|
||||||
|
|
||||||
|
user_ids.insert(&message.author);
|
||||||
|
channel_ids.insert(&message.channel);
|
||||||
|
}
|
||||||
|
SnapshotContent::User(user) => {
|
||||||
|
user_ids.insert(&user.id);
|
||||||
|
}
|
||||||
|
SnapshotContent::Server(server) => {
|
||||||
|
for channel in &server.channels {
|
||||||
|
channel_ids.insert(channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect user and channel IDs
|
||||||
|
let user_ids: Vec<String> = user_ids.into_iter().map(|s| s.to_owned()).collect();
|
||||||
|
let channel_ids: Vec<String> = channel_ids.into_iter().map(|s| s.to_owned()).collect();
|
||||||
|
|
||||||
|
// Fetch users and channels
|
||||||
|
let users = db.fetch_users(&user_ids).await?;
|
||||||
|
let channels = db.fetch_channels(&channel_ids).await?;
|
||||||
|
|
||||||
|
// Pull out first server from channels if possible
|
||||||
|
let server = if let Some(server_id) = channels.iter().find_map(|channel| match channel {
|
||||||
|
Channel::TextChannel { server, .. } => Some(server),
|
||||||
|
_ => None,
|
||||||
|
}) {
|
||||||
|
Some(db.fetch_server(server_id).await?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
// Return snapshot with context
|
||||||
|
Ok(Json(SnapshotWithContext {
|
||||||
|
snapshot,
|
||||||
|
users,
|
||||||
|
channels,
|
||||||
|
server,
|
||||||
|
}))
|
||||||
|
}
|
||||||
@@ -1,11 +1,21 @@
|
|||||||
use revolt_rocket_okapi::revolt_okapi::openapi3::OpenApi;
|
use revolt_rocket_okapi::revolt_okapi::openapi3::OpenApi;
|
||||||
use rocket::Route;
|
use rocket::Route;
|
||||||
|
|
||||||
|
mod edit_report;
|
||||||
|
mod fetch_report;
|
||||||
|
mod fetch_reports;
|
||||||
mod report_content;
|
mod report_content;
|
||||||
|
|
||||||
|
mod fetch_snapshot;
|
||||||
|
|
||||||
pub fn routes() -> (Vec<Route>, OpenApi) {
|
pub fn routes() -> (Vec<Route>, OpenApi) {
|
||||||
openapi_get_routes_spec![
|
openapi_get_routes_spec![
|
||||||
// Reports
|
// Reports
|
||||||
report_content::report_content
|
edit_report::edit_report,
|
||||||
|
fetch_report::fetch_report,
|
||||||
|
fetch_reports::fetch_reports,
|
||||||
|
report_content::report_content,
|
||||||
|
// Snapshots
|
||||||
|
fetch_snapshot::fetch_snapshot
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use revolt_quark::events::client::EventV1;
|
use revolt_quark::events::client::EventV1;
|
||||||
use revolt_quark::models::report::ReportedContent;
|
use revolt_quark::models::message::{MessageFilter, MessageQuery, MessageSort, MessageTimePeriod};
|
||||||
|
use revolt_quark::models::report::{ReportStatus, ReportedContent};
|
||||||
use revolt_quark::models::snapshot::{Snapshot, SnapshotContent};
|
use revolt_quark::models::snapshot::{Snapshot, SnapshotContent};
|
||||||
use revolt_quark::models::{Report, User};
|
use revolt_quark::models::{Report, User};
|
||||||
use revolt_quark::{Db, Error, Result};
|
use revolt_quark::{Db, Error, Result};
|
||||||
@@ -55,26 +56,34 @@ pub async fn report_content(db: &Db, user: User, data: Json<DataReportContent>)
|
|||||||
|
|
||||||
// Collect prior context
|
// Collect prior context
|
||||||
let prior_context = db
|
let prior_context = db
|
||||||
.fetch_messages(
|
.fetch_messages(MessageQuery {
|
||||||
&message.channel,
|
filter: MessageFilter {
|
||||||
Some(15),
|
channel: Some(message.channel.to_string()),
|
||||||
Some(message.id.to_string()),
|
..Default::default()
|
||||||
None,
|
},
|
||||||
None,
|
limit: Some(15),
|
||||||
None,
|
time_period: MessageTimePeriod::Absolute {
|
||||||
)
|
before: Some(message.id.to_string()),
|
||||||
|
after: None,
|
||||||
|
sort: Some(MessageSort::Latest),
|
||||||
|
},
|
||||||
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Collect leading context
|
// Collect leading context
|
||||||
let leading_context = db
|
let leading_context = db
|
||||||
.fetch_messages(
|
.fetch_messages(MessageQuery {
|
||||||
&message.channel,
|
filter: MessageFilter {
|
||||||
Some(15),
|
channel: Some(message.channel.to_string()),
|
||||||
None,
|
..Default::default()
|
||||||
Some(message.id.to_string()),
|
},
|
||||||
None,
|
limit: Some(15),
|
||||||
None,
|
time_period: MessageTimePeriod::Absolute {
|
||||||
)
|
before: None,
|
||||||
|
after: Some(message.id.to_string()),
|
||||||
|
sort: Some(MessageSort::Oldest),
|
||||||
|
},
|
||||||
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
(
|
(
|
||||||
@@ -149,6 +158,8 @@ pub async fn report_content(db: &Db, user: User, data: Json<DataReportContent>)
|
|||||||
author_id: user.id,
|
author_id: user.id,
|
||||||
content: data.content,
|
content: data.content,
|
||||||
additional_context: data.additional_context,
|
additional_context: data.additional_context,
|
||||||
|
status: ReportStatus::Created {},
|
||||||
|
notes: String::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
db.insert_report(&report).await?;
|
db.insert_report(&report).await?;
|
||||||
|
|||||||
@@ -11,20 +11,15 @@ use ulid::Ulid;
|
|||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
|
||||||
/// # Channel Type
|
/// # Channel Type
|
||||||
#[derive(Serialize, Deserialize, JsonSchema)]
|
#[derive(Serialize, Deserialize, JsonSchema, Default)]
|
||||||
enum ChannelType {
|
enum ChannelType {
|
||||||
/// Text Channel
|
/// Text Channel
|
||||||
|
#[default]
|
||||||
Text,
|
Text,
|
||||||
/// Voice Channel
|
/// Voice Channel
|
||||||
Voice,
|
Voice,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ChannelType {
|
|
||||||
fn default() -> Self {
|
|
||||||
ChannelType::Text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// # Channel Data
|
/// # Channel Data
|
||||||
#[derive(Validate, Serialize, Deserialize, JsonSchema)]
|
#[derive(Validate, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct DataCreateChannel {
|
pub struct DataCreateChannel {
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ pub struct DataEditServer {
|
|||||||
/// System message configuration
|
/// System message configuration
|
||||||
system_messages: Option<SystemMessageChannels>,
|
system_messages: Option<SystemMessageChannels>,
|
||||||
|
|
||||||
|
/// Bitfield of server flags
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub flags: Option<i32>,
|
||||||
|
|
||||||
// Whether this server is age-restricted
|
// Whether this server is age-restricted
|
||||||
// nsfw: Option<bool>,
|
// nsfw: Option<bool>,
|
||||||
/// Whether this server is public and should show up on [Revolt Discover](https://rvlt.gg)
|
/// Whether this server is public and should show up on [Revolt Discover](https://rvlt.gg)
|
||||||
@@ -92,6 +96,11 @@ pub async fn req(
|
|||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check we are privileged if changing sensitive fields
|
||||||
|
if data.flags.is_some() && !user.privileged {
|
||||||
|
return Err(Error::NotPrivileged);
|
||||||
|
}
|
||||||
|
|
||||||
if data.categories.is_some() {
|
if data.categories.is_some() {
|
||||||
permissions
|
permissions
|
||||||
.throw_permission(db, Permission::ManageChannel)
|
.throw_permission(db, Permission::ManageChannel)
|
||||||
@@ -105,6 +114,7 @@ pub async fn req(
|
|||||||
banner,
|
banner,
|
||||||
categories,
|
categories,
|
||||||
system_messages,
|
system_messages,
|
||||||
|
flags,
|
||||||
// nsfw,
|
// nsfw,
|
||||||
discoverable,
|
discoverable,
|
||||||
analytics,
|
analytics,
|
||||||
@@ -116,6 +126,7 @@ pub async fn req(
|
|||||||
description,
|
description,
|
||||||
categories,
|
categories,
|
||||||
system_messages,
|
system_messages,
|
||||||
|
flags,
|
||||||
// nsfw,
|
// nsfw,
|
||||||
discoverable,
|
discoverable,
|
||||||
analytics,
|
analytics,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use revolt_quark::models::user::{FieldsUser, PartialUser, User};
|
use revolt_quark::models::user::{FieldsUser, PartialUser, User};
|
||||||
use revolt_quark::models::File;
|
use revolt_quark::models::File;
|
||||||
use revolt_quark::{Database, Error, Result};
|
use revolt_quark::{Database, Error, Ref, Result};
|
||||||
|
|
||||||
use revolt_quark::models::user::UserStatus;
|
use revolt_quark::models::user::UserStatus;
|
||||||
use rocket::serde::json::Json;
|
use rocket::serde::json::Json;
|
||||||
@@ -24,6 +24,10 @@ pub struct UserProfileData {
|
|||||||
/// # User Data
|
/// # User Data
|
||||||
#[derive(Validate, Serialize, Deserialize, JsonSchema)]
|
#[derive(Validate, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct DataEditUser {
|
pub struct DataEditUser {
|
||||||
|
/// Attachment Id for avatar
|
||||||
|
#[validate(length(min = 1, max = 128))]
|
||||||
|
avatar: Option<String>,
|
||||||
|
|
||||||
/// New user status
|
/// New user status
|
||||||
#[validate]
|
#[validate]
|
||||||
status: Option<UserStatus>,
|
status: Option<UserStatus>,
|
||||||
@@ -32,9 +36,14 @@ pub struct DataEditUser {
|
|||||||
/// This is applied as a partial.
|
/// This is applied as a partial.
|
||||||
#[validate]
|
#[validate]
|
||||||
profile: Option<UserProfileData>,
|
profile: Option<UserProfileData>,
|
||||||
/// Attachment Id for avatar
|
|
||||||
#[validate(length(min = 1, max = 128))]
|
/// Bitfield of user badges
|
||||||
avatar: Option<String>,
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
badges: Option<i32>,
|
||||||
|
/// Enum of user flags
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
flags: Option<i32>,
|
||||||
|
|
||||||
/// Fields to remove from user object
|
/// Fields to remove from user object
|
||||||
#[validate(length(min = 1))]
|
#[validate(length(min = 1))]
|
||||||
remove: Option<Vec<FieldsUser>>,
|
remove: Option<Vec<FieldsUser>>,
|
||||||
@@ -44,19 +53,36 @@ pub struct DataEditUser {
|
|||||||
///
|
///
|
||||||
/// Edit currently authenticated user.
|
/// Edit currently authenticated user.
|
||||||
#[openapi(tag = "User Information")]
|
#[openapi(tag = "User Information")]
|
||||||
#[patch("/@me", data = "<data>")]
|
#[patch("/<target>", data = "<data>")]
|
||||||
pub async fn req(
|
pub async fn req(
|
||||||
db: &State<Database>,
|
db: &State<Database>,
|
||||||
mut user: User,
|
mut user: User,
|
||||||
|
target: Ref,
|
||||||
data: Json<DataEditUser>,
|
data: Json<DataEditUser>,
|
||||||
) -> Result<Json<User>> {
|
) -> Result<Json<User>> {
|
||||||
let data = data.into_inner();
|
let data = data.into_inner();
|
||||||
data.validate()
|
data.validate()
|
||||||
.map_err(|error| Error::FailedValidation { error })?;
|
.map_err(|error| Error::FailedValidation { error })?;
|
||||||
|
|
||||||
|
// If we want to edit a different user than self, ensure we have
|
||||||
|
// permissions and subsequently replace the user in question
|
||||||
|
if target.id != "@me" {
|
||||||
|
if !user.privileged {
|
||||||
|
return Err(Error::NotPrivileged);
|
||||||
|
}
|
||||||
|
|
||||||
|
user = target.as_user(db).await?;
|
||||||
|
// Otherwise, filter out invalid edit fields
|
||||||
|
} else if data.badges.is_some() || data.flags.is_some() {
|
||||||
|
return Err(Error::NotPrivileged);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit out early if nothing is changed
|
||||||
if data.status.is_none()
|
if data.status.is_none()
|
||||||
&& data.profile.is_none()
|
&& data.profile.is_none()
|
||||||
&& data.avatar.is_none()
|
&& data.avatar.is_none()
|
||||||
|
&& data.badges.is_none()
|
||||||
|
&& data.flags.is_none()
|
||||||
&& data.remove.is_none()
|
&& data.remove.is_none()
|
||||||
{
|
{
|
||||||
return Ok(Json(user));
|
return Ok(Json(user));
|
||||||
@@ -83,7 +109,11 @@ pub async fn req(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut partial: PartialUser = Default::default();
|
let mut partial: PartialUser = PartialUser {
|
||||||
|
badges: data.badges,
|
||||||
|
flags: data.flags,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
// 2. Apply new avatar
|
// 2. Apply new avatar
|
||||||
if let Some(avatar) = data.avatar {
|
if let Some(avatar) = data.avatar {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "revolt-quark"
|
name = "revolt-quark"
|
||||||
version = "0.5.9"
|
version = "0.5.17"
|
||||||
license = "AGPL-3.0-or-later"
|
license = "AGPL-3.0-or-later"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ default = [ "test" ]
|
|||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
validator = { version = "0.14", features = ["derive"] }
|
validator = { version = "0.14", features = ["derive"] }
|
||||||
iso8601-timestamp = { version = "0.1.8", features = ["schema", "bson"] }
|
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
|
# Formats
|
||||||
bincode = "1.3.3"
|
bincode = "1.3.3"
|
||||||
@@ -67,8 +67,7 @@ impl_ops = "0.1.1"
|
|||||||
num_enum = "0.5.6"
|
num_enum = "0.5.6"
|
||||||
reqwest = "0.11.10"
|
reqwest = "0.11.10"
|
||||||
bitfield = "0.13.2"
|
bitfield = "0.13.2"
|
||||||
once_cell = "1.13.0"
|
once_cell = "1.17.1"
|
||||||
lazy_static = "1.4.0"
|
|
||||||
async-lock = "2.6.0"
|
async-lock = "2.6.0"
|
||||||
|
|
||||||
lru = { version = "0.7.6", optional = true }
|
lru = { version = "0.7.6", optional = true }
|
||||||
|
|||||||
@@ -557,7 +557,7 @@ impl EventV1 {
|
|||||||
|
|
||||||
/// Publish private event
|
/// Publish private event
|
||||||
pub async fn private(self, id: String) {
|
pub async fn private(self, id: String) {
|
||||||
self.p(format!("{}!", id)).await;
|
self.p(format!("{id}!")).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Publish internal global event
|
/// Publish internal global event
|
||||||
|
|||||||
10
crates/quark/src/impl/dummy/admin/stats.rs
Normal file
10
crates/quark/src/impl/dummy/admin/stats.rs
Normal 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!()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 crate::{AbstractMessage, Result};
|
||||||
|
|
||||||
use super::super::DummyDb;
|
use super::super::DummyDb;
|
||||||
@@ -41,28 +41,8 @@ impl AbstractMessage for DummyDb {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fetch_messages(
|
async fn fetch_messages(&self, _query: MessageQuery) -> Result<Vec<Message>> {
|
||||||
&self,
|
Ok(vec![])
|
||||||
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()])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a new reaction to a message
|
/// Add a new reaction to a message
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use crate::AbstractDatabase;
|
|||||||
|
|
||||||
pub mod admin {
|
pub mod admin {
|
||||||
pub mod migrations;
|
pub mod migrations;
|
||||||
|
pub mod stats;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod media {
|
pub mod media {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use crate::models::report::PartialReport;
|
||||||
use crate::models::Report;
|
use crate::models::Report;
|
||||||
use crate::{AbstractReport, Result};
|
use crate::{AbstractReport, Result};
|
||||||
|
|
||||||
@@ -9,4 +10,16 @@ impl AbstractReport for DummyDb {
|
|||||||
info!("Insert {:?}", report);
|
info!("Insert {:?}", report);
|
||||||
Ok(())
|
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![])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,4 +9,8 @@ impl AbstractSnapshot for DummyDb {
|
|||||||
info!("Insert {:?}", snapshot);
|
info!("Insert {:?}", snapshot);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn fetch_snapshot(&self, _report_id: &str) -> Result<Snapshot> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,13 +5,11 @@ use crate::{
|
|||||||
Database, Error, Result,
|
Database, Error, Result,
|
||||||
};
|
};
|
||||||
|
|
||||||
lazy_static! {
|
static ALPHABET: [char; 54] = [
|
||||||
static ref ALPHABET: [char; 54] = [
|
|
||||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
|
'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',
|
'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'
|
'e', 'f', 'g', 'h', 'j', 'k', 'm', 'n', 'p', 'q', 'r', 's', 't', 'v', 'w', 'x', 'y', 'z'
|
||||||
];
|
];
|
||||||
}
|
|
||||||
|
|
||||||
impl Invite {
|
impl Invite {
|
||||||
/// Get the invite code for this invite
|
/// Get the invite code for this invite
|
||||||
@@ -30,7 +28,7 @@ impl Invite {
|
|||||||
|
|
||||||
/// Create a new invite from given information
|
/// Create a new invite from given information
|
||||||
pub async fn create(db: &Database, creator: &User, target: &Channel) -> Result<Invite> {
|
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 {
|
let invite = match &target {
|
||||||
Channel::Group { id, .. } => Ok(Invite::Group {
|
Channel::Group { id, .. } => Ok(Invite::Group {
|
||||||
code,
|
code,
|
||||||
|
|||||||
@@ -385,7 +385,7 @@ impl SendableEmbed {
|
|||||||
impl BulkMessageResponse {
|
impl BulkMessageResponse {
|
||||||
pub async fn transform(
|
pub async fn transform(
|
||||||
db: &Database,
|
db: &Database,
|
||||||
channel: &Channel,
|
channel: Option<&Channel>,
|
||||||
messages: Vec<Message>,
|
messages: Vec<Message>,
|
||||||
include_users: Option<bool>,
|
include_users: Option<bool>,
|
||||||
) -> Result<BulkMessageResponse> {
|
) -> Result<BulkMessageResponse> {
|
||||||
@@ -394,7 +394,8 @@ impl BulkMessageResponse {
|
|||||||
let users = User::fetch_foreign_users(db, &user_ids).await?;
|
let users = User::fetch_foreign_users(db, &user_ids).await?;
|
||||||
|
|
||||||
Ok(match channel {
|
Ok(match channel {
|
||||||
Channel::TextChannel { server, .. } | Channel::VoiceChannel { server, .. } => {
|
Some(Channel::TextChannel { server, .. })
|
||||||
|
| Some(Channel::VoiceChannel { server, .. }) => {
|
||||||
BulkMessageResponse::MessagesAndUsers {
|
BulkMessageResponse::MessagesAndUsers {
|
||||||
messages,
|
messages,
|
||||||
users,
|
users,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use std::{collections::HashSet, str::FromStr};
|
use std::{collections::HashSet, str::FromStr};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
use ulid::Ulid;
|
use ulid::Ulid;
|
||||||
|
|
||||||
@@ -8,13 +9,10 @@ use crate::{
|
|||||||
Database, Result,
|
Database, Result,
|
||||||
};
|
};
|
||||||
|
|
||||||
lazy_static! {
|
static PERMISSIBLE_EMOJIS: Lazy<HashSet<String>> = Lazy::new(|| include_str!(crate::asset!("emojis.txt"))
|
||||||
/// Permissible emojis
|
|
||||||
static ref PERMISSIBLE_EMOJIS: HashSet<String> = include_str!(crate::asset!("emojis.txt"))
|
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.map(|x| x.into())
|
.map(|x| x.into())
|
||||||
.collect();
|
.collect());
|
||||||
}
|
|
||||||
|
|
||||||
impl Emoji {
|
impl Emoji {
|
||||||
/// Get parent id
|
/// Get parent id
|
||||||
|
|||||||
@@ -28,3 +28,7 @@ pub mod users {
|
|||||||
pub mod user;
|
pub mod user;
|
||||||
pub mod user_settings;
|
pub mod user_settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub mod safety {
|
||||||
|
pub mod report;
|
||||||
|
}
|
||||||
|
|||||||
9
crates/quark/src/impl/generic/safety/report.rs
Normal file
9
crates/quark/src/impl/generic/safety/report.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -57,6 +57,14 @@ pub async fn create_database(db: &MongoDb) {
|
|||||||
.await
|
.await
|
||||||
.expect("Failed to create user_settings collection.");
|
.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)
|
db.create_collection("bots", None)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to create bots collection.");
|
.expect("Failed to create bots collection.");
|
||||||
@@ -103,18 +111,18 @@ pub async fn create_database(db: &MongoDb) {
|
|||||||
},
|
},
|
||||||
"name": "content"
|
"name": "content"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"key": {
|
|
||||||
"channel": 1_i32
|
|
||||||
},
|
|
||||||
"name": "channel"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"key": {
|
"key": {
|
||||||
"channel": 1_i32,
|
"channel": 1_i32,
|
||||||
"_id": 1_i32
|
"_id": 1_i32
|
||||||
},
|
},
|
||||||
"name": "channel_id_compound"
|
"name": "channel_id_compound"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": {
|
||||||
|
"author": 1_i32
|
||||||
|
},
|
||||||
|
"name": "author"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use std::time::Duration;
|
use std::{time::Duration, ops::BitXor};
|
||||||
|
|
||||||
use bson::{Bson, DateTime};
|
use bson::{Bson, DateTime};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
@@ -16,7 +16,7 @@ struct MigrationInfo {
|
|||||||
revision: i32,
|
revision: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const LATEST_REVISION: i32 = 18;
|
pub const LATEST_REVISION: i32 = 21;
|
||||||
|
|
||||||
pub async fn migrate_database(db: &MongoDb) {
|
pub async fn migrate_database(db: &MongoDb) {
|
||||||
let migrations = db.col::<Document>("migrations");
|
let migrations = db.col::<Document>("migrations");
|
||||||
@@ -503,13 +503,8 @@ pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 {
|
|||||||
|
|
||||||
update.insert(
|
update.insert(
|
||||||
"default_permissions",
|
"default_permissions",
|
||||||
(*DEFAULT_PERMISSION_SERVER
|
|
||||||
// Remove Send Message permission if it wasn't originally granted
|
// Remove Send Message permission if it wasn't originally granted
|
||||||
^ (if has_send {
|
DEFAULT_PERMISSION_SERVER.bitxor(if has_send { 0 } else { Permission::SendMessage as u64}) as i64,
|
||||||
0
|
|
||||||
} else {
|
|
||||||
Permission::SendMessage as u64
|
|
||||||
})) as i64,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(Bson::Document(mut roles)) = document.remove("roles") {
|
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.");
|
.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`.
|
// Need to migrate fields on attachments, change `user_id`, `object_id`, etc to `parent`.
|
||||||
|
|
||||||
// Reminder to update LATEST_REVISION when adding new migrations.
|
// Reminder to update LATEST_REVISION when adding new migrations.
|
||||||
|
|||||||
90
crates/quark/src/impl/mongo/admin/stats.rs
Normal file
90
crates/quark/src/impl/mongo/admin/stats.rs
Normal 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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,9 @@ use bson::{to_bson, Document};
|
|||||||
use futures::try_join;
|
use futures::try_join;
|
||||||
use mongodb::options::FindOptions;
|
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::r#impl::mongo::DocumentId;
|
||||||
use crate::{AbstractMessage, Error, Result};
|
use crate::{AbstractMessage, Error, Result};
|
||||||
|
|
||||||
@@ -139,26 +141,60 @@ impl AbstractMessage for MongoDb {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fetch_messages(
|
async fn fetch_messages(&self, query: MessageQuery) -> Result<Vec<Message>> {
|
||||||
&self,
|
let mut filter = doc! {};
|
||||||
channel: &str,
|
|
||||||
limit: Option<i64>,
|
// 1. Apply message filters
|
||||||
before: Option<String>,
|
if let Some(channel) = query.filter.channel {
|
||||||
after: Option<String>,
|
filter.insert("channel", channel);
|
||||||
sort: Option<MessageSort>,
|
}
|
||||||
nearby: Option<String>,
|
|
||||||
) -> Result<Vec<Message>> {
|
if let Some(author) = query.filter.author {
|
||||||
let limit = limit.unwrap_or(50);
|
filter.insert("author", author);
|
||||||
Ok(if let Some(nearby) = nearby {
|
}
|
||||||
|
|
||||||
|
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! {
|
||||||
|
"$lt": &nearby
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
newer_message_filter.insert(
|
||||||
|
"_id",
|
||||||
|
doc! {
|
||||||
|
"$gte": &nearby
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// 3.2. Execute in both directions
|
||||||
let (a, b) = try_join!(
|
let (a, b) = try_join!(
|
||||||
self.find_with_options::<_, Message>(
|
self.find_with_options::<_, Message>(
|
||||||
COL,
|
COL,
|
||||||
doc! {
|
newer_message_filter,
|
||||||
"channel": channel,
|
|
||||||
"_id": {
|
|
||||||
"$gte": &nearby
|
|
||||||
}
|
|
||||||
},
|
|
||||||
FindOptions::builder()
|
FindOptions::builder()
|
||||||
.limit(limit / 2 + 1)
|
.limit(limit / 2 + 1)
|
||||||
.sort(doc! {
|
.sort(doc! {
|
||||||
@@ -168,12 +204,7 @@ impl AbstractMessage for MongoDb {
|
|||||||
),
|
),
|
||||||
self.find_with_options::<_, Message>(
|
self.find_with_options::<_, Message>(
|
||||||
COL,
|
COL,
|
||||||
doc! {
|
older_message_filter,
|
||||||
"channel": channel,
|
|
||||||
"_id": {
|
|
||||||
"$lt": &nearby
|
|
||||||
}
|
|
||||||
},
|
|
||||||
FindOptions::builder()
|
FindOptions::builder()
|
||||||
.limit(limit / 2)
|
.limit(limit / 2)
|
||||||
.sort(doc! {
|
.sort(doc! {
|
||||||
@@ -183,94 +214,56 @@ impl AbstractMessage for MongoDb {
|
|||||||
)
|
)
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
[a, b].concat()
|
Ok([a, b].concat())
|
||||||
} else {
|
|
||||||
let mut query = doc! { "channel": channel };
|
|
||||||
if let Some(before) = before {
|
|
||||||
query.insert("_id", doc! { "$lt": before });
|
|
||||||
}
|
}
|
||||||
|
MessageTimePeriod::Absolute {
|
||||||
if let Some(after) = after {
|
before,
|
||||||
query.insert("_id", doc! { "$gt": after });
|
after,
|
||||||
}
|
sort,
|
||||||
|
} => {
|
||||||
let sort: i32 = if let MessageSort::Latest = sort.unwrap_or(MessageSort::Latest) {
|
// 3.1. Apply message ID filter
|
||||||
-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) {
|
if let Some(doc) = match (before, after) {
|
||||||
(Some(before), Some(after)) => Some(doc! {
|
(Some(before), Some(after)) => Some(doc! {
|
||||||
"lt": before,
|
"$lt": before,
|
||||||
"gt": after
|
"$gt": after
|
||||||
}),
|
}),
|
||||||
(Some(before), _) => Some(doc! {
|
(Some(before), _) => Some(doc! {
|
||||||
"lt": before
|
"$lt": before
|
||||||
}),
|
}),
|
||||||
(_, Some(after)) => Some(doc! {
|
(_, Some(after)) => Some(doc! {
|
||||||
"gt": after
|
"$gt": after
|
||||||
}),
|
}),
|
||||||
_ => None,
|
_ => None,
|
||||||
} {
|
} {
|
||||||
filter.insert("_id", doc);
|
filter.insert("_id", doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3.2. Execute with given message sort
|
||||||
self.find_with_options(
|
self.find_with_options(
|
||||||
COL,
|
COL,
|
||||||
filter,
|
filter,
|
||||||
FindOptions::builder()
|
FindOptions::builder()
|
||||||
.projection(if let MessageSort::Relevance = &sort {
|
.limit(limit)
|
||||||
|
.sort(match sort.unwrap_or(MessageSort::Latest) {
|
||||||
|
// Sort by relevance, fallback to latest
|
||||||
|
MessageSort::Relevance => {
|
||||||
|
if is_search_query {
|
||||||
doc! {
|
doc! {
|
||||||
"score": {
|
"score": {
|
||||||
"$meta": "textScore"
|
"$meta": "textScore"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
doc! {}
|
doc! {
|
||||||
})
|
"_id": -1_i32
|
||||||
.limit(limit)
|
|
||||||
.sort(match &sort {
|
|
||||||
MessageSort::Relevance => doc! {
|
|
||||||
"score": {
|
|
||||||
"$meta": "textScore"
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
}
|
||||||
|
// Sort by latest first
|
||||||
MessageSort::Latest => doc! {
|
MessageSort::Latest => doc! {
|
||||||
"_id": -1_i32
|
"_id": -1_i32
|
||||||
},
|
},
|
||||||
|
// Sort by oldest first
|
||||||
MessageSort::Oldest => doc! {
|
MessageSort::Oldest => doc! {
|
||||||
"_id": 1_i32
|
"_id": 1_i32
|
||||||
},
|
},
|
||||||
@@ -279,6 +272,8 @@ impl AbstractMessage for MongoDb {
|
|||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Add a new reaction to a message
|
/// Add a new reaction to a message
|
||||||
async fn add_reaction(&self, id: &str, emoji: &str, user: &str) -> Result<()> {
|
async fn add_reaction(&self, id: &str, emoji: &str, user: &str) -> Result<()> {
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ impl AbstractAttachment for MongoDb {
|
|||||||
parent_type: &str,
|
parent_type: &str,
|
||||||
parent_id: &str,
|
parent_id: &str,
|
||||||
) -> Result<File> {
|
) -> Result<File> {
|
||||||
let key = format!("{}_id", parent_type);
|
let key = format!("{parent_type}_id");
|
||||||
match self
|
match self
|
||||||
.find_one::<File>(
|
.find_one::<File>(
|
||||||
COL,
|
COL,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ use crate::{util::manipulation::prefix_keys, AbstractDatabase, Error, Result};
|
|||||||
|
|
||||||
pub mod admin {
|
pub mod admin {
|
||||||
pub mod migrations;
|
pub mod migrations;
|
||||||
|
pub mod stats;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod media {
|
pub mod media {
|
||||||
@@ -90,15 +91,23 @@ impl MongoDb {
|
|||||||
where
|
where
|
||||||
O: Into<Option<FindOptions>>,
|
O: Into<Option<FindOptions>>,
|
||||||
{
|
{
|
||||||
Ok(self
|
let result = self.col::<T>(collection).find(projection, options).await;
|
||||||
.col::<T>(collection)
|
Ok(if cfg!(debug_assertions) {
|
||||||
.find(projection, options)
|
result.unwrap()
|
||||||
.await
|
} else {
|
||||||
.map_err(|_| Error::DatabaseError {
|
result.map_err(|_| Error::DatabaseError {
|
||||||
operation: "find",
|
operation: "find",
|
||||||
with: collection,
|
with: collection,
|
||||||
})?
|
})?
|
||||||
.filter_map(|s| async { s.ok() })
|
}
|
||||||
|
.filter_map(|s| async {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
// Hard fail on invalid documents
|
||||||
|
Some(s.unwrap())
|
||||||
|
} else {
|
||||||
|
s.ok()
|
||||||
|
}
|
||||||
|
})
|
||||||
.collect::<Vec<T>>()
|
.collect::<Vec<T>>()
|
||||||
.await)
|
.await)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use crate::models::report::PartialReport;
|
||||||
use crate::models::Report;
|
use crate::models::Report;
|
||||||
use crate::{AbstractReport, Result};
|
use crate::{AbstractReport, Result};
|
||||||
|
|
||||||
@@ -10,4 +11,18 @@ impl AbstractReport for MongoDb {
|
|||||||
async fn insert_report(&self, report: &Report) -> Result<()> {
|
async fn insert_report(&self, report: &Report) -> Result<()> {
|
||||||
self.insert_one(COL, report).await.map(|_| ())
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,4 +10,14 @@ impl AbstractSnapshot for MongoDb {
|
|||||||
async fn insert_snapshot(&self, snapshot: &Snapshot) -> Result<()> {
|
async fn insert_snapshot(&self, snapshot: &Snapshot) -> Result<()> {
|
||||||
self.insert_one(COL, snapshot).await.map(|_| ())
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use bson::Document;
|
use bson::Document;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use mongodb::options::{Collation, CollationStrength, FindOneOptions, FindOptions};
|
use mongodb::options::{Collation, CollationStrength, FindOneOptions, FindOptions};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
use crate::models::user::{FieldsUser, PartialUser, RelationshipStatus, User};
|
use crate::models::user::{FieldsUser, PartialUser, RelationshipStatus, User};
|
||||||
use crate::r#impl::mongo::IntoDocumentPath;
|
use crate::r#impl::mongo::IntoDocumentPath;
|
||||||
@@ -8,16 +9,14 @@ use crate::{AbstractUser, Error, Result};
|
|||||||
|
|
||||||
use super::super::MongoDb;
|
use super::super::MongoDb;
|
||||||
|
|
||||||
lazy_static! {
|
static FIND_USERNAME_OPTIONS: Lazy<FindOneOptions> = Lazy::new(|| FindOneOptions::builder()
|
||||||
static ref FIND_USERNAME_OPTIONS: FindOneOptions = FindOneOptions::builder()
|
|
||||||
.collation(
|
.collation(
|
||||||
Collation::builder()
|
Collation::builder()
|
||||||
.locale("en")
|
.locale("en")
|
||||||
.strength(CollationStrength::Secondary)
|
.strength(CollationStrength::Secondary)
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
.build();
|
.build());
|
||||||
}
|
|
||||||
|
|
||||||
static COL: &str = "users";
|
static COL: &str = "users";
|
||||||
|
|
||||||
@@ -265,7 +264,7 @@ impl AbstractUser for MongoDb {
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"_id": target_id,
|
"_id": target_id,
|
||||||
"status": format!("{:?}", relationship)
|
"status": format!("{relationship:?}")
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ extern crate impl_ops;
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate optional_struct;
|
extern crate optional_struct;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate lazy_static;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate bitfield;
|
extern crate bitfield;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate bson;
|
extern crate bson;
|
||||||
|
|||||||
122
crates/quark/src/models/admin/stats.rs
Normal file
122
crates/quark/src/models/admin/stats.rs
Normal 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>,
|
||||||
|
}
|
||||||
@@ -160,10 +160,11 @@ pub struct Message {
|
|||||||
/// # Message Sort
|
/// # Message Sort
|
||||||
///
|
///
|
||||||
/// Sort used for retrieving messages
|
/// Sort used for retrieving messages
|
||||||
#[derive(Serialize, Deserialize, JsonSchema)]
|
#[derive(Serialize, Deserialize, JsonSchema, Debug, Default)]
|
||||||
#[cfg_attr(feature = "rocket_impl", derive(FromFormField))]
|
#[cfg_attr(feature = "rocket_impl", derive(FromFormField))]
|
||||||
pub enum MessageSort {
|
pub enum MessageSort {
|
||||||
/// Sort by the most relevant messages
|
/// Sort by the most relevant messages
|
||||||
|
#[default]
|
||||||
Relevance,
|
Relevance,
|
||||||
/// Sort by the newest messages first
|
/// Sort by the newest messages first
|
||||||
Latest,
|
Latest,
|
||||||
@@ -171,10 +172,54 @@ pub enum MessageSort {
|
|||||||
Oldest,
|
Oldest,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for MessageSort {
|
/// # Message Time Period
|
||||||
fn default() -> MessageSort {
|
///
|
||||||
MessageSort::Relevance
|
/// 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
|
/// # Bulk Message Response
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
/// Metadata associated with file
|
/// Metadata associated with file
|
||||||
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)]
|
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, Default)]
|
||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
pub enum Metadata {
|
pub enum Metadata {
|
||||||
/// File is just a generic uncategorised file
|
/// File is just a generic uncategorised file
|
||||||
|
#[default]
|
||||||
File,
|
File,
|
||||||
/// File contains textual data and should be displayed as such
|
/// File contains textual data and should be displayed as such
|
||||||
Text,
|
Text,
|
||||||
@@ -16,12 +17,6 @@ pub enum Metadata {
|
|||||||
Audio,
|
Audio,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Metadata {
|
|
||||||
fn default() -> Metadata {
|
|
||||||
Metadata::File
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Representation of a File on Revolt
|
/// Representation of a File on Revolt
|
||||||
/// Generated by Autumn
|
/// Generated by Autumn
|
||||||
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, Default)]
|
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, Default)]
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
mod admin {
|
mod admin {
|
||||||
pub mod migrations;
|
pub mod migrations;
|
||||||
pub mod simple;
|
pub mod simple;
|
||||||
|
pub mod stats;
|
||||||
}
|
}
|
||||||
|
|
||||||
mod media {
|
mod media {
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ pub enum UserReportReason {
|
|||||||
Underage,
|
Underage,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The content being reported
|
||||||
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)]
|
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)]
|
||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
pub enum ReportedContent {
|
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)]
|
#[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 {
|
pub struct Report {
|
||||||
/// Unique Id
|
/// Unique Id
|
||||||
#[serde(rename = "_id")]
|
#[serde(rename = "_id")]
|
||||||
@@ -82,4 +100,11 @@ pub struct Report {
|
|||||||
pub content: ReportedContent,
|
pub content: ReportedContent,
|
||||||
/// Additional report context
|
/// Additional report context
|
||||||
pub additional_context: String,
|
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,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::models::{Message, Server, User};
|
use crate::models::{Channel, Message, Server, User};
|
||||||
|
|
||||||
/// Enum to map into different models
|
/// Enum to map into different models
|
||||||
/// that can be saved in a snapshot
|
/// that can be saved in a snapshot
|
||||||
@@ -9,11 +9,11 @@ use crate::models::{Message, Server, User};
|
|||||||
pub enum SnapshotContent {
|
pub enum SnapshotContent {
|
||||||
Message {
|
Message {
|
||||||
/// Context before the message
|
/// Context before the message
|
||||||
#[serde(rename = "_prior_context")]
|
#[serde(rename = "_prior_context", default)]
|
||||||
prior_context: Vec<Message>,
|
prior_context: Vec<Message>,
|
||||||
|
|
||||||
/// Context after the message
|
/// Context after the message
|
||||||
#[serde(rename = "_leading_context")]
|
#[serde(rename = "_leading_context", default)]
|
||||||
leading_context: Vec<Message>,
|
leading_context: Vec<Message>,
|
||||||
|
|
||||||
/// Message
|
/// Message
|
||||||
@@ -35,3 +35,20 @@ pub struct Snapshot {
|
|||||||
/// Snapshot of content
|
/// Snapshot of content
|
||||||
pub content: SnapshotContent,
|
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>,
|
||||||
|
}
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ pub struct Server {
|
|||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub banner: Option<File>,
|
pub banner: Option<File>,
|
||||||
|
|
||||||
/// Enum of server flags
|
/// Bitfield of server flags
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub flags: Option<i32>,
|
pub flags: Option<i32>,
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use num_enum::TryFromPrimitive;
|
use num_enum::TryFromPrimitive;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::ops;
|
use once_cell::sync::Lazy;
|
||||||
|
use std::ops::{self, Add};
|
||||||
|
|
||||||
/// Permission value on Revolt
|
/// 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!(+ |a: &Permission, b: &Permission| -> u64 { *a as u64 | *b as u64 });
|
||||||
impl_op_ex_commutative!(+ |a: &u64, b: &Permission| -> u64 { *a | *b as u64 });
|
impl_op_ex_commutative!(+ |a: &u64, b: &Permission| -> u64 { *a | *b as u64 });
|
||||||
|
|
||||||
lazy_static! {
|
pub static ALLOW_IN_TIMEOUT: Lazy<u64> = Lazy::new(|| Permission::ViewChannel + Permission::ReadMessageHistory);
|
||||||
pub static ref ALLOW_IN_TIMEOUT: u64 = Permission::ViewChannel + Permission::ReadMessageHistory;
|
pub static DEFAULT_PERMISSION_VIEW_ONLY: Lazy<u64> = Lazy::new(|| Permission::ViewChannel + Permission::ReadMessageHistory);
|
||||||
pub static ref DEFAULT_PERMISSION_VIEW_ONLY: u64 =
|
pub static DEFAULT_PERMISSION: Lazy<u64> = Lazy::new(|| DEFAULT_PERMISSION_VIEW_ONLY.add(
|
||||||
Permission::ViewChannel + Permission::ReadMessageHistory;
|
Permission::SendMessage
|
||||||
pub static ref DEFAULT_PERMISSION: u64 = *DEFAULT_PERMISSION_VIEW_ONLY
|
|
||||||
+ Permission::SendMessage
|
|
||||||
+ Permission::InviteOthers
|
+ Permission::InviteOthers
|
||||||
+ Permission::SendEmbeds
|
+ Permission::SendEmbeds
|
||||||
+ Permission::UploadFiles
|
+ Permission::UploadFiles
|
||||||
+ Permission::Connect
|
+ Permission::Connect
|
||||||
+ Permission::Speak;
|
+ Permission::Speak));
|
||||||
pub static ref DEFAULT_PERMISSION_SAVED_MESSAGES: u64 = Permission::GrantAllSafe as u64;
|
pub static DEFAULT_PERMISSION_SAVED_MESSAGES: u64 = Permission::GrantAllSafe as u64;
|
||||||
pub static ref DEFAULT_PERMISSION_DIRECT_MESSAGE: u64 = *DEFAULT_PERMISSION
|
pub static DEFAULT_PERMISSION_DIRECT_MESSAGE: Lazy<u64> = Lazy::new(|| DEFAULT_PERMISSION.add(Permission::ManageChannel + Permission::React));
|
||||||
+ Permission::ManageChannel
|
pub static DEFAULT_PERMISSION_SERVER: Lazy<u64> = Lazy::new(|| DEFAULT_PERMISSION.add(Permission::React + Permission::ChangeNickname + Permission::ChangeAvatar));
|
||||||
+ Permission::React;
|
|
||||||
pub static ref DEFAULT_PERMISSION_SERVER: u64 =
|
|
||||||
*DEFAULT_PERMISSION + Permission::React + Permission::ChangeNickname + Permission::ChangeAvatar;
|
|
||||||
}
|
|
||||||
|
|
||||||
bitfield! {
|
bitfield! {
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ async fn calculate_channel_permission(
|
|||||||
let value: PermissionValue = match channel {
|
let value: PermissionValue = match channel {
|
||||||
Channel::SavedMessages { user, .. } => {
|
Channel::SavedMessages { user, .. } => {
|
||||||
if user == &data.perspective.id {
|
if user == &data.perspective.id {
|
||||||
(*DEFAULT_PERMISSION_SAVED_MESSAGES).into()
|
DEFAULT_PERMISSION_SAVED_MESSAGES.into()
|
||||||
} else {
|
} else {
|
||||||
0_u64.into()
|
0_u64.into()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,6 +51,10 @@ pub fn get_relationship(a: &User, b: &str) -> RelationshipStatus {
|
|||||||
async fn calculate_permission(data: &mut PermissionCalculator<'_>, db: &crate::Database) -> u32 {
|
async fn calculate_permission(data: &mut PermissionCalculator<'_>, db: &crate::Database) -> u32 {
|
||||||
let user = data.user.get().unwrap();
|
let user = data.user.get().unwrap();
|
||||||
|
|
||||||
|
if data.perspective.privileged {
|
||||||
|
return u32::MAX;
|
||||||
|
}
|
||||||
|
|
||||||
if data.perspective.id == user.id {
|
if data.perspective.id == user.id {
|
||||||
return u32::MAX;
|
return u32::MAX;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
lazy_static! {
|
pub static REGION_ID: Lazy<u16> = Lazy::new(|| env::var("REGION_ID")
|
||||||
pub static ref REGION_ID: u16 = env::var("REGION_ID")
|
|
||||||
.unwrap_or_else(|_| "0".to_string())
|
.unwrap_or_else(|_| "0".to_string())
|
||||||
.parse()
|
.parse()
|
||||||
.unwrap();
|
.unwrap());
|
||||||
pub static ref REGION_KEY: String = format!("region{}", &*REGION_ID);
|
|
||||||
}
|
pub static REGION_KEY: Lazy<String> = Lazy::new(|| format!("region{}", &*REGION_ID));
|
||||||
|
|
||||||
/// Compact presence information for a user
|
/// Compact presence information for a user
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
use crate::Database;
|
use crate::Database;
|
||||||
|
|
||||||
use deadqueue::limited::Queue;
|
use deadqueue::limited::Queue;
|
||||||
use mongodb::bson::doc;
|
|
||||||
use std::{collections::HashMap, time::Duration};
|
use std::{collections::HashMap, time::Duration};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
use super::DelayedTask;
|
use super::DelayedTask;
|
||||||
|
|
||||||
@@ -38,9 +38,7 @@ struct Task {
|
|||||||
event: AckEvent,
|
event: AckEvent,
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
static Q: Lazy<Queue<Data>> = Lazy::new(|| Queue::new(10_000));
|
||||||
static ref Q: Queue<Data> = Queue::new(10_000);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Queue a new task for a worker
|
/// Queue a new task for a worker
|
||||||
pub async fn queue(channel: String, user: String, event: AckEvent) {
|
pub async fn queue(channel: String, user: String, event: AckEvent) {
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
use crate::{models::channel::PartialChannel, Database};
|
use crate::{models::channel::PartialChannel, Database};
|
||||||
|
|
||||||
use deadqueue::limited::Queue;
|
use deadqueue::limited::Queue;
|
||||||
use mongodb::bson::doc;
|
|
||||||
use std::{collections::HashMap, time::Duration};
|
use std::{collections::HashMap, time::Duration};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
use super::DelayedTask;
|
use super::DelayedTask;
|
||||||
|
|
||||||
@@ -26,9 +26,7 @@ struct Task {
|
|||||||
is_dm: bool,
|
is_dm: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
static Q: Lazy<Queue<Data>> = Lazy::new(|| Queue::new(10_000));
|
||||||
static ref Q: Queue<Data> = Queue::new(10_000);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Queue a new task for a worker
|
/// Queue a new task for a worker
|
||||||
pub async fn queue(channel: String, id: String, is_dm: bool) {
|
pub async fn queue(channel: String, id: String, is_dm: bool) {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use async_lock::Semaphore;
|
|||||||
use async_std::task::spawn;
|
use async_std::task::spawn;
|
||||||
use deadqueue::limited::Queue;
|
use deadqueue::limited::Queue;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
/// Task information
|
/// Task information
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -21,9 +22,8 @@ struct EmbedTask {
|
|||||||
content: String,
|
content: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
static Q: Lazy<Queue<EmbedTask>> = Lazy::new(|| Queue::new(10_000));
|
||||||
static ref Q: Queue<EmbedTask> = Queue::new(10_000);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Queue a new task for a worker
|
/// Queue a new task for a worker
|
||||||
pub async fn queue(channel: String, id: String, content: String) {
|
pub async fn queue(channel: String, id: String, content: String) {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use crate::bson::doc;
|
|
||||||
use crate::util::variables::delta::VAPID_PRIVATE_KEY;
|
use crate::util::variables::delta::VAPID_PRIVATE_KEY;
|
||||||
|
|
||||||
use authifier::Database;
|
use authifier::Database;
|
||||||
use deadqueue::limited::Queue;
|
use deadqueue::limited::Queue;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
use web_push::{
|
use web_push::{
|
||||||
ContentEncoding, SubscriptionInfo, SubscriptionKeys, VapidSignatureBuilder, WebPushClient,
|
ContentEncoding, SubscriptionInfo, SubscriptionKeys, VapidSignatureBuilder, WebPushClient,
|
||||||
WebPushMessageBuilder,
|
WebPushMessageBuilder,
|
||||||
@@ -17,9 +17,8 @@ struct PushTask {
|
|||||||
payload: String,
|
payload: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
static Q: Lazy<Queue<PushTask>> = Lazy::new(|| Queue::new(10_000));
|
||||||
static ref Q: Queue<PushTask> = Queue::new(10_000);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Queue a new task for a worker
|
/// Queue a new task for a worker
|
||||||
pub async fn queue(recipients: Vec<String>, payload: String) {
|
pub async fn queue(recipients: Vec<String>, payload: String) {
|
||||||
|
|||||||
6
crates/quark/src/traits/admin/stats.rs
Normal file
6
crates/quark/src/traits/admin/stats.rs
Normal 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>;
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::models::message::{AppendMessage, Message, MessageSort, PartialMessage};
|
use crate::models::message::{AppendMessage, Message, MessageQuery, PartialMessage};
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
@@ -21,27 +21,8 @@ pub trait AbstractMessage: Sync + Send {
|
|||||||
/// Delete messages from a channel by their ids and corresponding channel id
|
/// Delete messages from a channel by their ids and corresponding channel id
|
||||||
async fn delete_messages(&self, channel: &str, ids: Vec<String>) -> Result<()>;
|
async fn delete_messages(&self, channel: &str, ids: Vec<String>) -> Result<()>;
|
||||||
|
|
||||||
/// Fetch multiple messages
|
/// Fetch multiple messages by given query
|
||||||
async fn fetch_messages(
|
async fn fetch_messages(&self, query: MessageQuery) -> Result<Vec<Message>>;
|
||||||
&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>>;
|
|
||||||
|
|
||||||
/// Add a new reaction to a message
|
/// Add a new reaction to a message
|
||||||
async fn add_reaction(&self, id: &str, emoji: &str, user: &str) -> Result<()>;
|
async fn add_reaction(&self, id: &str, emoji: &str, user: &str) -> Result<()>;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
mod admin {
|
mod admin {
|
||||||
pub mod migrations;
|
pub mod migrations;
|
||||||
|
pub mod stats;
|
||||||
}
|
}
|
||||||
|
|
||||||
mod media {
|
mod media {
|
||||||
@@ -33,6 +34,7 @@ mod safety {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub use admin::migrations::AbstractMigrations;
|
pub use admin::migrations::AbstractMigrations;
|
||||||
|
pub use admin::stats::AbstractStats;
|
||||||
|
|
||||||
pub use media::attachment::AbstractAttachment;
|
pub use media::attachment::AbstractAttachment;
|
||||||
pub use media::emoji::AbstractEmoji;
|
pub use media::emoji::AbstractEmoji;
|
||||||
@@ -58,6 +60,7 @@ pub trait AbstractDatabase:
|
|||||||
Sync
|
Sync
|
||||||
+ Send
|
+ Send
|
||||||
+ AbstractMigrations
|
+ AbstractMigrations
|
||||||
|
+ AbstractStats
|
||||||
+ AbstractAttachment
|
+ AbstractAttachment
|
||||||
+ AbstractEmoji
|
+ AbstractEmoji
|
||||||
+ AbstractChannel
|
+ AbstractChannel
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use crate::models::report::PartialReport;
|
||||||
use crate::models::Report;
|
use crate::models::Report;
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
|
|
||||||
@@ -5,4 +6,13 @@ use crate::Result;
|
|||||||
pub trait AbstractReport: Sync + Send {
|
pub trait AbstractReport: Sync + Send {
|
||||||
/// Insert a new report into the database
|
/// Insert a new report into the database
|
||||||
async fn insert_report(&self, report: &Report) -> Result<()>;
|
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>>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,4 +5,7 @@ use crate::Result;
|
|||||||
pub trait AbstractSnapshot: Sync + Send {
|
pub trait AbstractSnapshot: Sync + Send {
|
||||||
/// Insert a new snapshot into the database
|
/// Insert a new snapshot into the database
|
||||||
async fn insert_snapshot(&self, snapshot: &Snapshot) -> Result<()>;
|
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>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use linkify::{LinkFinder, LinkKind};
|
|||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{collections::HashSet, sync::Arc};
|
use std::{collections::HashSet, sync::Arc};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
use crate::{models::attachment::File, Error, Result};
|
use crate::{models::attachment::File, Error, Result};
|
||||||
|
|
||||||
@@ -175,6 +176,9 @@ pub enum Embed {
|
|||||||
None,
|
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 {
|
impl Embed {
|
||||||
/// Generate embeds from given content
|
/// Generate embeds from given content
|
||||||
pub async fn generate(
|
pub async fn generate(
|
||||||
@@ -183,10 +187,7 @@ impl Embed {
|
|||||||
max_embeds: usize,
|
max_embeds: usize,
|
||||||
semaphore: Arc<Semaphore>,
|
semaphore: Arc<Semaphore>,
|
||||||
) -> Result<Vec<Embed>> {
|
) -> 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.
|
// Ignore code blocks.
|
||||||
let content = RE_CODE.replace_all(&content, "");
|
let content = RE_CODE.replace_all(&content, "");
|
||||||
|
|||||||
@@ -83,6 +83,11 @@ impl Ref {
|
|||||||
pub async fn as_webhook(&self, db: &Database) -> Result<Webhook> {
|
pub async fn as_webhook(&self, db: &Database) -> Result<Webhook> {
|
||||||
db.fetch_webhook(&self.id).await
|
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 {
|
impl<'r> FromParam<'r> for Ref {
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ pub enum Error {
|
|||||||
permission: UserPermission,
|
permission: UserPermission,
|
||||||
},
|
},
|
||||||
NotElevated,
|
NotElevated,
|
||||||
|
NotPrivileged,
|
||||||
CannotGiveMissingPermissions,
|
CannotGiveMissingPermissions,
|
||||||
NotOwner,
|
NotOwner,
|
||||||
|
|
||||||
@@ -174,6 +175,7 @@ impl<'r> Responder<'r, 'static> for Error {
|
|||||||
Error::MissingPermission { .. } => Status::Forbidden,
|
Error::MissingPermission { .. } => Status::Forbidden,
|
||||||
Error::MissingUserPermission { .. } => Status::Forbidden,
|
Error::MissingUserPermission { .. } => Status::Forbidden,
|
||||||
Error::NotElevated => Status::Forbidden,
|
Error::NotElevated => Status::Forbidden,
|
||||||
|
Error::NotPrivileged => Status::Forbidden,
|
||||||
Error::CannotGiveMissingPermissions => Status::Forbidden,
|
Error::CannotGiveMissingPermissions => Status::Forbidden,
|
||||||
Error::NotOwner => Status::Forbidden,
|
Error::NotOwner => Status::Forbidden,
|
||||||
|
|
||||||
|
|||||||
@@ -1,73 +1,50 @@
|
|||||||
use std::env;
|
use std::env;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
lazy_static! {
|
// Application Settings
|
||||||
// Application Settings
|
pub static PUBLIC_URL: Lazy<String> = Lazy::new(|| env::var("REVOLT_PUBLIC_URL").expect("Missing REVOLT_PUBLIC_URL environment variable."));
|
||||||
pub static ref PUBLIC_URL: String =
|
pub static APP_URL: Lazy<String> = Lazy::new(|| env::var("REVOLT_APP_URL").expect("Missing REVOLT_APP_URL environment variable."));
|
||||||
env::var("REVOLT_PUBLIC_URL").expect("Missing REVOLT_PUBLIC_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 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 =
|
pub static AUTUMN_URL: Lazy<String> = Lazy::new(|| env::var("AUTUMN_PUBLIC_URL").unwrap_or_else(|_| "https://example.com".to_string()));
|
||||||
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 ref JANUARY_URL: String =
|
pub static JANUARY_CONCURRENT_CONNECTIONS: Lazy<usize> = Lazy::new(|| env::var("JANUARY_CONCURRENT_CONNECTIONS").map_or(50, |v| v.parse().unwrap()));
|
||||||
env::var("JANUARY_PUBLIC_URL").unwrap_or_else(|_| "https://example.com".to_string());
|
pub static VOSO_URL: Lazy<String> = Lazy::new(|| env::var("VOSO_PUBLIC_URL").unwrap_or_else(|_| "https://example.com".to_string()));
|
||||||
pub static ref JANUARY_CONCURRENT_CONNECTIONS: usize =
|
pub static VOSO_WS_HOST: Lazy<String> = Lazy::new(|| env::var("VOSO_WS_HOST").unwrap_or_else(|_| "wss://example.com".to_string()));
|
||||||
env::var("JANUARY_CONCURRENT_CONNECTIONS").map_or(50, |v| v.parse().unwrap());
|
pub static VOSO_MANAGE_TOKEN: Lazy<String> = Lazy::new(|| env::var("VOSO_MANAGE_TOKEN").unwrap_or_else(|_| "0".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 =
|
pub static HCAPTCHA_KEY: Lazy<String> = Lazy::new(|| env::var("REVOLT_HCAPTCHA_KEY").unwrap_or_else(|_| "0x0000000000000000000000000000000000000000".to_string()));
|
||||||
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 ref HCAPTCHA_SITEKEY: String =
|
pub static VAPID_PRIVATE_KEY: Lazy<String> = Lazy::new(|| env::var("REVOLT_VAPID_PRIVATE_KEY").expect("Missing REVOLT_VAPID_PRIVATE_KEY environment variable."));
|
||||||
env::var("REVOLT_HCAPTCHA_SITEKEY").unwrap_or_else(|_| "10000000-ffff-ffff-ffff-000000000001".to_string());
|
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 ref VAPID_PRIVATE_KEY: String =
|
pub static AUTHIFIER_SHIELD_KEY: Lazy<Option<String>> = Lazy::new(|| env::var("REVOLT_AUTHIFIER_SHIELD_KEY").ok());
|
||||||
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();
|
|
||||||
|
|
||||||
// Application Flags
|
// Application Flags
|
||||||
pub static ref INVITE_ONLY: bool = env::var("REVOLT_INVITE_ONLY").map_or(false, |v| v == "1");
|
pub static INVITE_ONLY: Lazy<bool> = Lazy::new(|| 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(
|
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_HOST").is_ok()
|
||||||
&& env::var("REVOLT_SMTP_USERNAME").is_ok()
|
&& env::var("REVOLT_SMTP_USERNAME").is_ok()
|
||||||
&& env::var("REVOLT_SMTP_PASSWORD").is_ok()
|
&& env::var("REVOLT_SMTP_PASSWORD").is_ok()
|
||||||
&& env::var("REVOLT_SMTP_FROM").is_ok(),
|
&& env::var("REVOLT_SMTP_FROM").is_ok(),
|
||||||
|v| v == *"1"
|
|v| v == *"1"
|
||||||
);
|
));
|
||||||
pub static ref USE_HCAPTCHA: bool = env::var("REVOLT_HCAPTCHA_KEY").is_ok();
|
pub static USE_HCAPTCHA: Lazy<bool> = Lazy::new(|| env::var("REVOLT_HCAPTCHA_KEY").is_ok());
|
||||||
pub static ref USE_AUTUMN: bool = env::var("AUTUMN_PUBLIC_URL").is_ok();
|
pub static USE_AUTUMN: Lazy<bool> = Lazy::new(|| env::var("AUTUMN_PUBLIC_URL").is_ok());
|
||||||
pub static ref USE_JANUARY: bool = env::var("JANUARY_PUBLIC_URL").is_ok();
|
pub static USE_JANUARY: Lazy<bool> = Lazy::new(|| 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();
|
pub static USE_VOSO: Lazy<bool> = Lazy::new(|| env::var("VOSO_PUBLIC_URL").is_ok() && env::var("VOSO_MANAGE_TOKEN").is_ok());
|
||||||
|
|
||||||
// SMTP Settings
|
// SMTP Settings
|
||||||
pub static ref SMTP_HOST: String =
|
pub static SMTP_HOST: Lazy<String> = Lazy::new(|| env::var("REVOLT_SMTP_HOST").unwrap_or_else(|_| "".to_string()));
|
||||||
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 ref SMTP_USERNAME: String =
|
pub static SMTP_PASSWORD: Lazy<String> = Lazy::new(|| env::var("REVOLT_SMTP_PASSWORD").unwrap_or_else(|_| "".to_string()));
|
||||||
env::var("REVOLT_SMTP_USERNAME").unwrap_or_else(|_| "".to_string());
|
pub static SMTP_FROM: Lazy<String> = Lazy::new(|| env::var("REVOLT_SMTP_FROM").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
|
// Application Logic Settings
|
||||||
pub static ref MAX_GROUP_SIZE: usize =
|
pub static MAX_GROUP_SIZE: Lazy<usize> = Lazy::new(|| env::var("REVOLT_MAX_GROUP_SIZE").unwrap_or_else(|_| "50".to_string()).parse().unwrap());
|
||||||
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 ref MAX_BOT_COUNT: usize =
|
pub static MAX_EMBED_COUNT: Lazy<usize> = Lazy::new(|| env::var("REVOLT_MAX_EMBED_COUNT").unwrap_or_else(|_| "5".to_string()).parse().unwrap());
|
||||||
env::var("REVOLT_MAX_BOT_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 ref MAX_EMBED_COUNT: usize =
|
pub static EARLY_ADOPTER_BADGE: Lazy<i64> = Lazy::new(|| env::var("REVOLT_EARLY_ADOPTER_BADGE").unwrap_or_else(|_| "0".to_string()).parse().unwrap());
|
||||||
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() {
|
pub fn preflight_checks() {
|
||||||
format!("url = {}", *APP_URL);
|
format!("url = {}", *APP_URL);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use rocket::request::{FromRequest, Outcome};
|
|||||||
use schemars::schema::{InstanceType, SchemaObject, SingleOrVec};
|
use schemars::schema::{InstanceType, SchemaObject, SingleOrVec};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
#[derive(Validate, Serialize, Deserialize)]
|
#[derive(Validate, Serialize, Deserialize)]
|
||||||
pub struct IdempotencyKey {
|
pub struct IdempotencyKey {
|
||||||
@@ -16,9 +17,7 @@ pub struct IdempotencyKey {
|
|||||||
key: String,
|
key: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
static TOKEN_CACHE: Lazy<Mutex<lru::LruCache<String, ()>>> = Lazy::new(|| Mutex::new(lru::LruCache::new(100)));
|
||||||
static ref TOKEN_CACHE: Mutex<lru::LruCache<String, ()>> = Mutex::new(lru::LruCache::new(100));
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IdempotencyKey {
|
impl IdempotencyKey {
|
||||||
// Backwards compatibility.
|
// Backwards compatibility.
|
||||||
|
|||||||
@@ -22,17 +22,16 @@ use revolt_rocket_okapi::request::{OpenApiFromRequest, RequestHeaderInput};
|
|||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
/// Ratelimit Bucket
|
/// Ratelimit Bucket
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
struct Entry {
|
struct Entry {
|
||||||
used: u8,
|
used: u8,
|
||||||
reset: u128,
|
reset: u128,
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
static MAP: Lazy<DashMap<u64, Entry>> = Lazy::new(DashMap::new);
|
||||||
static ref MAP: DashMap<u64, Entry> = DashMap::new();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the current time from Unix Epoch as a Duration
|
/// Get the current time from Unix Epoch as a Duration
|
||||||
fn now() -> Duration {
|
fn now() -> Duration {
|
||||||
@@ -51,7 +50,7 @@ impl Entry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Deduct one unit from the bucket and save
|
/// Deduct one unit from the bucket and save
|
||||||
pub fn deduct(mut self) {
|
pub fn deduct(&mut self) {
|
||||||
let current_time = now().as_millis();
|
let current_time = now().as_millis();
|
||||||
if current_time > self.reset {
|
if current_time > self.reset {
|
||||||
self.used = 1;
|
self.used = 1;
|
||||||
@@ -193,7 +192,7 @@ impl Ratelimiter {
|
|||||||
|
|
||||||
let key = key.finish();
|
let key = key.finish();
|
||||||
let limit = resolve_bucket_limit(bucket);
|
let limit = resolve_bucket_limit(bucket);
|
||||||
let entry = Entry::from(key);
|
let mut entry = Entry::from(key);
|
||||||
|
|
||||||
let remaining = entry.get_remaining(limit);
|
let remaining = entry.get_remaining(limit);
|
||||||
if remaining > 0 {
|
if remaining > 0 {
|
||||||
|
|||||||
Reference in New Issue
Block a user