forked from jmug/stoatchat
Compare commits
20
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
49035f4817 | ||
|
|
c0ebaa0bd3 | ||
|
|
b98b244fc3 | ||
|
|
c8d5128b0c | ||
|
|
0578a05a05 | ||
|
|
9a412b3e08 | ||
|
|
8deec1f80a | ||
|
|
26afbeed84 | ||
|
|
9975de01bc | ||
|
|
ac525466b8 | ||
|
|
c7a04e4559 | ||
|
|
5bbe30edbc | ||
|
|
31c7dc0577 | ||
|
|
aba5c7d8af | ||
|
|
2f4ea4cabb | ||
|
|
7e801a31bd | ||
|
|
eb4870c9d5 | ||
|
|
b1fdd7fc74 | ||
|
|
bf3e027067 | ||
|
|
a7a7e49e18 |
@@ -59,6 +59,9 @@ REVOLT_UNSAFE_NO_EMAIL=1
|
|||||||
## Application Settings
|
## Application Settings
|
||||||
##
|
##
|
||||||
|
|
||||||
|
# Whether to enable staging only features
|
||||||
|
REVOLT_IS_STAGING=1
|
||||||
|
|
||||||
# Whether to only allow users to sign up if they have an invite code
|
# Whether to only allow users to sign up if they have an invite code
|
||||||
REVOLT_INVITE_ONLY=0
|
REVOLT_INVITE_ONLY=0
|
||||||
|
|
||||||
|
|||||||
Generated
+28
-8
@@ -806,6 +806,12 @@ dependencies = [
|
|||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "decancer"
|
||||||
|
version = "1.6.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "808127a7de612079ec37bfc1abc48ed77a6015a971a8bd7d4178d79147cbc839"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derivative"
|
name = "derivative"
|
||||||
version = "2.2.0"
|
version = "2.2.0"
|
||||||
@@ -2837,7 +2843,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "revolt-bonfire"
|
name = "revolt-bonfire"
|
||||||
version = "0.6.0-rc.1"
|
version = "0.6.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-std",
|
"async-std",
|
||||||
"async-tungstenite",
|
"async-tungstenite",
|
||||||
@@ -2854,7 +2860,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "revolt-database"
|
name = "revolt-database"
|
||||||
version = "0.6.0-rc.1"
|
version = "0.6.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-recursion",
|
"async-recursion",
|
||||||
"async-std",
|
"async-std",
|
||||||
@@ -2867,7 +2873,9 @@ dependencies = [
|
|||||||
"mongodb",
|
"mongodb",
|
||||||
"nanoid",
|
"nanoid",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
"rand 0.8.5",
|
||||||
"redis-kiss",
|
"redis-kiss",
|
||||||
|
"regex",
|
||||||
"revolt-models",
|
"revolt-models",
|
||||||
"revolt-permissions",
|
"revolt-permissions",
|
||||||
"revolt-presence",
|
"revolt-presence",
|
||||||
@@ -2878,11 +2886,12 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"ulid 1.0.0",
|
"ulid 1.0.0",
|
||||||
|
"unicode-segmentation",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "revolt-delta"
|
name = "revolt-delta"
|
||||||
version = "0.6.0-rc.1"
|
version = "0.6.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-channel",
|
"async-channel",
|
||||||
"async-std",
|
"async-std",
|
||||||
@@ -2904,6 +2913,7 @@ dependencies = [
|
|||||||
"reqwest",
|
"reqwest",
|
||||||
"revolt-database",
|
"revolt-database",
|
||||||
"revolt-models",
|
"revolt-models",
|
||||||
|
"revolt-permissions",
|
||||||
"revolt-quark",
|
"revolt-quark",
|
||||||
"revolt-result",
|
"revolt-result",
|
||||||
"revolt_rocket_okapi",
|
"revolt_rocket_okapi",
|
||||||
@@ -2921,8 +2931,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "revolt-models"
|
name = "revolt-models"
|
||||||
version = "0.6.0-rc.1"
|
version = "0.6.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"revolt-permissions",
|
||||||
"revolt_optional_struct",
|
"revolt_optional_struct",
|
||||||
"schemars",
|
"schemars",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -2931,11 +2942,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "revolt-permissions"
|
name = "revolt-permissions"
|
||||||
version = "0.6.0-rc.1"
|
version = "0.6.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-std",
|
"async-std",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"auto_ops",
|
"auto_ops",
|
||||||
|
"bson",
|
||||||
"num_enum 0.6.1",
|
"num_enum 0.6.1",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"schemars",
|
"schemars",
|
||||||
@@ -2944,7 +2956,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "revolt-presence"
|
name = "revolt-presence"
|
||||||
version = "0.6.0-rc.1"
|
version = "0.6.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-std",
|
"async-std",
|
||||||
"log",
|
"log",
|
||||||
@@ -2955,7 +2967,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "revolt-quark"
|
name = "revolt-quark"
|
||||||
version = "0.6.0-rc.1"
|
version = "0.6.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-lock",
|
"async-lock",
|
||||||
"async-recursion",
|
"async-recursion",
|
||||||
@@ -2968,6 +2980,7 @@ dependencies = [
|
|||||||
"bson",
|
"bson",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
"deadqueue",
|
"deadqueue",
|
||||||
|
"decancer",
|
||||||
"dotenv",
|
"dotenv",
|
||||||
"futures",
|
"futures",
|
||||||
"impl_ops",
|
"impl_ops",
|
||||||
@@ -2985,6 +2998,7 @@ dependencies = [
|
|||||||
"redis-kiss",
|
"redis-kiss",
|
||||||
"regex",
|
"regex",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
|
"revolt-database",
|
||||||
"revolt-models",
|
"revolt-models",
|
||||||
"revolt-presence",
|
"revolt-presence",
|
||||||
"revolt-result",
|
"revolt-result",
|
||||||
@@ -3006,7 +3020,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "revolt-result"
|
name = "revolt-result"
|
||||||
version = "0.6.0-rc.1"
|
version = "0.6.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"revolt_okapi",
|
"revolt_okapi",
|
||||||
"revolt_rocket_okapi",
|
"revolt_rocket_okapi",
|
||||||
@@ -4324,6 +4338,12 @@ dependencies = [
|
|||||||
"tinyvec",
|
"tinyvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-segmentation"
|
||||||
|
version = "1.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-xid"
|
name = "unicode-xid"
|
||||||
version = "0.0.4"
|
version = "0.0.4"
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
# Build Stage
|
# Build Stage
|
||||||
FROM --platform="${BUILDPLATFORM}" rust:slim
|
FROM --platform="${BUILDPLATFORM}" rust:1.70.0-slim
|
||||||
USER 0:0
|
USER 0:0
|
||||||
WORKDIR /home/rust/src
|
WORKDIR /home/rust/src
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "revolt-bonfire"
|
name = "revolt-bonfire"
|
||||||
version = "0.6.0-rc.1"
|
version = "0.6.3"
|
||||||
license = "AGPL-3.0-or-later"
|
license = "AGPL-3.0-or-later"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "revolt-database"
|
name = "revolt-database"
|
||||||
version = "0.6.0-rc.1"
|
version = "0.6.3"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "AGPL-3.0-or-later"
|
license = "AGPL-3.0-or-later"
|
||||||
authors = [ "Paul Makles <me@insrt.uk>" ]
|
authors = [ "Paul Makles <me@insrt.uk>" ]
|
||||||
@@ -22,13 +22,14 @@ default = [ "mongodb", "async-std-runtime" ]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# Core
|
# Core
|
||||||
revolt-result = { version = "0.6.0-rc.1", path = "../result" }
|
revolt-result = { version = "0.6.3", path = "../result" }
|
||||||
revolt-models = { version = "0.6.0-rc.1", path = "../models" }
|
revolt-models = { version = "0.6.3", path = "../models" }
|
||||||
revolt-presence = { version = "0.6.0-rc.1", path = "../presence" }
|
revolt-presence = { version = "0.6.3", path = "../presence" }
|
||||||
revolt-permissions = { version = "0.6.0-rc.1", path = "../permissions", features = [ "serde" ] }
|
revolt-permissions = { version = "0.6.3", path = "../permissions", features = [ "serde", "bson" ] }
|
||||||
|
|
||||||
# Utility
|
# Utility
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
rand = "0.8.5"
|
||||||
ulid = "1.0.0"
|
ulid = "1.0.0"
|
||||||
nanoid = "0.4.0"
|
nanoid = "0.4.0"
|
||||||
once_cell = "1.17"
|
once_cell = "1.17"
|
||||||
@@ -46,6 +47,10 @@ redis-kiss = { version = "0.1.4" }
|
|||||||
bson = { optional = true, version = "2.1.0" }
|
bson = { optional = true, version = "2.1.0" }
|
||||||
mongodb = { optional = true, version = "2.1.0", default-features = false }
|
mongodb = { optional = true, version = "2.1.0", default-features = false }
|
||||||
|
|
||||||
|
# Database Migration
|
||||||
|
unicode-segmentation = "1.10.1"
|
||||||
|
regex = "1"
|
||||||
|
|
||||||
# Async Language Features
|
# Async Language Features
|
||||||
futures = "0.3.19"
|
futures = "0.3.19"
|
||||||
async-trait = "0.1.51"
|
async-trait = "0.1.51"
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ use std::{collections::HashMap, sync::Arc};
|
|||||||
use futures::lock::Mutex;
|
use futures::lock::Mutex;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
AccountStrike, Bot, File, Member, MemberCompositeKey, Server, User, UserSettings, Webhook,
|
AccountStrike, Bot, Channel, File, Member, MemberCompositeKey, Server, User, UserSettings,
|
||||||
|
Webhook,
|
||||||
};
|
};
|
||||||
|
|
||||||
database_derived!(
|
database_derived!(
|
||||||
@@ -18,13 +19,12 @@ database_derived!(
|
|||||||
pub server_members: Arc<Mutex<HashMap<MemberCompositeKey, Member>>>,
|
pub server_members: Arc<Mutex<HashMap<MemberCompositeKey, Member>>>,
|
||||||
pub servers: Arc<Mutex<HashMap<String, Server>>>,
|
pub servers: Arc<Mutex<HashMap<String, Server>>>,
|
||||||
pub files: Arc<Mutex<HashMap<String, File>>>,
|
pub files: Arc<Mutex<HashMap<String, File>>>,
|
||||||
|
|
||||||
pub server_bans: Arc<Mutex<HashMap<String, ()>>>,
|
pub server_bans: Arc<Mutex<HashMap<String, ()>>>,
|
||||||
pub safety_reports: Arc<Mutex<HashMap<String, ()>>>,
|
pub safety_reports: Arc<Mutex<HashMap<String, ()>>>,
|
||||||
pub safety_snapshots: Arc<Mutex<HashMap<String, ()>>>,
|
pub safety_snapshots: Arc<Mutex<HashMap<String, ()>>>,
|
||||||
pub emoji: Arc<Mutex<HashMap<String, ()>>>,
|
pub emoji: Arc<Mutex<HashMap<String, ()>>>,
|
||||||
pub messages: Arc<Mutex<HashMap<String, ()>>>,
|
pub messages: Arc<Mutex<HashMap<String, ()>>>,
|
||||||
pub channels: Arc<Mutex<HashMap<String, ()>>>,
|
pub channels: Arc<Mutex<HashMap<String, Channel>>>,
|
||||||
pub channel_invites: Arc<Mutex<HashMap<String, ()>>>,
|
pub channel_invites: Arc<Mutex<HashMap<String, ()>>>,
|
||||||
pub channel_unreads: Arc<Mutex<HashMap<String, ()>>>,
|
pub channel_unreads: Arc<Mutex<HashMap<String, ()>>>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
use authifier::AuthifierEvent;
|
use authifier::AuthifierEvent;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use revolt_models::v0::{FieldsWebhook, PartialWebhook, Webhook};
|
use revolt_models::v0::{
|
||||||
|
Channel, FieldsChannel, FieldsWebhook, PartialChannel, PartialWebhook, Webhook,
|
||||||
|
};
|
||||||
use revolt_result::Error;
|
use revolt_result::Error;
|
||||||
|
|
||||||
use crate::Database;
|
use crate::Database;
|
||||||
@@ -101,38 +103,6 @@ pub enum EventV1 {
|
|||||||
/// Bulk delete messages
|
/// Bulk delete messages
|
||||||
BulkMessageDelete { channel: String, ids: Vec<String> },
|
BulkMessageDelete { channel: String, ids: Vec<String> },
|
||||||
|
|
||||||
/// New channel
|
|
||||||
ChannelCreate(Channel),
|
|
||||||
|
|
||||||
/// Update existing channel
|
|
||||||
ChannelUpdate {
|
|
||||||
id: String,
|
|
||||||
data: PartialChannel,
|
|
||||||
clear: Vec<FieldsChannel>,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Delete channel
|
|
||||||
ChannelDelete { id: String },
|
|
||||||
|
|
||||||
/// User joins a group
|
|
||||||
ChannelGroupJoin { id: String, user: String },
|
|
||||||
|
|
||||||
/// User leaves a group
|
|
||||||
ChannelGroupLeave { id: String, user: String },
|
|
||||||
|
|
||||||
/// User started typing in a channel
|
|
||||||
ChannelStartTyping { id: String, user: String },
|
|
||||||
|
|
||||||
/// User stopped typing in a channel
|
|
||||||
ChannelStopTyping { id: String, user: String },
|
|
||||||
|
|
||||||
/// User acknowledged message in channel
|
|
||||||
ChannelAck {
|
|
||||||
id: String,
|
|
||||||
user: String,
|
|
||||||
message_id: String,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// New server
|
/// New server
|
||||||
ServerCreate {
|
ServerCreate {
|
||||||
id: String,
|
id: String,
|
||||||
@@ -212,6 +182,38 @@ pub enum EventV1 {
|
|||||||
|
|
||||||
/// New report
|
/// New report
|
||||||
ReportCreate(Report), */
|
ReportCreate(Report), */
|
||||||
|
/// New channel
|
||||||
|
ChannelCreate(Channel),
|
||||||
|
|
||||||
|
/// Update existing channel
|
||||||
|
ChannelUpdate {
|
||||||
|
id: String,
|
||||||
|
data: PartialChannel,
|
||||||
|
clear: Vec<FieldsChannel>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Delete channel
|
||||||
|
ChannelDelete { id: String },
|
||||||
|
|
||||||
|
/// User joins a group
|
||||||
|
ChannelGroupJoin { id: String, user: String },
|
||||||
|
|
||||||
|
/// User leaves a group
|
||||||
|
ChannelGroupLeave { id: String, user: String },
|
||||||
|
|
||||||
|
/// User started typing in a channel
|
||||||
|
ChannelStartTyping { id: String, user: String },
|
||||||
|
|
||||||
|
/// User stopped typing in a channel
|
||||||
|
ChannelStopTyping { id: String, user: String },
|
||||||
|
|
||||||
|
/// User acknowledged message in channel
|
||||||
|
ChannelAck {
|
||||||
|
id: String,
|
||||||
|
user: String,
|
||||||
|
message_id: String,
|
||||||
|
},
|
||||||
|
|
||||||
/// New webhook
|
/// New webhook
|
||||||
WebhookCreate(Webhook),
|
WebhookCreate(Webhook),
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,10 @@ pub async fn create_database(db: &MongoDb) {
|
|||||||
.await
|
.await
|
||||||
.expect("Failed to create channel_unreads collection.");
|
.expect("Failed to create channel_unreads collection.");
|
||||||
|
|
||||||
|
db.create_collection("channel_webhooks", None)
|
||||||
|
.await
|
||||||
|
.expect("Failed to create channel_webhooks collection.");
|
||||||
|
|
||||||
db.create_collection("migrations", None)
|
db.create_collection("migrations", None)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to create migrations collection.");
|
.expect("Failed to create migrations collection.");
|
||||||
@@ -72,6 +76,10 @@ pub async fn create_database(db: &MongoDb) {
|
|||||||
.await
|
.await
|
||||||
.expect("Failed to create bots collection.");
|
.expect("Failed to create bots collection.");
|
||||||
|
|
||||||
|
db.create_collection("ratelimit_events", None)
|
||||||
|
.await
|
||||||
|
.expect("Failed to create ratelimit_events collection.");
|
||||||
|
|
||||||
db.create_collection(
|
db.create_collection(
|
||||||
"pubsub",
|
"pubsub",
|
||||||
CreateCollectionOptions::builder()
|
CreateCollectionOptions::builder()
|
||||||
@@ -91,6 +99,18 @@ pub async fn create_database(db: &MongoDb) {
|
|||||||
"username": 1_i32
|
"username": 1_i32
|
||||||
},
|
},
|
||||||
"name": "username",
|
"name": "username",
|
||||||
|
"unique": false,
|
||||||
|
"collation": {
|
||||||
|
"locale": "en",
|
||||||
|
"strength": 2_i32
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": {
|
||||||
|
"username": 1_i32,
|
||||||
|
"discriminator": 1_i32
|
||||||
|
},
|
||||||
|
"name": "username_discriminator",
|
||||||
"unique": true,
|
"unique": true,
|
||||||
"collation": {
|
"collation": {
|
||||||
"locale": "en",
|
"locale": "en",
|
||||||
@@ -193,5 +213,24 @@ pub async fn create_database(db: &MongoDb) {
|
|||||||
.await
|
.await
|
||||||
.expect("Failed to save migration info.");
|
.expect("Failed to save migration info.");
|
||||||
|
|
||||||
|
db.run_command(
|
||||||
|
doc! {
|
||||||
|
"createIndexes": "ratelimit_events",
|
||||||
|
"indexes": [
|
||||||
|
{
|
||||||
|
"key": {
|
||||||
|
"_id": 1_i32,
|
||||||
|
"target_id": 1_i32,
|
||||||
|
"event_type": 1_i32,
|
||||||
|
},
|
||||||
|
"name": "compound_key"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("Failed to create ratelimit_events index.");
|
||||||
|
|
||||||
info!("Created database.");
|
info!("Created database.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
use std::{ops::BitXor, time::Duration};
|
use std::{collections::HashSet, ops::BitXor, time::Duration};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
mongodb::{
|
mongodb::{
|
||||||
bson::{doc, from_bson, from_document, to_document, Bson, DateTime, Document},
|
bson::{doc, from_bson, from_document, to_document, Bson, DateTime, Document},
|
||||||
options::FindOptions,
|
options::FindOptions,
|
||||||
},
|
},
|
||||||
MongoDb,
|
MongoDb, DISCRIMINATOR_SEARCH_SPACE,
|
||||||
};
|
};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
|
use rand::seq::SliceRandom;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
struct MigrationInfo {
|
struct MigrationInfo {
|
||||||
@@ -16,7 +18,7 @@ struct MigrationInfo {
|
|||||||
revision: i32,
|
revision: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const LATEST_REVISION: i32 = 23;
|
pub const LATEST_REVISION: i32 = 26;
|
||||||
|
|
||||||
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");
|
||||||
@@ -768,8 +770,214 @@ pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 {
|
|||||||
.expect("Failed to update server members.");
|
.expect("Failed to update server members.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if revision <= 23 {
|
||||||
|
info!("Running migration [revision 23 / 10-06-2023]: Generate discriminators for users.");
|
||||||
|
|
||||||
|
db.db()
|
||||||
|
.run_command(
|
||||||
|
doc! {
|
||||||
|
"dropIndexes": "users",
|
||||||
|
"index": "username"
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("Failed to drop existing username index.");
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct UserInformation {
|
||||||
|
#[serde(rename = "_id")]
|
||||||
|
id: String,
|
||||||
|
username: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
let re_username = regex::Regex::new(r"^(\p{L}|[\d_.-])+$").unwrap();
|
||||||
|
|
||||||
|
let users: Vec<UserInformation> = db
|
||||||
|
.col::<UserInformation>("users")
|
||||||
|
.find(doc! {}, None)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.map(|doc| doc.expect("id and username"))
|
||||||
|
.collect()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let search_space: Vec<String> = DISCRIMINATOR_SEARCH_SPACE.iter().cloned().collect();
|
||||||
|
let mut claimed: HashSet<String> = HashSet::new();
|
||||||
|
|
||||||
|
for i in 0..users.len() {
|
||||||
|
let info = &users[i];
|
||||||
|
let mut discriminator = {
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
search_space.choose(&mut rng).unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
if re_username.is_match(&info.username) {
|
||||||
|
while claimed.contains(&format!("{}#{}", info.username, discriminator)) {
|
||||||
|
let new_discriminator = {
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
search_space.choose(&mut rng).unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"Re-rolled {} to {new_discriminator} from {discriminator}",
|
||||||
|
info.username
|
||||||
|
);
|
||||||
|
|
||||||
|
discriminator = new_discriminator;
|
||||||
|
}
|
||||||
|
|
||||||
|
claimed.insert(format!("{}#{}", info.username, discriminator));
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"({}/{}) Migrating user \"{}\" to #{} - compliant",
|
||||||
|
i + 1,
|
||||||
|
users.len(),
|
||||||
|
info.username,
|
||||||
|
discriminator
|
||||||
|
);
|
||||||
|
|
||||||
|
db.col::<UserInformation>("users")
|
||||||
|
.update_one(
|
||||||
|
doc! {
|
||||||
|
"_id": &info.id
|
||||||
|
},
|
||||||
|
doc! {
|
||||||
|
"$set": {
|
||||||
|
"discriminator": discriminator
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
} else {
|
||||||
|
let mut sanitised = info
|
||||||
|
.username
|
||||||
|
.graphemes(true)
|
||||||
|
.filter(|s| re_username.is_match(s))
|
||||||
|
.collect::<String>();
|
||||||
|
|
||||||
|
while sanitised.len() < 2 {
|
||||||
|
sanitised += "_";
|
||||||
|
}
|
||||||
|
|
||||||
|
while claimed.contains(&format!("{}#{}", sanitised, discriminator)) {
|
||||||
|
let new_discriminator = {
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
search_space.choose(&mut rng).unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
info!("Re-rolled {sanitised} to {new_discriminator} from {discriminator}");
|
||||||
|
discriminator = new_discriminator;
|
||||||
|
}
|
||||||
|
|
||||||
|
claimed.insert(format!("{}#{}", sanitised, discriminator));
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"({}/{}) Migrating user \"{}\" to #{} - sanitised: \"{}\"",
|
||||||
|
i + 1,
|
||||||
|
users.len(),
|
||||||
|
info.username,
|
||||||
|
discriminator,
|
||||||
|
sanitised
|
||||||
|
);
|
||||||
|
|
||||||
|
db.col::<UserInformation>("users")
|
||||||
|
.update_one(
|
||||||
|
doc! {
|
||||||
|
"_id": &info.id
|
||||||
|
},
|
||||||
|
doc! {
|
||||||
|
"$set": {
|
||||||
|
"username": sanitised,
|
||||||
|
"discriminator": discriminator,
|
||||||
|
"display_name": &info.username
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if revision <= 24 {
|
||||||
|
info!("Running migration [revision 24 / 09-06-2023]: Add collection `channel_webhooks` if not exists, update users index.");
|
||||||
|
|
||||||
|
db.db()
|
||||||
|
.create_collection("channel_webhooks", None)
|
||||||
|
.await
|
||||||
|
.ok();
|
||||||
|
|
||||||
|
db.db()
|
||||||
|
.run_command(
|
||||||
|
doc! {
|
||||||
|
"createIndexes": "users",
|
||||||
|
"indexes": [
|
||||||
|
{
|
||||||
|
"key": {
|
||||||
|
"username": 1_i32
|
||||||
|
},
|
||||||
|
"name": "username",
|
||||||
|
"unique": false,
|
||||||
|
"collation": {
|
||||||
|
"locale": "en",
|
||||||
|
"strength": 2_i32
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": {
|
||||||
|
"username": 1_i32,
|
||||||
|
"discriminator": 1_i32
|
||||||
|
},
|
||||||
|
"name": "username_discriminator",
|
||||||
|
"unique": true,
|
||||||
|
"collation": {
|
||||||
|
"locale": "en",
|
||||||
|
"strength": 2_i32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("Failed to create username index.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if revision <= 25 {
|
||||||
|
info!("Running migration [revision 25 / 15-06-2023]: Add collection `ratelimit_events` with index.");
|
||||||
|
|
||||||
|
db.db()
|
||||||
|
.create_collection("ratelimit_events", None)
|
||||||
|
.await
|
||||||
|
.ok();
|
||||||
|
|
||||||
|
db.db()
|
||||||
|
.run_command(
|
||||||
|
doc! {
|
||||||
|
"createIndexes": "ratelimit_events",
|
||||||
|
"indexes": [
|
||||||
|
{
|
||||||
|
"key": {
|
||||||
|
"_id": 1_i32,
|
||||||
|
"target_id": 1_i32,
|
||||||
|
"event_type": 1_i32,
|
||||||
|
},
|
||||||
|
"name": "compound_key"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("Failed to create ratelimit_events 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.
|
||||||
LATEST_REVISION
|
LATEST_REVISION.max(revision)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
mod model;
|
||||||
|
mod ops;
|
||||||
|
|
||||||
|
pub use model::*;
|
||||||
|
pub use ops::*;
|
||||||
@@ -0,0 +1,529 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use revolt_permissions::OverrideField;
|
||||||
|
use revolt_result::Result;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{events::client::EventV1, Database, File, IntoDocumentPath};
|
||||||
|
|
||||||
|
auto_derived!(
|
||||||
|
pub enum Channel {
|
||||||
|
/// Personal "Saved Notes" channel which allows users to save messages
|
||||||
|
SavedMessages {
|
||||||
|
/// Unique Id
|
||||||
|
#[serde(rename = "_id")]
|
||||||
|
id: String,
|
||||||
|
/// Id of the user this channel belongs to
|
||||||
|
user: String,
|
||||||
|
},
|
||||||
|
/// Direct message channel between two users
|
||||||
|
DirectMessage {
|
||||||
|
/// Unique Id
|
||||||
|
#[serde(rename = "_id")]
|
||||||
|
id: String,
|
||||||
|
|
||||||
|
/// Whether this direct message channel is currently open on both sides
|
||||||
|
active: bool,
|
||||||
|
/// 2-tuple of user ids participating in direct message
|
||||||
|
recipients: Vec<String>,
|
||||||
|
/// Id of the last message sent in this channel
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
last_message_id: Option<String>,
|
||||||
|
},
|
||||||
|
/// Group channel between 1 or more participants
|
||||||
|
Group {
|
||||||
|
/// Unique Id
|
||||||
|
#[serde(rename = "_id")]
|
||||||
|
id: String,
|
||||||
|
|
||||||
|
/// Display name of the channel
|
||||||
|
name: String,
|
||||||
|
/// User id of the owner of the group
|
||||||
|
owner: String,
|
||||||
|
/// Channel description
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
description: Option<String>,
|
||||||
|
/// Array of user ids participating in channel
|
||||||
|
recipients: Vec<String>,
|
||||||
|
|
||||||
|
/// Custom icon attachment
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
icon: Option<File>,
|
||||||
|
/// Id of the last message sent in this channel
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
last_message_id: Option<String>,
|
||||||
|
|
||||||
|
/// Permissions assigned to members of this group
|
||||||
|
/// (does not apply to the owner of the group)
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
permissions: Option<i64>,
|
||||||
|
|
||||||
|
/// Whether this group is marked as not safe for work
|
||||||
|
#[serde(skip_serializing_if = "crate::if_false", default)]
|
||||||
|
nsfw: bool,
|
||||||
|
},
|
||||||
|
/// Text channel belonging to a server
|
||||||
|
TextChannel {
|
||||||
|
/// Unique Id
|
||||||
|
#[serde(rename = "_id")]
|
||||||
|
id: String,
|
||||||
|
/// Id of the server this channel belongs to
|
||||||
|
server: String,
|
||||||
|
|
||||||
|
/// Display name of the channel
|
||||||
|
name: String,
|
||||||
|
/// Channel description
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
description: Option<String>,
|
||||||
|
|
||||||
|
/// Custom icon attachment
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
icon: Option<File>,
|
||||||
|
/// Id of the last message sent in this channel
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
last_message_id: Option<String>,
|
||||||
|
|
||||||
|
/// Default permissions assigned to users in this channel
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
default_permissions: Option<OverrideField>,
|
||||||
|
/// Permissions assigned based on role to this channel
|
||||||
|
#[serde(
|
||||||
|
default = "HashMap::<String, OverrideField>::new",
|
||||||
|
skip_serializing_if = "HashMap::<String, OverrideField>::is_empty"
|
||||||
|
)]
|
||||||
|
role_permissions: HashMap<String, OverrideField>,
|
||||||
|
|
||||||
|
/// Whether this channel is marked as not safe for work
|
||||||
|
#[serde(skip_serializing_if = "crate::if_false", default)]
|
||||||
|
nsfw: bool,
|
||||||
|
},
|
||||||
|
/// Voice channel belonging to a server
|
||||||
|
VoiceChannel {
|
||||||
|
/// Unique Id
|
||||||
|
#[serde(rename = "_id")]
|
||||||
|
id: String,
|
||||||
|
/// Id of the server this channel belongs to
|
||||||
|
server: String,
|
||||||
|
|
||||||
|
/// Display name of the channel
|
||||||
|
name: String,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
/// Channel description
|
||||||
|
description: Option<String>,
|
||||||
|
/// Custom icon attachment
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
icon: Option<File>,
|
||||||
|
|
||||||
|
/// Default permissions assigned to users in this channel
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
default_permissions: Option<OverrideField>,
|
||||||
|
/// Permissions assigned based on role to this channel
|
||||||
|
#[serde(
|
||||||
|
default = "HashMap::<String, OverrideField>::new",
|
||||||
|
skip_serializing_if = "HashMap::<String, OverrideField>::is_empty"
|
||||||
|
)]
|
||||||
|
role_permissions: HashMap<String, OverrideField>,
|
||||||
|
|
||||||
|
/// Whether this channel is marked as not safe for work
|
||||||
|
#[serde(skip_serializing_if = "crate::if_false", default)]
|
||||||
|
nsfw: bool,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
auto_derived!(
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct PartialChannel {
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub name: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub owner: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub description: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub icon: Option<File>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub nsfw: Option<bool>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub active: Option<bool>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub permissions: Option<i64>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub role_permissions: Option<HashMap<String, OverrideField>>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub default_permissions: Option<OverrideField>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub last_message_id: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Optional fields on channel object
|
||||||
|
pub enum FieldsChannel {
|
||||||
|
Description,
|
||||||
|
Icon,
|
||||||
|
DefaultPermissions,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
impl Channel {
|
||||||
|
/// Create a channel
|
||||||
|
pub async fn create(&self, db: &Database) -> Result<()> {
|
||||||
|
db.insert_channel(self).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add user to a group
|
||||||
|
pub async fn add_user_to_group(
|
||||||
|
&mut self,
|
||||||
|
db: &Database,
|
||||||
|
user_id: &str,
|
||||||
|
_by_id: &str,
|
||||||
|
) -> Result<()> {
|
||||||
|
if let Channel::Group { recipients, .. } = self {
|
||||||
|
if recipients.contains(&String::from(user_id)) {
|
||||||
|
return Err(create_error!(AlreadyInGroup));
|
||||||
|
}
|
||||||
|
|
||||||
|
recipients.push(String::from(user_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
match &self {
|
||||||
|
Channel::Group { id, .. } => {
|
||||||
|
db.add_user_to_group(id, user_id).await?;
|
||||||
|
|
||||||
|
EventV1::ChannelGroupJoin {
|
||||||
|
id: id.to_string(),
|
||||||
|
user: user_id.to_string(),
|
||||||
|
}
|
||||||
|
.p(id.to_string())
|
||||||
|
.await;
|
||||||
|
|
||||||
|
EventV1::ChannelCreate(self.clone().into())
|
||||||
|
.private(user_id.to_string())
|
||||||
|
.await;
|
||||||
|
|
||||||
|
/* TODO: SystemMessage::UserAdded {
|
||||||
|
id: user.to_string(),
|
||||||
|
by: by.to_string(),
|
||||||
|
}
|
||||||
|
.into_message(id.to_string())
|
||||||
|
.create(db, self, None)
|
||||||
|
.await
|
||||||
|
.ok(); */
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
_ => Err(create_error!(InvalidOperation)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Map out whether it is a direct DM
|
||||||
|
pub fn is_direct_dm(&self) -> bool {
|
||||||
|
matches!(self, Channel::DirectMessage { .. })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check whether has a user as a recipient
|
||||||
|
pub fn contains_user(&self, user_id: &str) -> bool {
|
||||||
|
match self {
|
||||||
|
Channel::Group { recipients, .. } => recipients.contains(&String::from(user_id)),
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get list of recipients
|
||||||
|
pub fn users(&self) -> Result<Vec<String>> {
|
||||||
|
match self {
|
||||||
|
Channel::Group { recipients, .. } => Ok(recipients.to_owned()),
|
||||||
|
_ => Err(create_error!(NotFound)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a reference to this channel's id
|
||||||
|
pub fn id(&self) -> String {
|
||||||
|
match self {
|
||||||
|
Channel::DirectMessage { id, .. }
|
||||||
|
| Channel::Group { id, .. }
|
||||||
|
| Channel::SavedMessages { id, .. }
|
||||||
|
| Channel::TextChannel { id, .. }
|
||||||
|
| Channel::VoiceChannel { id, .. } => id.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set role permission on a channel
|
||||||
|
pub async fn set_role_permission(
|
||||||
|
&mut self,
|
||||||
|
db: &Database,
|
||||||
|
role_id: &str,
|
||||||
|
permissions: OverrideField,
|
||||||
|
) -> Result<()> {
|
||||||
|
match self {
|
||||||
|
Channel::TextChannel {
|
||||||
|
id,
|
||||||
|
server,
|
||||||
|
role_permissions,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
| Channel::VoiceChannel {
|
||||||
|
id,
|
||||||
|
server,
|
||||||
|
role_permissions,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
db.set_channel_role_permission(id, role_id, permissions)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
role_permissions.insert(role_id.to_string(), permissions);
|
||||||
|
|
||||||
|
EventV1::ChannelUpdate {
|
||||||
|
id: id.clone(),
|
||||||
|
data: PartialChannel {
|
||||||
|
role_permissions: Some(role_permissions.clone()),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
clear: vec![],
|
||||||
|
}
|
||||||
|
.p(server.clone())
|
||||||
|
.await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
_ => Err(create_error!(InvalidOperation)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update channel data
|
||||||
|
pub async fn update<'a>(
|
||||||
|
&mut self,
|
||||||
|
db: &Database,
|
||||||
|
partial: PartialChannel,
|
||||||
|
remove: Vec<FieldsChannel>,
|
||||||
|
) -> Result<()> {
|
||||||
|
for field in &remove {
|
||||||
|
self.remove_field(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.apply_options(partial.clone());
|
||||||
|
|
||||||
|
db.update_channel(&self.id(), &partial, remove.clone())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove a field from Channel object
|
||||||
|
pub fn remove_field(&mut self, field: &FieldsChannel) {
|
||||||
|
match field {
|
||||||
|
FieldsChannel::Description => match self {
|
||||||
|
Self::Group { description, .. }
|
||||||
|
| Self::TextChannel { description, .. }
|
||||||
|
| Self::VoiceChannel { description, .. } => {
|
||||||
|
description.take();
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
FieldsChannel::Icon => match self {
|
||||||
|
Self::Group { icon, .. }
|
||||||
|
| Self::TextChannel { icon, .. }
|
||||||
|
| Self::VoiceChannel { icon, .. } => {
|
||||||
|
icon.take();
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
FieldsChannel::DefaultPermissions => match self {
|
||||||
|
Self::TextChannel {
|
||||||
|
default_permissions,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
| Self::VoiceChannel {
|
||||||
|
default_permissions,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
default_permissions.take();
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove multiple fields from Channel object
|
||||||
|
pub fn remove_fields(&mut self, partial: Vec<FieldsChannel>) {
|
||||||
|
for field in partial {
|
||||||
|
self.remove_field(&field)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply partial channel to channel
|
||||||
|
pub fn apply_options(&mut self, partial: PartialChannel) {
|
||||||
|
match self {
|
||||||
|
Self::SavedMessages { .. } => {}
|
||||||
|
Self::DirectMessage { active, .. } => {
|
||||||
|
if let Some(v) = partial.active {
|
||||||
|
*active = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::Group {
|
||||||
|
name,
|
||||||
|
owner,
|
||||||
|
description,
|
||||||
|
icon,
|
||||||
|
nsfw,
|
||||||
|
permissions,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
if let Some(v) = partial.name {
|
||||||
|
*name = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(v) = partial.owner {
|
||||||
|
*owner = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(v) = partial.description {
|
||||||
|
description.replace(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(v) = partial.icon {
|
||||||
|
icon.replace(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(v) = partial.nsfw {
|
||||||
|
*nsfw = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(v) = partial.permissions {
|
||||||
|
permissions.replace(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::TextChannel {
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
icon,
|
||||||
|
nsfw,
|
||||||
|
default_permissions,
|
||||||
|
role_permissions,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
| Self::VoiceChannel {
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
icon,
|
||||||
|
nsfw,
|
||||||
|
default_permissions,
|
||||||
|
role_permissions,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
if let Some(v) = partial.name {
|
||||||
|
*name = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(v) = partial.description {
|
||||||
|
description.replace(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(v) = partial.icon {
|
||||||
|
icon.replace(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(v) = partial.nsfw {
|
||||||
|
*nsfw = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(v) = partial.role_permissions {
|
||||||
|
*role_permissions = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(v) = partial.default_permissions {
|
||||||
|
default_permissions.replace(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove user from a group
|
||||||
|
pub async fn remove_user_from_group(
|
||||||
|
&self,
|
||||||
|
db: &Database,
|
||||||
|
user_id: &str,
|
||||||
|
_by_id: Option<&str>,
|
||||||
|
silent: bool,
|
||||||
|
) -> Result<()> {
|
||||||
|
match &self {
|
||||||
|
Channel::Group {
|
||||||
|
id,
|
||||||
|
owner,
|
||||||
|
recipients,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
if user_id == owner {
|
||||||
|
if let Some(new_owner) = recipients.iter().find(|x| *x != user_id) {
|
||||||
|
db.update_channel(
|
||||||
|
id,
|
||||||
|
&PartialChannel {
|
||||||
|
owner: Some(new_owner.into()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
vec![],
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
/* TODO: SystemMessage::ChannelOwnershipChanged {
|
||||||
|
from: owner.to_string(),
|
||||||
|
to: new_owner.into(),
|
||||||
|
}
|
||||||
|
.into_message(id.to_string())
|
||||||
|
.create(db, self, None)
|
||||||
|
.await
|
||||||
|
.ok(); */
|
||||||
|
} else {
|
||||||
|
db.delete_channel(self).await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EventV1::ChannelGroupLeave {
|
||||||
|
id: id.to_string(),
|
||||||
|
user: user_id.to_string(),
|
||||||
|
}
|
||||||
|
.p(id.to_string())
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if !silent {
|
||||||
|
/* TODO: if let Some(_by) = by_id {
|
||||||
|
SystemMessage::UserRemove {
|
||||||
|
id: user_id.to_string(),
|
||||||
|
by: by.to_string(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
SystemMessage::UserLeft {
|
||||||
|
id: user_id.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.into_message(id.to_string())
|
||||||
|
.create(db, self, None)
|
||||||
|
.await
|
||||||
|
.ok(); */
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => Err(create_error!(InvalidOperation)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete a channel
|
||||||
|
pub async fn delete(&self, db: &Database) -> Result<()> {
|
||||||
|
db.delete_channel(self).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoDocumentPath for FieldsChannel {
|
||||||
|
fn as_path(&self) -> Option<&'static str> {
|
||||||
|
Some(match self {
|
||||||
|
FieldsChannel::Description => "description",
|
||||||
|
FieldsChannel::Icon => "icon",
|
||||||
|
FieldsChannel::DefaultPermissions => "default_permissions",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
use crate::{revolt_result::Result, Channel, FieldsChannel, PartialChannel};
|
||||||
|
use revolt_permissions::OverrideField;
|
||||||
|
mod mongodb;
|
||||||
|
mod reference;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait AbstractChannels: Sync + Send {
|
||||||
|
/// Insert a new channel in the database
|
||||||
|
async fn insert_channel(&self, channel: &Channel) -> Result<()>;
|
||||||
|
|
||||||
|
/// Fetch a channel from the database
|
||||||
|
async fn fetch_channel(&self, channel_id: &str) -> Result<Channel>;
|
||||||
|
|
||||||
|
/// Fetch all channels from the database
|
||||||
|
async fn fetch_channels<'a>(&self, ids: &'a [String]) -> Result<Vec<Channel>>;
|
||||||
|
|
||||||
|
/// Fetch all direct messages for a user
|
||||||
|
async fn find_direct_messages(&self, user_id: &str) -> Result<Vec<Channel>>;
|
||||||
|
|
||||||
|
// Fetch saved messages channel
|
||||||
|
async fn find_saved_messages_channel(&self, user_id: &str) -> Result<Channel>;
|
||||||
|
|
||||||
|
// Fetch direct message channel (DM or Saved Messages)
|
||||||
|
async fn find_direct_message_channel(&self, user_a: &str, user_b: &str) -> Result<Channel>;
|
||||||
|
|
||||||
|
/// Insert a user to a group
|
||||||
|
async fn add_user_to_group(&self, channel_id: &str, user_id: &str) -> Result<()>;
|
||||||
|
|
||||||
|
/// Insert channel role permissions
|
||||||
|
async fn set_channel_role_permission(
|
||||||
|
&self,
|
||||||
|
channel_id: &str,
|
||||||
|
role_id: &str,
|
||||||
|
permissions: OverrideField,
|
||||||
|
) -> Result<()>;
|
||||||
|
|
||||||
|
// Update channel
|
||||||
|
async fn update_channel(
|
||||||
|
&self,
|
||||||
|
id: &str,
|
||||||
|
channel_id: &PartialChannel,
|
||||||
|
remove: Vec<FieldsChannel>,
|
||||||
|
) -> Result<()>;
|
||||||
|
|
||||||
|
// Remove a user from a group
|
||||||
|
async fn remove_user_from_group(&self, channel_id: &str, user_id: &str) -> Result<()>;
|
||||||
|
|
||||||
|
// Delete a channel
|
||||||
|
async fn delete_channel(&self, channel_id: &Channel) -> Result<()>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,193 @@
|
|||||||
|
use super::AbstractChannels;
|
||||||
|
use crate::{Channel, FieldsChannel, IntoDocumentPath, MongoDb, PartialChannel};
|
||||||
|
use bson::Document;
|
||||||
|
use futures::StreamExt;
|
||||||
|
use revolt_permissions::OverrideField;
|
||||||
|
use revolt_result::Result;
|
||||||
|
|
||||||
|
static COL: &str = "channels";
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl AbstractChannels for MongoDb {
|
||||||
|
/// Insert a new channel in the database
|
||||||
|
async fn insert_channel(&self, channel: &Channel) -> Result<()> {
|
||||||
|
query!(self, insert_one, COL, &channel).map(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch a channel from the database
|
||||||
|
async fn fetch_channel(&self, channel_id: &str) -> Result<Channel> {
|
||||||
|
query!(self, find_one_by_id, COL, channel_id)?.ok_or_else(|| create_error!(NotFound))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch all channels from the database
|
||||||
|
async fn fetch_channels<'a>(&self, ids: &'a [String]) -> Result<Vec<Channel>> {
|
||||||
|
Ok(self
|
||||||
|
.col::<Channel>(COL)
|
||||||
|
.find(
|
||||||
|
doc! {
|
||||||
|
"_id": {
|
||||||
|
"$in": ids
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|_| create_database_error!("fetch", "channels"))?
|
||||||
|
.filter_map(|s| async {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
Some(s.unwrap())
|
||||||
|
} else {
|
||||||
|
s.ok()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
.await)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch all direct messages for a user
|
||||||
|
async fn find_direct_messages(&self, user_id: &str) -> Result<Vec<Channel>> {
|
||||||
|
query!(
|
||||||
|
self,
|
||||||
|
find,
|
||||||
|
COL,
|
||||||
|
doc! {
|
||||||
|
"$or": [
|
||||||
|
{
|
||||||
|
"$or": [
|
||||||
|
{
|
||||||
|
"channel_type": "DirectMessage"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"channel_type": "Group"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"recipients": user_id
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"channel_type": "SavedMessages",
|
||||||
|
"user": user_id
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch saved messages channel
|
||||||
|
async fn find_saved_messages_channel(&self, user_id: &str) -> Result<Channel> {
|
||||||
|
query!(
|
||||||
|
self,
|
||||||
|
find_one,
|
||||||
|
COL,
|
||||||
|
doc! {
|
||||||
|
"channel_type": "SavedMessages",
|
||||||
|
"user": user_id
|
||||||
|
}
|
||||||
|
)?
|
||||||
|
.ok_or_else(|| create_error!(InternalError))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch direct message channel (DM or Saved Messages)
|
||||||
|
async fn find_direct_message_channel(&self, user_a: &str, user_b: &str) -> Result<Channel> {
|
||||||
|
let doc = match (user_a, user_b) {
|
||||||
|
self_user if self_user.0 == self_user.1 => {
|
||||||
|
doc! {
|
||||||
|
"channel_type": "SavedMessages",
|
||||||
|
"user": self_user.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
users => {
|
||||||
|
doc! {
|
||||||
|
"channel_type": "DirectMessage",
|
||||||
|
"recipients": {
|
||||||
|
"$all": [ users.0, users.1 ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
query!(self, find_one, COL, doc)?.ok_or_else(|| create_error!(NotFound))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert a user to a group
|
||||||
|
async fn add_user_to_group(&self, channel: &str, user: &str) -> Result<()> {
|
||||||
|
self.col::<Document>(COL)
|
||||||
|
.update_one(
|
||||||
|
doc! {
|
||||||
|
"_id": channel
|
||||||
|
},
|
||||||
|
doc! {
|
||||||
|
"$push": {
|
||||||
|
"recipients": user
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(|_| ())
|
||||||
|
.map_err(|_| create_database_error!("update_one", "channel"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert channel role permissions
|
||||||
|
async fn set_channel_role_permission(
|
||||||
|
&self,
|
||||||
|
channel: &str,
|
||||||
|
role: &str,
|
||||||
|
permissions: OverrideField,
|
||||||
|
) -> Result<()> {
|
||||||
|
self.col::<Document>(COL)
|
||||||
|
.update_one(
|
||||||
|
doc! { "_id": channel },
|
||||||
|
doc! {
|
||||||
|
"$set": {
|
||||||
|
"role_permissions.".to_owned() + role: permissions
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(|_| ())
|
||||||
|
.map_err(|_| create_database_error!("update_one", "channel"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update channel
|
||||||
|
async fn update_channel(
|
||||||
|
&self,
|
||||||
|
id: &str,
|
||||||
|
channel: &PartialChannel,
|
||||||
|
remove: Vec<FieldsChannel>,
|
||||||
|
) -> Result<()> {
|
||||||
|
query!(
|
||||||
|
self,
|
||||||
|
update_one_by_id,
|
||||||
|
COL,
|
||||||
|
id,
|
||||||
|
channel,
|
||||||
|
remove.iter().map(|x| x as &dyn IntoDocumentPath).collect(),
|
||||||
|
None
|
||||||
|
)
|
||||||
|
.map(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove a user from a group
|
||||||
|
async fn remove_user_from_group(&self, channel: &str, user: &str) -> Result<()> {
|
||||||
|
self.col::<Document>(COL)
|
||||||
|
.update_one(
|
||||||
|
doc! {
|
||||||
|
"_id": channel
|
||||||
|
},
|
||||||
|
doc! {
|
||||||
|
"$pull": {
|
||||||
|
"recipients": user
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(|_| ())
|
||||||
|
.map_err(|_| create_database_error!("update_one", "channels"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete a channel
|
||||||
|
async fn delete_channel(&self, channel: &Channel) -> Result<()> {
|
||||||
|
query!(self, delete_one_by_id, COL, &channel.id()).map(|_| ())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
use std::collections::hash_map::Entry;
|
||||||
|
|
||||||
|
use super::AbstractChannels;
|
||||||
|
use crate::ReferenceDb;
|
||||||
|
use crate::{Channel, FieldsChannel, PartialChannel};
|
||||||
|
use revolt_permissions::OverrideField;
|
||||||
|
use revolt_result::Result;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl AbstractChannels for ReferenceDb {
|
||||||
|
/// Insert a new channel in the database
|
||||||
|
async fn insert_channel(&self, channel: &Channel) -> Result<()> {
|
||||||
|
let mut channels = self.channels.lock().await;
|
||||||
|
if let Entry::Vacant(entry) = channels.entry(channel.id()) {
|
||||||
|
entry.insert(channel.clone());
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(create_database_error!("insert", "channel"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch a channel from the database
|
||||||
|
async fn fetch_channel(&self, channel_id: &str) -> Result<Channel> {
|
||||||
|
let channels = self.channels.lock().await;
|
||||||
|
channels
|
||||||
|
.get(channel_id)
|
||||||
|
.cloned()
|
||||||
|
.ok_or_else(|| create_error!(NotFound))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch all channels from the database
|
||||||
|
async fn fetch_channels<'a>(&self, ids: &'a [String]) -> Result<Vec<Channel>> {
|
||||||
|
let channels = self.channels.lock().await;
|
||||||
|
ids.iter()
|
||||||
|
.map(|id| {
|
||||||
|
channels
|
||||||
|
.get(id)
|
||||||
|
.cloned()
|
||||||
|
.ok_or_else(|| create_error!(NotFound))
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch all direct messages for a user
|
||||||
|
async fn find_direct_messages(&self, user_id: &str) -> Result<Vec<Channel>> {
|
||||||
|
let channels = self.channels.lock().await;
|
||||||
|
Ok(channels
|
||||||
|
.values()
|
||||||
|
.filter(|channel| channel.contains_user(user_id))
|
||||||
|
.cloned()
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch saved messages channel
|
||||||
|
async fn find_saved_messages_channel(&self, user_id: &str) -> Result<Channel> {
|
||||||
|
let channels = self.channels.lock().await;
|
||||||
|
channels
|
||||||
|
.get(user_id)
|
||||||
|
.cloned()
|
||||||
|
.ok_or_else(|| create_database_error!("fetch", "channel"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch direct message channel (DM or Saved Messages)
|
||||||
|
async fn find_direct_message_channel(&self, user_a: &str, user_b: &str) -> Result<Channel> {
|
||||||
|
let channels = self.channels.lock().await;
|
||||||
|
for (_, data) in channels.iter() {
|
||||||
|
if data.contains_user(user_a) && data.contains_user(user_b) {
|
||||||
|
return Ok(data.to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(create_error!(NotFound))
|
||||||
|
}
|
||||||
|
/// Insert a user to a group
|
||||||
|
async fn add_user_to_group(&self, channel_id: &str, user_id: &str) -> Result<()> {
|
||||||
|
let mut channels = self.channels.lock().await;
|
||||||
|
|
||||||
|
if let Some(Channel::Group { recipients, .. }) = channels.get_mut(channel_id) {
|
||||||
|
recipients.push(String::from(user_id));
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(create_error!(InvalidOperation))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Insert channel role permissions
|
||||||
|
async fn set_channel_role_permission(
|
||||||
|
&self,
|
||||||
|
channel_id: &str,
|
||||||
|
role_id: &str,
|
||||||
|
permissions: OverrideField,
|
||||||
|
) -> Result<()> {
|
||||||
|
let mut channels = self.channels.lock().await;
|
||||||
|
|
||||||
|
if let Some(mut channel) = channels.get_mut(channel_id) {
|
||||||
|
match &mut channel {
|
||||||
|
Channel::TextChannel {
|
||||||
|
role_permissions, ..
|
||||||
|
}
|
||||||
|
| Channel::VoiceChannel {
|
||||||
|
role_permissions, ..
|
||||||
|
} => {
|
||||||
|
if role_permissions.get(role_id).is_some() {
|
||||||
|
role_permissions.remove(role_id);
|
||||||
|
role_permissions.insert(String::from(role_id), permissions);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(create_error!(NotFound))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Err(create_error!(NotFound)),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(create_error!(NotFound))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update channel
|
||||||
|
async fn update_channel(
|
||||||
|
&self,
|
||||||
|
id: &str,
|
||||||
|
channel: &PartialChannel,
|
||||||
|
remove: Vec<FieldsChannel>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let mut channels = self.channels.lock().await;
|
||||||
|
if let Some(channel_data) = channels.get_mut(id) {
|
||||||
|
channel_data.apply_options(channel.to_owned());
|
||||||
|
channel_data.remove_fields(remove);
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(create_error!(NotFound))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove a user from a group
|
||||||
|
async fn remove_user_from_group(&self, channel: &str, user: &str) -> Result<()> {
|
||||||
|
let mut channels = self.channels.lock().await;
|
||||||
|
if let Some(channel_data) = channels.get_mut(channel) {
|
||||||
|
if channel_data.users()?.contains(&String::from(user)) {
|
||||||
|
channel_data.users()?.retain(|x| x != user);
|
||||||
|
return Ok(());
|
||||||
|
} else {
|
||||||
|
return Err(create_error!(NotFound));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(create_error!(NotFound))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete a channel
|
||||||
|
async fn delete_channel(&self, channel: &Channel) -> Result<()> {
|
||||||
|
let mut channels = self.channels.lock().await;
|
||||||
|
if channels.remove(&channel.id()).is_some() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(create_error!(NotFound))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
mod admin_migrations;
|
mod admin_migrations;
|
||||||
mod bots;
|
mod bots;
|
||||||
mod channel_webhooks;
|
mod channel_webhooks;
|
||||||
|
mod channels;
|
||||||
mod files;
|
mod files;
|
||||||
|
mod ratelimit_events;
|
||||||
mod safety_strikes;
|
mod safety_strikes;
|
||||||
mod server_members;
|
mod server_members;
|
||||||
mod servers;
|
mod servers;
|
||||||
@@ -11,7 +13,9 @@ mod users;
|
|||||||
pub use admin_migrations::*;
|
pub use admin_migrations::*;
|
||||||
pub use bots::*;
|
pub use bots::*;
|
||||||
pub use channel_webhooks::*;
|
pub use channel_webhooks::*;
|
||||||
|
pub use channels::*;
|
||||||
pub use files::*;
|
pub use files::*;
|
||||||
|
pub use ratelimit_events::*;
|
||||||
pub use safety_strikes::*;
|
pub use safety_strikes::*;
|
||||||
pub use server_members::*;
|
pub use server_members::*;
|
||||||
pub use servers::*;
|
pub use servers::*;
|
||||||
@@ -25,13 +29,15 @@ pub trait AbstractDatabase:
|
|||||||
+ Send
|
+ Send
|
||||||
+ admin_migrations::AbstractMigrations
|
+ admin_migrations::AbstractMigrations
|
||||||
+ bots::AbstractBots
|
+ bots::AbstractBots
|
||||||
|
+ channels::AbstractChannels
|
||||||
|
+ channel_webhooks::AbstractWebhooks
|
||||||
+ files::AbstractAttachments
|
+ files::AbstractAttachments
|
||||||
|
+ ratelimit_events::AbstractRatelimitEvents
|
||||||
+ safety_strikes::AbstractAccountStrikes
|
+ safety_strikes::AbstractAccountStrikes
|
||||||
+ server_members::AbstractServerMembers
|
+ server_members::AbstractServerMembers
|
||||||
+ servers::AbstractServers
|
+ servers::AbstractServers
|
||||||
+ user_settings::AbstractUserSettings
|
+ user_settings::AbstractUserSettings
|
||||||
+ users::AbstractUsers
|
+ users::AbstractUsers
|
||||||
+ channel_webhooks::AbstractWebhooks
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
mod model;
|
||||||
|
mod ops;
|
||||||
|
|
||||||
|
pub use model::*;
|
||||||
|
pub use ops::*;
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
auto_derived!(
|
||||||
|
/// Ratelimit Event
|
||||||
|
pub struct RatelimitEvent {
|
||||||
|
/// Id
|
||||||
|
#[serde(rename = "_id")]
|
||||||
|
pub id: String,
|
||||||
|
/// Relevant Object Id
|
||||||
|
pub target_id: String,
|
||||||
|
/// Type of event
|
||||||
|
pub event_type: RatelimitEventType,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Event type
|
||||||
|
pub enum RatelimitEventType {
|
||||||
|
DiscriminatorChange,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
impl fmt::Display for RatelimitEventType {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
fmt::Debug::fmt(self, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use crate::{revolt_result::Result, RatelimitEvent, RatelimitEventType};
|
||||||
|
mod mongodb;
|
||||||
|
mod reference;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait AbstractRatelimitEvents: Sync + Send {
|
||||||
|
/// Insert a new ratelimit event
|
||||||
|
async fn insert_ratelimit_event(&self, event: &RatelimitEvent) -> Result<()>;
|
||||||
|
|
||||||
|
/// Count number of events in given duration and check if we've hit the limit
|
||||||
|
async fn has_ratelimited(
|
||||||
|
&self,
|
||||||
|
target_id: &str,
|
||||||
|
event_type: RatelimitEventType,
|
||||||
|
period: Duration,
|
||||||
|
count: usize,
|
||||||
|
) -> Result<bool>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
use std::time::{Duration, SystemTime};
|
||||||
|
|
||||||
|
use super::AbstractRatelimitEvents;
|
||||||
|
use crate::{MongoDb, RatelimitEvent, RatelimitEventType};
|
||||||
|
use revolt_result::Result;
|
||||||
|
use ulid::Ulid;
|
||||||
|
|
||||||
|
static COL: &str = "ratelimit_events";
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl AbstractRatelimitEvents for MongoDb {
|
||||||
|
/// Insert a new ratelimit event
|
||||||
|
async fn insert_ratelimit_event(&self, event: &RatelimitEvent) -> Result<()> {
|
||||||
|
query!(self, insert_one, COL, &event).map(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Count number of events in given duration and check if we've hit the limit
|
||||||
|
async fn has_ratelimited(
|
||||||
|
&self,
|
||||||
|
target_id: &str,
|
||||||
|
event_type: RatelimitEventType,
|
||||||
|
period: Duration,
|
||||||
|
count: usize,
|
||||||
|
) -> Result<bool> {
|
||||||
|
self.col::<RatelimitEvent>(COL)
|
||||||
|
.count_documents(
|
||||||
|
doc! {
|
||||||
|
"_id": {
|
||||||
|
"$gte": Ulid::from_datetime(SystemTime::now() - period).to_string()
|
||||||
|
},
|
||||||
|
"target_id": target_id,
|
||||||
|
"event_type": event_type.to_string()
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(|c| c as usize >= count)
|
||||||
|
.map_err(|_| create_database_error!("count_documents", COL))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use super::AbstractRatelimitEvents;
|
||||||
|
use crate::RatelimitEvent;
|
||||||
|
use crate::RatelimitEventType;
|
||||||
|
use crate::ReferenceDb;
|
||||||
|
use revolt_result::Result;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl AbstractRatelimitEvents for ReferenceDb {
|
||||||
|
/// Insert a new ratelimit event
|
||||||
|
async fn insert_ratelimit_event(&self, _event: &RatelimitEvent) -> Result<()> {
|
||||||
|
// TODO: implement
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Count number of events in given duration and check if we've hit the limit
|
||||||
|
async fn has_ratelimited(
|
||||||
|
&self,
|
||||||
|
_target_id: &str,
|
||||||
|
_event_type: RatelimitEventType,
|
||||||
|
_period: Duration,
|
||||||
|
_count: usize,
|
||||||
|
) -> Result<bool> {
|
||||||
|
// TODO: implement
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use crate::{Database, File};
|
use crate::{Database, File};
|
||||||
|
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
use revolt_result::{Error, ErrorType, Result};
|
use revolt_result::{Error, ErrorType, Result};
|
||||||
|
|
||||||
auto_derived_partial!(
|
auto_derived_partial!(
|
||||||
@@ -10,6 +13,11 @@ auto_derived_partial!(
|
|||||||
pub id: String,
|
pub id: String,
|
||||||
/// Username
|
/// Username
|
||||||
pub username: String,
|
pub username: String,
|
||||||
|
/// Discriminator
|
||||||
|
pub discriminator: String,
|
||||||
|
/// Display name
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub display_name: Option<String>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
/// Avatar attachment
|
/// Avatar attachment
|
||||||
pub avatar: Option<File>,
|
pub avatar: Option<File>,
|
||||||
@@ -195,3 +203,17 @@ impl User {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub static DISCRIMINATOR_SEARCH_SPACE: Lazy<HashSet<String>> = Lazy::new(|| {
|
||||||
|
let mut set = (2..9999)
|
||||||
|
.map(|v| format!("{:0>4}", v))
|
||||||
|
.collect::<HashSet<String>>();
|
||||||
|
|
||||||
|
for discrim in [
|
||||||
|
123, 1234, 1111, 2222, 3333, 4444, 5555, 6666, 7777, 8888, 9999,
|
||||||
|
] {
|
||||||
|
set.remove(&format!("{:0>4}", discrim));
|
||||||
|
}
|
||||||
|
|
||||||
|
set.into_iter().collect()
|
||||||
|
});
|
||||||
|
|||||||
@@ -80,6 +80,123 @@ impl From<crate::FieldsWebhook> for FieldsWebhook {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<crate::Channel> for Channel {
|
||||||
|
fn from(value: crate::Channel) -> Self {
|
||||||
|
match value {
|
||||||
|
crate::Channel::SavedMessages { id, user } => Channel::SavedMessages { id, user },
|
||||||
|
crate::Channel::DirectMessage {
|
||||||
|
id,
|
||||||
|
active,
|
||||||
|
recipients,
|
||||||
|
last_message_id,
|
||||||
|
} => Channel::DirectMessage {
|
||||||
|
id,
|
||||||
|
active,
|
||||||
|
recipients,
|
||||||
|
last_message_id,
|
||||||
|
},
|
||||||
|
crate::Channel::Group {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
owner,
|
||||||
|
description,
|
||||||
|
recipients,
|
||||||
|
icon,
|
||||||
|
last_message_id,
|
||||||
|
permissions,
|
||||||
|
nsfw,
|
||||||
|
} => Channel::Group {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
owner,
|
||||||
|
description,
|
||||||
|
recipients,
|
||||||
|
icon: icon.map(|file| file.into()),
|
||||||
|
last_message_id,
|
||||||
|
permissions,
|
||||||
|
nsfw,
|
||||||
|
},
|
||||||
|
crate::Channel::TextChannel {
|
||||||
|
id,
|
||||||
|
server,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
icon,
|
||||||
|
last_message_id,
|
||||||
|
default_permissions,
|
||||||
|
role_permissions,
|
||||||
|
nsfw,
|
||||||
|
} => Channel::TextChannel {
|
||||||
|
id,
|
||||||
|
server,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
icon: icon.map(|file| file.into()),
|
||||||
|
last_message_id,
|
||||||
|
default_permissions,
|
||||||
|
role_permissions,
|
||||||
|
nsfw,
|
||||||
|
},
|
||||||
|
crate::Channel::VoiceChannel {
|
||||||
|
id,
|
||||||
|
server,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
icon,
|
||||||
|
default_permissions,
|
||||||
|
role_permissions,
|
||||||
|
nsfw,
|
||||||
|
} => Channel::VoiceChannel {
|
||||||
|
id,
|
||||||
|
server,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
icon: icon.map(|file| file.into()),
|
||||||
|
default_permissions,
|
||||||
|
role_permissions,
|
||||||
|
nsfw,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::PartialChannel> for PartialChannel {
|
||||||
|
fn from(value: crate::PartialChannel) -> Self {
|
||||||
|
PartialChannel {
|
||||||
|
name: value.name,
|
||||||
|
owner: value.owner,
|
||||||
|
description: value.description,
|
||||||
|
icon: value.icon.map(|file| file.into()),
|
||||||
|
nsfw: value.nsfw,
|
||||||
|
active: value.active,
|
||||||
|
permissions: value.permissions,
|
||||||
|
role_permissions: value.role_permissions,
|
||||||
|
default_permissions: value.default_permissions,
|
||||||
|
last_message_id: value.last_message_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<FieldsChannel> for crate::FieldsChannel {
|
||||||
|
fn from(value: FieldsChannel) -> Self {
|
||||||
|
match value {
|
||||||
|
FieldsChannel::Description => crate::FieldsChannel::Description,
|
||||||
|
FieldsChannel::Icon => crate::FieldsChannel::Icon,
|
||||||
|
FieldsChannel::DefaultPermissions => crate::FieldsChannel::DefaultPermissions,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::FieldsChannel> for FieldsChannel {
|
||||||
|
fn from(value: crate::FieldsChannel) -> Self {
|
||||||
|
match value {
|
||||||
|
crate::FieldsChannel::Description => FieldsChannel::Description,
|
||||||
|
crate::FieldsChannel::Icon => FieldsChannel::Icon,
|
||||||
|
crate::FieldsChannel::DefaultPermissions => FieldsChannel::DefaultPermissions,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<crate::File> for File {
|
impl From<crate::File> for File {
|
||||||
fn from(value: crate::File) -> Self {
|
fn from(value: crate::File) -> Self {
|
||||||
File {
|
File {
|
||||||
@@ -140,6 +257,8 @@ impl crate::User {
|
|||||||
|
|
||||||
User {
|
User {
|
||||||
username: self.username,
|
username: self.username,
|
||||||
|
discriminator: self.discriminator,
|
||||||
|
display_name: self.display_name,
|
||||||
avatar: self.avatar.map(|file| file.into()),
|
avatar: self.avatar.map(|file| file.into()),
|
||||||
relations: vec![],
|
relations: vec![],
|
||||||
badges: self.badges.unwrap_or_default() as u32,
|
badges: self.badges.unwrap_or_default() as u32,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use schemars::{
|
|||||||
JsonSchema,
|
JsonSchema,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{Bot, Database};
|
use crate::{Bot, Database, Webhook};
|
||||||
|
|
||||||
/// Reference to some object in the database
|
/// Reference to some object in the database
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
@@ -26,6 +26,11 @@ impl Reference {
|
|||||||
pub async fn as_bot(&self, db: &Database) -> Result<Bot> {
|
pub async fn as_bot(&self, db: &Database) -> Result<Bot> {
|
||||||
db.fetch_bot(&self.id).await
|
db.fetch_bot(&self.id).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fetch webhook from Ref
|
||||||
|
pub async fn as_webhook(&self, db: &Database) -> Result<Webhook> {
|
||||||
|
db.fetch_webhook(&self.id).await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "rocket-impl")]
|
#[cfg(feature = "rocket-impl")]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "revolt-models"
|
name = "revolt-models"
|
||||||
version = "0.6.0-rc.1"
|
version = "0.6.3"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "AGPL-3.0-or-later"
|
license = "AGPL-3.0-or-later"
|
||||||
authors = [ "Paul Makles <me@insrt.uk>" ]
|
authors = [ "Paul Makles <me@insrt.uk>" ]
|
||||||
@@ -9,14 +9,17 @@ description = "Revolt Backend: API Models"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
serde = [ "dep:serde" ]
|
serde = [ "dep:serde", "revolt-permissions/serde" ]
|
||||||
schemas = [ "dep:schemars" ]
|
schemas = [ "dep:schemars", "revolt-permissions/schemas" ]
|
||||||
validator = [ "dep:validator" ]
|
validator = [ "dep:validator" ]
|
||||||
partials = [ "dep:revolt_optional_struct", "serde", "schemas" ]
|
partials = [ "dep:revolt_optional_struct", "serde", "schemas" ]
|
||||||
|
|
||||||
default = [ "serde", "partials" ]
|
default = [ "serde", "partials" ]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
# Core
|
||||||
|
revolt-permissions = { version = "0.6.3", path = "../permissions" }
|
||||||
|
|
||||||
# Serialisation
|
# Serialisation
|
||||||
revolt_optional_struct = { version = "0.2.0", optional = true }
|
revolt_optional_struct = { version = "0.2.0", optional = true }
|
||||||
serde = { version = "1", features = ["derive"], optional = true }
|
serde = { version = "1", features = ["derive"], optional = true }
|
||||||
|
|||||||
@@ -0,0 +1,208 @@
|
|||||||
|
use super::File;
|
||||||
|
|
||||||
|
use revolt_permissions::OverrideField;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
auto_derived!(
|
||||||
|
/// Channel
|
||||||
|
pub enum Channel {
|
||||||
|
/// Personal "Saved Notes" channel which allows users to save messages
|
||||||
|
SavedMessages {
|
||||||
|
/// Unique Id
|
||||||
|
#[cfg_attr(feature = "serde", serde(rename = "_id"))]
|
||||||
|
id: String,
|
||||||
|
/// Id of the user this channel belongs to
|
||||||
|
user: String,
|
||||||
|
},
|
||||||
|
/// Direct message channel between two users
|
||||||
|
DirectMessage {
|
||||||
|
/// Unique Id
|
||||||
|
#[cfg_attr(feature = "serde", serde(rename = "_id"))]
|
||||||
|
id: String,
|
||||||
|
|
||||||
|
/// Whether this direct message channel is currently open on both sides
|
||||||
|
active: bool,
|
||||||
|
/// 2-tuple of user ids participating in direct message
|
||||||
|
recipients: Vec<String>,
|
||||||
|
/// Id of the last message sent in this channel
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
|
last_message_id: Option<String>,
|
||||||
|
},
|
||||||
|
/// Group channel between 1 or more participants
|
||||||
|
Group {
|
||||||
|
/// Unique Id
|
||||||
|
#[cfg_attr(feature = "serde", serde(rename = "_id"))]
|
||||||
|
id: String,
|
||||||
|
|
||||||
|
/// Display name of the channel
|
||||||
|
name: String,
|
||||||
|
/// User id of the owner of the group
|
||||||
|
owner: String,
|
||||||
|
/// Channel description
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
|
description: Option<String>,
|
||||||
|
/// Array of user ids participating in channel
|
||||||
|
recipients: Vec<String>,
|
||||||
|
|
||||||
|
/// Custom icon attachment
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
|
icon: Option<File>,
|
||||||
|
/// Id of the last message sent in this channel
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
|
last_message_id: Option<String>,
|
||||||
|
|
||||||
|
/// Permissions assigned to members of this group
|
||||||
|
/// (does not apply to the owner of the group)
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
|
permissions: Option<i64>,
|
||||||
|
|
||||||
|
/// Whether this group is marked as not safe for work
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "serde",
|
||||||
|
serde(skip_serializing_if = "crate::if_false", default)
|
||||||
|
)]
|
||||||
|
nsfw: bool,
|
||||||
|
},
|
||||||
|
/// Text channel belonging to a server
|
||||||
|
TextChannel {
|
||||||
|
/// Unique Id
|
||||||
|
#[cfg_attr(feature = "serde", serde(rename = "_id"))]
|
||||||
|
id: String,
|
||||||
|
/// Id of the server this channel belongs to
|
||||||
|
server: String,
|
||||||
|
|
||||||
|
/// Display name of the channel
|
||||||
|
name: String,
|
||||||
|
/// Channel description
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
|
description: Option<String>,
|
||||||
|
|
||||||
|
/// Custom icon attachment
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
|
icon: Option<File>,
|
||||||
|
/// Id of the last message sent in this channel
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
|
last_message_id: Option<String>,
|
||||||
|
|
||||||
|
/// Default permissions assigned to users in this channel
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
|
default_permissions: Option<OverrideField>,
|
||||||
|
/// Permissions assigned based on role to this channel
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "serde",
|
||||||
|
serde(
|
||||||
|
default = "HashMap::<String, OverrideField>::new",
|
||||||
|
skip_serializing_if = "HashMap::<String, OverrideField>::is_empty"
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
role_permissions: HashMap<String, OverrideField>,
|
||||||
|
|
||||||
|
/// Whether this channel is marked as not safe for work
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "serde",
|
||||||
|
serde(skip_serializing_if = "crate::if_false", default)
|
||||||
|
)]
|
||||||
|
nsfw: bool,
|
||||||
|
},
|
||||||
|
/// Voice channel belonging to a server
|
||||||
|
VoiceChannel {
|
||||||
|
/// Unique Id
|
||||||
|
#[cfg_attr(feature = "serde", serde(rename = "_id"))]
|
||||||
|
id: String,
|
||||||
|
/// Id of the server this channel belongs to
|
||||||
|
server: String,
|
||||||
|
|
||||||
|
/// Display name of the channel
|
||||||
|
name: String,
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
|
/// Channel description
|
||||||
|
description: Option<String>,
|
||||||
|
/// Custom icon attachment
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
|
icon: Option<File>,
|
||||||
|
|
||||||
|
/// Default permissions assigned to users in this channel
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
|
default_permissions: Option<OverrideField>,
|
||||||
|
/// Permissions assigned based on role to this channel
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "serde",
|
||||||
|
serde(
|
||||||
|
default = "HashMap::<String, OverrideField>::new",
|
||||||
|
skip_serializing_if = "HashMap::<String, OverrideField>::is_empty"
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
role_permissions: HashMap<String, OverrideField>,
|
||||||
|
|
||||||
|
/// Whether this channel is marked as not safe for work
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "serde",
|
||||||
|
serde(skip_serializing_if = "crate::if_false", default)
|
||||||
|
)]
|
||||||
|
nsfw: bool,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Partial representation of a channel
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct PartialChannel {
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
|
pub name: Option<String>,
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
|
pub owner: Option<String>,
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
|
pub description: Option<String>,
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
|
pub icon: Option<File>,
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
|
pub nsfw: Option<bool>,
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
|
pub active: Option<bool>,
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
|
pub permissions: Option<i64>,
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
|
pub role_permissions: Option<HashMap<String, OverrideField>>,
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
|
pub default_permissions: Option<OverrideField>,
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
|
pub last_message_id: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Optional fields on channel object
|
||||||
|
pub enum FieldsChannel {
|
||||||
|
Description,
|
||||||
|
Icon,
|
||||||
|
DefaultPermissions,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// New webhook information
|
||||||
|
#[cfg_attr(feature = "validator", derive(validator::Validate))]
|
||||||
|
pub struct DataEditChannel {
|
||||||
|
/// Channel name
|
||||||
|
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 32)))]
|
||||||
|
pub name: Option<String>,
|
||||||
|
|
||||||
|
/// Channel description
|
||||||
|
#[cfg_attr(feature = "validator", validate(length(min = 0, max = 1024)))]
|
||||||
|
pub description: Option<String>,
|
||||||
|
|
||||||
|
/// Group owner
|
||||||
|
pub owner: Option<String>,
|
||||||
|
|
||||||
|
/// Icon
|
||||||
|
///
|
||||||
|
/// Provide an Autumn attachment Id.
|
||||||
|
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 128)))]
|
||||||
|
pub icon: Option<String>,
|
||||||
|
|
||||||
|
/// Whether this channel is age-restricted
|
||||||
|
pub nsfw: Option<bool>,
|
||||||
|
|
||||||
|
/// Whether this channel is archived
|
||||||
|
pub archived: Option<bool>,
|
||||||
|
|
||||||
|
/// Fields to remove from channel
|
||||||
|
#[cfg_attr(feature = "serde", serde(default))]
|
||||||
|
pub remove: Option<Vec<FieldsChannel>>,
|
||||||
|
}
|
||||||
|
);
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
mod account_strikes;
|
mod account_strikes;
|
||||||
mod bots;
|
mod bots;
|
||||||
mod channel_webhooks;
|
mod channel_webhooks;
|
||||||
|
mod channels;
|
||||||
mod files;
|
mod files;
|
||||||
mod users;
|
mod users;
|
||||||
|
|
||||||
pub use account_strikes::*;
|
pub use account_strikes::*;
|
||||||
pub use bots::*;
|
pub use bots::*;
|
||||||
pub use channel_webhooks::*;
|
pub use channel_webhooks::*;
|
||||||
|
pub use channels::*;
|
||||||
pub use files::*;
|
pub use files::*;
|
||||||
pub use users::*;
|
pub use users::*;
|
||||||
|
|||||||
@@ -8,6 +8,11 @@ auto_derived!(
|
|||||||
pub id: String,
|
pub id: String,
|
||||||
/// Username
|
/// Username
|
||||||
pub username: String,
|
pub username: String,
|
||||||
|
/// Discriminator
|
||||||
|
pub discriminator: String,
|
||||||
|
/// Display name
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub display_name: Option<String>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
/// Avatar attachment
|
/// Avatar attachment
|
||||||
pub avatar: Option<File>,
|
pub avatar: Option<File>,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "revolt-permissions"
|
name = "revolt-permissions"
|
||||||
version = "0.6.0-rc.1"
|
version = "0.6.3"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "AGPL-3.0-or-later"
|
license = "AGPL-3.0-or-later"
|
||||||
authors = [ "Paul Makles <me@insrt.uk>" ]
|
authors = [ "Paul Makles <me@insrt.uk>" ]
|
||||||
@@ -9,10 +9,12 @@ description = "Revolt Backend: Permission Logic"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
bson = ["dep:bson"]
|
||||||
serde = [ "dep:serde" ]
|
serde = [ "dep:serde" ]
|
||||||
schemas = [ "dep:schemars" ]
|
schemas = [ "dep:schemars" ]
|
||||||
try-from-primitive = [ "dep:num_enum" ]
|
try-from-primitive = [ "dep:num_enum" ]
|
||||||
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
# Async
|
# Async
|
||||||
async-std = { version = "1.8.0", features = ["attributes"] }
|
async-std = { version = "1.8.0", features = ["attributes"] }
|
||||||
@@ -28,6 +30,7 @@ async-trait = "0.1.51"
|
|||||||
|
|
||||||
# Serialisation
|
# Serialisation
|
||||||
serde = { version = "1", features = ["derive"], optional = true }
|
serde = { version = "1", features = ["derive"], optional = true }
|
||||||
|
bson = { version = "2.1.0", optional = true}
|
||||||
|
|
||||||
# Spec Generation
|
# Spec Generation
|
||||||
schemars = { version = "0.8.8", optional = true }
|
schemars = { version = "0.8.8", optional = true }
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
|
#[cfg(feature = "schemas")]
|
||||||
|
use schemars::JsonSchema;
|
||||||
|
|
||||||
/// Representation of a single permission override
|
/// Representation of a single permission override
|
||||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
#[cfg_attr(feature = "schemas", derive(JsonSchema))]
|
||||||
pub struct Override {
|
pub struct Override {
|
||||||
/// Allow bit flags
|
/// Allow bit flags
|
||||||
pub allow: u64,
|
pub allow: u64,
|
||||||
@@ -8,10 +12,43 @@ pub struct Override {
|
|||||||
pub deny: u64,
|
pub deny: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Data permissions Field - contains both allow and deny
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
#[cfg_attr(feature = "schemas", derive(JsonSchema))]
|
||||||
|
pub struct DataPermissionsField {
|
||||||
|
pub permissions: Override,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Data permissions Value - contains allow
|
||||||
|
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
#[cfg_attr(feature = "schemas", derive(JsonSchema))]
|
||||||
|
pub struct DataPermissionsValue {
|
||||||
|
pub permissions: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Data permissions Poly - can contain either Value or Field
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
#[cfg_attr(feature = "schemas", derive(JsonSchema))]
|
||||||
|
#[cfg_attr(feature = "serde", serde(untagged))]
|
||||||
|
pub enum DataPermissionPoly {
|
||||||
|
Value {
|
||||||
|
/// Permission values to set for members in a `Group`
|
||||||
|
permissions: u64,
|
||||||
|
},
|
||||||
|
Field {
|
||||||
|
/// Allow / deny values to set for members in this `TextChannel` or `VoiceChannel`
|
||||||
|
permissions: Override,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
/// Representation of a single permission override
|
/// Representation of a single permission override
|
||||||
/// as it appears on models and in the database
|
/// as it appears on models and in the database
|
||||||
#[derive(/*JsonSchema, */ Debug, Clone, Copy, Default, Eq, PartialEq)]
|
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
#[cfg_attr(feature = "schemas", derive(JsonSchema))]
|
||||||
pub struct OverrideField {
|
pub struct OverrideField {
|
||||||
/// Allow bit flags
|
/// Allow bit flags
|
||||||
a: i64,
|
a: i64,
|
||||||
@@ -49,8 +86,12 @@ impl From<OverrideField> for Override {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*impl From<OverrideField> for Bson {
|
#[cfg(feature = "bson")]
|
||||||
|
use bson::Bson;
|
||||||
|
|
||||||
|
#[cfg(feature = "bson")]
|
||||||
|
impl From<OverrideField> for Bson {
|
||||||
fn from(v: OverrideField) -> Self {
|
fn from(v: OverrideField) -> Self {
|
||||||
Self::Document(bson::to_document(&v).unwrap())
|
Self::Document(bson::to_document(&v).unwrap())
|
||||||
}
|
}
|
||||||
}*/
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "revolt-presence"
|
name = "revolt-presence"
|
||||||
version = "0.6.0-rc.1"
|
version = "0.6.3"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "AGPL-3.0-or-later"
|
license = "AGPL-3.0-or-later"
|
||||||
authors = [ "Paul Makles <me@insrt.uk>" ]
|
authors = [ "Paul Makles <me@insrt.uk>" ]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "revolt-result"
|
name = "revolt-result"
|
||||||
version = "0.6.0-rc.1"
|
version = "0.6.3"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "AGPL-3.0-or-later"
|
license = "AGPL-3.0-or-later"
|
||||||
authors = [ "Paul Makles <me@insrt.uk>" ]
|
authors = [ "Paul Makles <me@insrt.uk>" ]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "revolt-delta"
|
name = "revolt-delta"
|
||||||
version = "0.6.0-rc.1"
|
version = "0.6.3"
|
||||||
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"
|
||||||
@@ -59,6 +59,7 @@ revolt-quark = { path = "../quark" }
|
|||||||
revolt-database = { path = "../core/database", features = [ "rocket-impl", "redis-is-patched" ] }
|
revolt-database = { path = "../core/database", features = [ "rocket-impl", "redis-is-patched" ] }
|
||||||
revolt-models = { path = "../core/models", features = [ "schemas", "validator" ] }
|
revolt-models = { path = "../core/models", features = [ "schemas", "validator" ] }
|
||||||
revolt-result = { path = "../core/result", features = [ "rocket", "okapi" ] }
|
revolt-result = { path = "../core/result", features = [ "rocket", "okapi" ] }
|
||||||
|
revolt-permissions = { path = "../core/permissions", features = [ "schemas" ] }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
vergen = "7.5.0"
|
vergen = "7.5.0"
|
||||||
|
|||||||
@@ -38,14 +38,12 @@ pub async fn create_bot(db: &Db, user: User, info: Json<DataCreateBot>) -> Resul
|
|||||||
return Err(Error::ReachedMaximumBots);
|
return Err(Error::ReachedMaximumBots);
|
||||||
}
|
}
|
||||||
|
|
||||||
if db.is_username_taken(&info.name).await? {
|
|
||||||
return Err(Error::UsernameTaken);
|
|
||||||
}
|
|
||||||
|
|
||||||
let id = Ulid::new().to_string();
|
let id = Ulid::new().to_string();
|
||||||
|
let username = User::validate_username(info.name)?;
|
||||||
let bot_user = User {
|
let bot_user = User {
|
||||||
id: id.clone(),
|
id: id.clone(),
|
||||||
username: info.name.trim().to_string(),
|
discriminator: User::find_discriminator(db, &username, None).await?,
|
||||||
|
username,
|
||||||
bot: Some(BotInformation {
|
bot: Some(BotInformation {
|
||||||
owner: user.id.clone(),
|
owner: user.id.clone(),
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -58,10 +58,6 @@ pub async fn edit_bot(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(name) = data.name {
|
if let Some(name) = data.name {
|
||||||
if db.is_username_taken(&name).await? {
|
|
||||||
return Err(Error::UsernameTaken);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut user = db.fetch_user(&bot.id).await?;
|
let mut user = db.fetch_user(&bot.id).await?;
|
||||||
user.update_username(db, name).await?;
|
user.update_username(db, name).await?;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,11 +53,7 @@ pub async fn req(
|
|||||||
return Err(Error::CannotEditMessage);
|
return Err(Error::CannotEditMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(new_embeds) = &edit.embeds {
|
Message::validate_sum(&edit.content, edit.embeds.as_deref().unwrap_or_default())?;
|
||||||
Message::validate_sum(&edit.content, new_embeds)?;
|
|
||||||
} else {
|
|
||||||
Message::validate_sum(&edit.content, &vec![])?;
|
|
||||||
}
|
|
||||||
|
|
||||||
message.edited = Some(Timestamp::now_utc());
|
message.edited = Some(Timestamp::now_utc());
|
||||||
let mut partial = PartialMessage {
|
let mut partial = PartialMessage {
|
||||||
@@ -85,7 +81,7 @@ pub async fn req(
|
|||||||
new_embeds.clear();
|
new_embeds.clear();
|
||||||
|
|
||||||
for embed in embeds {
|
for embed in embeds {
|
||||||
new_embeds.push(embed.clone().into_embed(db, message.id.clone()).await?);
|
new_embeds.push(embed.clone().into_embed(db, &message.id).await?);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,14 +47,14 @@ pub async fn message_send(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check permissions for embeds
|
// Check permissions for embeds
|
||||||
if !data.embeds.is_empty() {
|
if data.embeds.as_ref().is_some_and(|v| !v.is_empty()) {
|
||||||
permissions
|
permissions
|
||||||
.throw_permission(db, Permission::SendEmbeds)
|
.throw_permission(db, Permission::SendEmbeds)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check permissions for files
|
// Check permissions for files
|
||||||
if !data.attachments.is_empty() {
|
if data.attachments.as_ref().is_some_and(|v| !v.is_empty()) {
|
||||||
permissions
|
permissions
|
||||||
.throw_permission(db, Permission::UploadFiles)
|
.throw_permission(db, Permission::UploadFiles)
|
||||||
.await?;
|
.await?;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use revolt_quark::variables::delta::IS_STAGING;
|
||||||
use revolt_rocket_okapi::{revolt_okapi::openapi3::OpenApi, settings::OpenApiSettings};
|
use revolt_rocket_okapi::{revolt_okapi::openapi3::OpenApi, settings::OpenApiSettings};
|
||||||
pub use rocket::http::Status;
|
pub use rocket::http::Status;
|
||||||
pub use rocket::response::Redirect;
|
pub use rocket::response::Redirect;
|
||||||
@@ -20,6 +21,7 @@ mod webhooks;
|
|||||||
pub fn mount(mut rocket: Rocket<Build>) -> Rocket<Build> {
|
pub fn mount(mut rocket: Rocket<Build>) -> Rocket<Build> {
|
||||||
let settings = OpenApiSettings::default();
|
let settings = OpenApiSettings::default();
|
||||||
|
|
||||||
|
if *IS_STAGING {
|
||||||
mount_endpoints_and_merged_docs! {
|
mount_endpoints_and_merged_docs! {
|
||||||
rocket, "/".to_owned(), settings,
|
rocket, "/".to_owned(), settings,
|
||||||
"/" => (vec![], custom_openapi_spec()),
|
"/" => (vec![], custom_openapi_spec()),
|
||||||
@@ -40,6 +42,27 @@ pub fn mount(mut rocket: Rocket<Build>) -> Rocket<Build> {
|
|||||||
"/sync" => sync::routes(),
|
"/sync" => sync::routes(),
|
||||||
"/webhooks" => webhooks::routes()
|
"/webhooks" => webhooks::routes()
|
||||||
};
|
};
|
||||||
|
} else {
|
||||||
|
mount_endpoints_and_merged_docs! {
|
||||||
|
rocket, "/".to_owned(), settings,
|
||||||
|
"/" => (vec![], custom_openapi_spec()),
|
||||||
|
"" => openapi_get_routes_spec![root::root, root::ping],
|
||||||
|
"/admin" => admin::routes(),
|
||||||
|
"/users" => users::routes(),
|
||||||
|
"/bots" => bots::routes(),
|
||||||
|
"/channels" => channels::routes(),
|
||||||
|
"/servers" => servers::routes(),
|
||||||
|
"/invites" => invites::routes(),
|
||||||
|
"/custom" => customisation::routes(),
|
||||||
|
"/safety" => safety::routes(),
|
||||||
|
"/auth/account" => rocket_authifier::routes::account::routes(),
|
||||||
|
"/auth/session" => rocket_authifier::routes::session::routes(),
|
||||||
|
"/auth/mfa" => rocket_authifier::routes::mfa::routes(),
|
||||||
|
"/onboard" => onboard::routes(),
|
||||||
|
"/push" => push::routes(),
|
||||||
|
"/sync" => sync::routes()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
rocket
|
rocket
|
||||||
}
|
}
|
||||||
@@ -298,11 +321,9 @@ fn custom_openapi_spec() -> OpenApi {
|
|||||||
},
|
},
|
||||||
Tag {
|
Tag {
|
||||||
name: "Webhooks".to_owned(),
|
name: "Webhooks".to_owned(),
|
||||||
description: Some(
|
description: Some("Send messages from 3rd party services".to_owned()),
|
||||||
"Send messages from 3rd party services".to_owned(),
|
|
||||||
),
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,9 +34,10 @@ pub async fn req(
|
|||||||
data.validate()
|
data.validate()
|
||||||
.map_err(|error| Error::FailedValidation { error })?;
|
.map_err(|error| Error::FailedValidation { error })?;
|
||||||
|
|
||||||
let username = User::validate_username(db, data.username).await?;
|
let username = User::validate_username(data.username)?;
|
||||||
let user = User {
|
let user = User {
|
||||||
id: session.user_id,
|
id: session.user_id,
|
||||||
|
discriminator: User::find_discriminator(db, &username, None).await?,
|
||||||
username,
|
username,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ struct BannedUser {
|
|||||||
pub id: String,
|
pub id: String,
|
||||||
/// Username of the banned user
|
/// Username of the banned user
|
||||||
pub username: String,
|
pub username: String,
|
||||||
|
/// Discriminator of the banned user
|
||||||
|
pub discriminator: String,
|
||||||
/// Avatar of the banned user
|
/// Avatar of the banned user
|
||||||
pub avatar: Option<File>,
|
pub avatar: Option<File>,
|
||||||
}
|
}
|
||||||
@@ -32,6 +34,7 @@ impl From<User> for BannedUser {
|
|||||||
BannedUser {
|
BannedUser {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
username: user.username,
|
username: user.username,
|
||||||
|
discriminator: user.discriminator,
|
||||||
avatar: user.avatar,
|
avatar: user.avatar,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,9 @@
|
|||||||
use rocket::serde::json::Json;
|
use revolt_permissions::DataPermissionsValue;
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
use revolt_quark::{
|
use revolt_quark::{
|
||||||
models::{server::PartialServer, Server, User},
|
models::{server::PartialServer, Server, User},
|
||||||
perms, Db, Permission, Ref, Result,
|
perms, Db, Permission, Ref, Result,
|
||||||
};
|
};
|
||||||
|
use rocket::serde::json::Json;
|
||||||
/// # Permission Value
|
|
||||||
#[derive(Deserialize, JsonSchema)]
|
|
||||||
pub struct DataSetServerDefaultPermission {
|
|
||||||
/// Default member permission value
|
|
||||||
permissions: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// # Set Default Permission
|
/// # Set Default Permission
|
||||||
///
|
///
|
||||||
/// Sets permissions for the default role in this server.
|
/// Sets permissions for the default role in this server.
|
||||||
@@ -22,7 +13,7 @@ pub async fn req(
|
|||||||
db: &Db,
|
db: &Db,
|
||||||
user: User,
|
user: User,
|
||||||
target: Ref,
|
target: Ref,
|
||||||
data: Json<DataSetServerDefaultPermission>,
|
data: Json<DataPermissionsValue>,
|
||||||
) -> Result<Json<Server>> {
|
) -> Result<Json<Server>> {
|
||||||
let data = data.into_inner();
|
let data = data.into_inner();
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ use rocket::State;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
|
||||||
|
use crate::util::regex::RE_DISPLAY_NAME;
|
||||||
|
|
||||||
/// # Profile Data
|
/// # Profile Data
|
||||||
#[derive(Validate, Serialize, Deserialize, Debug, JsonSchema)]
|
#[derive(Validate, Serialize, Deserialize, Debug, JsonSchema)]
|
||||||
pub struct UserProfileData {
|
pub struct UserProfileData {
|
||||||
@@ -24,6 +26,9 @@ pub struct UserProfileData {
|
|||||||
/// # User Data
|
/// # User Data
|
||||||
#[derive(Validate, Serialize, Deserialize, JsonSchema)]
|
#[derive(Validate, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct DataEditUser {
|
pub struct DataEditUser {
|
||||||
|
/// New display name
|
||||||
|
#[validate(length(min = 2, max = 32), regex = "RE_DISPLAY_NAME")]
|
||||||
|
display_name: Option<String>,
|
||||||
/// Attachment Id for avatar
|
/// Attachment Id for avatar
|
||||||
#[validate(length(min = 1, max = 128))]
|
#[validate(length(min = 1, max = 128))]
|
||||||
avatar: Option<String>,
|
avatar: Option<String>,
|
||||||
@@ -84,7 +89,8 @@ pub async fn req(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Exit out early if nothing is changed
|
// Exit out early if nothing is changed
|
||||||
if data.status.is_none()
|
if data.display_name.is_none()
|
||||||
|
&& 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.badges.is_none()
|
||||||
@@ -116,6 +122,7 @@ pub async fn req(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut partial: PartialUser = PartialUser {
|
let mut partial: PartialUser = PartialUser {
|
||||||
|
display_name: data.display_name,
|
||||||
badges: data.badges,
|
badges: data.badges,
|
||||||
flags: data.flags,
|
flags: data.flags,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
/// # User Lookup Information
|
/// # User Lookup Information
|
||||||
#[derive(Serialize, Deserialize, JsonSchema)]
|
#[derive(Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct DataSendFriendRequest {
|
pub struct DataSendFriendRequest {
|
||||||
|
/// Username and discriminator combo separated by #
|
||||||
username: String,
|
username: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,7 +22,8 @@ pub async fn req(
|
|||||||
user: User,
|
user: User,
|
||||||
data: Json<DataSendFriendRequest>,
|
data: Json<DataSendFriendRequest>,
|
||||||
) -> Result<Json<User>> {
|
) -> Result<Json<User>> {
|
||||||
let mut target = db.fetch_user_by_username(&data.username).await?;
|
if let Some((username, discriminator)) = data.username.split_once('#') {
|
||||||
|
let mut target = db.fetch_user_by_username(username, discriminator).await?;
|
||||||
|
|
||||||
if user.bot.is_some() || target.bot.is_some() {
|
if user.bot.is_some() || target.bot.is_some() {
|
||||||
return Err(Error::IsBot);
|
return Err(Error::IsBot);
|
||||||
@@ -29,4 +31,7 @@ pub async fn req(
|
|||||||
|
|
||||||
user.add_friend(db, &mut target).await?;
|
user.add_friend(db, &mut target).await?;
|
||||||
Ok(Json(target.with_auto_perspective(db, &user).await))
|
Ok(Json(target.with_auto_perspective(db, &user).await))
|
||||||
|
} else {
|
||||||
|
Err(Error::InvalidProperty)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use revolt_database::Database;
|
use revolt_database::{util::reference::Reference, Database};
|
||||||
use revolt_quark::{models::User, perms, Db, Error, Permission, Result};
|
use revolt_quark::{models::User, perms, Db, Error, Permission, Result};
|
||||||
use rocket::State;
|
use rocket::State;
|
||||||
use rocket_empty::EmptyResponse;
|
use rocket_empty::EmptyResponse;
|
||||||
@@ -12,13 +12,9 @@ pub async fn webhook_delete(
|
|||||||
db: &State<Database>,
|
db: &State<Database>,
|
||||||
legacy_db: &Db,
|
legacy_db: &Db,
|
||||||
user: User,
|
user: User,
|
||||||
webhook_id: String,
|
webhook_id: Reference,
|
||||||
) -> Result<EmptyResponse> {
|
) -> Result<EmptyResponse> {
|
||||||
let webhook = db
|
let webhook = webhook_id.as_webhook(db).await.map_err(Error::from_core)?;
|
||||||
.fetch_webhook(&webhook_id)
|
|
||||||
.await
|
|
||||||
.map_err(Error::from_core)?;
|
|
||||||
|
|
||||||
let channel = legacy_db.fetch_channel(&webhook.channel_id).await?;
|
let channel = legacy_db.fetch_channel(&webhook.channel_id).await?;
|
||||||
|
|
||||||
perms(&user)
|
perms(&user)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use revolt_database::Database;
|
use revolt_database::{util::reference::Reference, Database};
|
||||||
use revolt_result::Result;
|
use revolt_result::Result;
|
||||||
use rocket::State;
|
use rocket::State;
|
||||||
use rocket_empty::EmptyResponse;
|
use rocket_empty::EmptyResponse;
|
||||||
@@ -10,10 +10,10 @@ use rocket_empty::EmptyResponse;
|
|||||||
#[delete("/<webhook_id>/<token>")]
|
#[delete("/<webhook_id>/<token>")]
|
||||||
pub async fn webhook_delete_token(
|
pub async fn webhook_delete_token(
|
||||||
db: &State<Database>,
|
db: &State<Database>,
|
||||||
webhook_id: String,
|
webhook_id: Reference,
|
||||||
token: String,
|
token: String,
|
||||||
) -> Result<EmptyResponse> {
|
) -> Result<EmptyResponse> {
|
||||||
let webhook = db.fetch_webhook(&webhook_id).await?;
|
let webhook = webhook_id.as_webhook(db).await?;
|
||||||
webhook.assert_token(&token)?;
|
webhook.assert_token(&token)?;
|
||||||
webhook.delete(db).await.map(|_| EmptyResponse)
|
webhook.delete(db).await.map(|_| EmptyResponse)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use revolt_database::{Database, PartialWebhook};
|
use revolt_database::{util::reference::Reference, Database, PartialWebhook};
|
||||||
use revolt_models::v0::{DataEditWebhook, Webhook};
|
use revolt_models::v0::{DataEditWebhook, Webhook};
|
||||||
use revolt_quark::{models::User, perms, Db, Error, Permission, Result};
|
use revolt_quark::{models::User, perms, Db, Error, Permission, Result};
|
||||||
use rocket::{serde::json::Json, State};
|
use rocket::{serde::json::Json, State};
|
||||||
@@ -12,7 +12,7 @@ use validator::Validate;
|
|||||||
pub async fn webhook_edit(
|
pub async fn webhook_edit(
|
||||||
db: &State<Database>,
|
db: &State<Database>,
|
||||||
legacy_db: &Db,
|
legacy_db: &Db,
|
||||||
webhook_id: String,
|
webhook_id: Reference,
|
||||||
user: User,
|
user: User,
|
||||||
data: Json<DataEditWebhook>,
|
data: Json<DataEditWebhook>,
|
||||||
) -> Result<Json<Webhook>> {
|
) -> Result<Json<Webhook>> {
|
||||||
@@ -20,11 +20,7 @@ pub async fn webhook_edit(
|
|||||||
data.validate()
|
data.validate()
|
||||||
.map_err(|error| Error::FailedValidation { error })?;
|
.map_err(|error| Error::FailedValidation { error })?;
|
||||||
|
|
||||||
let mut webhook = db
|
let mut webhook = webhook_id.as_webhook(db).await.map_err(Error::from_core)?;
|
||||||
.fetch_webhook(&webhook_id)
|
|
||||||
.await
|
|
||||||
.map_err(Error::from_core)?;
|
|
||||||
|
|
||||||
let channel = legacy_db.fetch_channel(&webhook.channel_id).await?;
|
let channel = legacy_db.fetch_channel(&webhook.channel_id).await?;
|
||||||
|
|
||||||
perms(&user)
|
perms(&user)
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use revolt_database::util::reference::Reference;
|
||||||
use revolt_database::{Database, PartialWebhook};
|
use revolt_database::{Database, PartialWebhook};
|
||||||
use revolt_models::v0::{DataEditWebhook, Webhook};
|
use revolt_models::v0::{DataEditWebhook, Webhook};
|
||||||
use revolt_models::validator::Validate;
|
use revolt_models::validator::Validate;
|
||||||
@@ -11,7 +12,7 @@ use rocket::{serde::json::Json, State};
|
|||||||
#[patch("/<webhook_id>/<token>", data = "<data>")]
|
#[patch("/<webhook_id>/<token>", data = "<data>")]
|
||||||
pub async fn webhook_edit_token(
|
pub async fn webhook_edit_token(
|
||||||
db: &State<Database>,
|
db: &State<Database>,
|
||||||
webhook_id: String,
|
webhook_id: Reference,
|
||||||
token: String,
|
token: String,
|
||||||
data: Json<DataEditWebhook>,
|
data: Json<DataEditWebhook>,
|
||||||
) -> Result<Json<Webhook>> {
|
) -> Result<Json<Webhook>> {
|
||||||
@@ -22,7 +23,7 @@ pub async fn webhook_edit_token(
|
|||||||
})
|
})
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let mut webhook = db.fetch_webhook(&webhook_id).await?;
|
let mut webhook = webhook_id.as_webhook(db).await?;
|
||||||
webhook.assert_token(&token)?;
|
webhook.assert_token(&token)?;
|
||||||
|
|
||||||
if data.name.is_none() && data.avatar.is_none() && data.remove.is_empty() {
|
if data.name.is_none() && data.avatar.is_none() && data.remove.is_empty() {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use revolt_database::Database;
|
use revolt_database::{util::reference::Reference, Database};
|
||||||
use revolt_quark::{
|
use revolt_quark::{
|
||||||
models::message::{DataMessageSend, Message},
|
models::message::{DataMessageSend, Message},
|
||||||
types::push::MessageAuthor,
|
types::push::MessageAuthor,
|
||||||
@@ -17,7 +17,7 @@ use validator::Validate;
|
|||||||
pub async fn webhook_execute(
|
pub async fn webhook_execute(
|
||||||
db: &State<Database>,
|
db: &State<Database>,
|
||||||
legacy_db: &Db,
|
legacy_db: &Db,
|
||||||
webhook_id: String,
|
webhook_id: Reference,
|
||||||
token: String,
|
token: String,
|
||||||
data: Json<DataMessageSend>,
|
data: Json<DataMessageSend>,
|
||||||
idempotency: IdempotencyKey,
|
idempotency: IdempotencyKey,
|
||||||
@@ -26,11 +26,7 @@ pub async fn webhook_execute(
|
|||||||
data.validate()
|
data.validate()
|
||||||
.map_err(|error| Error::FailedValidation { error })?;
|
.map_err(|error| Error::FailedValidation { error })?;
|
||||||
|
|
||||||
let webhook = db
|
let webhook = webhook_id.as_webhook(db).await.map_err(Error::from_core)?;
|
||||||
.fetch_webhook(&webhook_id)
|
|
||||||
.await
|
|
||||||
.map_err(Error::from_core)?;
|
|
||||||
|
|
||||||
webhook.assert_token(&token).map_err(Error::from_core)?;
|
webhook.assert_token(&token).map_err(Error::from_core)?;
|
||||||
|
|
||||||
// TODO: webhooks can currently always send masquerades, files, embeds, reactions (interactions)
|
// TODO: webhooks can currently always send masquerades, files, embeds, reactions (interactions)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use revolt_database::Database;
|
use revolt_database::{util::reference::Reference, Database};
|
||||||
use revolt_models::v0::Webhook;
|
use revolt_models::v0::Webhook;
|
||||||
use revolt_quark::{
|
use revolt_quark::{
|
||||||
models::{message::SendableEmbed, Message},
|
models::{message::SendableEmbed, Message},
|
||||||
@@ -752,16 +752,12 @@ fn convert_event(data: &str, event_name: &str) -> Result<Event> {
|
|||||||
pub async fn webhook_execute_github(
|
pub async fn webhook_execute_github(
|
||||||
db: &State<Database>,
|
db: &State<Database>,
|
||||||
legacy_db: &Db,
|
legacy_db: &Db,
|
||||||
webhook_id: String,
|
webhook_id: Reference,
|
||||||
token: String,
|
token: String,
|
||||||
event: EventHeader<'_>,
|
event: EventHeader<'_>,
|
||||||
data: String,
|
data: String,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let webhook = db
|
let webhook = webhook_id.as_webhook(db).await.map_err(Error::from_core)?;
|
||||||
.fetch_webhook(&webhook_id)
|
|
||||||
.await
|
|
||||||
.map_err(Error::from_core)?;
|
|
||||||
|
|
||||||
webhook.assert_token(&token).map_err(Error::from_core)?;
|
webhook.assert_token(&token).map_err(Error::from_core)?;
|
||||||
|
|
||||||
let channel = legacy_db.fetch_channel(&webhook.channel_id).await?;
|
let channel = legacy_db.fetch_channel(&webhook.channel_id).await?;
|
||||||
@@ -1069,7 +1065,7 @@ pub async fn webhook_execute_github(
|
|||||||
let message_id = Ulid::new().to_string();
|
let message_id = Ulid::new().to_string();
|
||||||
|
|
||||||
let embed = sendable_embed
|
let embed = sendable_embed
|
||||||
.into_embed(legacy_db, message_id.clone())
|
.into_embed(legacy_db, &message_id)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let mut message = Message {
|
let mut message = Message {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use revolt_database::Database;
|
use revolt_database::{util::reference::Reference, Database};
|
||||||
use revolt_models::v0::{ResponseWebhook, Webhook};
|
use revolt_models::v0::{ResponseWebhook, Webhook};
|
||||||
use revolt_quark::{models::User, perms, Db, Error, Permission, Result};
|
use revolt_quark::{models::User, perms, Db, Error, Permission, Result};
|
||||||
use rocket::{serde::json::Json, State};
|
use rocket::{serde::json::Json, State};
|
||||||
@@ -11,14 +11,10 @@ use rocket::{serde::json::Json, State};
|
|||||||
pub async fn webhook_fetch(
|
pub async fn webhook_fetch(
|
||||||
db: &State<Database>,
|
db: &State<Database>,
|
||||||
legacy_db: &Db,
|
legacy_db: &Db,
|
||||||
webhook_id: String,
|
webhook_id: Reference,
|
||||||
user: User,
|
user: User,
|
||||||
) -> Result<Json<ResponseWebhook>> {
|
) -> Result<Json<ResponseWebhook>> {
|
||||||
let webhook = db
|
let webhook = webhook_id.as_webhook(db).await.map_err(Error::from_core)?;
|
||||||
.fetch_webhook(&webhook_id)
|
|
||||||
.await
|
|
||||||
.map_err(Error::from_core)?;
|
|
||||||
|
|
||||||
let channel = legacy_db.fetch_channel(&webhook.channel_id).await?;
|
let channel = legacy_db.fetch_channel(&webhook.channel_id).await?;
|
||||||
|
|
||||||
perms(&user)
|
perms(&user)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use revolt_database::Database;
|
use revolt_database::{util::reference::Reference, Database};
|
||||||
use revolt_models::v0::Webhook;
|
use revolt_models::v0::Webhook;
|
||||||
use revolt_result::Result;
|
use revolt_result::Result;
|
||||||
use rocket::{serde::json::Json, State};
|
use rocket::{serde::json::Json, State};
|
||||||
@@ -10,10 +10,10 @@ use rocket::{serde::json::Json, State};
|
|||||||
#[get("/<webhook_id>/<token>")]
|
#[get("/<webhook_id>/<token>")]
|
||||||
pub async fn webhook_fetch_token(
|
pub async fn webhook_fetch_token(
|
||||||
db: &State<Database>,
|
db: &State<Database>,
|
||||||
webhook_id: String,
|
webhook_id: Reference,
|
||||||
token: String,
|
token: String,
|
||||||
) -> Result<Json<Webhook>> {
|
) -> Result<Json<Webhook>> {
|
||||||
let webhook = db.fetch_webhook(&webhook_id).await?;
|
let webhook = webhook_id.as_webhook(db).await?;
|
||||||
webhook.assert_token(&token)?;
|
webhook.assert_token(&token)?;
|
||||||
Ok(Json(webhook.into()))
|
Ok(Json(webhook.into()))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
|
/// Regex for valid display names
|
||||||
|
///
|
||||||
|
/// Block zero width space
|
||||||
|
/// Block newline and carriage return
|
||||||
|
pub static RE_DISPLAY_NAME: Lazy<Regex> = Lazy::new(|| Regex::new(r"^[^\u200B\n\r]+$").unwrap());
|
||||||
|
|
||||||
/// Regex for valid usernames
|
/// Regex for valid usernames
|
||||||
///
|
///
|
||||||
/// Block zero width space
|
/// Block zero width space
|
||||||
/// Block lookalike characters
|
/// Block lookalike characters
|
||||||
pub static RE_USERNAME: Lazy<Regex> =
|
pub static RE_USERNAME: Lazy<Regex> = Lazy::new(|| Regex::new(r"^(\p{L}|[\d_.-])+$").unwrap());
|
||||||
Lazy::new(|| Regex::new(r"^[^\u200BА-Яа-яΑ-Ωα-ω@#:\n\r\[\]]+$").unwrap());
|
|
||||||
|
|
||||||
/// Regex for valid emoji names
|
/// Regex for valid emoji names
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "revolt-quark"
|
name = "revolt-quark"
|
||||||
version = "0.6.0-rc.1"
|
version = "0.6.3"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "AGPL-3.0-or-later"
|
license = "AGPL-3.0-or-later"
|
||||||
|
|
||||||
@@ -64,6 +64,7 @@ nanoid = "0.4.0"
|
|||||||
linkify = "0.8.1"
|
linkify = "0.8.1"
|
||||||
dotenv = "0.15.0"
|
dotenv = "0.15.0"
|
||||||
indexmap = "1.9.1"
|
indexmap = "1.9.1"
|
||||||
|
decancer = "1.6.2"
|
||||||
impl_ops = "0.1.1"
|
impl_ops = "0.1.1"
|
||||||
num_enum = "0.5.6"
|
num_enum = "0.5.6"
|
||||||
reqwest = "0.11.10"
|
reqwest = "0.11.10"
|
||||||
@@ -93,4 +94,5 @@ sentry = "0.25.0"
|
|||||||
# Core
|
# Core
|
||||||
revolt-result = { path = "../core/result", features = [ "serde", "schemas" ] }
|
revolt-result = { path = "../core/result", features = [ "serde", "schemas" ] }
|
||||||
revolt-presence = { path = "../core/presence", features = [ "redis-is-patched" ] }
|
revolt-presence = { path = "../core/presence", features = [ "redis-is-patched" ] }
|
||||||
|
revolt-database = { path = "../core/database" }
|
||||||
revolt-models = { path = "../core/models" }
|
revolt-models = { path = "../core/models" }
|
||||||
|
|||||||
@@ -71,3 +71,14 @@ impl From<Database> for authifier::Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Database> for revolt_database::Database {
|
||||||
|
fn from(val: Database) -> Self {
|
||||||
|
match val {
|
||||||
|
Database::Dummy(_) => revolt_database::Database::Reference(Default::default()),
|
||||||
|
Database::MongoDb(MongoDb(client)) => revolt_database::Database::MongoDb(
|
||||||
|
revolt_database::MongoDb(client, "revolt".to_string()),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,11 +9,12 @@ impl AbstractUser for DummyDb {
|
|||||||
Ok(User {
|
Ok(User {
|
||||||
id: id.into(),
|
id: id.into(),
|
||||||
username: "username".into(),
|
username: "username".into(),
|
||||||
|
discriminator: "0000".into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fetch_user_by_username(&self, username: &str) -> Result<User> {
|
async fn fetch_user_by_username(&self, username: &str, _discriminator: &str) -> Result<User> {
|
||||||
self.fetch_user(username).await
|
self.fetch_user(username).await
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,8 +46,8 @@ impl AbstractUser for DummyDb {
|
|||||||
Ok(vec![self.fetch_user("id").await.unwrap()])
|
Ok(vec![self.fetch_user("id").await.unwrap()])
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn is_username_taken(&self, _username: &str) -> Result<bool> {
|
async fn fetch_discriminators_in_use(&self, _username: &str) -> Result<Vec<String>> {
|
||||||
Ok(false)
|
Ok(vec![])
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fetch_mutual_user_ids(&self, _user_a: &str, _user_b: &str) -> Result<Vec<String>> {
|
async fn fetch_mutual_user_ids(&self, _user_a: &str, _user_b: &str) -> Result<Vec<String>> {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use crate::{
|
|||||||
},
|
},
|
||||||
tasks::{ack::AckEvent, process_embeds},
|
tasks::{ack::AckEvent, process_embeds},
|
||||||
types::push::MessageAuthor,
|
types::push::MessageAuthor,
|
||||||
variables::delta::{MAX_ATTACHMENT_COUNT, MAX_REPLY_COUNT},
|
variables::delta::{MAX_ATTACHMENT_COUNT, MAX_REPLY_COUNT, MAX_EMBED_COUNT},
|
||||||
web::idempotency::IdempotencyKey,
|
web::idempotency::IdempotencyKey,
|
||||||
Database, Error, OverrideField, Ref, Result,
|
Database, Error, OverrideField, Ref, Result,
|
||||||
};
|
};
|
||||||
@@ -411,14 +411,14 @@ impl Channel {
|
|||||||
mut idempotency: IdempotencyKey,
|
mut idempotency: IdempotencyKey,
|
||||||
generate_embeds: bool,
|
generate_embeds: bool,
|
||||||
) -> Result<Message> {
|
) -> Result<Message> {
|
||||||
Message::validate_sum(&data.content, &data.embeds)?;
|
Message::validate_sum(&data.content, data.embeds.as_deref().unwrap_or_default())?;
|
||||||
|
|
||||||
idempotency.consume_nonce(data.nonce).await?;
|
idempotency.consume_nonce(data.nonce).await?;
|
||||||
|
|
||||||
// Check the message is not empty
|
// Check the message is not empty
|
||||||
if (data.content.as_ref().map_or(true, |v| v.is_empty()))
|
if (data.content.as_ref().map_or(true, |v| v.is_empty()))
|
||||||
&& (data.attachments.is_empty())
|
&& (data.attachments.as_ref().map_or(true, |v| v.is_empty()))
|
||||||
&& (data.embeds.is_empty())
|
&& (data.embeds.as_ref().map_or(true, |v| v.is_empty()))
|
||||||
{
|
{
|
||||||
return Err(Error::EmptyMessage);
|
return Err(Error::EmptyMessage);
|
||||||
}
|
}
|
||||||
@@ -497,15 +497,21 @@ impl Channel {
|
|||||||
|
|
||||||
// Add attachments to message.
|
// Add attachments to message.
|
||||||
let mut attachments = vec![];
|
let mut attachments = vec![];
|
||||||
if data.attachments.len() > *MAX_ATTACHMENT_COUNT {
|
if data.attachments.as_ref().is_some_and(|v| v.len() > *MAX_ATTACHMENT_COUNT) {
|
||||||
return Err(Error::TooManyAttachments {
|
return Err(Error::TooManyAttachments {
|
||||||
max: *MAX_ATTACHMENT_COUNT,
|
max: *MAX_ATTACHMENT_COUNT,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
for attachment_id in data.attachments {
|
if data.embeds.as_ref().is_some_and(|v| v.len() > *MAX_EMBED_COUNT) {
|
||||||
|
return Err(Error::TooManyEmbeds {
|
||||||
|
max: *MAX_EMBED_COUNT
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for attachment_id in data.attachments.as_deref().unwrap_or_default() {
|
||||||
attachments.push(
|
attachments.push(
|
||||||
db.find_and_use_attachment(&attachment_id, "attachments", "message", &message_id)
|
db.find_and_use_attachment(attachment_id, "attachments", "message", &message_id)
|
||||||
.await?,
|
.await?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -516,8 +522,8 @@ impl Channel {
|
|||||||
|
|
||||||
// Process included embeds.
|
// Process included embeds.
|
||||||
let mut embeds = vec![];
|
let mut embeds = vec![];
|
||||||
for sendable_embed in data.embeds {
|
for sendable_embed in data.embeds.unwrap_or_default() {
|
||||||
embeds.push(sendable_embed.into_embed(db, message_id.clone()).await?)
|
embeds.push(sendable_embed.into_embed(db, &message_id).await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !embeds.is_empty() {
|
if !embeds.is_empty() {
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ impl Message {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Validate the sum of content of a message is under threshold
|
/// Validate the sum of content of a message is under threshold
|
||||||
pub fn validate_sum(content: &Option<String>, embeds: &Vec<SendableEmbed>) -> Result<()> {
|
pub fn validate_sum(content: &Option<String>, embeds: &[SendableEmbed]) -> Result<()> {
|
||||||
let mut running_total = 0;
|
let mut running_total = 0;
|
||||||
if let Some(content) = content {
|
if let Some(content) = content {
|
||||||
running_total += content.len();
|
running_total += content.len();
|
||||||
@@ -353,13 +353,13 @@ impl From<SystemMessage> for String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SendableEmbed {
|
impl SendableEmbed {
|
||||||
pub async fn into_embed(self, db: &Database, message_id: String) -> Result<Embed> {
|
pub async fn into_embed(self, db: &Database, message_id: &str) -> Result<Embed> {
|
||||||
self.validate()
|
self.validate()
|
||||||
.map_err(|error| Error::FailedValidation { error })?;
|
.map_err(|error| Error::FailedValidation { error })?;
|
||||||
|
|
||||||
let media = if let Some(id) = self.media {
|
let media = if let Some(id) = self.media {
|
||||||
Some(
|
Some(
|
||||||
db.find_and_use_attachment(&id, "attachments", "message", &message_id)
|
db.find_and_use_attachment(&id, "attachments", "message", message_id)
|
||||||
.await?,
|
.await?,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -8,8 +8,13 @@ use crate::{perms, Database, Error, Result};
|
|||||||
|
|
||||||
use futures::try_join;
|
use futures::try_join;
|
||||||
use impl_ops::impl_op_ex_commutative;
|
use impl_ops::impl_op_ex_commutative;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use rand::seq::SliceRandom;
|
||||||
|
use revolt_database::RatelimitEventType;
|
||||||
use revolt_presence::filter_online;
|
use revolt_presence::filter_online;
|
||||||
|
use std::collections::HashSet;
|
||||||
use std::ops;
|
use std::ops;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
impl_op_ex_commutative!(+ |a: &i32, b: &Badges| -> i32 { *a | *b as i32 });
|
impl_op_ex_commutative!(+ |a: &i32, b: &Badges| -> i32 { *a | *b as i32 });
|
||||||
|
|
||||||
@@ -65,6 +70,7 @@ impl User {
|
|||||||
x.background = None;
|
x.background = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
FieldsUser::DisplayName => self.display_name = None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,18 +175,15 @@ impl User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Sanitise and validate a username can be used
|
/// Sanitise and validate a username can be used
|
||||||
pub async fn validate_username(db: &Database, username: String) -> Result<String> {
|
pub fn validate_username(username: String) -> Result<String> {
|
||||||
// Trim surrounding spaces
|
|
||||||
let username = username.trim().to_string();
|
|
||||||
|
|
||||||
// Make sure username is still at least 3 characters
|
|
||||||
if username.len() < 2 {
|
|
||||||
return Err(Error::InvalidUsername);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy the username for validation
|
// Copy the username for validation
|
||||||
let username_lowercase = username.to_lowercase();
|
let username_lowercase = username.to_lowercase();
|
||||||
|
|
||||||
|
// Block homoglyphs
|
||||||
|
if decancer::cure(&username_lowercase).into_str() != username_lowercase {
|
||||||
|
return Err(Error::InvalidUsername);
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure the username itself isn't blocked
|
// Ensure the username itself isn't blocked
|
||||||
const BLOCKED_USERNAMES: &[&str] = &["admin", "revolt"];
|
const BLOCKED_USERNAMES: &[&str] = &["admin", "revolt"];
|
||||||
|
|
||||||
@@ -199,25 +202,96 @@ impl User {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure the username isn't taken
|
Ok(username)
|
||||||
if db.is_username_taken(&username).await? {
|
}
|
||||||
|
|
||||||
|
// Find a free discriminator for a given username
|
||||||
|
pub async fn find_discriminator(
|
||||||
|
db: &Database,
|
||||||
|
username: &str,
|
||||||
|
preferred: Option<(String, String)>,
|
||||||
|
) -> Result<String> {
|
||||||
|
let search_space: &HashSet<String> = &DISCRIMINATOR_SEARCH_SPACE_QUARK;
|
||||||
|
let used_discriminators: HashSet<String> = db
|
||||||
|
.fetch_discriminators_in_use(username)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let available_discriminators: Vec<&String> =
|
||||||
|
search_space.difference(&used_discriminators).collect();
|
||||||
|
|
||||||
|
if available_discriminators.is_empty() {
|
||||||
return Err(Error::UsernameTaken);
|
return Err(Error::UsernameTaken);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(username)
|
if let Some((preferred, target_id)) = preferred {
|
||||||
|
if available_discriminators.contains(&&preferred) {
|
||||||
|
return Ok(preferred);
|
||||||
|
} else {
|
||||||
|
let rvdb: revolt_database::Database = db.clone().into();
|
||||||
|
if rvdb
|
||||||
|
.has_ratelimited(
|
||||||
|
&target_id,
|
||||||
|
RatelimitEventType::DiscriminatorChange,
|
||||||
|
Duration::from_secs(60 * 60 * 24),
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(Error::from_core)?
|
||||||
|
{
|
||||||
|
return Err(Error::DiscriminatorChangeRatelimited);
|
||||||
|
}
|
||||||
|
|
||||||
|
rvdb.insert_ratelimit_event(&revolt_database::RatelimitEvent {
|
||||||
|
id: ulid::Ulid::new().to_string(),
|
||||||
|
target_id,
|
||||||
|
event_type: RatelimitEventType::DiscriminatorChange,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(Error::from_core)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
Ok(available_discriminators
|
||||||
|
.choose(&mut rng)
|
||||||
|
.expect("we can assert this has an element")
|
||||||
|
.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update a user's username
|
/// Update a user's username
|
||||||
pub async fn update_username(&mut self, db: &Database, username: String) -> Result<()> {
|
pub async fn update_username(&mut self, db: &Database, username: String) -> Result<()> {
|
||||||
|
let username = User::validate_username(username)?;
|
||||||
|
if self.username.to_lowercase() == username.to_lowercase() {
|
||||||
self.update(
|
self.update(
|
||||||
db,
|
db,
|
||||||
PartialUser {
|
PartialUser {
|
||||||
username: Some(User::validate_username(db, username).await?),
|
username: Some(username),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
vec![],
|
vec![],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
} else {
|
||||||
|
self.update(
|
||||||
|
db,
|
||||||
|
PartialUser {
|
||||||
|
discriminator: Some(
|
||||||
|
User::find_discriminator(
|
||||||
|
db,
|
||||||
|
&username,
|
||||||
|
Some((self.discriminator.to_string(), self.id.clone())),
|
||||||
|
)
|
||||||
|
.await?,
|
||||||
|
),
|
||||||
|
username: Some(username),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
vec![],
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apply a certain relationship between two users
|
/// Apply a certain relationship between two users
|
||||||
@@ -407,3 +481,17 @@ impl User {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub static DISCRIMINATOR_SEARCH_SPACE_QUARK: Lazy<HashSet<String>> = Lazy::new(|| {
|
||||||
|
let mut set = (2..9999)
|
||||||
|
.map(|v| format!("{:0>4}", v))
|
||||||
|
.collect::<HashSet<String>>();
|
||||||
|
|
||||||
|
for discrim in [
|
||||||
|
123, 1234, 1111, 2222, 3333, 4444, 5555, 6666, 7777, 8888, 9999,
|
||||||
|
] {
|
||||||
|
set.remove(&format!("{:0>4}", discrim));
|
||||||
|
}
|
||||||
|
|
||||||
|
set.into_iter().collect()
|
||||||
|
});
|
||||||
|
|||||||
@@ -9,14 +9,16 @@ use crate::{AbstractUser, Error, Result};
|
|||||||
|
|
||||||
use super::super::MongoDb;
|
use super::super::MongoDb;
|
||||||
|
|
||||||
static FIND_USERNAME_OPTIONS: Lazy<FindOneOptions> = Lazy::new(|| FindOneOptions::builder()
|
static FIND_USERNAME_OPTIONS: Lazy<FindOneOptions> = Lazy::new(|| {
|
||||||
|
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";
|
||||||
|
|
||||||
@@ -26,11 +28,12 @@ impl AbstractUser for MongoDb {
|
|||||||
self.find_one_by_id(COL, id).await
|
self.find_one_by_id(COL, id).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fetch_user_by_username(&self, username: &str) -> Result<User> {
|
async fn fetch_user_by_username(&self, username: &str, discriminator: &str) -> Result<User> {
|
||||||
self.find_one_with_options(
|
self.find_one_with_options(
|
||||||
COL,
|
COL,
|
||||||
doc! {
|
doc! {
|
||||||
"username": username
|
"username": username,
|
||||||
|
"discriminator": discriminator
|
||||||
},
|
},
|
||||||
FIND_USERNAME_OPTIONS.clone(),
|
FIND_USERNAME_OPTIONS.clone(),
|
||||||
)
|
)
|
||||||
@@ -106,13 +109,34 @@ impl AbstractUser for MongoDb {
|
|||||||
Ok(users)
|
Ok(users)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn is_username_taken(&self, username: &str) -> Result<bool> {
|
async fn fetch_discriminators_in_use(&self, username: &str) -> Result<Vec<String>> {
|
||||||
// ! FIXME: move this up to generic
|
Ok(self
|
||||||
match self.fetch_user_by_username(username).await {
|
.col::<Document>(COL)
|
||||||
Ok(_) => Ok(true),
|
.find(
|
||||||
Err(Error::NotFound) => Ok(false),
|
doc! {
|
||||||
Err(error) => Err(error),
|
"username": username
|
||||||
}
|
},
|
||||||
|
FindOptions::builder()
|
||||||
|
.collation(
|
||||||
|
Collation::builder()
|
||||||
|
.locale("en")
|
||||||
|
.strength(CollationStrength::Secondary)
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
.projection(doc! { "_id": 0, "discriminator": 1 })
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "find",
|
||||||
|
with: "users",
|
||||||
|
})?
|
||||||
|
.filter_map(|s| async { s.ok() })
|
||||||
|
.collect::<Vec<Document>>()
|
||||||
|
.await
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|x| x.get_str("discriminator").ok().map(|x| x.to_string()))
|
||||||
|
.collect::<Vec<String>>())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fetch_mutual_user_ids(&self, user_a: &str, user_b: &str) -> Result<Vec<String>> {
|
async fn fetch_mutual_user_ids(&self, user_a: &str, user_b: &str) -> Result<Vec<String>> {
|
||||||
@@ -313,6 +337,7 @@ impl IntoDocumentPath for FieldsUser {
|
|||||||
FieldsUser::ProfileContent => "profile.content",
|
FieldsUser::ProfileContent => "profile.content",
|
||||||
FieldsUser::StatusPresence => "status.presence",
|
FieldsUser::StatusPresence => "status.presence",
|
||||||
FieldsUser::StatusText => "status.text",
|
FieldsUser::StatusText => "status.text",
|
||||||
|
FieldsUser::DisplayName => "display_name",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -269,16 +269,15 @@ pub struct DataMessageSend {
|
|||||||
#[validate(length(min = 0, max = 2000))]
|
#[validate(length(min = 0, max = 2000))]
|
||||||
pub content: Option<String>,
|
pub content: Option<String>,
|
||||||
/// Attachments to include in message
|
/// Attachments to include in message
|
||||||
#[serde(default)]
|
|
||||||
pub attachments: Vec<String>,
|
pub attachments: Option<Vec<String>>,
|
||||||
/// Messages to reply to
|
/// Messages to reply to
|
||||||
pub replies: Option<Vec<Reply>>,
|
pub replies: Option<Vec<Reply>>,
|
||||||
/// Embeds to include in message
|
/// Embeds to include in message
|
||||||
///
|
///
|
||||||
/// Text embed content contributes to the content length cap
|
/// Text embed content contributes to the content length cap
|
||||||
#[serde(default)]
|
#[validate]
|
||||||
#[validate(length(min = 0, max = 10))]
|
pub embeds: Option<Vec<SendableEmbed>>,
|
||||||
pub embeds: Vec<SendableEmbed>,
|
|
||||||
/// Masquerade to apply to this message
|
/// Masquerade to apply to this message
|
||||||
#[validate]
|
#[validate]
|
||||||
pub masquerade: Option<Masquerade>,
|
pub masquerade: Option<Masquerade>,
|
||||||
|
|||||||
@@ -128,6 +128,11 @@ pub struct User {
|
|||||||
pub id: String,
|
pub id: String,
|
||||||
/// Username
|
/// Username
|
||||||
pub username: String,
|
pub username: String,
|
||||||
|
/// Discriminator
|
||||||
|
pub discriminator: String,
|
||||||
|
/// Display name
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub display_name: Option<String>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
/// Avatar attachment
|
/// Avatar attachment
|
||||||
pub avatar: Option<File>,
|
pub avatar: Option<File>,
|
||||||
@@ -172,6 +177,7 @@ pub enum FieldsUser {
|
|||||||
StatusPresence,
|
StatusPresence,
|
||||||
ProfileContent,
|
ProfileContent,
|
||||||
ProfileBackground,
|
ProfileBackground,
|
||||||
|
DisplayName,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enumeration providing a hint to the type of user we are handling
|
/// Enumeration providing a hint to the type of user we are handling
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ pub trait AbstractUser: Sync + Send {
|
|||||||
async fn fetch_user(&self, id: &str) -> Result<User>;
|
async fn fetch_user(&self, id: &str) -> Result<User>;
|
||||||
|
|
||||||
/// Fetch a user from the database by their username
|
/// Fetch a user from the database by their username
|
||||||
async fn fetch_user_by_username(&self, username: &str) -> Result<User>;
|
async fn fetch_user_by_username(&self, username: &str, discriminator: &str) -> Result<User>;
|
||||||
|
|
||||||
/// Fetch a user from the database by their session token
|
/// Fetch a user from the database by their session token
|
||||||
async fn fetch_user_by_token(&self, token: &str) -> Result<User>;
|
async fn fetch_user_by_token(&self, token: &str) -> Result<User>;
|
||||||
@@ -29,8 +29,8 @@ pub trait AbstractUser: Sync + Send {
|
|||||||
/// Fetch multiple users by their ids
|
/// Fetch multiple users by their ids
|
||||||
async fn fetch_users<'a>(&self, ids: &'a [String]) -> Result<Vec<User>>;
|
async fn fetch_users<'a>(&self, ids: &'a [String]) -> Result<Vec<User>>;
|
||||||
|
|
||||||
/// Check whether a username is already in use by another user
|
/// Fetch all discriminators in use for a username
|
||||||
async fn is_username_taken(&self, username: &str) -> Result<bool>;
|
async fn fetch_discriminators_in_use(&self, username: &str) -> Result<Vec<String>>;
|
||||||
|
|
||||||
/// Fetch ids of users that both users are friends with
|
/// Fetch ids of users that both users are friends with
|
||||||
async fn fetch_mutual_user_ids(&self, user_a: &str, user_b: &str) -> Result<Vec<String>>;
|
async fn fetch_mutual_user_ids(&self, user_a: &str, user_b: &str) -> Result<Vec<String>>;
|
||||||
|
|||||||
@@ -8,6 +8,6 @@ pub fn prefix_keys<T: Serialize>(t: &T, prefix: &str) -> HashMap<String, serde_j
|
|||||||
let v: HashMap<String, serde_json::Value> = serde_json::from_str(&v).unwrap();
|
let v: HashMap<String, serde_json::Value> = serde_json::from_str(&v).unwrap();
|
||||||
v.into_iter()
|
v.into_iter()
|
||||||
.filter(|(_k, v)| !v.is_null())
|
.filter(|(_k, v)| !v.is_null())
|
||||||
.map(|(k, v)| (prefix.to_owned() + &k, v))
|
.map(|(k, v)| (format!("{}{}", prefix.to_owned(), k), v))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ pub enum Error {
|
|||||||
// ? User related errors
|
// ? User related errors
|
||||||
UsernameTaken,
|
UsernameTaken,
|
||||||
InvalidUsername,
|
InvalidUsername,
|
||||||
|
DiscriminatorChangeRatelimited,
|
||||||
UnknownUser,
|
UnknownUser,
|
||||||
AlreadyFriends,
|
AlreadyFriends,
|
||||||
AlreadySentRequest,
|
AlreadySentRequest,
|
||||||
@@ -53,6 +54,9 @@ pub enum Error {
|
|||||||
TooManyChannels {
|
TooManyChannels {
|
||||||
max: usize,
|
max: usize,
|
||||||
},
|
},
|
||||||
|
TooManyEmbeds {
|
||||||
|
max: usize,
|
||||||
|
},
|
||||||
EmptyMessage,
|
EmptyMessage,
|
||||||
PayloadTooLarge,
|
PayloadTooLarge,
|
||||||
CannotRemoveYourself,
|
CannotRemoveYourself,
|
||||||
@@ -162,6 +166,7 @@ impl<'r> Responder<'r, 'static> for Error {
|
|||||||
|
|
||||||
Error::UnknownUser => Status::NotFound,
|
Error::UnknownUser => Status::NotFound,
|
||||||
Error::InvalidUsername => Status::BadRequest,
|
Error::InvalidUsername => Status::BadRequest,
|
||||||
|
Error::DiscriminatorChangeRatelimited => Status::TooManyRequests,
|
||||||
Error::UsernameTaken => Status::Conflict,
|
Error::UsernameTaken => Status::Conflict,
|
||||||
Error::AlreadyFriends => Status::Conflict,
|
Error::AlreadyFriends => Status::Conflict,
|
||||||
Error::AlreadySentRequest => Status::Conflict,
|
Error::AlreadySentRequest => Status::Conflict,
|
||||||
@@ -191,6 +196,7 @@ impl<'r> Responder<'r, 'static> for Error {
|
|||||||
Error::TooManyEmoji { .. } => Status::BadRequest,
|
Error::TooManyEmoji { .. } => Status::BadRequest,
|
||||||
Error::TooManyChannels { .. } => Status::BadRequest,
|
Error::TooManyChannels { .. } => Status::BadRequest,
|
||||||
Error::TooManyRoles { .. } => Status::BadRequest,
|
Error::TooManyRoles { .. } => Status::BadRequest,
|
||||||
|
Error::TooManyEmbeds { .. } => Status::BadRequest,
|
||||||
|
|
||||||
Error::ReachedMaximumBots => Status::BadRequest,
|
Error::ReachedMaximumBots => Status::BadRequest,
|
||||||
Error::IsBot => Status::BadRequest,
|
Error::IsBot => Status::BadRequest,
|
||||||
|
|||||||
@@ -1,56 +1,141 @@
|
|||||||
use std::env;
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
use std::env;
|
||||||
|
|
||||||
// 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 PUBLIC_URL: Lazy<String> = Lazy::new(|| {
|
||||||
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 APP_URL: Lazy<String> =
|
||||||
|
Lazy::new(|| env::var("REVOLT_APP_URL").expect("Missing REVOLT_APP_URL environment variable."));
|
||||||
|
pub static EXTERNAL_WS_URL: Lazy<String> = Lazy::new(|| {
|
||||||
|
env::var("REVOLT_EXTERNAL_WS_URL")
|
||||||
|
.expect("Missing REVOLT_EXTERNAL_WS_URL environment variable.")
|
||||||
|
});
|
||||||
|
|
||||||
pub static AUTUMN_URL: Lazy<String> = Lazy::new(|| env::var("AUTUMN_PUBLIC_URL").unwrap_or_else(|_| "https://example.com".to_string()));
|
pub static AUTUMN_URL: Lazy<String> = Lazy::new(|| {
|
||||||
pub static JANUARY_URL: Lazy<String> = Lazy::new(|| env::var("JANUARY_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_CONCURRENT_CONNECTIONS: Lazy<usize> = Lazy::new(|| env::var("JANUARY_CONCURRENT_CONNECTIONS").map_or(50, |v| v.parse().unwrap()));
|
});
|
||||||
pub static VOSO_URL: Lazy<String> = Lazy::new(|| env::var("VOSO_PUBLIC_URL").unwrap_or_else(|_| "https://example.com".to_string()));
|
pub static JANUARY_URL: Lazy<String> = Lazy::new(|| {
|
||||||
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_PUBLIC_URL").unwrap_or_else(|_| "https://example.com".to_string())
|
||||||
pub static VOSO_MANAGE_TOKEN: Lazy<String> = Lazy::new(|| env::var("VOSO_MANAGE_TOKEN").unwrap_or_else(|_| "0".to_string()));
|
});
|
||||||
|
pub static JANUARY_CONCURRENT_CONNECTIONS: Lazy<usize> =
|
||||||
|
Lazy::new(|| env::var("JANUARY_CONCURRENT_CONNECTIONS").map_or(50, |v| v.parse().unwrap()));
|
||||||
|
pub static VOSO_URL: Lazy<String> =
|
||||||
|
Lazy::new(|| env::var("VOSO_PUBLIC_URL").unwrap_or_else(|_| "https://example.com".to_string()));
|
||||||
|
pub static VOSO_WS_HOST: Lazy<String> =
|
||||||
|
Lazy::new(|| env::var("VOSO_WS_HOST").unwrap_or_else(|_| "wss://example.com".to_string()));
|
||||||
|
pub static VOSO_MANAGE_TOKEN: Lazy<String> =
|
||||||
|
Lazy::new(|| env::var("VOSO_MANAGE_TOKEN").unwrap_or_else(|_| "0".to_string()));
|
||||||
|
|
||||||
pub static HCAPTCHA_KEY: Lazy<String> = Lazy::new(|| env::var("REVOLT_HCAPTCHA_KEY").unwrap_or_else(|_| "0x0000000000000000000000000000000000000000".to_string()));
|
pub static HCAPTCHA_KEY: Lazy<String> = Lazy::new(|| {
|
||||||
pub static HCAPTCHA_SITEKEY: Lazy<String> = Lazy::new(|| env::var("REVOLT_HCAPTCHA_SITEKEY").unwrap_or_else(|_| "10000000-ffff-ffff-ffff-000000000001".to_string()));
|
env::var("REVOLT_HCAPTCHA_KEY")
|
||||||
pub static VAPID_PRIVATE_KEY: Lazy<String> = Lazy::new(|| env::var("REVOLT_VAPID_PRIVATE_KEY").expect("Missing REVOLT_VAPID_PRIVATE_KEY environment variable."));
|
.unwrap_or_else(|_| "0x0000000000000000000000000000000000000000".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 AUTHIFIER_SHIELD_KEY: Lazy<Option<String>> = Lazy::new(|| env::var("REVOLT_AUTHIFIER_SHIELD_KEY").ok());
|
pub static HCAPTCHA_SITEKEY: Lazy<String> = Lazy::new(|| {
|
||||||
|
env::var("REVOLT_HCAPTCHA_SITEKEY")
|
||||||
|
.unwrap_or_else(|_| "10000000-ffff-ffff-ffff-000000000001".to_string())
|
||||||
|
});
|
||||||
|
pub static VAPID_PRIVATE_KEY: Lazy<String> = Lazy::new(|| {
|
||||||
|
env::var("REVOLT_VAPID_PRIVATE_KEY")
|
||||||
|
.expect("Missing REVOLT_VAPID_PRIVATE_KEY environment variable.")
|
||||||
|
});
|
||||||
|
pub static VAPID_PUBLIC_KEY: Lazy<String> = Lazy::new(|| {
|
||||||
|
env::var("REVOLT_VAPID_PUBLIC_KEY")
|
||||||
|
.expect("Missing REVOLT_VAPID_PUBLIC_KEY environment variable.")
|
||||||
|
});
|
||||||
|
pub static AUTHIFIER_SHIELD_KEY: Lazy<Option<String>> =
|
||||||
|
Lazy::new(|| env::var("REVOLT_AUTHIFIER_SHIELD_KEY").ok());
|
||||||
|
|
||||||
// Application Flags
|
// Application Flags
|
||||||
pub static INVITE_ONLY: Lazy<bool> = Lazy::new(|| env::var("REVOLT_INVITE_ONLY").map_or(false, |v| v == "1"));
|
pub static INVITE_ONLY: Lazy<bool> =
|
||||||
pub static USE_EMAIL: Lazy<bool> = Lazy::new(|| env::var("REVOLT_USE_EMAIL_VERIFICATION").map_or(
|
Lazy::new(|| env::var("REVOLT_INVITE_ONLY").map_or(false, |v| v == "1"));
|
||||||
|
pub static USE_EMAIL: Lazy<bool> = Lazy::new(|| {
|
||||||
|
env::var("REVOLT_USE_EMAIL_VERIFICATION").map_or(
|
||||||
env::var("REVOLT_SMTP_HOST").is_ok()
|
env::var("REVOLT_SMTP_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 USE_HCAPTCHA: Lazy<bool> = Lazy::new(|| env::var("REVOLT_HCAPTCHA_KEY").is_ok());
|
pub static USE_HCAPTCHA: Lazy<bool> = Lazy::new(|| env::var("REVOLT_HCAPTCHA_KEY").is_ok());
|
||||||
pub static USE_AUTUMN: Lazy<bool> = Lazy::new(|| env::var("AUTUMN_PUBLIC_URL").is_ok());
|
pub static USE_AUTUMN: Lazy<bool> = Lazy::new(|| env::var("AUTUMN_PUBLIC_URL").is_ok());
|
||||||
pub static USE_JANUARY: Lazy<bool> = Lazy::new(|| env::var("JANUARY_PUBLIC_URL").is_ok());
|
pub static USE_JANUARY: Lazy<bool> = Lazy::new(|| env::var("JANUARY_PUBLIC_URL").is_ok());
|
||||||
pub static USE_VOSO: Lazy<bool> = Lazy::new(|| env::var("VOSO_PUBLIC_URL").is_ok() && env::var("VOSO_MANAGE_TOKEN").is_ok());
|
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 SMTP_HOST: Lazy<String> = Lazy::new(|| env::var("REVOLT_SMTP_HOST").unwrap_or_else(|_| "".to_string()));
|
pub static SMTP_HOST: Lazy<String> =
|
||||||
pub static SMTP_USERNAME: Lazy<String> = Lazy::new(|| env::var("REVOLT_SMTP_USERNAME").unwrap_or_else(|_| "".to_string()));
|
Lazy::new(|| env::var("REVOLT_SMTP_HOST").unwrap_or_else(|_| "".to_string()));
|
||||||
pub static SMTP_PASSWORD: Lazy<String> = Lazy::new(|| env::var("REVOLT_SMTP_PASSWORD").unwrap_or_else(|_| "".to_string()));
|
pub static SMTP_USERNAME: Lazy<String> =
|
||||||
pub static SMTP_FROM: Lazy<String> = Lazy::new(|| env::var("REVOLT_SMTP_FROM").unwrap_or_else(|_| "".to_string()));
|
Lazy::new(|| env::var("REVOLT_SMTP_USERNAME").unwrap_or_else(|_| "".to_string()));
|
||||||
|
pub static SMTP_PASSWORD: Lazy<String> =
|
||||||
|
Lazy::new(|| env::var("REVOLT_SMTP_PASSWORD").unwrap_or_else(|_| "".to_string()));
|
||||||
|
pub static SMTP_FROM: Lazy<String> =
|
||||||
|
Lazy::new(|| env::var("REVOLT_SMTP_FROM").unwrap_or_else(|_| "".to_string()));
|
||||||
|
|
||||||
// Application Logic Settings
|
// Application Logic Settings
|
||||||
pub static MAX_GROUP_SIZE: Lazy<usize> = Lazy::new(|| env::var("REVOLT_MAX_GROUP_SIZE").unwrap_or_else(|_| "50".to_string()).parse().unwrap());
|
pub static MAX_GROUP_SIZE: Lazy<usize> = Lazy::new(|| {
|
||||||
pub static MAX_BOT_COUNT: Lazy<usize> = Lazy::new(|| env::var("REVOLT_MAX_BOT_COUNT").unwrap_or_else(|_| "10".to_string()).parse().unwrap());
|
env::var("REVOLT_MAX_GROUP_SIZE")
|
||||||
pub static MAX_EMBED_COUNT: Lazy<usize> = Lazy::new(|| env::var("REVOLT_MAX_EMBED_COUNT").unwrap_or_else(|_| "5".to_string()).parse().unwrap());
|
.unwrap_or_else(|_| "50".to_string())
|
||||||
pub static MAX_SERVER_COUNT: Lazy<usize> = Lazy::new(|| env::var("REVOLT_MAX_SERVER_COUNT").unwrap_or_else(|_| "100".to_string()).parse().unwrap());
|
.parse()
|
||||||
pub static MAX_CHANNEL_COUNT: Lazy<usize> = Lazy::new(|| env::var("REVOLT_MAX_CHANNEL_COUNT").unwrap_or_else(|_| "200".to_string()).parse().unwrap());
|
.unwrap()
|
||||||
pub static MAX_ROLE_COUNT: Lazy<usize> = Lazy::new(|| env::var("REVOLT_MAX_ROLE_COUNT").unwrap_or_else(|_| "200".to_string()).parse().unwrap());
|
});
|
||||||
pub static MAX_EMOJI_COUNT: Lazy<usize> = Lazy::new(|| env::var("REVOLT_MAX_EMOJI_COUNT").unwrap_or_else(|_| "100".to_string()).parse().unwrap());
|
pub static MAX_BOT_COUNT: Lazy<usize> = Lazy::new(|| {
|
||||||
pub static MAX_ATTACHMENT_COUNT: Lazy<usize> = Lazy::new(|| env::var("REVOLT_MAX_ATTACHMENT_COUNT").unwrap_or_else(|_| "5".to_string()).parse().unwrap());
|
env::var("REVOLT_MAX_BOT_COUNT")
|
||||||
pub static MAX_REPLY_COUNT: Lazy<usize> = Lazy::new(|| env::var("REVOLT_MAX_REPLY_COUNT").unwrap_or_else(|_| "5".to_string()).parse().unwrap());
|
.unwrap_or_else(|_| "10".to_string())
|
||||||
|
.parse()
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
|
pub static MAX_EMBED_COUNT: Lazy<usize> = Lazy::new(|| {
|
||||||
|
env::var("REVOLT_MAX_EMBED_COUNT")
|
||||||
|
.unwrap_or_else(|_| "5".to_string())
|
||||||
|
.parse()
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
|
pub static MAX_SERVER_COUNT: Lazy<usize> = Lazy::new(|| {
|
||||||
|
env::var("REVOLT_MAX_SERVER_COUNT")
|
||||||
|
.unwrap_or_else(|_| "100".to_string())
|
||||||
|
.parse()
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
|
pub static MAX_CHANNEL_COUNT: Lazy<usize> = Lazy::new(|| {
|
||||||
|
env::var("REVOLT_MAX_CHANNEL_COUNT")
|
||||||
|
.unwrap_or_else(|_| "200".to_string())
|
||||||
|
.parse()
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
|
pub static MAX_ROLE_COUNT: Lazy<usize> = Lazy::new(|| {
|
||||||
|
env::var("REVOLT_MAX_ROLE_COUNT")
|
||||||
|
.unwrap_or_else(|_| "200".to_string())
|
||||||
|
.parse()
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
|
pub static MAX_EMOJI_COUNT: Lazy<usize> = Lazy::new(|| {
|
||||||
|
env::var("REVOLT_MAX_EMOJI_COUNT")
|
||||||
|
.unwrap_or_else(|_| "100".to_string())
|
||||||
|
.parse()
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
|
pub static MAX_ATTACHMENT_COUNT: Lazy<usize> = Lazy::new(|| {
|
||||||
|
env::var("REVOLT_MAX_ATTACHMENT_COUNT")
|
||||||
|
.unwrap_or_else(|_| "5".to_string())
|
||||||
|
.parse()
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
|
pub static MAX_REPLY_COUNT: Lazy<usize> = Lazy::new(|| {
|
||||||
|
env::var("REVOLT_MAX_REPLY_COUNT")
|
||||||
|
.unwrap_or_else(|_| "5".to_string())
|
||||||
|
.parse()
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
|
|
||||||
pub static EARLY_ADOPTER_BADGE: Lazy<i64> = Lazy::new(|| env::var("REVOLT_EARLY_ADOPTER_BADGE").unwrap_or_else(|_| "0".to_string()).parse().unwrap());
|
pub static EARLY_ADOPTER_BADGE: Lazy<i64> = Lazy::new(|| {
|
||||||
|
env::var("REVOLT_EARLY_ADOPTER_BADGE")
|
||||||
|
.unwrap_or_else(|_| "0".to_string())
|
||||||
|
.parse()
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
|
|
||||||
pub fn preflight_checks() {
|
pub fn preflight_checks() {
|
||||||
format!("url = {}", *APP_URL);
|
format!("url = {}", *APP_URL);
|
||||||
@@ -80,3 +165,7 @@ pub fn preflight_checks() {
|
|||||||
warn!("No Captcha key specified! Remember to add hCaptcha key.");
|
warn!("No Captcha key specified! Remember to add hCaptcha key.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Production / staging configuration
|
||||||
|
pub static IS_STAGING: Lazy<bool> =
|
||||||
|
Lazy::new(|| env::var("REVOLT_IS_STAGING").map_or(false, |v| v == "1"));
|
||||||
|
|||||||
@@ -101,16 +101,19 @@ pub struct Ratelimiter {
|
|||||||
fn resolve_bucket<'r>(request: &'r rocket::Request<'_>) -> (&'r str, Option<&'r str>) {
|
fn resolve_bucket<'r>(request: &'r rocket::Request<'_>) -> (&'r str, Option<&'r str>) {
|
||||||
if let Some(segment) = request.routed_segment(0) {
|
if let Some(segment) = request.routed_segment(0) {
|
||||||
let resource = request.routed_segment(1);
|
let resource = request.routed_segment(1);
|
||||||
match (segment, resource) {
|
|
||||||
("users", _) => {
|
let method = request.method();
|
||||||
|
match (segment, resource, method) {
|
||||||
|
("users", target, Method::Patch) => ("user_edit", target),
|
||||||
|
("users", _, _) => {
|
||||||
if let Some("default_avatar") = request.routed_segment(2) {
|
if let Some("default_avatar") = request.routed_segment(2) {
|
||||||
return ("default_avatar", None);
|
return ("default_avatar", None);
|
||||||
}
|
}
|
||||||
|
|
||||||
("users", None)
|
("users", None)
|
||||||
}
|
}
|
||||||
("bots", _) => ("bots", None),
|
("bots", _, _) => ("bots", None),
|
||||||
("channels", Some(id)) => {
|
("channels", Some(id), _) => {
|
||||||
if request.method() == Method::Post {
|
if request.method() == Method::Post {
|
||||||
if let Some("messages") = request.routed_segment(2) {
|
if let Some("messages") = request.routed_segment(2) {
|
||||||
return ("messaging", Some(id));
|
return ("messaging", Some(id));
|
||||||
@@ -119,17 +122,17 @@ fn resolve_bucket<'r>(request: &'r rocket::Request<'_>) -> (&'r str, Option<&'r
|
|||||||
|
|
||||||
("channels", Some(id))
|
("channels", Some(id))
|
||||||
}
|
}
|
||||||
("servers", Some(id)) => ("servers", Some(id)),
|
("servers", Some(id), _) => ("servers", Some(id)),
|
||||||
("auth", _) => {
|
("auth", _, _) => {
|
||||||
if request.method() == Method::Delete {
|
if request.method() == Method::Delete {
|
||||||
("auth_delete", None)
|
("auth_delete", None)
|
||||||
} else {
|
} else {
|
||||||
("auth", None)
|
("auth", None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
("swagger", _) => ("swagger", None),
|
("swagger", _, _) => ("swagger", None),
|
||||||
("safety", Some("report")) => ("safety_report", Some("report")),
|
("safety", Some("report"), _) => ("safety_report", Some("report")),
|
||||||
("safety", _) => ("safety", None),
|
("safety", _, _) => ("safety", None),
|
||||||
_ => ("any", None),
|
_ => ("any", None),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -140,6 +143,7 @@ fn resolve_bucket<'r>(request: &'r rocket::Request<'_>) -> (&'r str, Option<&'r
|
|||||||
/// Resolve per-bucket limits
|
/// Resolve per-bucket limits
|
||||||
fn resolve_bucket_limit(bucket: &str) -> u8 {
|
fn resolve_bucket_limit(bucket: &str) -> u8 {
|
||||||
match bucket {
|
match bucket {
|
||||||
|
"user_edit" => 2,
|
||||||
"users" => 20,
|
"users" => 20,
|
||||||
"bots" => 10,
|
"bots" => 10,
|
||||||
"messaging" => 10,
|
"messaging" => 10,
|
||||||
|
|||||||
Reference in New Issue
Block a user