Compare commits
31
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb813e91f0 | ||
|
|
a6bc9fcbb3 | ||
|
|
cd0737911b | ||
|
|
7705354d4d | ||
|
|
39ac8b2278 | ||
|
|
c5494aa1ca | ||
|
|
1a96bf5a4b | ||
|
|
37a1244751 | ||
|
|
bf39f18f8d | ||
|
|
95be7dc87a | ||
|
|
9be171c7b6 | ||
|
|
786fbb8ae4 | ||
|
|
6c0210e8a1 | ||
|
|
456bf7b42a | ||
|
|
21ffea0f9f | ||
|
|
96d90215d2 | ||
|
|
29dcf714f2 | ||
|
|
cb97004d3f | ||
|
|
0b6e5dd9db | ||
|
|
7132877201 | ||
|
|
4d42fb74e7 | ||
|
|
866ace3381 | ||
|
|
0798e81862 | ||
|
|
2fa5ac41ac | ||
|
|
25ae1555a2 | ||
|
|
881888bd1c | ||
|
|
41f20c2239 | ||
|
|
38f1381fea | ||
|
|
6838b0b1f0 | ||
|
|
055deab105 | ||
|
|
6d2073120d |
@@ -0,0 +1,2 @@
|
|||||||
|
[profile.default]
|
||||||
|
slow-timeout = { period = "3s", terminate-after = 2 }
|
||||||
+20
-13
@@ -20,6 +20,11 @@ jobs:
|
|||||||
toolchain: stable
|
toolchain: stable
|
||||||
override: true
|
override: true
|
||||||
components: rustfmt, clippy
|
components: rustfmt, clippy
|
||||||
|
- name: Install cargo-nextest
|
||||||
|
uses: baptiste0928/cargo-install@v1
|
||||||
|
with:
|
||||||
|
crate: cargo-nextest
|
||||||
|
locked: true
|
||||||
|
|
||||||
- name: Run cargo build
|
- name: Run cargo build
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
@@ -30,25 +35,27 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
docker-compose -f docker-compose.db.yml up -d
|
docker-compose -f docker-compose.db.yml up -d
|
||||||
|
|
||||||
- name: Run cargo test
|
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
command: test
|
|
||||||
|
|
||||||
- name: Run cargo test (with MongoDB)
|
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
env:
|
|
||||||
MONGODB: mongodb://localhost
|
|
||||||
with:
|
|
||||||
command: test
|
|
||||||
|
|
||||||
- name: Copy .env.example
|
- name: Copy .env.example
|
||||||
if: github.event_name != 'pull_request' && github.ref_name == 'master'
|
|
||||||
run: |
|
run: |
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
|
|
||||||
|
- name: Run cargo test
|
||||||
|
env:
|
||||||
|
TEST_DB: REFERENCE
|
||||||
|
run: |
|
||||||
|
cargo nextest run
|
||||||
|
|
||||||
|
- name: Run cargo test (with MongoDB)
|
||||||
|
env:
|
||||||
|
TEST_DB: MONGODB
|
||||||
|
MONGODB: mongodb://localhost
|
||||||
|
run: |
|
||||||
|
cargo nextest run
|
||||||
|
|
||||||
- name: Start API in background
|
- name: Start API in background
|
||||||
if: github.event_name != 'pull_request' && github.ref_name == 'master'
|
if: github.event_name != 'pull_request' && github.ref_name == 'master'
|
||||||
|
env:
|
||||||
|
TEST_DB: REFERENCE
|
||||||
run: |
|
run: |
|
||||||
cargo run --bin revolt-delta &
|
cargo run --bin revolt-delta &
|
||||||
|
|
||||||
|
|||||||
Vendored
+2
-1
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"rust-analyzer.checkOnSave.command": "clippy",
|
"rust-analyzer.checkOnSave.command": "clippy",
|
||||||
"nixEnvSelector.suggestion": false
|
"nixEnvSelector.suggestion": false,
|
||||||
|
"nixEnvSelector.nixFile": "${workspaceRoot}/default.nix"
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+14
-13
@@ -347,9 +347,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "authifier"
|
name = "authifier"
|
||||||
version = "1.0.7"
|
version = "1.0.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a84411393a4326726ead660991ce8047f1865fa89900fb6c18e1e6c513cf1f9b"
|
checksum = "30269caf0aaf1e1b542b150030e9688bf41d50026e09a51efd9408f332636c9d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-std",
|
"async-std",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@@ -3494,7 +3494,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "revolt-bonfire"
|
name = "revolt-bonfire"
|
||||||
version = "0.6.8"
|
version = "0.6.9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-std",
|
"async-std",
|
||||||
"async-tungstenite",
|
"async-tungstenite",
|
||||||
@@ -3512,7 +3512,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "revolt-config"
|
name = "revolt-config"
|
||||||
version = "0.6.8"
|
version = "0.6.9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-std",
|
"async-std",
|
||||||
"cached",
|
"cached",
|
||||||
@@ -3524,7 +3524,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "revolt-database"
|
name = "revolt-database"
|
||||||
version = "0.6.8"
|
version = "0.6.9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-lock",
|
"async-lock",
|
||||||
"async-recursion",
|
"async-recursion",
|
||||||
@@ -3569,10 +3569,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "revolt-delta"
|
name = "revolt-delta"
|
||||||
version = "0.6.8"
|
version = "0.6.9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-channel",
|
"async-channel",
|
||||||
"async-std",
|
"async-std",
|
||||||
|
"authifier",
|
||||||
"bitfield",
|
"bitfield",
|
||||||
"chrono",
|
"chrono",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
@@ -3614,7 +3615,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "revolt-models"
|
name = "revolt-models"
|
||||||
version = "0.6.8"
|
version = "0.6.9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"iso8601-timestamp 0.2.11",
|
"iso8601-timestamp 0.2.11",
|
||||||
@@ -3630,7 +3631,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "revolt-permissions"
|
name = "revolt-permissions"
|
||||||
version = "0.6.8"
|
version = "0.6.9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-std",
|
"async-std",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@@ -3645,7 +3646,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "revolt-presence"
|
name = "revolt-presence"
|
||||||
version = "0.6.8"
|
version = "0.6.9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-std",
|
"async-std",
|
||||||
"log",
|
"log",
|
||||||
@@ -3656,7 +3657,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "revolt-quark"
|
name = "revolt-quark"
|
||||||
version = "0.6.8"
|
version = "0.6.9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-lock",
|
"async-lock",
|
||||||
"async-recursion",
|
"async-recursion",
|
||||||
@@ -3709,7 +3710,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "revolt-result"
|
name = "revolt-result"
|
||||||
version = "0.6.8"
|
version = "0.6.9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"revolt_okapi",
|
"revolt_okapi",
|
||||||
"revolt_rocket_okapi",
|
"revolt_rocket_okapi",
|
||||||
@@ -3859,9 +3860,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rocket_authifier"
|
name = "rocket_authifier"
|
||||||
version = "1.0.7"
|
version = "1.0.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d4554a81937341d8c3cd3e43431ac2d23624444dc3a88e7ffd3ff66593779293"
|
checksum = "5f89a12311f60e9288833fc3ce6029bce5d5c61870ceef74d4a50668a8b520ad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"authifier",
|
"authifier",
|
||||||
"iso8601-timestamp 0.1.10",
|
"iso8601-timestamp 0.1.10",
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
|
resolver = "2"
|
||||||
members = ["crates/delta", "crates/bonfire", "crates/quark", "crates/core/*"]
|
members = ["crates/delta", "crates/bonfire", "crates/quark", "crates/core/*"]
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
# mobc-redis = { git = "https://github.com/insertish/mobc", rev = "8b880bb59f2ba80b4c7bc40c649c113d8857a186" }
|
# mobc-redis = { git = "https://github.com/insertish/mobc", rev = "8b880bb59f2ba80b4c7bc40c649c113d8857a186" }
|
||||||
redis22 = { package = "redis", version = "0.22.3", git = "https://github.com/revoltchat/redis-rs", rev = "1a41faf356fd21aebba71cea7eb7eb2653e5f0ef" }
|
redis22 = { package = "redis", version = "0.22.3", git = "https://github.com/revoltchat/redis-rs", rev = "1a41faf356fd21aebba71cea7eb7eb2653e5f0ef" }
|
||||||
redis23 = { package = "redis", version = "0.23.1", git = "https://github.com/revoltchat/redis-rs", rev = "f8ca28ab85da59d2ccde526b4d2fb390eff5a5f9" }
|
redis23 = { package = "redis", version = "0.23.1", git = "https://github.com/revoltchat/redis-rs", rev = "f8ca28ab85da59d2ccde526b4d2fb390eff5a5f9" }
|
||||||
|
# authifier = { package = "authifier", version = "1.0.8", path = "../authifier/crates/authifier" }
|
||||||
|
# rocket_authifier = { package = "rocket_authifier", version = "1.0.8", path = "../authifier/crates/rocket_authifier" }
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ disallowed-methods = [
|
|||||||
# Shouldn't need to access these directly
|
# Shouldn't need to access these directly
|
||||||
"revolt_database::models::bots::model::Bot::remove_field",
|
"revolt_database::models::bots::model::Bot::remove_field",
|
||||||
"revolt_database::models::messages::model::Message::attach_sendable_embed",
|
"revolt_database::models::messages::model::Message::attach_sendable_embed",
|
||||||
|
"revolt_database::models::users::model::User::set_relationship",
|
||||||
|
"revolt_database::models::users::model::User::apply_relationship",
|
||||||
|
|
||||||
# Prefer to use Object::create()
|
# Prefer to use Object::create()
|
||||||
"revolt_database::models::bots::ops::AbstractBots::insert_bot",
|
"revolt_database::models::bots::ops::AbstractBots::insert_bot",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "revolt-bonfire"
|
name = "revolt-bonfire"
|
||||||
version = "0.6.8"
|
version = "0.6.9"
|
||||||
license = "AGPL-3.0-or-later"
|
license = "AGPL-3.0-or-later"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "revolt-config"
|
name = "revolt-config"
|
||||||
version = "0.6.8"
|
version = "0.6.9"
|
||||||
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>"]
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
[database]
|
||||||
|
mongodb = "mongodb://localhost"
|
||||||
|
redis = "redis://localhost/"
|
||||||
@@ -13,7 +13,12 @@ static CONFIG_BUILDER: Lazy<RwLock<Config>> = Lazy::new(|| {
|
|||||||
FileFormat::Toml,
|
FileFormat::Toml,
|
||||||
));
|
));
|
||||||
|
|
||||||
if std::path::Path::new("Revolt.toml").exists() {
|
if std::env::var("TEST_DB").is_ok() {
|
||||||
|
builder = builder.add_source(File::from_str(
|
||||||
|
include_str!("../Revolt.test.toml"),
|
||||||
|
FileFormat::Toml,
|
||||||
|
));
|
||||||
|
} else if std::path::Path::new("Revolt.toml").exists() {
|
||||||
builder = builder.add_source(File::new("Revolt.toml", FileFormat::Toml));
|
builder = builder.add_source(File::new("Revolt.toml", FileFormat::Toml));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "revolt-database"
|
name = "revolt-database"
|
||||||
version = "0.6.8"
|
version = "0.6.9"
|
||||||
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>"]
|
||||||
@@ -23,11 +23,11 @@ default = ["mongodb", "async-std-runtime", "tasks"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# Core
|
# Core
|
||||||
revolt-config = { version = "0.6.8", path = "../config" }
|
revolt-config = { version = "0.6.9", path = "../config" }
|
||||||
revolt-result = { version = "0.6.8", path = "../result" }
|
revolt-result = { version = "0.6.9", path = "../result" }
|
||||||
revolt-models = { version = "0.6.8", path = "../models" }
|
revolt-models = { version = "0.6.9", path = "../models" }
|
||||||
revolt-presence = { version = "0.6.8", path = "../presence" }
|
revolt-presence = { version = "0.6.9", path = "../presence" }
|
||||||
revolt-permissions = { version = "0.6.8", path = "../permissions", features = [
|
revolt-permissions = { version = "0.6.9", path = "../permissions", features = [
|
||||||
"serde",
|
"serde",
|
||||||
"bson",
|
"bson",
|
||||||
] }
|
] }
|
||||||
@@ -86,4 +86,4 @@ fcm = "0.9.2"
|
|||||||
web-push = "0.10.0"
|
web-push = "0.10.0"
|
||||||
|
|
||||||
# Authifier
|
# Authifier
|
||||||
authifier = { version = "1.0" }
|
authifier = { version = "1.0.8" }
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"_object_type": "User",
|
||||||
|
"_id": "__ID:0__",
|
||||||
|
"username": "Owner",
|
||||||
|
"discriminator": "0001"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_object_type": "User",
|
||||||
|
"_id": "__ID:1__",
|
||||||
|
"username": "Moderator",
|
||||||
|
"discriminator": "0001"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_object_type": "User",
|
||||||
|
"_id": "__ID:2__",
|
||||||
|
"username": "User",
|
||||||
|
"discriminator": "0001"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_object_type": "Channel",
|
||||||
|
"_id": "__ID:3__",
|
||||||
|
"channel_type": "TextChannel",
|
||||||
|
"name": "General",
|
||||||
|
"server": "__ID:4__",
|
||||||
|
"default_permissions": {
|
||||||
|
"a": 0,
|
||||||
|
"d": 1048576
|
||||||
|
},
|
||||||
|
"role_permissions": {
|
||||||
|
"__ID:5__": {
|
||||||
|
"a": 1048576,
|
||||||
|
"d": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_object_type": "Server",
|
||||||
|
"_id": "__ID:4__",
|
||||||
|
"owner": "__ID:0__",
|
||||||
|
"name": "Server",
|
||||||
|
"channels": ["__ID:3__"],
|
||||||
|
"roles": {
|
||||||
|
"__ID:5__": {
|
||||||
|
"name": "Moderator",
|
||||||
|
"permissions": {
|
||||||
|
"a": 545270208,
|
||||||
|
"d": 0
|
||||||
|
},
|
||||||
|
"rank": 3
|
||||||
|
},
|
||||||
|
"__ID:6__": {
|
||||||
|
"name": "Owner",
|
||||||
|
"permissions": {
|
||||||
|
"a": 0,
|
||||||
|
"d": 0
|
||||||
|
},
|
||||||
|
"rank": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"default_permissions": 4000322560
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_object_type": "ServerMember",
|
||||||
|
"_id": {
|
||||||
|
"user": "__ID:0__",
|
||||||
|
"server": "__ID:4__"
|
||||||
|
},
|
||||||
|
"roles": ["__ID:6__"],
|
||||||
|
"joined_at": 1698318340195
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_object_type": "ServerMember",
|
||||||
|
"_id": {
|
||||||
|
"user": "__ID:1__",
|
||||||
|
"server": "__ID:4__"
|
||||||
|
},
|
||||||
|
"roles": ["__ID:5__"],
|
||||||
|
"joined_at": 1698318340195
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_object_type": "ServerMember",
|
||||||
|
"_id": {
|
||||||
|
"user": "__ID:2__",
|
||||||
|
"server": "__ID:4__"
|
||||||
|
},
|
||||||
|
"joined_at": 1698318340195
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
mod mongodb;
|
mod mongodb;
|
||||||
mod reference;
|
mod reference;
|
||||||
|
|
||||||
|
use rand::Rng;
|
||||||
|
use revolt_config::config;
|
||||||
|
|
||||||
pub use self::mongodb::*;
|
pub use self::mongodb::*;
|
||||||
pub use self::reference::*;
|
pub use self::reference::*;
|
||||||
|
|
||||||
@@ -31,25 +34,44 @@ impl DatabaseInfo {
|
|||||||
/// Create a database client from the given database information
|
/// Create a database client from the given database information
|
||||||
#[async_recursion]
|
#[async_recursion]
|
||||||
pub async fn connect(self) -> Result<Database, String> {
|
pub async fn connect(self) -> Result<Database, String> {
|
||||||
|
let config = config().await;
|
||||||
|
|
||||||
Ok(match self {
|
Ok(match self {
|
||||||
DatabaseInfo::Auto => {
|
DatabaseInfo::Auto => {
|
||||||
if let Ok(uri) = std::env::var("MONGODB") {
|
if std::env::var("TEST_DB").is_ok() {
|
||||||
return DatabaseInfo::MongoDb {
|
DatabaseInfo::Test(format!(
|
||||||
uri,
|
"revolt_test_{}",
|
||||||
|
rand::thread_rng().gen_range(1_000_000..10_000_000)
|
||||||
|
))
|
||||||
|
.connect()
|
||||||
|
.await?
|
||||||
|
} else if !config.database.mongodb.is_empty() {
|
||||||
|
DatabaseInfo::MongoDb {
|
||||||
|
uri: config.database.mongodb,
|
||||||
database_name: "revolt".to_string(),
|
database_name: "revolt".to_string(),
|
||||||
}
|
}
|
||||||
.connect()
|
.connect()
|
||||||
.await;
|
.await?
|
||||||
|
} else {
|
||||||
|
DatabaseInfo::Reference.connect().await?
|
||||||
}
|
}
|
||||||
|
|
||||||
DatabaseInfo::Reference.connect().await?
|
|
||||||
}
|
}
|
||||||
DatabaseInfo::Test(database_name) => {
|
DatabaseInfo::Test(database_name) => {
|
||||||
if let Ok(uri) = std::env::var("MONGODB") {
|
match std::env::var("TEST_DB")
|
||||||
return DatabaseInfo::MongoDb { uri, database_name }.connect().await;
|
.expect("`TEST_DB` environment variable should be set to REFERENCE or MONGODB")
|
||||||
|
.as_str()
|
||||||
|
{
|
||||||
|
"REFERENCE" => DatabaseInfo::Reference.connect().await?,
|
||||||
|
"MONGODB" => {
|
||||||
|
DatabaseInfo::MongoDb {
|
||||||
|
uri: config.database.mongodb,
|
||||||
|
database_name,
|
||||||
|
}
|
||||||
|
.connect()
|
||||||
|
.await?
|
||||||
|
}
|
||||||
|
_ => unreachable!("must specify REFERENCE or MONGODB"),
|
||||||
}
|
}
|
||||||
|
|
||||||
DatabaseInfo::Reference.connect().await?
|
|
||||||
}
|
}
|
||||||
DatabaseInfo::Reference => Database::Reference(Default::default()),
|
DatabaseInfo::Reference => Database::Reference(Default::default()),
|
||||||
DatabaseInfo::MongoDb { uri, database_name } => {
|
DatabaseInfo::MongoDb { uri, database_name } => {
|
||||||
@@ -65,14 +87,3 @@ impl DatabaseInfo {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Database> for authifier::Database {
|
|
||||||
fn from(value: Database) -> Self {
|
|
||||||
match value {
|
|
||||||
Database::Reference(_) => Default::default(),
|
|
||||||
Database::MongoDb(MongoDb(client, _)) => authifier::Database::MongoDb(
|
|
||||||
authifier::database::MongoDb(client.database("revolt")),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use futures::lock::Mutex;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Bot, Channel, ChannelCompositeKey, ChannelUnread, Emoji, File, Invite, Member,
|
Bot, Channel, ChannelCompositeKey, ChannelUnread, Emoji, File, Invite, Member,
|
||||||
MemberCompositeKey, Message, Server, ServerBan, User, UserSettings, Webhook,
|
MemberCompositeKey, Message, RatelimitEvent, Server, ServerBan, User, UserSettings, Webhook,
|
||||||
};
|
};
|
||||||
|
|
||||||
database_derived!(
|
database_derived!(
|
||||||
@@ -19,6 +19,7 @@ database_derived!(
|
|||||||
pub emojis: Arc<Mutex<HashMap<String, Emoji>>>,
|
pub emojis: Arc<Mutex<HashMap<String, Emoji>>>,
|
||||||
pub files: Arc<Mutex<HashMap<String, File>>>,
|
pub files: Arc<Mutex<HashMap<String, File>>>,
|
||||||
pub messages: Arc<Mutex<HashMap<String, Message>>>,
|
pub messages: Arc<Mutex<HashMap<String, Message>>>,
|
||||||
|
pub ratelimit_events: Arc<Mutex<HashMap<String, RatelimitEvent>>>,
|
||||||
pub user_settings: Arc<Mutex<HashMap<String, UserSettings>>>,
|
pub user_settings: Arc<Mutex<HashMap<String, UserSettings>>>,
|
||||||
pub users: Arc<Mutex<HashMap<String, User>>>,
|
pub users: Arc<Mutex<HashMap<String, User>>>,
|
||||||
pub server_bans: Arc<Mutex<HashMap<MemberCompositeKey, ServerBan>>>,
|
pub server_bans: Arc<Mutex<HashMap<MemberCompositeKey, ServerBan>>>,
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ use serde::{Deserialize, Serialize};
|
|||||||
use revolt_models::v0::{
|
use revolt_models::v0::{
|
||||||
AppendMessage, Channel, Emoji, FieldsChannel, FieldsMember, FieldsRole, FieldsServer,
|
AppendMessage, Channel, Emoji, FieldsChannel, FieldsMember, FieldsRole, FieldsServer,
|
||||||
FieldsUser, FieldsWebhook, MemberCompositeKey, Message, PartialChannel, PartialMember,
|
FieldsUser, FieldsWebhook, MemberCompositeKey, Message, PartialChannel, PartialMember,
|
||||||
PartialMessage, PartialRole, PartialServer, PartialUser, PartialWebhook, Server, UserSettings,
|
PartialMessage, PartialRole, PartialServer, PartialUser, PartialWebhook, Server, User,
|
||||||
Webhook,
|
UserSettings, Webhook,
|
||||||
};
|
};
|
||||||
use revolt_result::Error;
|
use revolt_result::Error;
|
||||||
|
|
||||||
@@ -154,17 +154,12 @@ pub enum EventV1 {
|
|||||||
event_id: Option<String>,
|
event_id: Option<String>,
|
||||||
},
|
},
|
||||||
|
|
||||||
/*/// Relationship with another user changed
|
/// Relationship with another user changed
|
||||||
UserRelationship {
|
UserRelationship { id: String, user: User },
|
||||||
id: String,
|
|
||||||
user: User,
|
|
||||||
// ! this field can be deprecated
|
|
||||||
status: RelationshipStatus,
|
|
||||||
},*/
|
|
||||||
/// Settings updated remotely
|
/// Settings updated remotely
|
||||||
UserSettingsUpdate { id: String, update: UserSettings },
|
UserSettingsUpdate { id: String, update: UserSettings },
|
||||||
|
|
||||||
/*/// User has been platform banned or deleted their account
|
/// User has been platform banned or deleted their account
|
||||||
///
|
///
|
||||||
/// Clients should remove the following associated data:
|
/// Clients should remove the following associated data:
|
||||||
/// - Messages
|
/// - Messages
|
||||||
@@ -173,7 +168,7 @@ pub enum EventV1 {
|
|||||||
/// - Server Memberships
|
/// - Server Memberships
|
||||||
///
|
///
|
||||||
/// User flags are specified to explain why a wipe is occurring though not all reasons will necessarily ever appear.
|
/// User flags are specified to explain why a wipe is occurring though not all reasons will necessarily ever appear.
|
||||||
UserPlatformWipe { user_id: String, flags: i32 }, */
|
UserPlatformWipe { user_id: String, flags: i32 },
|
||||||
/// New emoji
|
/// New emoji
|
||||||
EmojiCreate(Emoji),
|
EmojiCreate(Emoji),
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ mod tests {
|
|||||||
db.migrate_database().await.unwrap();
|
db.migrate_database().await.unwrap();
|
||||||
|
|
||||||
// Migrate the existing database
|
// Migrate the existing database
|
||||||
db.migrate_database().await.unwrap()
|
db.migrate_database().await.unwrap();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use revolt_config::config;
|
||||||
use revolt_result::Result;
|
use revolt_result::Result;
|
||||||
use ulid::Ulid;
|
use ulid::Ulid;
|
||||||
|
|
||||||
@@ -79,9 +80,8 @@ impl Bot {
|
|||||||
return Err(create_error!(IsBot));
|
return Err(create_error!(IsBot));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: config
|
let config = config().await;
|
||||||
let max_bot_count = 5;
|
if db.get_number_of_bots_by_user(&owner.id).await? >= config.features.limits.default.bots {
|
||||||
if db.get_number_of_bots_by_user(&owner.id).await? >= max_bot_count {
|
|
||||||
return Err(create_error!(ReachedMaximumBots));
|
return Err(create_error!(ReachedMaximumBots));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use revolt_config::config;
|
||||||
use revolt_models::v0::{self, MessageAuthor};
|
use revolt_models::v0::{self, MessageAuthor};
|
||||||
use revolt_permissions::OverrideField;
|
use revolt_permissions::OverrideField;
|
||||||
use revolt_result::Result;
|
use revolt_result::Result;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{events::client::EventV1, Database, File, IntoDocumentPath, SystemMessage, User};
|
use crate::{
|
||||||
|
events::client::EventV1, Database, File, IntoDocumentPath, PartialServer, Server,
|
||||||
|
SystemMessage, User,
|
||||||
|
};
|
||||||
|
|
||||||
auto_derived!(
|
auto_derived!(
|
||||||
#[serde(tag = "channel_type")]
|
#[serde(tag = "channel_type")]
|
||||||
@@ -188,12 +192,82 @@ impl Channel {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
|
/// Create a new server channel
|
||||||
|
pub async fn create_server_channel(
|
||||||
|
db: &Database,
|
||||||
|
server: &mut Server,
|
||||||
|
data: v0::DataCreateServerChannel,
|
||||||
|
update_server: bool,
|
||||||
|
) -> Result<Channel> {
|
||||||
|
let config = config().await;
|
||||||
|
if server.channels.len() > config.features.limits.default.server_channels {
|
||||||
|
return Err(create_error!(TooManyChannels {
|
||||||
|
max: config.features.limits.default.server_channels,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
let id = ulid::Ulid::new().to_string();
|
||||||
|
let channel = match data.channel_type {
|
||||||
|
v0::LegacyServerChannelType::Text => Channel::TextChannel {
|
||||||
|
id: id.clone(),
|
||||||
|
server: server.id.to_owned(),
|
||||||
|
name: data.name,
|
||||||
|
description: data.description,
|
||||||
|
icon: None,
|
||||||
|
last_message_id: None,
|
||||||
|
default_permissions: None,
|
||||||
|
role_permissions: HashMap::new(),
|
||||||
|
nsfw: data.nsfw.unwrap_or(false),
|
||||||
|
},
|
||||||
|
v0::LegacyServerChannelType::Voice => Channel::VoiceChannel {
|
||||||
|
id: id.clone(),
|
||||||
|
server: server.id.to_owned(),
|
||||||
|
name: data.name,
|
||||||
|
description: data.description,
|
||||||
|
icon: None,
|
||||||
|
default_permissions: None,
|
||||||
|
role_permissions: HashMap::new(),
|
||||||
|
nsfw: data.nsfw.unwrap_or(false),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
db.insert_channel(&channel).await?;
|
||||||
|
|
||||||
|
if update_server {
|
||||||
|
server
|
||||||
|
.update(
|
||||||
|
db,
|
||||||
|
PartialServer {
|
||||||
|
channels: Some([server.channels.clone(), [id].into()].concat()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
vec![],
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
EventV1::ChannelCreate(channel.clone().into())
|
||||||
|
.p(server.id.clone())
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(channel)
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a group
|
/// Create a group
|
||||||
pub async fn create_group(
|
pub async fn create_group(
|
||||||
db: &Database,
|
db: &Database,
|
||||||
data: v0::DataCreateGroup,
|
mut data: v0::DataCreateGroup,
|
||||||
owner_id: String,
|
owner_id: String,
|
||||||
) -> Result<Channel> {
|
) -> Result<Channel> {
|
||||||
|
data.users.insert(owner_id.to_string());
|
||||||
|
|
||||||
|
let config = config().await;
|
||||||
|
if data.users.len() > config.features.limits.default.group_size {
|
||||||
|
return Err(create_error!(GroupTooLarge {
|
||||||
|
max: config.features.limits.default.group_size,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
let recipients = data.users.into_iter().collect::<Vec<String>>();
|
let recipients = data.users.into_iter().collect::<Vec<String>>();
|
||||||
let channel = Channel::Group {
|
let channel = Channel::Group {
|
||||||
id: ulid::Ulid::new().to_string(),
|
id: ulid::Ulid::new().to_string(),
|
||||||
@@ -247,10 +321,6 @@ impl Channel {
|
|||||||
.p(id.to_string())
|
.p(id.to_string())
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
EventV1::ChannelCreate(self.clone().into())
|
|
||||||
.private(user.id.to_string())
|
|
||||||
.await;
|
|
||||||
|
|
||||||
SystemMessage::UserAdded {
|
SystemMessage::UserAdded {
|
||||||
id: user.id.to_string(),
|
id: user.id.to_string(),
|
||||||
by: by_id.to_string(),
|
by: by_id.to_string(),
|
||||||
@@ -268,6 +338,10 @@ impl Channel {
|
|||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
|
EventV1::ChannelCreate(self.clone().into())
|
||||||
|
.private(user.id.to_string())
|
||||||
|
.await;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
_ => Err(create_error!(InvalidOperation)),
|
_ => Err(create_error!(InvalidOperation)),
|
||||||
@@ -295,7 +369,7 @@ impl Channel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a reference to this channel's id
|
/// Clone this channel's id
|
||||||
pub fn id(&self) -> String {
|
pub fn id(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
Channel::DirectMessage { id, .. }
|
Channel::DirectMessage { id, .. }
|
||||||
@@ -614,3 +688,36 @@ impl IntoDocumentPath for FieldsChannel {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
|
||||||
|
|
||||||
|
use crate::{fixture, util::permissions::DatabasePermissionQuery};
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn permissions_text_channel() {
|
||||||
|
database_test!(|db| async move {
|
||||||
|
fixture!(db, "server_with_roles",
|
||||||
|
owner user 0
|
||||||
|
moderator user 1
|
||||||
|
user user 2
|
||||||
|
channel channel 3);
|
||||||
|
|
||||||
|
let mut query = DatabasePermissionQuery::new(&db, &owner).channel(&channel);
|
||||||
|
assert!(calculate_channel_permissions(&mut query)
|
||||||
|
.await
|
||||||
|
.has_channel_permission(ChannelPermission::SendMessage));
|
||||||
|
|
||||||
|
let mut query = DatabasePermissionQuery::new(&db, &moderator).channel(&channel);
|
||||||
|
assert!(calculate_channel_permissions(&mut query)
|
||||||
|
.await
|
||||||
|
.has_channel_permission(ChannelPermission::SendMessage));
|
||||||
|
|
||||||
|
let mut query = DatabasePermissionQuery::new(&db, &user).channel(&channel);
|
||||||
|
assert!(!calculate_channel_permissions(&mut query)
|
||||||
|
.await
|
||||||
|
.has_channel_permission(ChannelPermission::SendMessage));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,15 +22,25 @@ impl AbstractAttachments for ReferenceDb {
|
|||||||
async fn find_and_use_attachment(
|
async fn find_and_use_attachment(
|
||||||
&self,
|
&self,
|
||||||
id: &str,
|
id: &str,
|
||||||
_tag: &str,
|
tag: &str,
|
||||||
_parent_type: &str,
|
parent_type: &str,
|
||||||
_parent_id: &str,
|
parent_id: &str,
|
||||||
) -> Result<File> {
|
) -> Result<File> {
|
||||||
let mut files = self.files.lock().await;
|
let mut files = self.files.lock().await;
|
||||||
if let Some(file) = files.get_mut(id) {
|
if let Some(file) = files.get_mut(id) {
|
||||||
// TODO: check tag
|
if file.tag == tag {
|
||||||
// TODO: set parent ID
|
match parent_type {
|
||||||
Ok(file.clone())
|
"message" => file.message_id = Some(parent_id.to_owned()),
|
||||||
|
"user" => file.user_id = Some(parent_id.to_owned()),
|
||||||
|
"object" => file.object_id = Some(parent_id.to_owned()),
|
||||||
|
"server" => file.server_id = Some(parent_id.to_owned()),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(file.clone())
|
||||||
|
} else {
|
||||||
|
Err(create_error!(NotFound))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(create_error!(NotFound))
|
Err(create_error!(NotFound))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ impl AbstractMessages for ReferenceDb {
|
|||||||
.cloned()
|
.cloned()
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// TODO: sorting, etc
|
// TODO: sorting, etc (will be required for tests)
|
||||||
|
|
||||||
Ok(matched_messages)
|
Ok(matched_messages)
|
||||||
|
|
||||||
|
|||||||
@@ -1,28 +1,46 @@
|
|||||||
|
use std::cmp::Ordering;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
use std::time::SystemTime;
|
||||||
|
|
||||||
use super::AbstractRatelimitEvents;
|
use super::AbstractRatelimitEvents;
|
||||||
use crate::RatelimitEvent;
|
use crate::RatelimitEvent;
|
||||||
use crate::RatelimitEventType;
|
use crate::RatelimitEventType;
|
||||||
use crate::ReferenceDb;
|
use crate::ReferenceDb;
|
||||||
use revolt_result::Result;
|
use revolt_result::Result;
|
||||||
|
use ulid::Ulid;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl AbstractRatelimitEvents for ReferenceDb {
|
impl AbstractRatelimitEvents for ReferenceDb {
|
||||||
/// Insert a new ratelimit event
|
/// Insert a new ratelimit event
|
||||||
async fn insert_ratelimit_event(&self, _event: &RatelimitEvent) -> Result<()> {
|
async fn insert_ratelimit_event(&self, event: &RatelimitEvent) -> Result<()> {
|
||||||
// TODO: implement
|
let mut ratelimit_events = self.ratelimit_events.lock().await;
|
||||||
unimplemented!()
|
if ratelimit_events.contains_key(&event.id) {
|
||||||
|
Err(create_database_error!("insert", "message"))
|
||||||
|
} else {
|
||||||
|
ratelimit_events.insert(event.id.to_string(), event.clone());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Count number of events in given duration and check if we've hit the limit
|
/// Count number of events in given duration and check if we've hit the limit
|
||||||
async fn has_ratelimited(
|
async fn has_ratelimited(
|
||||||
&self,
|
&self,
|
||||||
_target_id: &str,
|
target_id: &str,
|
||||||
_event_type: RatelimitEventType,
|
event_type: RatelimitEventType,
|
||||||
_period: Duration,
|
period: Duration,
|
||||||
_count: usize,
|
count: usize,
|
||||||
) -> Result<bool> {
|
) -> Result<bool> {
|
||||||
// TODO: implement
|
let ratelimit_events = self.ratelimit_events.lock().await;
|
||||||
unimplemented!()
|
let gte_cmp_id = Ulid::from_datetime(SystemTime::now() - period).to_string();
|
||||||
|
|
||||||
|
Ok(ratelimit_events
|
||||||
|
.iter()
|
||||||
|
.filter(|(id, event)| {
|
||||||
|
id.cmp(&>e_cmp_id) == Ordering::Greater
|
||||||
|
&& event.target_id == target_id
|
||||||
|
&& event.event_type == event_type
|
||||||
|
})
|
||||||
|
.count()
|
||||||
|
>= count)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
|
|||||||
use revolt_result::{create_error, Result};
|
use revolt_result::{create_error, Result};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
events::client::EventV1, util::permissions::DatabasePermissionQuery, Database, File, Server,
|
events::client::EventV1, util::permissions::DatabasePermissionQuery, Channel, Database, File,
|
||||||
SystemMessage, User,
|
Server, SystemMessage, User,
|
||||||
};
|
};
|
||||||
|
|
||||||
auto_derived_partial!(
|
auto_derived_partial!(
|
||||||
@@ -80,9 +80,8 @@ impl Member {
|
|||||||
db: &Database,
|
db: &Database,
|
||||||
server: &Server,
|
server: &Server,
|
||||||
user: &User,
|
user: &User,
|
||||||
// channels: Option<Vec<Channel>>,
|
channels: Option<Vec<Channel>>,
|
||||||
//) -> Result<Vec<Channel>> {
|
) -> Result<Vec<Channel>> {
|
||||||
) -> Result<()> {
|
|
||||||
if db.fetch_ban(&server.id, &user.id).await.is_ok() {
|
if db.fetch_ban(&server.id, &user.id).await.is_ok() {
|
||||||
return Err(create_error!(Banned));
|
return Err(create_error!(Banned));
|
||||||
}
|
}
|
||||||
@@ -101,9 +100,10 @@ impl Member {
|
|||||||
|
|
||||||
db.insert_member(&member).await?;
|
db.insert_member(&member).await?;
|
||||||
|
|
||||||
let mut channels = vec![];
|
let should_fetch = channels.is_none();
|
||||||
|
let mut channels = channels.unwrap_or_default();
|
||||||
|
|
||||||
if true {
|
if should_fetch {
|
||||||
let query = DatabasePermissionQuery::new(db, user).server(server);
|
let query = DatabasePermissionQuery::new(db, user).server(server);
|
||||||
let existing_channels = db.fetch_channels(&server.channels).await?;
|
let existing_channels = db.fetch_channels(&server.channels).await?;
|
||||||
|
|
||||||
@@ -152,8 +152,7 @@ impl Member {
|
|||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ok(channels)
|
Ok(channels)
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update member data
|
/// Update member data
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
use revolt_permissions::OverrideField;
|
use revolt_models::v0::{self, DataCreateServerChannel};
|
||||||
|
use revolt_permissions::{OverrideField, DEFAULT_PERMISSION_SERVER};
|
||||||
use revolt_result::Result;
|
use revolt_result::Result;
|
||||||
use ulid::Ulid;
|
use ulid::Ulid;
|
||||||
|
|
||||||
use crate::{events::client::EventV1, Database, File};
|
use crate::{events::client::EventV1, Channel, Database, File, User};
|
||||||
|
|
||||||
auto_derived_partial!(
|
auto_derived_partial!(
|
||||||
/// Server
|
/// Server
|
||||||
@@ -131,8 +132,51 @@ auto_derived!(
|
|||||||
#[allow(clippy::disallowed_methods)]
|
#[allow(clippy::disallowed_methods)]
|
||||||
impl Server {
|
impl Server {
|
||||||
/// Create a server
|
/// Create a server
|
||||||
pub async fn create(&self, db: &Database) -> Result<()> {
|
pub async fn create(
|
||||||
db.insert_server(self).await
|
db: &Database,
|
||||||
|
data: v0::DataCreateServer,
|
||||||
|
owner: &User,
|
||||||
|
create_default_channels: bool,
|
||||||
|
) -> Result<(Server, Vec<Channel>)> {
|
||||||
|
let mut server = Server {
|
||||||
|
id: ulid::Ulid::new().to_string(),
|
||||||
|
owner: owner.id.to_string(),
|
||||||
|
name: data.name,
|
||||||
|
description: data.description,
|
||||||
|
channels: vec![],
|
||||||
|
nsfw: data.nsfw.unwrap_or(false),
|
||||||
|
default_permissions: *DEFAULT_PERMISSION_SERVER as i64,
|
||||||
|
|
||||||
|
analytics: false,
|
||||||
|
banner: None,
|
||||||
|
categories: None,
|
||||||
|
discoverable: false,
|
||||||
|
flags: None,
|
||||||
|
icon: None,
|
||||||
|
roles: HashMap::new(),
|
||||||
|
system_messages: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let channels: Vec<Channel> = if create_default_channels {
|
||||||
|
vec![
|
||||||
|
Channel::create_server_channel(
|
||||||
|
db,
|
||||||
|
&mut server,
|
||||||
|
DataCreateServerChannel {
|
||||||
|
channel_type: v0::LegacyServerChannelType::Text,
|
||||||
|
name: "General".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.await?,
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
};
|
||||||
|
|
||||||
|
db.insert_server(&server).await?;
|
||||||
|
Ok((server, channels))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update server data
|
/// Update server data
|
||||||
@@ -209,80 +253,7 @@ impl Server {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* /// Create a new member in a server
|
/*
|
||||||
pub async fn create_member(
|
|
||||||
&self,
|
|
||||||
db: &Database,
|
|
||||||
user: User,
|
|
||||||
channels: Option<Vec<Channel>>,
|
|
||||||
) -> Result<Vec<Channel>> {
|
|
||||||
if db.fetch_ban(&self.id, &user.id).await.is_ok() {
|
|
||||||
return Err(Error::Banned);
|
|
||||||
}
|
|
||||||
|
|
||||||
let member = Member {
|
|
||||||
id: MemberCompositeKey {
|
|
||||||
server: self.id.clone(),
|
|
||||||
user: user.id.clone(),
|
|
||||||
},
|
|
||||||
joined_at: Timestamp::now_utc(),
|
|
||||||
nickname: None,
|
|
||||||
avatar: None,
|
|
||||||
roles: vec![],
|
|
||||||
timeout: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
db.insert_member(&member).await?;
|
|
||||||
|
|
||||||
let should_fetch = channels.is_none();
|
|
||||||
let mut channels = channels.unwrap_or_default();
|
|
||||||
|
|
||||||
if should_fetch {
|
|
||||||
let perm = perms(&user).server(self).member(&member);
|
|
||||||
let existing_channels = db.fetch_channels(&self.channels).await?;
|
|
||||||
for channel in existing_channels {
|
|
||||||
if perm
|
|
||||||
.clone()
|
|
||||||
.channel(&channel)
|
|
||||||
.has_permission(db, Permission::ViewChannel)
|
|
||||||
.await?
|
|
||||||
{
|
|
||||||
channels.push(channel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* // TODO: EventV1::ServerMemberJoin {
|
|
||||||
id: self.id.clone(),
|
|
||||||
user: user.id.clone(),
|
|
||||||
}
|
|
||||||
.p(self.id.clone())
|
|
||||||
.await;
|
|
||||||
|
|
||||||
EventV1::ServerCreate {
|
|
||||||
id: self.id.clone(),
|
|
||||||
server: self.clone(),
|
|
||||||
channels: channels.clone(),
|
|
||||||
}
|
|
||||||
.private(user.id.clone())
|
|
||||||
.await; */
|
|
||||||
|
|
||||||
if let Some(id) = self
|
|
||||||
.system_messages
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|x| x.user_joined.as_ref())
|
|
||||||
{
|
|
||||||
SystemMessage::UserJoined {
|
|
||||||
id: user.id.clone(),
|
|
||||||
}
|
|
||||||
.into_message(id.to_string())
|
|
||||||
.create_no_web_push(db, id, false)
|
|
||||||
.await
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(channels)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Remove a member from a server
|
/// Remove a member from a server
|
||||||
pub async fn remove_member(
|
pub async fn remove_member(
|
||||||
@@ -450,3 +421,36 @@ impl SystemMessageChannels {
|
|||||||
ids
|
ids
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use revolt_permissions::{calculate_server_permissions, ChannelPermission};
|
||||||
|
|
||||||
|
use crate::{fixture, util::permissions::DatabasePermissionQuery};
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn permissions() {
|
||||||
|
database_test!(|db| async move {
|
||||||
|
fixture!(db, "server_with_roles",
|
||||||
|
owner user 0
|
||||||
|
moderator user 1
|
||||||
|
user user 2
|
||||||
|
server server 4);
|
||||||
|
|
||||||
|
let mut query = DatabasePermissionQuery::new(&db, &owner).server(&server);
|
||||||
|
assert!(calculate_server_permissions(&mut query)
|
||||||
|
.await
|
||||||
|
.has_channel_permission(ChannelPermission::GrantAllSafe));
|
||||||
|
|
||||||
|
let mut query = DatabasePermissionQuery::new(&db, &moderator).server(&server);
|
||||||
|
assert!(calculate_server_permissions(&mut query)
|
||||||
|
.await
|
||||||
|
.has_channel_permission(ChannelPermission::BanMembers));
|
||||||
|
|
||||||
|
let mut query = DatabasePermissionQuery::new(&db, &user).server(&server);
|
||||||
|
assert!(!calculate_server_permissions(&mut query)
|
||||||
|
.await
|
||||||
|
.has_channel_permission(ChannelPermission::BanMembers));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::Database;
|
use crate::{events::client::EventV1, Database};
|
||||||
|
|
||||||
use revolt_result::Result;
|
use revolt_result::Result;
|
||||||
|
|
||||||
@@ -16,12 +16,12 @@ impl UserSettingsImpl for UserSettings {
|
|||||||
async fn set(self, db: &Database, user: &str) -> Result<()> {
|
async fn set(self, db: &Database, user: &str) -> Result<()> {
|
||||||
db.set_user_settings(user, &self).await?;
|
db.set_user_settings(user, &self).await?;
|
||||||
|
|
||||||
/* // TODO: EventV1::UserSettingsUpdate {
|
EventV1::UserSettingsUpdate {
|
||||||
id: user.to_string(),
|
id: user.to_string(),
|
||||||
update: self,
|
update: self,
|
||||||
}
|
}
|
||||||
.private(user.to_string())
|
.private(user.to_string())
|
||||||
.await; */
|
.await;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use crate::{events::client::EventV1, Database, File, RatelimitEvent};
|
|||||||
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use rand::seq::SliceRandom;
|
use rand::seq::SliceRandom;
|
||||||
|
use revolt_config::config;
|
||||||
use revolt_result::{create_error, Error, ErrorType, Result};
|
use revolt_result::{create_error, Error, ErrorType, Result};
|
||||||
use ulid::Ulid;
|
use ulid::Ulid;
|
||||||
|
|
||||||
@@ -197,6 +198,13 @@ impl User {
|
|||||||
RelationshipStatus::None
|
RelationshipStatus::None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_friends_with(&self, user_b: &str) -> bool {
|
||||||
|
matches!(
|
||||||
|
self.relationship_with(user_b),
|
||||||
|
RelationshipStatus::Friend | RelationshipStatus::User
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// Check whether two users have a mutual connection
|
/// Check whether two users have a mutual connection
|
||||||
///
|
///
|
||||||
/// This will check if user and user_b share a server or a group.
|
/// This will check if user and user_b share a server or a group.
|
||||||
@@ -211,6 +219,18 @@ impl User {
|
|||||||
.is_empty())
|
.is_empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if this user can acquire another server
|
||||||
|
pub async fn can_acquire_server(&self, db: &Database) -> Result<()> {
|
||||||
|
let config = config().await;
|
||||||
|
if db.fetch_server_count(&self.id).await? <= config.features.limits.default.servers {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(create_error!(TooManyServers {
|
||||||
|
max: config.features.limits.default.servers
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Sanitise and validate a username can be used
|
/// Sanitise and validate a username can be used
|
||||||
pub fn validate_username(username: String) -> Result<String> {
|
pub fn validate_username(username: String) -> Result<String> {
|
||||||
// Copy the username for validation
|
// Copy the username for validation
|
||||||
@@ -341,6 +361,167 @@ impl User {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set a relationship to another user
|
||||||
|
pub async fn set_relationship(
|
||||||
|
&mut self,
|
||||||
|
db: &Database,
|
||||||
|
user_b: &User,
|
||||||
|
status: RelationshipStatus,
|
||||||
|
) -> Result<()> {
|
||||||
|
db.set_relationship(&self.id, &user_b.id, &status).await?;
|
||||||
|
|
||||||
|
if let RelationshipStatus::None | RelationshipStatus::User = status {
|
||||||
|
if let Some(relations) = &mut self.relations {
|
||||||
|
relations.retain(|relation| relation.id != user_b.id);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let relation = Relationship {
|
||||||
|
id: user_b.id.to_string(),
|
||||||
|
status,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(relations) = &mut self.relations {
|
||||||
|
relations.retain(|relation| relation.id != user_b.id);
|
||||||
|
relations.push(relation);
|
||||||
|
} else {
|
||||||
|
self.relations = Some(vec![relation]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply a certain relationship between two users
|
||||||
|
pub async fn apply_relationship(
|
||||||
|
&mut self,
|
||||||
|
db: &Database,
|
||||||
|
target: &mut User,
|
||||||
|
local: RelationshipStatus,
|
||||||
|
remote: RelationshipStatus,
|
||||||
|
) -> Result<()> {
|
||||||
|
target.set_relationship(db, self, remote).await?;
|
||||||
|
self.set_relationship(db, target, local).await?;
|
||||||
|
|
||||||
|
EventV1::UserRelationship {
|
||||||
|
id: target.id.clone(),
|
||||||
|
user: self.clone().into(db, Some(&*target)).await,
|
||||||
|
}
|
||||||
|
.private(target.id.clone())
|
||||||
|
.await;
|
||||||
|
|
||||||
|
EventV1::UserRelationship {
|
||||||
|
id: self.id.clone(),
|
||||||
|
user: target.clone().into(db, Some(&*self)).await,
|
||||||
|
}
|
||||||
|
.private(self.id.clone())
|
||||||
|
.await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add another user as a friend
|
||||||
|
pub async fn add_friend(&mut self, db: &Database, target: &mut User) -> Result<()> {
|
||||||
|
match self.relationship_with(&target.id) {
|
||||||
|
RelationshipStatus::User => Err(create_error!(NoEffect)),
|
||||||
|
RelationshipStatus::Friend => Err(create_error!(AlreadyFriends)),
|
||||||
|
RelationshipStatus::Outgoing => Err(create_error!(AlreadySentRequest)),
|
||||||
|
RelationshipStatus::Blocked => Err(create_error!(Blocked)),
|
||||||
|
RelationshipStatus::BlockedOther => Err(create_error!(BlockedByOther)),
|
||||||
|
RelationshipStatus::Incoming => {
|
||||||
|
self.apply_relationship(
|
||||||
|
db,
|
||||||
|
target,
|
||||||
|
RelationshipStatus::Friend,
|
||||||
|
RelationshipStatus::Friend,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
RelationshipStatus::None => {
|
||||||
|
self.apply_relationship(
|
||||||
|
db,
|
||||||
|
target,
|
||||||
|
RelationshipStatus::Outgoing,
|
||||||
|
RelationshipStatus::Incoming,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove another user as a friend
|
||||||
|
pub async fn remove_friend(&mut self, db: &Database, target: &mut User) -> Result<()> {
|
||||||
|
match self.relationship_with(&target.id) {
|
||||||
|
RelationshipStatus::Friend
|
||||||
|
| RelationshipStatus::Outgoing
|
||||||
|
| RelationshipStatus::Incoming => {
|
||||||
|
self.apply_relationship(
|
||||||
|
db,
|
||||||
|
target,
|
||||||
|
RelationshipStatus::None,
|
||||||
|
RelationshipStatus::None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
_ => Err(create_error!(NoEffect)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Block another user
|
||||||
|
pub async fn block_user(&mut self, db: &Database, target: &mut User) -> Result<()> {
|
||||||
|
match self.relationship_with(&target.id) {
|
||||||
|
RelationshipStatus::User | RelationshipStatus::Blocked => Err(create_error!(NoEffect)),
|
||||||
|
RelationshipStatus::BlockedOther => {
|
||||||
|
self.apply_relationship(
|
||||||
|
db,
|
||||||
|
target,
|
||||||
|
RelationshipStatus::Blocked,
|
||||||
|
RelationshipStatus::Blocked,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
RelationshipStatus::None
|
||||||
|
| RelationshipStatus::Friend
|
||||||
|
| RelationshipStatus::Incoming
|
||||||
|
| RelationshipStatus::Outgoing => {
|
||||||
|
self.apply_relationship(
|
||||||
|
db,
|
||||||
|
target,
|
||||||
|
RelationshipStatus::Blocked,
|
||||||
|
RelationshipStatus::BlockedOther,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unblock another user
|
||||||
|
pub async fn unblock_user(&mut self, db: &Database, target: &mut User) -> Result<()> {
|
||||||
|
match self.relationship_with(&target.id) {
|
||||||
|
RelationshipStatus::Blocked => match target.relationship_with(&self.id) {
|
||||||
|
RelationshipStatus::Blocked => {
|
||||||
|
self.apply_relationship(
|
||||||
|
db,
|
||||||
|
target,
|
||||||
|
RelationshipStatus::BlockedOther,
|
||||||
|
RelationshipStatus::Blocked,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
RelationshipStatus::BlockedOther => {
|
||||||
|
self.apply_relationship(
|
||||||
|
db,
|
||||||
|
target,
|
||||||
|
RelationshipStatus::None,
|
||||||
|
RelationshipStatus::None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
_ => Err(create_error!(InternalError)),
|
||||||
|
},
|
||||||
|
_ => Err(create_error!(NoEffect)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Update user data
|
/// Update user data
|
||||||
pub async fn update<'a>(
|
pub async fn update<'a>(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use revolt_result::Result;
|
use revolt_result::Result;
|
||||||
|
|
||||||
use crate::ReferenceDb;
|
|
||||||
use crate::{FieldsUser, PartialUser, RelationshipStatus, User};
|
use crate::{FieldsUser, PartialUser, RelationshipStatus, User};
|
||||||
|
use crate::{ReferenceDb, Relationship};
|
||||||
|
|
||||||
use super::AbstractUsers;
|
use super::AbstractUsers;
|
||||||
|
|
||||||
@@ -106,19 +106,49 @@ impl AbstractUsers for ReferenceDb {
|
|||||||
|
|
||||||
/// Set relationship with another user
|
/// Set relationship with another user
|
||||||
///
|
///
|
||||||
/// This should use pull_relationship if relationship is None.
|
/// This should use pull_relationship if relationship is None or User.
|
||||||
async fn set_relationship(
|
async fn set_relationship(
|
||||||
&self,
|
&self,
|
||||||
_user_id: &str,
|
user_id: &str,
|
||||||
_target_id: &str,
|
target_id: &str,
|
||||||
_relationship: &RelationshipStatus,
|
relationship: &RelationshipStatus,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
todo!()
|
if let RelationshipStatus::User | RelationshipStatus::None = &relationship {
|
||||||
|
self.pull_relationship(user_id, target_id).await
|
||||||
|
} else {
|
||||||
|
let mut users = self.users.lock().await;
|
||||||
|
let user = users
|
||||||
|
.get_mut(user_id)
|
||||||
|
.ok_or_else(|| create_error!(NotFound))?;
|
||||||
|
|
||||||
|
let relation = Relationship {
|
||||||
|
id: target_id.to_string(),
|
||||||
|
status: relationship.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(relations) = &mut user.relations {
|
||||||
|
relations.retain(|relation| relation.id != target_id);
|
||||||
|
relations.push(relation);
|
||||||
|
} else {
|
||||||
|
user.relations = Some(vec![relation]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove relationship with another user
|
/// Remove relationship with another user
|
||||||
async fn pull_relationship(&self, _user_id: &str, _target_id: &str) -> Result<()> {
|
async fn pull_relationship(&self, user_id: &str, target_id: &str) -> Result<()> {
|
||||||
todo!()
|
let mut users = self.users.lock().await;
|
||||||
|
let user = users
|
||||||
|
.get_mut(user_id)
|
||||||
|
.ok_or_else(|| create_error!(NotFound))?;
|
||||||
|
|
||||||
|
if let Some(relations) = &mut user.relations {
|
||||||
|
relations.retain(|relation| relation.id != target_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Delete a user by their id
|
/// Delete a user by their id
|
||||||
|
|||||||
@@ -13,19 +13,19 @@ impl<'r> FromRequest<'r> for User {
|
|||||||
.local_cache_async(async {
|
.local_cache_async(async {
|
||||||
let db = request.rocket().state::<Database>().expect("`Database`");
|
let db = request.rocket().state::<Database>().expect("`Database`");
|
||||||
|
|
||||||
let _header_bot_token = request
|
let header_bot_token = request
|
||||||
.headers()
|
.headers()
|
||||||
.get("x-bot-token")
|
.get("x-bot-token")
|
||||||
.next()
|
.next()
|
||||||
.map(|x| x.to_string());
|
.map(|x| x.to_string());
|
||||||
|
|
||||||
/* if let Some(bot_token) = header_bot_token {
|
if let Some(bot_token) = header_bot_token {
|
||||||
if let Ok(user) = User::from_token(db, &bot_token, UserHint::Bot).await {
|
if let Ok(bot) = db.fetch_bot_by_token(&bot_token).await {
|
||||||
return Some(user);
|
if let Ok(user) = db.fetch_user(&bot.id).await {
|
||||||
|
return Some(user);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else */
|
} else if let Outcome::Success(session) = request.guard::<Session>().await {
|
||||||
if let Outcome::Success(session) = request.guard::<Session>().await {
|
|
||||||
// This uses a guard so can't really easily be refactored into from_token at this stage.
|
|
||||||
if let Ok(user) = db.fetch_user(&session.user_id).await {
|
if let Ok(user) = db.fetch_user(&session.user_id).await {
|
||||||
return Some(user);
|
return Some(user);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,12 +13,12 @@ pub mod process_embeds;
|
|||||||
pub mod web_push;
|
pub mod web_push;
|
||||||
|
|
||||||
/// Spawn background workers
|
/// Spawn background workers
|
||||||
pub async fn start_workers(db: Database) {
|
pub async fn start_workers(db: Database, authifier_db: authifier::Database) {
|
||||||
for _ in 0..WORKER_COUNT {
|
for _ in 0..WORKER_COUNT {
|
||||||
task::spawn(ack::worker(db.clone()));
|
task::spawn(ack::worker(db.clone()));
|
||||||
task::spawn(last_message_id::worker(db.clone()));
|
task::spawn(last_message_id::worker(db.clone()));
|
||||||
task::spawn(process_embeds::worker(db.clone()));
|
task::spawn(process_embeds::worker(db.clone()));
|
||||||
task::spawn(web_push::worker(db.clone().into()));
|
task::spawn(web_push::worker(authifier_db.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
use revolt_models::v0::*;
|
use revolt_models::v0::*;
|
||||||
|
use revolt_permissions::{calculate_user_permissions, UserPermission};
|
||||||
|
|
||||||
|
use crate::{util::permissions::DatabasePermissionQuery, Database};
|
||||||
|
|
||||||
impl crate::Bot {
|
impl crate::Bot {
|
||||||
pub fn into_public_bot(self, user: crate::User) -> PublicBot {
|
pub fn into_public_bot(self, user: crate::User) -> PublicBot {
|
||||||
@@ -635,43 +638,69 @@ impl From<crate::FieldsRole> for FieldsRole {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl crate::User {
|
impl crate::User {
|
||||||
pub async fn into<'a, P>(self, perspective: P) -> User
|
pub async fn into<'a, P>(self, db: &Database, perspective: P) -> User
|
||||||
where
|
where
|
||||||
P: Into<Option<&'a crate::User>>,
|
P: Into<Option<&'a crate::User>>,
|
||||||
{
|
{
|
||||||
let relationship = if let Some(perspective) = perspective.into() {
|
let perspective = perspective.into();
|
||||||
|
let (relationship, can_see_profile) = if self.bot.is_some() {
|
||||||
|
(RelationshipStatus::None, true)
|
||||||
|
} else if let Some(perspective) = perspective {
|
||||||
|
let mut query = DatabasePermissionQuery::new(db, perspective).user(&self);
|
||||||
|
|
||||||
if perspective.id == self.id {
|
if perspective.id == self.id {
|
||||||
RelationshipStatus::User
|
(RelationshipStatus::User, true)
|
||||||
} else {
|
} else {
|
||||||
perspective
|
(
|
||||||
.relations
|
perspective
|
||||||
.as_ref()
|
.relations
|
||||||
.map(|relations| {
|
.as_ref()
|
||||||
relations
|
.map(|relations| {
|
||||||
.iter()
|
relations
|
||||||
.find(|relationship| relationship.id == self.id)
|
.iter()
|
||||||
.map(|relationship| relationship.status.clone().into())
|
.find(|relationship| relationship.id == self.id)
|
||||||
.unwrap_or_default()
|
.map(|relationship| relationship.status.clone().into())
|
||||||
})
|
.unwrap_or_default()
|
||||||
.unwrap_or_default()
|
})
|
||||||
|
.unwrap_or_default(),
|
||||||
|
calculate_user_permissions(&mut query)
|
||||||
|
.await
|
||||||
|
.has_user_permission(UserPermission::ViewProfile),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
RelationshipStatus::None
|
(RelationshipStatus::None, false)
|
||||||
};
|
};
|
||||||
|
|
||||||
// do permission stuff here
|
|
||||||
// TODO: implement permissions =)
|
|
||||||
let can_see_profile = false;
|
|
||||||
|
|
||||||
User {
|
User {
|
||||||
username: self.username,
|
username: self.username,
|
||||||
discriminator: self.discriminator,
|
discriminator: self.discriminator,
|
||||||
display_name: self.display_name,
|
display_name: self.display_name,
|
||||||
avatar: self.avatar.map(|file| file.into()),
|
avatar: self.avatar.map(|file| file.into()),
|
||||||
relations: vec![],
|
relations: if let Some(crate::User { id, .. }) = perspective {
|
||||||
|
if id == &self.id {
|
||||||
|
self.relations
|
||||||
|
.unwrap_or_default()
|
||||||
|
.into_iter()
|
||||||
|
.map(|relation| relation.into())
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
},
|
||||||
badges: self.badges.unwrap_or_default() as u32,
|
badges: self.badges.unwrap_or_default() as u32,
|
||||||
status: None,
|
status: if can_see_profile {
|
||||||
profile: None,
|
self.status.map(|status| status.into())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
profile: if can_see_profile {
|
||||||
|
self.profile.map(|profile| profile.into())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
flags: self.flags.unwrap_or_default() as u32,
|
flags: self.flags.unwrap_or_default() as u32,
|
||||||
privileged: self.privileged,
|
privileged: self.privileged,
|
||||||
bot: self.bot.map(|bot| bot.into()),
|
bot: self.bot.map(|bot| bot.into()),
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
|
|
||||||
use revolt_result::{create_error, Error, Result};
|
use revolt_result::{create_error, Result};
|
||||||
|
|
||||||
|
#[cfg(feature = "rocket-impl")]
|
||||||
|
use revolt_result::Error;
|
||||||
|
|
||||||
use async_std::sync::Mutex;
|
use async_std::sync::Mutex;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
|||||||
@@ -2,3 +2,6 @@ pub mod bridge;
|
|||||||
pub mod idempotency;
|
pub mod idempotency;
|
||||||
pub mod permissions;
|
pub mod permissions;
|
||||||
pub mod reference;
|
pub mod reference;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub mod test_fixtures;
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use revolt_permissions::{
|
use revolt_permissions::{
|
||||||
calculate_user_permissions, ChannelType, Override, PermissionQuery, RelationshipStatus,
|
calculate_user_permissions, ChannelType, Override, PermissionQuery, PermissionValue,
|
||||||
|
RelationshipStatus,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{Channel, Database, Member, Server, User};
|
use crate::{Channel, Database, Member, Server, User};
|
||||||
@@ -19,7 +20,8 @@ pub struct DatabasePermissionQuery<'a> {
|
|||||||
member: Option<Cow<'a, Member>>,
|
member: Option<Cow<'a, Member>>,
|
||||||
|
|
||||||
// flag_known_relationship: Option<&'a RelationshipStatus>,
|
// flag_known_relationship: Option<&'a RelationshipStatus>,
|
||||||
cached_user_permission: Option<u32>,
|
cached_user_permission: Option<PermissionValue>,
|
||||||
|
cached_mutual_connection: Option<bool>,
|
||||||
cached_permission: Option<u64>,
|
cached_permission: Option<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,6 +51,10 @@ impl PermissionQuery for DatabasePermissionQuery<'_> {
|
|||||||
/// Get the relationship with have with the currently selected user
|
/// Get the relationship with have with the currently selected user
|
||||||
async fn user_relationship(&mut self) -> RelationshipStatus {
|
async fn user_relationship(&mut self) -> RelationshipStatus {
|
||||||
if let Some(other_user) = &self.user {
|
if let Some(other_user) = &self.user {
|
||||||
|
if self.perspective.id == other_user.id {
|
||||||
|
return RelationshipStatus::User;
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(relations) = &self.perspective.relations {
|
if let Some(relations) = &self.perspective.relations {
|
||||||
for entry in relations {
|
for entry in relations {
|
||||||
if entry.id == other_user.id {
|
if entry.id == other_user.id {
|
||||||
@@ -82,14 +88,17 @@ impl PermissionQuery for DatabasePermissionQuery<'_> {
|
|||||||
|
|
||||||
/// Do we have a mutual connection with the currently selected user?
|
/// Do we have a mutual connection with the currently selected user?
|
||||||
async fn have_mutual_connection(&mut self) -> bool {
|
async fn have_mutual_connection(&mut self) -> bool {
|
||||||
if let Some(user) = &self.user {
|
if let Some(value) = self.cached_mutual_connection {
|
||||||
// TODO: cache result?
|
value
|
||||||
matches!(
|
} else if let Some(user) = &self.user {
|
||||||
self.perspective
|
let value = self
|
||||||
.has_mutual_connection(self.database, &user.id)
|
.perspective
|
||||||
.await,
|
.has_mutual_connection(self.database, &user.id)
|
||||||
Ok(true)
|
.await
|
||||||
)
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
self.cached_mutual_connection = Some(value);
|
||||||
|
matches!(value, true)
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
@@ -111,11 +120,15 @@ impl PermissionQuery for DatabasePermissionQuery<'_> {
|
|||||||
if let Some(server) = &self.server {
|
if let Some(server) = &self.server {
|
||||||
if self.member.is_some() {
|
if self.member.is_some() {
|
||||||
true
|
true
|
||||||
|
} else if let Ok(member) = self
|
||||||
|
.database
|
||||||
|
.fetch_member(&server.id, &self.perspective.id)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
self.member = Some(Cow::Owned(member));
|
||||||
|
true
|
||||||
} else {
|
} else {
|
||||||
self.database
|
false
|
||||||
.fetch_member(&server.id, &self.perspective.id)
|
|
||||||
.await
|
|
||||||
.is_ok()
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
@@ -331,6 +344,7 @@ impl PermissionQuery for DatabasePermissionQuery<'_> {
|
|||||||
| Cow::Owned(Channel::TextChannel { server, .. })
|
| Cow::Owned(Channel::TextChannel { server, .. })
|
||||||
| Cow::Borrowed(Channel::VoiceChannel { server, .. })
|
| Cow::Borrowed(Channel::VoiceChannel { server, .. })
|
||||||
| Cow::Owned(Channel::VoiceChannel { server, .. }) => {
|
| Cow::Owned(Channel::VoiceChannel { server, .. }) => {
|
||||||
|
// FIXME: may double fetch
|
||||||
if let Ok(server) = self.database.fetch_server(server).await {
|
if let Ok(server) = self.database.fetch_server(server).await {
|
||||||
self.server.replace(Cow::Owned(server));
|
self.server.replace(Cow::Owned(server));
|
||||||
}
|
}
|
||||||
@@ -352,6 +366,7 @@ impl<'a> DatabasePermissionQuery<'a> {
|
|||||||
server: None,
|
server: None,
|
||||||
member: None,
|
member: None,
|
||||||
|
|
||||||
|
cached_mutual_connection: None,
|
||||||
cached_user_permission: None,
|
cached_user_permission: None,
|
||||||
cached_permission: None,
|
cached_permission: None,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use schemars::{
|
|||||||
JsonSchema,
|
JsonSchema,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{Bot, Channel, Database, Emoji, Message, Webhook};
|
use crate::{Bot, Channel, Database, Emoji, Message, Server, User, Webhook};
|
||||||
|
|
||||||
/// Reference to some object in the database
|
/// Reference to some object in the database
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
@@ -42,6 +42,16 @@ impl Reference {
|
|||||||
db.fetch_message(&self.id).await
|
db.fetch_message(&self.id).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fetch server from Ref
|
||||||
|
pub async fn as_server(&self, db: &Database) -> Result<Server> {
|
||||||
|
db.fetch_server(&self.id).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch user from Ref
|
||||||
|
pub async fn as_user(&self, db: &Database) -> Result<User> {
|
||||||
|
db.fetch_user(&self.id).await
|
||||||
|
}
|
||||||
|
|
||||||
/// Fetch webhook from Ref
|
/// Fetch webhook from Ref
|
||||||
pub async fn as_webhook(&self, db: &Database) -> Result<Webhook> {
|
pub async fn as_webhook(&self, db: &Database) -> Result<Webhook> {
|
||||||
db.fetch_webhook(&self.id).await
|
db.fetch_webhook(&self.id).await
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use regex::{Captures, Regex};
|
||||||
|
use serde_json::from_str;
|
||||||
|
|
||||||
|
use crate::{Channel, Database, Member, Server, User};
|
||||||
|
|
||||||
|
static RE_ID: Lazy<Regex> = Lazy::new(|| Regex::new("__ID:(\\d+)__").unwrap());
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(tag = "_object_type")]
|
||||||
|
enum LoadedFixture {
|
||||||
|
User(User),
|
||||||
|
Channel(Channel),
|
||||||
|
Server(Server),
|
||||||
|
ServerMember(Member),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn load_fixture(db: &Database, input: &str) -> HashMap<String, String> {
|
||||||
|
let mut ids = HashMap::<String, String>::new();
|
||||||
|
let input = RE_ID.replace_all(input, |cap: &Captures| {
|
||||||
|
let d = cap.get(1).unwrap().as_str();
|
||||||
|
|
||||||
|
if !ids.contains_key(d) {
|
||||||
|
ids.insert(d.to_string(), ulid::Ulid::new().to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
ids.get(d).unwrap().clone()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Deserialise the fixtures
|
||||||
|
let items: Vec<LoadedFixture> = from_str(&input).expect("Failed to deserialise fixture");
|
||||||
|
|
||||||
|
// Load all of the items within
|
||||||
|
for item in items {
|
||||||
|
#[allow(clippy::disallowed_methods)]
|
||||||
|
match item {
|
||||||
|
LoadedFixture::User(user) => db.insert_user(&user).await.unwrap(),
|
||||||
|
LoadedFixture::Channel(channel) => db.insert_channel(&channel).await.unwrap(),
|
||||||
|
LoadedFixture::Server(server) => db.insert_server(&server).await.unwrap(),
|
||||||
|
LoadedFixture::ServerMember(member) => db.insert_member(&member).await.unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return IDs for ease of use
|
||||||
|
ids
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait FetchFixture {
|
||||||
|
async fn user(&self, db: &Database, d: usize) -> User;
|
||||||
|
async fn channel(&self, db: &Database, d: usize) -> Channel;
|
||||||
|
async fn server(&self, db: &Database, d: usize) -> Server;
|
||||||
|
async fn member(&self, db: &Database, d_server: usize, d_user: usize) -> Member;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl FetchFixture for HashMap<String, String> {
|
||||||
|
async fn user(&self, db: &Database, d: usize) -> User {
|
||||||
|
db.fetch_user(self.get(&d.to_string()).unwrap())
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn channel(&self, db: &Database, d: usize) -> Channel {
|
||||||
|
db.fetch_channel(self.get(&d.to_string()).unwrap())
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn server(&self, db: &Database, d: usize) -> Server {
|
||||||
|
db.fetch_server(self.get(&d.to_string()).unwrap())
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn member(&self, db: &Database, d_server: usize, d_user: usize) -> Member {
|
||||||
|
db.fetch_member(
|
||||||
|
self.get(&d_server.to_string()).unwrap(),
|
||||||
|
self.get(&d_user.to_string()).unwrap(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! fixture {
|
||||||
|
( $database:expr, $name:expr, $( $variable:ident $type:ident $id: expr )+ ) => {
|
||||||
|
use $crate::util::test_fixtures::FetchFixture;
|
||||||
|
|
||||||
|
let fixtures = $crate::util::test_fixtures::load_fixture(
|
||||||
|
&$database,
|
||||||
|
include_str!(concat!("../../../fixtures/", $name, ".json")),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
$(
|
||||||
|
let $variable = fixtures.$type(&$database, $id).await;
|
||||||
|
)+
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "revolt-models"
|
name = "revolt-models"
|
||||||
version = "0.6.8"
|
version = "0.6.9"
|
||||||
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>"]
|
||||||
@@ -18,8 +18,8 @@ default = ["serde", "partials"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# Core
|
# Core
|
||||||
revolt-config = { version = "0.6.8", path = "../config" }
|
revolt-config = { version = "0.6.9", path = "../config" }
|
||||||
revolt-permissions = { version = "0.6.8", path = "../permissions" }
|
revolt-permissions = { version = "0.6.9", path = "../permissions" }
|
||||||
|
|
||||||
# Utility
|
# Utility
|
||||||
regex = "1"
|
regex = "1"
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#[cfg(feature = "validator")]
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
|
||||||
use super::File;
|
use super::File;
|
||||||
@@ -80,7 +81,7 @@ auto_derived!(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Information for the webhook
|
/// Information for the webhook
|
||||||
#[derive(Validate)]
|
#[cfg_attr(feature = "validator", derive(Validate))]
|
||||||
pub struct CreateWebhookBody {
|
pub struct CreateWebhookBody {
|
||||||
#[validate(length(min = 1, max = 32))]
|
#[validate(length(min = 1, max = 32))]
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|||||||
@@ -212,22 +212,63 @@ auto_derived!(
|
|||||||
#[cfg_attr(feature = "validator", derive(validator::Validate))]
|
#[cfg_attr(feature = "validator", derive(validator::Validate))]
|
||||||
pub struct DataCreateGroup {
|
pub struct DataCreateGroup {
|
||||||
/// Group name
|
/// Group name
|
||||||
#[validate(length(min = 1, max = 32))]
|
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 32)))]
|
||||||
pub name: String,
|
pub name: String,
|
||||||
/// Group description
|
/// Group description
|
||||||
#[validate(length(min = 0, max = 1024))]
|
#[cfg_attr(feature = "validator", validate(length(min = 0, max = 1024)))]
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
/// Group icon
|
/// Group icon
|
||||||
#[validate(length(min = 1, max = 128))]
|
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 128)))]
|
||||||
pub icon: Option<String>,
|
pub icon: Option<String>,
|
||||||
/// Array of user IDs to add to the group
|
/// Array of user IDs to add to the group
|
||||||
///
|
///
|
||||||
/// Must be friends with these users.
|
/// Must be friends with these users.
|
||||||
#[validate(length(min = 0, max = 49))]
|
#[cfg_attr(feature = "validator", validate(length(min = 0, max = 49)))]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub users: HashSet<String>,
|
pub users: HashSet<String>,
|
||||||
/// Whether this group is age-restricted
|
/// Whether this group is age-restricted
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub nsfw: Option<bool>,
|
pub nsfw: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Server Channel Type
|
||||||
|
#[derive(Default)]
|
||||||
|
pub enum LegacyServerChannelType {
|
||||||
|
/// Text Channel
|
||||||
|
#[default]
|
||||||
|
Text,
|
||||||
|
/// Voice Channel
|
||||||
|
Voice,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create new server channel
|
||||||
|
#[derive(Default)]
|
||||||
|
#[cfg_attr(feature = "validator", derive(validator::Validate))]
|
||||||
|
pub struct DataCreateServerChannel {
|
||||||
|
/// Channel type
|
||||||
|
#[serde(rename = "type", default = "LegacyServerChannelType::default")]
|
||||||
|
pub channel_type: LegacyServerChannelType,
|
||||||
|
/// Channel name
|
||||||
|
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 32)))]
|
||||||
|
pub name: String,
|
||||||
|
/// Channel description
|
||||||
|
#[cfg_attr(feature = "validator", validate(length(min = 0, max = 1024)))]
|
||||||
|
pub description: Option<String>,
|
||||||
|
/// Whether this channel is age restricted
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub nsfw: Option<bool>,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
impl Channel {
|
||||||
|
/// Get a reference to this channel's id
|
||||||
|
pub fn id(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
Channel::DirectMessage { id, .. }
|
||||||
|
| Channel::Group { id, .. }
|
||||||
|
| Channel::SavedMessages { id, .. }
|
||||||
|
| Channel::TextChannel { id, .. }
|
||||||
|
| Channel::VoiceChannel { id, .. } => id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
|
#[cfg(feature = "validator")]
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
|
||||||
/// Regex for valid emoji names
|
/// Regex for valid emoji names
|
||||||
@@ -41,7 +43,7 @@ auto_derived!(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new emoji
|
/// Create a new emoji
|
||||||
#[derive(Validate)]
|
#[cfg_attr(feature = "validator", derive(Validate))]
|
||||||
pub struct DataCreateEmoji {
|
pub struct DataCreateEmoji {
|
||||||
/// Server name
|
/// Server name
|
||||||
#[validate(length(min = 1, max = 32), regex = "RE_EMOJI")]
|
#[validate(length(min = 1, max = 32), regex = "RE_EMOJI")]
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ use std::{
|
|||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use revolt_config::config;
|
use revolt_config::config;
|
||||||
|
|
||||||
|
#[cfg(feature = "validator")]
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
|
||||||
use iso8601_timestamp::Timestamp;
|
use iso8601_timestamp::Timestamp;
|
||||||
@@ -94,7 +96,7 @@ auto_derived!(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Name and / or avatar override information
|
/// Name and / or avatar override information
|
||||||
#[derive(Validate)]
|
#[cfg_attr(feature = "validator", derive(Validate))]
|
||||||
pub struct Masquerade {
|
pub struct Masquerade {
|
||||||
// FIXME: missing validation
|
// FIXME: missing validation
|
||||||
/// Replace the display name shown on this message
|
/// Replace the display name shown on this message
|
||||||
@@ -164,7 +166,8 @@ auto_derived!(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Representation of a text embed before it is sent.
|
/// Representation of a text embed before it is sent.
|
||||||
#[derive(Default, Validate)]
|
#[derive(Default)]
|
||||||
|
#[cfg_attr(feature = "validator", derive(Validate))]
|
||||||
pub struct SendableEmbed {
|
pub struct SendableEmbed {
|
||||||
#[validate(length(min = 1, max = 128))]
|
#[validate(length(min = 1, max = 128))]
|
||||||
pub icon_url: Option<String>,
|
pub icon_url: Option<String>,
|
||||||
@@ -188,7 +191,7 @@ auto_derived!(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Message to send
|
/// Message to send
|
||||||
#[derive(Validate)]
|
#[cfg_attr(feature = "validator", derive(Validate))]
|
||||||
pub struct DataMessageSend {
|
pub struct DataMessageSend {
|
||||||
/// Unique token to prevent duplicate message sending
|
/// Unique token to prevent duplicate message sending
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
use super::File;
|
use super::{Channel, File};
|
||||||
|
|
||||||
use revolt_permissions::OverrideField;
|
use revolt_permissions::OverrideField;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[cfg(feature = "validator")]
|
||||||
|
use validator::Validate;
|
||||||
|
|
||||||
auto_derived_partial!(
|
auto_derived_partial!(
|
||||||
/// Server
|
/// Server
|
||||||
pub struct Server {
|
pub struct Server {
|
||||||
@@ -141,4 +144,27 @@ auto_derived!(
|
|||||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub user_banned: Option<String>,
|
pub user_banned: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Information about new server to create
|
||||||
|
#[derive(Default)]
|
||||||
|
#[cfg_attr(feature = "validator", derive(Validate))]
|
||||||
|
pub struct DataCreateServer {
|
||||||
|
/// Server name
|
||||||
|
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 32)))]
|
||||||
|
pub name: String,
|
||||||
|
/// Server description
|
||||||
|
#[cfg_attr(feature = "validator", validate(length(min = 0, max = 1024)))]
|
||||||
|
pub description: Option<String>,
|
||||||
|
/// Whether this server is age-restricted
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
|
pub nsfw: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Information returned when creating server
|
||||||
|
pub struct CreateServerLegacyResponse {
|
||||||
|
/// Server object
|
||||||
|
pub server: Server,
|
||||||
|
/// Default channels
|
||||||
|
pub channels: Vec<Channel>,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "revolt-permissions"
|
name = "revolt-permissions"
|
||||||
version = "0.6.8"
|
version = "0.6.9"
|
||||||
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>"]
|
||||||
|
|||||||
@@ -5,37 +5,37 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// Calculate permissions against a user
|
/// Calculate permissions against a user
|
||||||
pub async fn calculate_user_permissions<P: PermissionQuery>(query: &mut P) -> u32 {
|
pub async fn calculate_user_permissions<P: PermissionQuery>(query: &mut P) -> PermissionValue {
|
||||||
if query.are_we_privileged().await {
|
if query.are_we_privileged().await {
|
||||||
return u32::MAX;
|
return u64::MAX.into();
|
||||||
}
|
}
|
||||||
|
|
||||||
if query.are_the_users_same().await {
|
if query.are_the_users_same().await {
|
||||||
return u32::MAX;
|
return u64::MAX.into();
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut permissions = 0_u32;
|
let mut permissions = 0_u64;
|
||||||
match query.user_relationship().await {
|
match query.user_relationship().await {
|
||||||
RelationshipStatus::Friend => return u32::MAX,
|
RelationshipStatus::Friend => return u64::MAX.into(),
|
||||||
RelationshipStatus::Blocked | RelationshipStatus::BlockedOther => {
|
RelationshipStatus::Blocked | RelationshipStatus::BlockedOther => {
|
||||||
return UserPermission::Access as u32
|
return (UserPermission::Access as u64).into()
|
||||||
}
|
}
|
||||||
RelationshipStatus::Incoming | RelationshipStatus::Outgoing => {
|
RelationshipStatus::Incoming | RelationshipStatus::Outgoing => {
|
||||||
permissions = UserPermission::Access as u32;
|
permissions = UserPermission::Access as u64;
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
if query.have_mutual_connection().await {
|
if query.have_mutual_connection().await {
|
||||||
permissions = UserPermission::Access + UserPermission::ViewProfile;
|
permissions = UserPermission::Access as u64 + UserPermission::ViewProfile as u64;
|
||||||
|
|
||||||
if query.user_is_bot().await || query.are_we_a_bot().await {
|
if query.user_is_bot().await || query.are_we_a_bot().await {
|
||||||
permissions += UserPermission::SendMessage as u32;
|
permissions += UserPermission::SendMessage as u64;
|
||||||
}
|
}
|
||||||
|
|
||||||
permissions
|
permissions.into()
|
||||||
} else {
|
} else {
|
||||||
permissions
|
permissions.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,9 +81,7 @@ pub async fn calculate_channel_permissions<P: PermissionQuery>(query: &mut P) ->
|
|||||||
query.set_recipient_as_user().await;
|
query.set_recipient_as_user().await;
|
||||||
|
|
||||||
let permissions = calculate_user_permissions(query).await;
|
let permissions = calculate_user_permissions(query).await;
|
||||||
if (permissions & UserPermission::SendMessage as u32)
|
if permissions.has_user_permission(UserPermission::SendMessage) {
|
||||||
== UserPermission::SendMessage as u32
|
|
||||||
{
|
|
||||||
(*DEFAULT_PERMISSION_DIRECT_MESSAGE).into()
|
(*DEFAULT_PERMISSION_DIRECT_MESSAGE).into()
|
||||||
} else {
|
} else {
|
||||||
(*DEFAULT_PERMISSION_VIEW_ONLY).into()
|
(*DEFAULT_PERMISSION_VIEW_ONLY).into()
|
||||||
@@ -106,7 +104,9 @@ pub async fn calculate_channel_permissions<P: PermissionQuery>(query: &mut P) ->
|
|||||||
ChannelType::ServerChannel => {
|
ChannelType::ServerChannel => {
|
||||||
query.set_server_from_channel().await;
|
query.set_server_from_channel().await;
|
||||||
|
|
||||||
if query.are_we_a_member().await {
|
if query.are_we_server_owner().await {
|
||||||
|
return ChannelPermission::GrantAllSafe.into();
|
||||||
|
} else if query.are_we_a_member().await {
|
||||||
let mut permissions = calculate_server_permissions(query).await;
|
let mut permissions = calculate_server_permissions(query).await;
|
||||||
permissions.apply(query.get_default_channel_permissions().await);
|
permissions.apply(query.get_default_channel_permissions().await);
|
||||||
|
|
||||||
@@ -121,7 +121,7 @@ pub async fn calculate_channel_permissions<P: PermissionQuery>(query: &mut P) ->
|
|||||||
if !permissions.has_channel_permission(ChannelPermission::ViewChannel) {
|
if !permissions.has_channel_permission(ChannelPermission::ViewChannel) {
|
||||||
permissions.revoke_all();
|
permissions.revoke_all();
|
||||||
}
|
}
|
||||||
|
|
||||||
permissions
|
permissions
|
||||||
} else {
|
} else {
|
||||||
0_u64.into()
|
0_u64.into()
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ pub use server::*;
|
|||||||
pub use user::*;
|
pub use user::*;
|
||||||
|
|
||||||
/// Holds a permission value to manipulate.
|
/// Holds a permission value to manipulate.
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct PermissionValue(u64);
|
pub struct PermissionValue(u64);
|
||||||
|
|
||||||
impl PermissionValue {
|
impl PermissionValue {
|
||||||
@@ -43,6 +43,11 @@ impl PermissionValue {
|
|||||||
(self.0 & v) == v
|
(self.0 & v) == v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check whether certain a user permission has been granted
|
||||||
|
pub fn has_user_permission(&self, permission: UserPermission) -> bool {
|
||||||
|
self.has(permission as u64)
|
||||||
|
}
|
||||||
|
|
||||||
/// Check whether certain a channel permission has been granted
|
/// Check whether certain a channel permission has been granted
|
||||||
pub fn has_channel_permission(&self, permission: ChannelPermission) -> bool {
|
pub fn has_channel_permission(&self, permission: ChannelPermission) -> bool {
|
||||||
self.has(permission as u64)
|
self.has(permission as u64)
|
||||||
|
|||||||
@@ -51,9 +51,9 @@ pub enum DataPermissionPoly {
|
|||||||
#[cfg_attr(feature = "schemas", derive(JsonSchema))]
|
#[cfg_attr(feature = "schemas", derive(JsonSchema))]
|
||||||
pub struct OverrideField {
|
pub struct OverrideField {
|
||||||
/// Allow bit flags
|
/// Allow bit flags
|
||||||
a: i64,
|
pub a: i64,
|
||||||
/// Disallow bit flags
|
/// Disallow bit flags
|
||||||
d: i64,
|
pub d: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Override {
|
impl Override {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ async fn validate_user_permissions() {
|
|||||||
let mut query = Scenario {};
|
let mut query = Scenario {};
|
||||||
|
|
||||||
let perms = calculate_user_permissions(&mut query).await;
|
let perms = calculate_user_permissions(&mut query).await;
|
||||||
assert_eq!(perms, u32::MAX);
|
assert!(perms.has(u64::MAX));
|
||||||
|
|
||||||
let perms = calculate_channel_permissions(&mut query).await;
|
let perms = calculate_channel_permissions(&mut query).await;
|
||||||
let value: u64 = perms.into();
|
let value: u64 = perms.into();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "revolt-presence"
|
name = "revolt-presence"
|
||||||
version = "0.6.8"
|
version = "0.6.9"
|
||||||
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>"]
|
||||||
|
|||||||
@@ -221,7 +221,6 @@ mod tests {
|
|||||||
// Create a few more sessions
|
// Create a few more sessions
|
||||||
let (first_session, second_session_id) = create_session(&user_id, 0).await;
|
let (first_session, second_session_id) = create_session(&user_id, 0).await;
|
||||||
assert!(!first_session);
|
assert!(!first_session);
|
||||||
dbg!(second_session_id);
|
|
||||||
assert_eq!(second_session_id as u8 & 1, 0);
|
assert_eq!(second_session_id as u8 & 1, 0);
|
||||||
|
|
||||||
let (first_session, other_session_id) = create_session(&other_id, 0).await;
|
let (first_session, other_session_id) = create_session(&other_id, 0).await;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "revolt-result"
|
name = "revolt-result"
|
||||||
version = "0.6.8"
|
version = "0.6.9"
|
||||||
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.8"
|
version = "0.6.9"
|
||||||
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"
|
||||||
@@ -57,7 +57,7 @@ rocket = { version = "0.5.0-rc.2", default-features = false, features = [
|
|||||||
] }
|
] }
|
||||||
rocket_cors = { git = "https://github.com/lawliet89/rocket_cors", rev = "c17e8145baa4790319fdb6a473e465b960f55e7c" }
|
rocket_cors = { git = "https://github.com/lawliet89/rocket_cors", rev = "c17e8145baa4790319fdb6a473e465b960f55e7c" }
|
||||||
rocket_empty = { version = "0.1.1", features = ["schema"] }
|
rocket_empty = { version = "0.1.1", features = ["schema"] }
|
||||||
rocket_authifier = { version = "1.0.7" }
|
rocket_authifier = { version = "1.0.8" }
|
||||||
rocket_prometheus = "0.10.0-rc.3"
|
rocket_prometheus = "0.10.0-rc.3"
|
||||||
|
|
||||||
# spec generation
|
# spec generation
|
||||||
@@ -68,6 +68,7 @@ revolt_rocket_okapi = { version = "0.9.1", features = ["swagger"] }
|
|||||||
revolt-quark = { path = "../quark" }
|
revolt-quark = { path = "../quark" }
|
||||||
|
|
||||||
# core
|
# core
|
||||||
|
authifier = "1.0.8"
|
||||||
revolt-config = { path = "../core/config" }
|
revolt-config = { path = "../core/config" }
|
||||||
revolt-database = { path = "../core/database", features = [
|
revolt-database = { path = "../core/database", features = [
|
||||||
"rocket-impl",
|
"rocket-impl",
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ extern crate serde_json;
|
|||||||
pub mod routes;
|
pub mod routes;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
|
||||||
|
use revolt_database::{Database, MongoDb};
|
||||||
use rocket::{Build, Rocket};
|
use rocket::{Build, Rocket};
|
||||||
use rocket_cors::{AllowedOrigins, CorsOptions};
|
use rocket_cors::{AllowedOrigins, CorsOptions};
|
||||||
use rocket_prometheus::PrometheusMetrics;
|
use rocket_prometheus::PrometheusMetrics;
|
||||||
@@ -33,7 +34,12 @@ pub async fn web() -> Rocket<Build> {
|
|||||||
|
|
||||||
// Setup Authifier
|
// Setup Authifier
|
||||||
let authifier = Authifier {
|
let authifier = Authifier {
|
||||||
database: db.clone().into(),
|
database: match db.clone() {
|
||||||
|
Database::Reference(_) => Default::default(),
|
||||||
|
Database::MongoDb(MongoDb(client, _)) => authifier::Database::MongoDb(
|
||||||
|
authifier::database::MongoDb(client.database("revolt")),
|
||||||
|
),
|
||||||
|
},
|
||||||
config: revolt_quark::util::authifier::config(),
|
config: revolt_quark::util::authifier::config(),
|
||||||
event_channel: Some(sender),
|
event_channel: Some(sender),
|
||||||
};
|
};
|
||||||
@@ -55,8 +61,14 @@ pub async fn web() -> Rocket<Build> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Launch background task workers
|
// Launch background task workers
|
||||||
async_std::task::spawn(revolt_database::tasks::start_workers(db.clone()));
|
async_std::task::spawn(revolt_database::tasks::start_workers(
|
||||||
async_std::task::spawn(revolt_quark::tasks::start_workers(legacy_db.clone()));
|
db.clone(),
|
||||||
|
authifier.database.clone(),
|
||||||
|
));
|
||||||
|
async_std::task::spawn(revolt_quark::tasks::start_workers(
|
||||||
|
legacy_db.clone(),
|
||||||
|
authifier.database.clone(),
|
||||||
|
));
|
||||||
|
|
||||||
// Configure CORS
|
// Configure CORS
|
||||||
let cors = CorsOptions {
|
let cors = CorsOptions {
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ mod test {
|
|||||||
drop(response);
|
drop(response);
|
||||||
|
|
||||||
let event = harness
|
let event = harness
|
||||||
.wait_for_event(|event| match event {
|
.wait_for_event(&bot.id, |event| match event {
|
||||||
EventV1::UserUpdate { id, .. } => id == &bot.id,
|
EventV1::UserUpdate { id, .. } => id == &bot.id,
|
||||||
_ => false,
|
_ => false,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ pub async fn fetch_bot(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Ok(Json(FetchBotResponse {
|
Ok(Json(FetchBotResponse {
|
||||||
user: db.fetch_user(&bot.id).await?.into(None).await,
|
user: db.fetch_user(&bot.id).await?.into(db, None).await,
|
||||||
bot: bot.into(),
|
bot: bot.into(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ pub async fn invite_bot(
|
|||||||
.await
|
.await
|
||||||
.throw_if_lacking_channel_permission(ChannelPermission::ManageServer)?;
|
.throw_if_lacking_channel_permission(ChannelPermission::ManageServer)?;
|
||||||
|
|
||||||
Member::create(db, &server, &bot_user)
|
Member::create(db, &server, &bot_user, None)
|
||||||
.await
|
.await
|
||||||
.map(|_| EmptyResponse)
|
.map(|_| EmptyResponse)
|
||||||
}
|
}
|
||||||
@@ -66,7 +66,7 @@ pub async fn invite_bot(
|
|||||||
mod test {
|
mod test {
|
||||||
use crate::{rocket, util::test::TestHarness};
|
use crate::{rocket, util::test::TestHarness};
|
||||||
use revolt_database::{events::client::EventV1, Bot, Channel, Server};
|
use revolt_database::{events::client::EventV1, Bot, Channel, Server};
|
||||||
use revolt_models::v0;
|
use revolt_models::v0::{self, DataCreateServer};
|
||||||
use rocket::http::{ContentType, Header, Status};
|
use rocket::http::{ContentType, Header, Status};
|
||||||
|
|
||||||
#[rocket::async_test]
|
#[rocket::async_test]
|
||||||
@@ -102,7 +102,7 @@ mod test {
|
|||||||
drop(response);
|
drop(response);
|
||||||
|
|
||||||
let event = harness
|
let event = harness
|
||||||
.wait_for_event(|event| match event {
|
.wait_for_event(&group.id(), |event| match event {
|
||||||
EventV1::ChannelGroupJoin { id, .. } => id == &group.id(),
|
EventV1::ChannelGroupJoin { id, .. } => id == &group.id(),
|
||||||
_ => false,
|
_ => false,
|
||||||
})
|
})
|
||||||
@@ -125,26 +125,17 @@ mod test {
|
|||||||
.await
|
.await
|
||||||
.expect("`Bot`");
|
.expect("`Bot`");
|
||||||
|
|
||||||
// FIXME: Server::create_server
|
let (server, _) = Server::create(
|
||||||
let server = Server {
|
&harness.db,
|
||||||
id: ulid::Ulid::new().to_string(),
|
DataCreateServer {
|
||||||
name: TestHarness::rand_string(),
|
name: TestHarness::rand_string(),
|
||||||
owner: user.id.to_string(),
|
..Default::default()
|
||||||
analytics: false,
|
},
|
||||||
discoverable: false,
|
&user,
|
||||||
nsfw: false,
|
false,
|
||||||
banner: None,
|
)
|
||||||
icon: None,
|
.await
|
||||||
categories: None,
|
.unwrap();
|
||||||
channels: vec![],
|
|
||||||
default_permissions: 0,
|
|
||||||
description: None,
|
|
||||||
flags: None,
|
|
||||||
roles: Default::default(),
|
|
||||||
system_messages: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
server.create(&harness.db).await.unwrap();
|
|
||||||
|
|
||||||
let response = harness
|
let response = harness
|
||||||
.client
|
.client
|
||||||
@@ -164,7 +155,7 @@ mod test {
|
|||||||
drop(response);
|
drop(response);
|
||||||
|
|
||||||
let event = harness
|
let event = harness
|
||||||
.wait_for_event(|event| match event {
|
.wait_for_event(&server.id, |event| match event {
|
||||||
EventV1::ServerMemberJoin { id, .. } => id == &server.id,
|
EventV1::ServerMemberJoin { id, .. } => id == &server.id,
|
||||||
_ => false,
|
_ => false,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,40 +1,184 @@
|
|||||||
use revolt_quark::{
|
use revolt_database::{
|
||||||
get_relationship,
|
util::{permissions::DatabasePermissionQuery, reference::Reference},
|
||||||
models::{user::RelationshipStatus, Channel, User},
|
Channel, Database, User,
|
||||||
perms, Db, EmptyResponse, Error, Permission, Ref, Result,
|
|
||||||
};
|
};
|
||||||
|
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
|
||||||
|
use revolt_result::{create_error, Result};
|
||||||
|
|
||||||
|
use rocket::State;
|
||||||
|
use rocket_empty::EmptyResponse;
|
||||||
|
|
||||||
/// # Add Member to Group
|
/// # Add Member to Group
|
||||||
///
|
///
|
||||||
/// Adds another user to the group.
|
/// Adds another user to the group.
|
||||||
#[openapi(tag = "Groups")]
|
#[openapi(tag = "Groups")]
|
||||||
#[put("/<target>/recipients/<member>")]
|
#[put("/<group_id>/recipients/<member_id>")]
|
||||||
pub async fn req(db: &Db, user: User, target: Ref, member: Ref) -> Result<EmptyResponse> {
|
pub async fn req(
|
||||||
|
db: &State<Database>,
|
||||||
|
user: User,
|
||||||
|
group_id: Reference,
|
||||||
|
member_id: Reference,
|
||||||
|
) -> Result<EmptyResponse> {
|
||||||
if user.bot.is_some() {
|
if user.bot.is_some() {
|
||||||
return Err(Error::IsBot);
|
return Err(create_error!(IsBot));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut channel = target.as_channel(db).await?;
|
let mut channel = group_id.as_channel(db).await?;
|
||||||
perms(&user)
|
let mut query = DatabasePermissionQuery::new(db, &user).channel(&channel);
|
||||||
.channel(&channel)
|
calculate_channel_permissions(&mut query)
|
||||||
.throw_permission_and_view_channel(db, Permission::InviteOthers)
|
.await
|
||||||
.await?;
|
.throw_if_lacking_channel_permission(ChannelPermission::InviteOthers)?;
|
||||||
|
|
||||||
match &channel {
|
match &channel {
|
||||||
Channel::Group { .. } => {
|
Channel::Group { .. } => {
|
||||||
let member = member.as_user(db).await?;
|
// FIXME: use permissions here?
|
||||||
if !matches!(
|
// interesting if users could block new group invites
|
||||||
get_relationship(&user, &member.id),
|
let member = member_id.as_user(db).await?;
|
||||||
RelationshipStatus::Friend
|
if !user.is_friends_with(&member.id) {
|
||||||
) {
|
return Err(create_error!(NotFriends));
|
||||||
return Err(Error::NotFriends);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
channel
|
channel
|
||||||
.add_user_to_group(db, &member.id, &user.id)
|
.add_user_to_group(db, &member, &user.id)
|
||||||
.await
|
.await
|
||||||
.map(|_| EmptyResponse)
|
.map(|_| EmptyResponse)
|
||||||
}
|
}
|
||||||
_ => Err(Error::InvalidOperation),
|
_ => Err(create_error!(InvalidOperation)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::{rocket, util::test::TestHarness};
|
||||||
|
use revolt_database::{events::client::EventV1, Channel, RelationshipStatus};
|
||||||
|
use revolt_models::v0;
|
||||||
|
use rocket::http::{Header, Status};
|
||||||
|
|
||||||
|
#[rocket::async_test]
|
||||||
|
async fn success_add_member() {
|
||||||
|
let mut harness = TestHarness::new().await;
|
||||||
|
let (_, session, mut user) = harness.new_user().await;
|
||||||
|
let (_, _, mut other_user) = harness.new_user().await;
|
||||||
|
|
||||||
|
#[allow(clippy::disallowed_methods)]
|
||||||
|
user.apply_relationship(
|
||||||
|
&harness.db,
|
||||||
|
&mut other_user,
|
||||||
|
RelationshipStatus::Friend,
|
||||||
|
RelationshipStatus::Friend,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let group = Channel::create_group(
|
||||||
|
&harness.db,
|
||||||
|
v0::DataCreateGroup {
|
||||||
|
name: TestHarness::rand_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
user.id.to_string(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let response = harness
|
||||||
|
.client
|
||||||
|
.put(format!(
|
||||||
|
"/channels/{}/recipients/{}",
|
||||||
|
group.id(),
|
||||||
|
other_user.id
|
||||||
|
))
|
||||||
|
.header(Header::new("x-session-token", session.token.to_string()))
|
||||||
|
.dispatch()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_eq!(response.status(), Status::NoContent);
|
||||||
|
drop(response);
|
||||||
|
|
||||||
|
harness
|
||||||
|
.wait_for_event(&format!("{}!", other_user.id), |event| match event {
|
||||||
|
EventV1::ChannelCreate(channel) => channel.id() == group.id(),
|
||||||
|
_ => false,
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let event = harness
|
||||||
|
.wait_for_event(&group.id(), |event| match event {
|
||||||
|
EventV1::ChannelGroupJoin { id, .. } => id == &group.id(),
|
||||||
|
_ => false,
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match event {
|
||||||
|
EventV1::ChannelGroupJoin { user, .. } => assert_eq!(user, other_user.id),
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let message = harness.wait_for_message(&group.id()).await;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
message.system,
|
||||||
|
Some(v0::SystemMessage::UserAdded {
|
||||||
|
id: other_user.id.to_string(),
|
||||||
|
by: user.id.to_string()
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rocket::async_test]
|
||||||
|
async fn fail_add_non_friend() {
|
||||||
|
let harness = TestHarness::new().await;
|
||||||
|
let (_, session, user) = harness.new_user().await;
|
||||||
|
let (_, _, other_user) = harness.new_user().await;
|
||||||
|
|
||||||
|
let group = Channel::create_group(
|
||||||
|
&harness.db,
|
||||||
|
v0::DataCreateGroup {
|
||||||
|
name: TestHarness::rand_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
user.id.to_string(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let response = harness
|
||||||
|
.client
|
||||||
|
.put(format!(
|
||||||
|
"/channels/{}/recipients/{}",
|
||||||
|
group.id(),
|
||||||
|
other_user.id
|
||||||
|
))
|
||||||
|
.header(Header::new("x-session-token", session.token.to_string()))
|
||||||
|
.dispatch()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_eq!(response.status(), Status::Forbidden);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rocket::async_test]
|
||||||
|
async fn fail_add_already_in_group() {
|
||||||
|
let harness = TestHarness::new().await;
|
||||||
|
let (_, session, user) = harness.new_user().await;
|
||||||
|
|
||||||
|
let group = Channel::create_group(
|
||||||
|
&harness.db,
|
||||||
|
v0::DataCreateGroup {
|
||||||
|
name: TestHarness::rand_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
user.id.to_string(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let response = harness
|
||||||
|
.client
|
||||||
|
.put(format!("/channels/{}/recipients/{}", group.id(), user.id))
|
||||||
|
.header(Header::new("x-session-token", session.token.to_string()))
|
||||||
|
.dispatch()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_eq!(response.status(), Status::Conflict);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
use revolt_config::config;
|
|
||||||
use revolt_database::{Channel, Database, RelationshipStatus, User};
|
use revolt_database::{Channel, Database, RelationshipStatus, User};
|
||||||
use revolt_models::v0;
|
use revolt_models::v0;
|
||||||
use revolt_result::{create_error, Result};
|
use revolt_result::{create_error, Result};
|
||||||
@@ -17,26 +16,17 @@ pub async fn create_group(
|
|||||||
user: User,
|
user: User,
|
||||||
data: Json<v0::DataCreateGroup>,
|
data: Json<v0::DataCreateGroup>,
|
||||||
) -> Result<Json<v0::Channel>> {
|
) -> Result<Json<v0::Channel>> {
|
||||||
let config = config().await;
|
|
||||||
if user.bot.is_some() {
|
if user.bot.is_some() {
|
||||||
return Err(create_error!(IsBot));
|
return Err(create_error!(IsBot));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut data = data.into_inner();
|
let data = data.into_inner();
|
||||||
data.validate().map_err(|error| {
|
data.validate().map_err(|error| {
|
||||||
create_error!(FailedValidation {
|
create_error!(FailedValidation {
|
||||||
error: error.to_string()
|
error: error.to_string()
|
||||||
})
|
})
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
data.users.insert(user.id.to_string());
|
|
||||||
|
|
||||||
if data.users.len() > config.features.limits.default.group_size {
|
|
||||||
return Err(create_error!(GroupTooLarge {
|
|
||||||
max: config.features.limits.default.group_size,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
for target in &data.users {
|
for target in &data.users {
|
||||||
match user.relationship_with(target) {
|
match user.relationship_with(target) {
|
||||||
RelationshipStatus::Friend | RelationshipStatus::User => {}
|
RelationshipStatus::Friend | RelationshipStatus::User => {}
|
||||||
@@ -52,12 +42,13 @@ pub async fn create_group(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::{rocket, util::test::TestHarness};
|
use crate::{rocket, util::test::TestHarness};
|
||||||
|
use revolt_database::events::client::EventV1;
|
||||||
use revolt_models::v0;
|
use revolt_models::v0;
|
||||||
use rocket::http::{ContentType, Header, Status};
|
use rocket::http::{ContentType, Header, Status};
|
||||||
|
|
||||||
#[rocket::async_test]
|
#[rocket::async_test]
|
||||||
async fn create_group() {
|
async fn create_group() {
|
||||||
let harness = TestHarness::new().await;
|
let mut harness = TestHarness::new().await;
|
||||||
let (_, session, user) = harness.new_user().await;
|
let (_, session, user) = harness.new_user().await;
|
||||||
|
|
||||||
let response = harness
|
let response = harness
|
||||||
@@ -88,7 +79,22 @@ mod test {
|
|||||||
assert_eq!(recipients.len(), 1);
|
assert_eq!(recipients.len(), 1);
|
||||||
assert!(harness.db.fetch_channel(&id).await.is_ok());
|
assert!(harness.db.fetch_channel(&id).await.is_ok());
|
||||||
|
|
||||||
// TODO: does not check for events
|
let event = harness
|
||||||
|
.wait_for_event(&format!("{}!", user.id), |event| match event {
|
||||||
|
EventV1::ChannelCreate(channel) => channel.id() == id,
|
||||||
|
_ => false,
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match event {
|
||||||
|
EventV1::ChannelCreate(v0::Channel::Group {
|
||||||
|
owner: channel_owner,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
assert_eq!(owner, channel_owner);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,3 +40,151 @@ pub async fn req(db: &Db, user: User, target: Ref, member: Ref) -> Result<EmptyR
|
|||||||
_ => Err(Error::InvalidOperation),
|
_ => Err(Error::InvalidOperation),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::{rocket, util::test::TestHarness};
|
||||||
|
use revolt_database::{events::client::EventV1, Channel, RelationshipStatus};
|
||||||
|
use revolt_models::v0;
|
||||||
|
use rocket::http::{Header, Status};
|
||||||
|
|
||||||
|
#[rocket::async_test]
|
||||||
|
async fn success_remove_member() {
|
||||||
|
let mut harness = TestHarness::new().await;
|
||||||
|
let (_, session, mut user) = harness.new_user().await;
|
||||||
|
let (_, _, mut other_user) = harness.new_user().await;
|
||||||
|
|
||||||
|
#[allow(clippy::disallowed_methods)]
|
||||||
|
user.apply_relationship(
|
||||||
|
&harness.db,
|
||||||
|
&mut other_user,
|
||||||
|
RelationshipStatus::Friend,
|
||||||
|
RelationshipStatus::Friend,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let group = Channel::create_group(
|
||||||
|
&harness.db,
|
||||||
|
v0::DataCreateGroup {
|
||||||
|
name: TestHarness::rand_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
user.id.to_string(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let response = harness
|
||||||
|
.client
|
||||||
|
.put(format!(
|
||||||
|
"/channels/{}/recipients/{}",
|
||||||
|
group.id(),
|
||||||
|
other_user.id
|
||||||
|
))
|
||||||
|
.header(Header::new("x-session-token", session.token.to_string()))
|
||||||
|
.dispatch()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_eq!(response.status(), Status::NoContent);
|
||||||
|
drop(response);
|
||||||
|
|
||||||
|
harness
|
||||||
|
.wait_for_event(&format!("{}!", other_user.id), |event| match event {
|
||||||
|
EventV1::ChannelCreate(channel) => channel.id() == group.id(),
|
||||||
|
_ => false,
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let event = harness
|
||||||
|
.wait_for_event(&group.id(), |event| match event {
|
||||||
|
EventV1::ChannelGroupJoin { id, .. } => id == &group.id(),
|
||||||
|
_ => false,
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match event {
|
||||||
|
EventV1::ChannelGroupJoin { user, .. } => assert_eq!(user, other_user.id),
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let message = harness.wait_for_message(&group.id()).await;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
message.system,
|
||||||
|
Some(v0::SystemMessage::UserAdded {
|
||||||
|
id: other_user.id.to_string(),
|
||||||
|
by: user.id.to_string()
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rocket::async_test]
|
||||||
|
async fn fail_not_in_group() {
|
||||||
|
let harness = TestHarness::new().await;
|
||||||
|
let (_, session, user) = harness.new_user().await;
|
||||||
|
let (_, _, other_user) = harness.new_user().await;
|
||||||
|
|
||||||
|
let group = Channel::create_group(
|
||||||
|
&harness.db,
|
||||||
|
v0::DataCreateGroup {
|
||||||
|
name: TestHarness::rand_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
user.id.to_string(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let response = harness
|
||||||
|
.client
|
||||||
|
.delete(format!(
|
||||||
|
"/channels/{}/recipients/{}",
|
||||||
|
group.id(),
|
||||||
|
other_user.id
|
||||||
|
))
|
||||||
|
.header(Header::new("x-session-token", session.token.to_string()))
|
||||||
|
.dispatch()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
dbg!(response.into_string().await);
|
||||||
|
// TODO: finish impl
|
||||||
|
// assert_eq!(response.status(), Status::NotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rocket::async_test]
|
||||||
|
async fn fail_not_group_owner() {
|
||||||
|
let harness = TestHarness::new().await;
|
||||||
|
let (_, _, user) = harness.new_user().await;
|
||||||
|
let (_, session, other_user) = harness.new_user().await;
|
||||||
|
let (_, _, user_to_be_kicked) = harness.new_user().await;
|
||||||
|
|
||||||
|
let group = Channel::create_group(
|
||||||
|
&harness.db,
|
||||||
|
v0::DataCreateGroup {
|
||||||
|
name: TestHarness::rand_string(),
|
||||||
|
users: vec![&other_user.id, &user_to_be_kicked.id]
|
||||||
|
.into_iter()
|
||||||
|
.cloned()
|
||||||
|
.collect(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
user.id.to_string(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let _response = harness
|
||||||
|
.client
|
||||||
|
.delete(format!(
|
||||||
|
"/channels/{}/recipients/{}",
|
||||||
|
group.id(),
|
||||||
|
user_to_be_kicked.id
|
||||||
|
))
|
||||||
|
.header(Header::new("x-session-token", session.token.to_string()))
|
||||||
|
.dispatch()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// TODO: impl assert_eq!(response.status(), Status::Forbidden);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -78,6 +78,11 @@ pub async fn req(
|
|||||||
|
|
||||||
// 3. Replace if we are given new embeds
|
// 3. Replace if we are given new embeds
|
||||||
if let Some(embeds) = edit.embeds {
|
if let Some(embeds) = edit.embeds {
|
||||||
|
// Ensure we have permissions to send embeds
|
||||||
|
permissions
|
||||||
|
.throw_permission_and_view_channel(db, Permission::SendEmbeds)
|
||||||
|
.await?;
|
||||||
|
|
||||||
new_embeds.clear();
|
new_embeds.clear();
|
||||||
|
|
||||||
for embed in embeds {
|
for embed in embeds {
|
||||||
@@ -89,14 +94,19 @@ pub async fn req(
|
|||||||
|
|
||||||
message.update(db, partial).await?;
|
message.update(db, partial).await?;
|
||||||
|
|
||||||
// Queue up a task for processing embeds
|
// Queue up a task for processing embeds if the we have sufficient permissions
|
||||||
if let Some(content) = edit.content {
|
if permissions
|
||||||
revolt_quark::tasks::process_embeds::queue(
|
.has_permission(db, Permission::SendEmbeds)
|
||||||
message.channel.to_string(),
|
.await?
|
||||||
message.id.to_string(),
|
{
|
||||||
content,
|
if let Some(content) = edit.content {
|
||||||
)
|
revolt_quark::tasks::process_embeds::queue(
|
||||||
.await;
|
message.channel.to_string(),
|
||||||
|
message.id.to_string(),
|
||||||
|
content,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Json(message))
|
Ok(Json(message))
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ pub async fn message_send(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create the message
|
// Create the message
|
||||||
let author: v0::User = user.clone().into(Some(&user)).await;
|
let author: v0::User = user.clone().into(db, Some(&user)).await;
|
||||||
Ok(Json(
|
Ok(Json(
|
||||||
Message::create_from_api(
|
Message::create_from_api(
|
||||||
db,
|
db,
|
||||||
@@ -70,7 +70,7 @@ pub async fn message_send(
|
|||||||
data,
|
data,
|
||||||
v0::MessageAuthor::User(&author),
|
v0::MessageAuthor::User(&author),
|
||||||
idempotency,
|
idempotency,
|
||||||
true,
|
permissions.has_channel_permission(ChannelPermission::SendEmbeds),
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
.into(),
|
.into(),
|
||||||
|
|||||||
@@ -138,6 +138,7 @@ pub async fn root() -> Result<Json<RevoltConfig>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
#[cfg(feature = "FIXME: THIS TEST CAUSES cargo test TO SEG FAULT, I HAVE NO CLUE HOW")]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::rocket;
|
use crate::rocket;
|
||||||
use rocket::http::Status;
|
use rocket::http::Status;
|
||||||
|
|||||||
@@ -1,119 +1,39 @@
|
|||||||
use std::collections::HashMap;
|
use revolt_database::util::permissions::DatabasePermissionQuery;
|
||||||
|
use revolt_database::{util::reference::Reference, Channel, Database, User};
|
||||||
use revolt_quark::{
|
use revolt_models::v0;
|
||||||
models::{server::PartialServer, Channel, User},
|
use revolt_permissions::{calculate_server_permissions, ChannelPermission};
|
||||||
perms, Db, Error, Permission, Ref, Result, variables::delta::MAX_CHANNEL_COUNT,
|
use revolt_result::{create_error, Result};
|
||||||
};
|
|
||||||
|
|
||||||
use rocket::serde::json::Json;
|
use rocket::serde::json::Json;
|
||||||
use serde::{Deserialize, Serialize};
|
use rocket::State;
|
||||||
use ulid::Ulid;
|
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
|
||||||
/// # Channel Type
|
|
||||||
#[derive(Serialize, Deserialize, JsonSchema, Default)]
|
|
||||||
enum ChannelType {
|
|
||||||
/// Text Channel
|
|
||||||
#[default]
|
|
||||||
Text,
|
|
||||||
/// Voice Channel
|
|
||||||
Voice,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// # Channel Data
|
|
||||||
#[derive(Validate, Serialize, Deserialize, JsonSchema)]
|
|
||||||
pub struct DataCreateChannel {
|
|
||||||
/// Channel type
|
|
||||||
#[serde(rename = "type", default = "ChannelType::default")]
|
|
||||||
channel_type: ChannelType,
|
|
||||||
/// Channel name
|
|
||||||
#[validate(length(min = 1, max = 32))]
|
|
||||||
name: String,
|
|
||||||
/// Channel description
|
|
||||||
#[validate(length(min = 0, max = 1024))]
|
|
||||||
description: Option<String>,
|
|
||||||
/// Whether this channel is age restricted
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
nsfw: Option<bool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// # Create Channel
|
/// # Create Channel
|
||||||
///
|
///
|
||||||
/// Create a new Text or Voice channel.
|
/// Create a new Text or Voice channel.
|
||||||
#[openapi(tag = "Server Information")]
|
#[openapi(tag = "Server Information")]
|
||||||
#[post("/<target>/channels", data = "<info>")]
|
#[post("/<server>/channels", data = "<data>")]
|
||||||
pub async fn req(
|
pub async fn create_server_channel(
|
||||||
db: &Db,
|
db: &State<Database>,
|
||||||
user: User,
|
user: User,
|
||||||
target: Ref,
|
server: Reference,
|
||||||
info: Json<DataCreateChannel>,
|
data: Json<v0::DataCreateServerChannel>,
|
||||||
) -> Result<Json<Channel>> {
|
) -> Result<Json<v0::Channel>> {
|
||||||
let info = info.into_inner();
|
let data = data.into_inner();
|
||||||
info.validate()
|
data.validate().map_err(|error| {
|
||||||
.map_err(|error| Error::FailedValidation { error })?;
|
create_error!(FailedValidation {
|
||||||
|
error: error.to_string()
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
|
||||||
let mut server = target.as_server(db).await?;
|
let mut server = server.as_server(db).await?;
|
||||||
perms(&user)
|
let mut query = DatabasePermissionQuery::new(db, &user).server(&server);
|
||||||
.server(&server)
|
calculate_server_permissions(&mut query)
|
||||||
.throw_permission(db, Permission::ManageChannel)
|
.await
|
||||||
.await?;
|
.throw_if_lacking_channel_permission(ChannelPermission::ManageChannel)?;
|
||||||
|
|
||||||
if server.channels.len() > *MAX_CHANNEL_COUNT {
|
Channel::create_server_channel(db, &mut server, data, true)
|
||||||
return Err(Error::TooManyChannels { max: *MAX_CHANNEL_COUNT })
|
.await
|
||||||
};
|
.map(|channel| channel.into())
|
||||||
|
.map(Json)
|
||||||
let id = Ulid::new().to_string();
|
|
||||||
let mut channels = server.channels.clone();
|
|
||||||
channels.push(id.clone());
|
|
||||||
|
|
||||||
let DataCreateChannel {
|
|
||||||
name,
|
|
||||||
description,
|
|
||||||
nsfw,
|
|
||||||
channel_type,
|
|
||||||
} = info;
|
|
||||||
let channel = match channel_type {
|
|
||||||
ChannelType::Text => Channel::TextChannel {
|
|
||||||
id,
|
|
||||||
server: server.id.clone(),
|
|
||||||
|
|
||||||
name,
|
|
||||||
description,
|
|
||||||
|
|
||||||
icon: None,
|
|
||||||
last_message_id: None,
|
|
||||||
|
|
||||||
default_permissions: None,
|
|
||||||
role_permissions: HashMap::new(),
|
|
||||||
|
|
||||||
nsfw: nsfw.unwrap_or(false),
|
|
||||||
},
|
|
||||||
ChannelType::Voice => Channel::VoiceChannel {
|
|
||||||
id,
|
|
||||||
server: server.id.clone(),
|
|
||||||
|
|
||||||
name,
|
|
||||||
description,
|
|
||||||
icon: None,
|
|
||||||
|
|
||||||
default_permissions: None,
|
|
||||||
role_permissions: HashMap::new(),
|
|
||||||
|
|
||||||
nsfw: nsfw.unwrap_or(false),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
channel.create(db).await?;
|
|
||||||
server
|
|
||||||
.update(
|
|
||||||
db,
|
|
||||||
PartialServer {
|
|
||||||
channels: Some(channels),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
vec![],
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(Json(channel))
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,12 +25,12 @@ mod server_fetch;
|
|||||||
|
|
||||||
pub fn routes() -> (Vec<Route>, OpenApi) {
|
pub fn routes() -> (Vec<Route>, OpenApi) {
|
||||||
openapi_get_routes_spec![
|
openapi_get_routes_spec![
|
||||||
server_create::req,
|
server_create::create_server,
|
||||||
server_delete::req,
|
server_delete::req,
|
||||||
server_fetch::req,
|
server_fetch::req,
|
||||||
server_edit::req,
|
server_edit::req,
|
||||||
server_ack::req,
|
server_ack::req,
|
||||||
channel_create::req,
|
channel_create::create_server_channel,
|
||||||
member_fetch_all::req,
|
member_fetch_all::req,
|
||||||
member_remove::req,
|
member_remove::req,
|
||||||
member_fetch::req,
|
member_fetch::req,
|
||||||
|
|||||||
@@ -1,102 +1,39 @@
|
|||||||
use std::collections::HashMap;
|
use revolt_database::{Database, Member, Server, User};
|
||||||
|
use revolt_models::v0;
|
||||||
use revolt_quark::{
|
use revolt_result::{create_error, Result};
|
||||||
models::{Channel, Server, User},
|
|
||||||
variables::delta::MAX_SERVER_COUNT,
|
|
||||||
Db, Error, Result, DEFAULT_PERMISSION_SERVER,
|
|
||||||
};
|
|
||||||
|
|
||||||
use rocket::serde::json::Json;
|
use rocket::serde::json::Json;
|
||||||
use serde::{Deserialize, Serialize};
|
use rocket::State;
|
||||||
use ulid::Ulid;
|
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
|
||||||
/// # Server Data
|
|
||||||
#[derive(Validate, Deserialize, JsonSchema)]
|
|
||||||
pub struct DataCreateServer {
|
|
||||||
/// Server name
|
|
||||||
#[validate(length(min = 1, max = 32))]
|
|
||||||
name: String,
|
|
||||||
/// Server description
|
|
||||||
#[validate(length(min = 0, max = 1024))]
|
|
||||||
description: Option<String>,
|
|
||||||
/// Whether this server is age-restricted
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
nsfw: Option<bool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// # Create Server Response
|
|
||||||
#[derive(Validate, Serialize, JsonSchema)]
|
|
||||||
pub struct CreateServerResponse {
|
|
||||||
/// Server object
|
|
||||||
server: Server,
|
|
||||||
/// Default channels
|
|
||||||
channels: Vec<Channel>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// # Create Server
|
/// # Create Server
|
||||||
///
|
///
|
||||||
/// Create a new server.
|
/// Create a new server.
|
||||||
#[openapi(tag = "Server Information")]
|
#[openapi(tag = "Server Information")]
|
||||||
#[post("/create", data = "<info>")]
|
#[post("/create", data = "<data>")]
|
||||||
pub async fn req(
|
pub async fn create_server(
|
||||||
db: &Db,
|
db: &State<Database>,
|
||||||
user: User,
|
user: User,
|
||||||
info: Json<DataCreateServer>,
|
data: Json<v0::DataCreateServer>,
|
||||||
) -> Result<Json<CreateServerResponse>> {
|
) -> Result<Json<v0::CreateServerLegacyResponse>> {
|
||||||
if user.bot.is_some() {
|
if user.bot.is_some() {
|
||||||
return Err(Error::IsBot);
|
return Err(create_error!(IsBot));
|
||||||
}
|
}
|
||||||
|
|
||||||
let info = info.into_inner();
|
let data = data.into_inner();
|
||||||
info.validate()
|
data.validate().map_err(|error| {
|
||||||
.map_err(|error| Error::FailedValidation { error })?;
|
create_error!(FailedValidation {
|
||||||
|
error: error.to_string()
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
|
||||||
if !user.can_acquire_server(db).await? {
|
user.can_acquire_server(db).await?;
|
||||||
return Err(Error::TooManyServers {
|
|
||||||
max: *MAX_SERVER_COUNT,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let DataCreateServer {
|
let (server, channels) = Server::create(db, data, &user, true).await?;
|
||||||
name,
|
let channels = Member::create(db, &server, &user, Some(channels)).await?;
|
||||||
description,
|
|
||||||
nsfw,
|
|
||||||
} = info;
|
|
||||||
|
|
||||||
let channel_id = Ulid::new().to_string();
|
Ok(Json(v0::CreateServerLegacyResponse {
|
||||||
let server_id = Ulid::new().to_string();
|
server: server.into(),
|
||||||
|
channels: channels.into_iter().map(|channel| channel.into()).collect(),
|
||||||
let channel = Channel::TextChannel {
|
}))
|
||||||
id: channel_id.clone(),
|
|
||||||
server: server_id.clone(),
|
|
||||||
|
|
||||||
name: "General".into(),
|
|
||||||
description: None,
|
|
||||||
|
|
||||||
icon: None,
|
|
||||||
last_message_id: None,
|
|
||||||
|
|
||||||
default_permissions: None,
|
|
||||||
role_permissions: HashMap::new(),
|
|
||||||
|
|
||||||
nsfw: nsfw.unwrap_or(false),
|
|
||||||
};
|
|
||||||
|
|
||||||
db.insert_channel(&channel).await?;
|
|
||||||
|
|
||||||
let server = Server {
|
|
||||||
id: server_id.clone(),
|
|
||||||
owner: user.id.clone(),
|
|
||||||
name,
|
|
||||||
description,
|
|
||||||
channels: vec![channel_id],
|
|
||||||
nsfw: nsfw.unwrap_or(false),
|
|
||||||
default_permissions: *DEFAULT_PERMISSION_SERVER as i64,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
server.create(db).await?;
|
|
||||||
let channels = server.create_member(db, user, Some(vec![channel])).await?;
|
|
||||||
Ok(Json(CreateServerResponse { server, channels }))
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use redis_kiss::redis::aio::PubSub;
|
use redis_kiss::redis::aio::PubSub;
|
||||||
use revolt_database::{events::client::EventV1, Database, DatabaseInfo, User};
|
use revolt_database::{events::client::EventV1, Database, User};
|
||||||
|
use revolt_models::v0;
|
||||||
use revolt_quark::authifier::{
|
use revolt_quark::authifier::{
|
||||||
models::{Account, Session},
|
models::{Account, Session},
|
||||||
Authifier,
|
Authifier,
|
||||||
@@ -13,7 +14,7 @@ pub struct TestHarness {
|
|||||||
authifier: Authifier,
|
authifier: Authifier,
|
||||||
pub db: Database,
|
pub db: Database,
|
||||||
sub: PubSub,
|
sub: PubSub,
|
||||||
event_buffer: Vec<EventV1>,
|
event_buffer: Vec<(String, EventV1)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TestHarness {
|
impl TestHarness {
|
||||||
@@ -24,20 +25,27 @@ impl TestHarness {
|
|||||||
.await
|
.await
|
||||||
.expect("valid rocket instance");
|
.expect("valid rocket instance");
|
||||||
|
|
||||||
let db = DatabaseInfo::Auto.connect().await.expect("`Database`");
|
|
||||||
let mut sub = redis_kiss::open_pubsub_connection()
|
let mut sub = redis_kiss::open_pubsub_connection()
|
||||||
.await
|
.await
|
||||||
.expect("`PubSub`");
|
.expect("`PubSub`");
|
||||||
|
|
||||||
sub.psubscribe("*").await.unwrap();
|
sub.psubscribe("*").await.unwrap();
|
||||||
|
|
||||||
|
let db = client
|
||||||
|
.rocket()
|
||||||
|
.state::<Database>()
|
||||||
|
.expect("`Database`")
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
let authifier = client
|
||||||
|
.rocket()
|
||||||
|
.state::<Authifier>()
|
||||||
|
.expect("`Authifier`")
|
||||||
|
.clone();
|
||||||
|
|
||||||
TestHarness {
|
TestHarness {
|
||||||
client,
|
client,
|
||||||
authifier: Authifier {
|
authifier,
|
||||||
database: db.clone().into(),
|
|
||||||
config: revolt_quark::util::authifier::config(),
|
|
||||||
event_channel: None,
|
|
||||||
},
|
|
||||||
db,
|
db,
|
||||||
sub,
|
sub,
|
||||||
event_buffer: vec![],
|
event_buffer: vec![],
|
||||||
@@ -80,12 +88,12 @@ impl TestHarness {
|
|||||||
(account, session, user)
|
(account, session, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn wait_for_event<F>(&mut self, predicate: F) -> EventV1
|
pub async fn wait_for_event<F>(&mut self, topic: &str, predicate: F) -> EventV1
|
||||||
where
|
where
|
||||||
F: Fn(&EventV1) -> bool,
|
F: Fn(&EventV1) -> bool,
|
||||||
{
|
{
|
||||||
for event in &self.event_buffer {
|
for (msg_topic, event) in &self.event_buffer {
|
||||||
if predicate(event) {
|
if topic == msg_topic && predicate(event) {
|
||||||
// does not remove from buffer
|
// does not remove from buffer
|
||||||
return event.clone();
|
return event.clone();
|
||||||
}
|
}
|
||||||
@@ -93,13 +101,15 @@ impl TestHarness {
|
|||||||
|
|
||||||
let mut stream = self.sub.on_message();
|
let mut stream = self.sub.on_message();
|
||||||
while let Some(item) = stream.next().await {
|
while let Some(item) = stream.next().await {
|
||||||
let payload: EventV1 = redis_kiss::decode_payload(&item.unwrap()).unwrap();
|
let item = item.unwrap();
|
||||||
|
let msg_topic = item.get_channel_name();
|
||||||
|
let payload: EventV1 = redis_kiss::decode_payload(&item).unwrap();
|
||||||
|
|
||||||
if predicate(&payload) {
|
if topic == msg_topic && predicate(&payload) {
|
||||||
return payload;
|
return payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.event_buffer.push(payload);
|
self.event_buffer.push((msg_topic.to_string(), payload));
|
||||||
}
|
}
|
||||||
|
|
||||||
// WARNING: if predicate is never satisfied, this will never return
|
// WARNING: if predicate is never satisfied, this will never return
|
||||||
@@ -107,4 +117,19 @@ impl TestHarness {
|
|||||||
|
|
||||||
unreachable!()
|
unreachable!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn wait_for_message(&mut self, channel_id: &str) -> v0::Message {
|
||||||
|
dbg!(&self.event_buffer);
|
||||||
|
|
||||||
|
match self
|
||||||
|
.wait_for_event(channel_id, |event| match event {
|
||||||
|
EventV1::Message(v0::Message { channel, .. }) => channel == channel_id,
|
||||||
|
_ => false,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
EventV1::Message(message) => message,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "revolt-quark"
|
name = "revolt-quark"
|
||||||
version = "0.6.8"
|
version = "0.6.9"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "AGPL-3.0-or-later"
|
license = "AGPL-3.0-or-later"
|
||||||
|
|
||||||
@@ -88,7 +88,7 @@ rocket_empty = { version = "0.1.1", optional = true, features = ["schema"] }
|
|||||||
rocket_cors = { optional = true, git = "https://github.com/lawliet89/rocket_cors", rev = "c17e8145baa4790319fdb6a473e465b960f55e7c" }
|
rocket_cors = { optional = true, git = "https://github.com/lawliet89/rocket_cors", rev = "c17e8145baa4790319fdb6a473e465b960f55e7c" }
|
||||||
|
|
||||||
# Authifier
|
# Authifier
|
||||||
authifier = { version = "1.0.7", features = ["async-std-runtime"] }
|
authifier = { version = "1.0.8", features = ["async-std-runtime"] }
|
||||||
|
|
||||||
# Sentry
|
# Sentry
|
||||||
sentry = "0.31.5"
|
sentry = "0.31.5"
|
||||||
|
|||||||
@@ -31,6 +31,18 @@ impl DatabaseInfo {
|
|||||||
pub async fn connect(self) -> Result<Database, String> {
|
pub async fn connect(self) -> Result<Database, String> {
|
||||||
Ok(match self {
|
Ok(match self {
|
||||||
DatabaseInfo::Auto => {
|
DatabaseInfo::Auto => {
|
||||||
|
if let Ok(test_db) = env::var("TEST_DB") {
|
||||||
|
return match test_db.as_str() {
|
||||||
|
"REFERENCE" => DatabaseInfo::Dummy.connect().await,
|
||||||
|
"MONGODB" => {
|
||||||
|
DatabaseInfo::MongoDb(env::var("MONGODB").expect("`MONGODB` env"))
|
||||||
|
.connect()
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
_ => unreachable!("must specify REFERENCE or MONGODB"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if let Ok(uri) = env::var("MONGODB") {
|
if let Ok(uri) = env::var("MONGODB") {
|
||||||
return DatabaseInfo::MongoDb(uri).connect().await;
|
return DatabaseInfo::MongoDb(uri).connect().await;
|
||||||
}
|
}
|
||||||
@@ -61,17 +73,6 @@ impl Deref for Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Database> for authifier::Database {
|
|
||||||
fn from(val: Database) -> Self {
|
|
||||||
match val {
|
|
||||||
Database::Dummy(_) => authifier::Database::default(),
|
|
||||||
Database::MongoDb(MongoDb(client)) => authifier::Database::MongoDb(
|
|
||||||
authifier::database::MongoDb(client.database("revolt")),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Database> for revolt_database::Database {
|
impl From<Database> for revolt_database::Database {
|
||||||
fn from(val: Database) -> Self {
|
fn from(val: Database) -> Self {
|
||||||
match val {
|
match val {
|
||||||
|
|||||||
@@ -13,12 +13,12 @@ pub mod process_embeds;
|
|||||||
pub mod web_push;
|
pub mod web_push;
|
||||||
|
|
||||||
/// Spawn background workers
|
/// Spawn background workers
|
||||||
pub async fn start_workers(db: Database) {
|
pub async fn start_workers(db: Database, authifier_db: authifier::Database) {
|
||||||
for _ in 0..WORKER_COUNT {
|
for _ in 0..WORKER_COUNT {
|
||||||
task::spawn(ack::worker(db.clone()));
|
task::spawn(ack::worker(db.clone()));
|
||||||
task::spawn(last_message_id::worker(db.clone()));
|
task::spawn(last_message_id::worker(db.clone()));
|
||||||
task::spawn(process_embeds::worker(db.clone()));
|
task::spawn(process_embeds::worker(db.clone()));
|
||||||
task::spawn(web_push::worker(db.clone().into()));
|
task::spawn(web_push::worker(authifier_db.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
-1
@@ -21,10 +21,14 @@ in pkgs.mkShell {
|
|||||||
# Database
|
# Database
|
||||||
# pkgs.mongodb
|
# pkgs.mongodb
|
||||||
|
|
||||||
# Rust
|
# Cargo
|
||||||
pkgs.cargo
|
pkgs.cargo
|
||||||
|
pkgs.cargo-nextest
|
||||||
|
|
||||||
|
# Rust
|
||||||
pkgs.rustc
|
pkgs.rustc
|
||||||
pkgs.clippy
|
pkgs.clippy
|
||||||
|
pkgs.rustfmt
|
||||||
pkgs.pkgconfig
|
pkgs.pkgconfig
|
||||||
pkgs.openssl.dev
|
pkgs.openssl.dev
|
||||||
];
|
];
|
||||||
|
|||||||
Reference in New Issue
Block a user