Compare commits
25
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf82972a80 | ||
|
|
ea00f0fec1 | ||
|
|
226dbca6e0 | ||
|
|
e4b5a23f71 | ||
|
|
9663caa1ff | ||
|
|
f9f0f409a8 | ||
|
|
8f4d6c864b | ||
|
|
513461c99a | ||
|
|
41ef76ff3e | ||
|
|
c451e431cd | ||
|
|
09a848f5a6 | ||
|
|
308193377d | ||
|
|
3d3326e68a | ||
|
|
88c2232a6c | ||
|
|
25016efbc2 | ||
|
|
db63ac4064 | ||
|
|
1f593a1708 | ||
|
|
219c16a69c | ||
|
|
aed8f69f34 | ||
|
|
196a856c68 | ||
|
|
9bf60e87e9 | ||
|
|
b90e7a4412 | ||
|
|
cb813e91f0 | ||
|
|
a6bc9fcbb3 | ||
|
|
cd0737911b |
Generated
+279
-111
@@ -104,6 +104,12 @@ version = "1.0.68"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61"
|
||||
|
||||
[[package]]
|
||||
name = "arc-swap"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6"
|
||||
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
version = "0.3.6"
|
||||
@@ -165,7 +171,7 @@ dependencies = [
|
||||
"futures-lite",
|
||||
"once_cell",
|
||||
"tokio 0.2.25",
|
||||
"tokio 1.18.2",
|
||||
"tokio 1.35.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -246,7 +252,7 @@ dependencies = [
|
||||
"log",
|
||||
"memchr",
|
||||
"once_cell",
|
||||
"pin-project-lite 0.2.9",
|
||||
"pin-project-lite 0.2.13",
|
||||
"pin-utils",
|
||||
"slab",
|
||||
"wasm-bindgen-futures",
|
||||
@@ -315,7 +321,7 @@ dependencies = [
|
||||
"futures-io",
|
||||
"futures-util",
|
||||
"log",
|
||||
"pin-project-lite 0.2.9",
|
||||
"pin-project-lite 0.2.13",
|
||||
"tungstenite",
|
||||
]
|
||||
|
||||
@@ -588,9 +594,19 @@ checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.1.0"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
|
||||
checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
|
||||
|
||||
[[package]]
|
||||
name = "bytes-utils"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35"
|
||||
dependencies = [
|
||||
"bytes 1.5.0",
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cache-padded"
|
||||
@@ -612,7 +628,7 @@ dependencies = [
|
||||
"instant",
|
||||
"once_cell",
|
||||
"thiserror",
|
||||
"tokio 1.18.2",
|
||||
"tokio 1.35.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -712,11 +728,11 @@ version = "4.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a604e93b79d1808327a6fca85a6f2d69de66461e7620f5a4cbf5fb4d1d7c948"
|
||||
dependencies = [
|
||||
"bytes 1.1.0",
|
||||
"bytes 1.5.0",
|
||||
"futures-core",
|
||||
"memchr",
|
||||
"pin-project-lite 0.2.9",
|
||||
"tokio 1.18.2",
|
||||
"pin-project-lite 0.2.13",
|
||||
"tokio 1.35.1",
|
||||
"tokio-util 0.7.2",
|
||||
]
|
||||
|
||||
@@ -784,6 +800,12 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cookie-factory"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "396de984970346b0d9e93d1415082923c679e5ae5c3ee3dcbd104f5610af126b"
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.3"
|
||||
@@ -809,6 +831,12 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc16"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "338089f42c427b86394a5ee60ff321da23a5c89c9d89514c829687b26359fcff"
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-queue"
|
||||
version = "0.3.5"
|
||||
@@ -1002,7 +1030,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16a2561fd313df162315935989dceb8c99db4ee1933358270a57a3cfb8c957f3"
|
||||
dependencies = [
|
||||
"crossbeam-queue",
|
||||
"tokio 1.18.2",
|
||||
"tokio 1.35.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1348,6 +1376,15 @@ dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "float-cmp"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
@@ -1371,14 +1408,40 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.0.1"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
|
||||
checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
|
||||
dependencies = [
|
||||
"matches",
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fred"
|
||||
version = "8.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3b2a2ac060e3266004c552235c241b481e438e2b1ea75715ea1176914ef2868"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"async-trait",
|
||||
"bytes 1.5.0",
|
||||
"bytes-utils",
|
||||
"crossbeam-queue",
|
||||
"float-cmp",
|
||||
"futures",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"parking_lot",
|
||||
"rand 0.8.5",
|
||||
"redis-protocol",
|
||||
"semver 1.0.9",
|
||||
"socket2 0.5.5",
|
||||
"tokio 1.35.1",
|
||||
"tokio-stream",
|
||||
"tokio-util 0.7.2",
|
||||
"url",
|
||||
"urlencoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fuchsia-cprng"
|
||||
version = "0.1.1"
|
||||
@@ -1460,7 +1523,7 @@ dependencies = [
|
||||
"futures-io",
|
||||
"memchr",
|
||||
"parking",
|
||||
"pin-project-lite 0.2.9",
|
||||
"pin-project-lite 0.2.13",
|
||||
"waker-fn",
|
||||
]
|
||||
|
||||
@@ -1472,7 +1535,7 @@ checksum = "45ec6fe3675af967e67c5536c0b9d44e34e6c52f86bedc4ea49c5317b8e94d06"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-task",
|
||||
"tokio 1.18.2",
|
||||
"tokio 1.35.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1517,7 +1580,7 @@ dependencies = [
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"memchr",
|
||||
"pin-project-lite 0.2.9",
|
||||
"pin-project-lite 0.2.13",
|
||||
"pin-utils",
|
||||
"slab",
|
||||
]
|
||||
@@ -1661,7 +1724,7 @@ version = "0.3.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57"
|
||||
dependencies = [
|
||||
"bytes 1.1.0",
|
||||
"bytes 1.5.0",
|
||||
"fnv",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
@@ -1669,7 +1732,7 @@ dependencies = [
|
||||
"http",
|
||||
"indexmap",
|
||||
"slab",
|
||||
"tokio 1.18.2",
|
||||
"tokio 1.35.1",
|
||||
"tokio-util 0.7.2",
|
||||
"tracing",
|
||||
]
|
||||
@@ -1802,7 +1865,7 @@ version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff8670570af52249509a86f5e3e18a08c60b177071826898fde8997cf5f6bfbb"
|
||||
dependencies = [
|
||||
"bytes 1.1.0",
|
||||
"bytes 1.5.0",
|
||||
"fnv",
|
||||
"itoa 1.0.2",
|
||||
]
|
||||
@@ -1823,9 +1886,9 @@ version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1"
|
||||
dependencies = [
|
||||
"bytes 1.1.0",
|
||||
"bytes 1.5.0",
|
||||
"http",
|
||||
"pin-project-lite 0.2.9",
|
||||
"pin-project-lite 0.2.13",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1885,7 +1948,7 @@ version = "0.14.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42dc3c131584288d375f2d07f822b0cb012d8c6fb899a5b9fdb3cb7eb9b6004f"
|
||||
dependencies = [
|
||||
"bytes 1.1.0",
|
||||
"bytes 1.5.0",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
@@ -1895,9 +1958,9 @@ dependencies = [
|
||||
"httparse",
|
||||
"httpdate 1.0.2",
|
||||
"itoa 1.0.2",
|
||||
"pin-project-lite 0.2.9",
|
||||
"pin-project-lite 0.2.13",
|
||||
"socket2 0.4.4",
|
||||
"tokio 1.18.2",
|
||||
"tokio 1.35.1",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
"want",
|
||||
@@ -1922,10 +1985,10 @@ version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
|
||||
dependencies = [
|
||||
"bytes 1.1.0",
|
||||
"bytes 1.5.0",
|
||||
"hyper 0.14.19",
|
||||
"native-tls",
|
||||
"tokio 1.18.2",
|
||||
"tokio 1.35.1",
|
||||
"tokio-native-tls",
|
||||
]
|
||||
|
||||
@@ -1946,6 +2009,16 @@ dependencies = [
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
|
||||
dependencies = [
|
||||
"unicode-bidi",
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "if_chain"
|
||||
version = "1.0.2"
|
||||
@@ -2183,7 +2256,7 @@ dependencies = [
|
||||
"futures-util",
|
||||
"hostname",
|
||||
"httpdate 1.0.2",
|
||||
"idna",
|
||||
"idna 0.2.3",
|
||||
"mime",
|
||||
"native-tls",
|
||||
"nom",
|
||||
@@ -2194,9 +2267,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.126"
|
||||
version = "0.2.153"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
|
||||
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
||||
|
||||
[[package]]
|
||||
name = "libgit2-sys"
|
||||
@@ -2429,14 +2502,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.8.3"
|
||||
version = "0.8.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "713d550d9b44d89174e066b7a6217ae06234c10cb47819a88290d2b353c31799"
|
||||
checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"windows-sys",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2466,7 +2538,7 @@ dependencies = [
|
||||
"log",
|
||||
"metrics",
|
||||
"thiserror",
|
||||
"tokio 1.18.2",
|
||||
"tokio 1.35.1",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
@@ -2518,7 +2590,7 @@ dependencies = [
|
||||
"strsim",
|
||||
"take_mut",
|
||||
"thiserror",
|
||||
"tokio 1.18.2",
|
||||
"tokio 1.35.1",
|
||||
"tokio-rustls",
|
||||
"tokio-util 0.7.2",
|
||||
"trust-dns-proto",
|
||||
@@ -2534,7 +2606,7 @@ version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f8f35e687561d5c1667590911e6698a8cb714a134a7505718a182e7bc9d3836"
|
||||
dependencies = [
|
||||
"bytes 1.1.0",
|
||||
"bytes 1.5.0",
|
||||
"encoding_rs",
|
||||
"futures-util",
|
||||
"http",
|
||||
@@ -2543,7 +2615,7 @@ dependencies = [
|
||||
"memchr",
|
||||
"mime",
|
||||
"spin 0.9.3",
|
||||
"tokio 1.18.2",
|
||||
"tokio 1.35.1",
|
||||
"tokio-util 0.6.10",
|
||||
"version_check",
|
||||
]
|
||||
@@ -2846,7 +2918,7 @@ dependencies = [
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-sys",
|
||||
"windows-sys 0.36.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2924,9 +2996,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.1.0"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
||||
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||
|
||||
[[package]]
|
||||
name = "pest"
|
||||
@@ -2999,9 +3071,9 @@ checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.9"
|
||||
version = "0.2.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
|
||||
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
@@ -3114,9 +3186,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.56"
|
||||
version = "1.0.78"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
|
||||
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -3334,15 +3406,15 @@ source = "git+https://github.com/revoltchat/redis-rs?rev=1a41faf356fd21aebba71ce
|
||||
dependencies = [
|
||||
"async-std",
|
||||
"async-trait",
|
||||
"bytes 1.1.0",
|
||||
"bytes 1.5.0",
|
||||
"combine",
|
||||
"futures-util",
|
||||
"itoa 1.0.2",
|
||||
"percent-encoding",
|
||||
"pin-project-lite 0.2.9",
|
||||
"pin-project-lite 0.2.13",
|
||||
"ryu",
|
||||
"sha1_smol",
|
||||
"tokio 1.18.2",
|
||||
"tokio 1.35.1",
|
||||
"tokio-util 0.7.2",
|
||||
"url",
|
||||
]
|
||||
@@ -3354,14 +3426,14 @@ source = "git+https://github.com/revoltchat/redis-rs?rev=f8ca28ab85da59d2ccde526
|
||||
dependencies = [
|
||||
"async-std",
|
||||
"async-trait",
|
||||
"bytes 1.1.0",
|
||||
"bytes 1.5.0",
|
||||
"combine",
|
||||
"futures-util",
|
||||
"itoa 1.0.2",
|
||||
"percent-encoding",
|
||||
"pin-project-lite 0.2.9",
|
||||
"pin-project-lite 0.2.13",
|
||||
"ryu",
|
||||
"tokio 1.18.2",
|
||||
"tokio 1.35.1",
|
||||
"tokio-util 0.7.2",
|
||||
"url",
|
||||
]
|
||||
@@ -3382,6 +3454,20 @@ dependencies = [
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redis-protocol"
|
||||
version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c31deddf734dc0a39d3112e73490e88b61a05e83e074d211f348404cee4d2c6"
|
||||
dependencies = [
|
||||
"bytes 1.5.0",
|
||||
"bytes-utils",
|
||||
"cookie-factory",
|
||||
"crc16",
|
||||
"log",
|
||||
"nom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.13"
|
||||
@@ -3453,7 +3539,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46a1f7aa4f35e5e8b4160449f51afc758f0ce6454315a9fa7d0d113e958c41eb"
|
||||
dependencies = [
|
||||
"base64 0.13.0",
|
||||
"bytes 1.1.0",
|
||||
"bytes 1.5.0",
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
@@ -3469,11 +3555,11 @@ dependencies = [
|
||||
"mime",
|
||||
"native-tls",
|
||||
"percent-encoding",
|
||||
"pin-project-lite 0.2.9",
|
||||
"pin-project-lite 0.2.13",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"tokio 1.18.2",
|
||||
"tokio 1.35.1",
|
||||
"tokio-native-tls",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
@@ -3494,10 +3580,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "revolt-bonfire"
|
||||
version = "0.6.8"
|
||||
version = "0.6.12"
|
||||
dependencies = [
|
||||
"async-std",
|
||||
"async-tungstenite",
|
||||
"bincode",
|
||||
"fred",
|
||||
"futures",
|
||||
"log",
|
||||
"once_cell",
|
||||
@@ -3512,7 +3600,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "revolt-config"
|
||||
version = "0.6.8"
|
||||
version = "0.6.12"
|
||||
dependencies = [
|
||||
"async-std",
|
||||
"cached",
|
||||
@@ -3524,7 +3612,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "revolt-database"
|
||||
version = "0.6.8"
|
||||
version = "0.6.12"
|
||||
dependencies = [
|
||||
"async-lock",
|
||||
"async-recursion",
|
||||
@@ -3569,7 +3657,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "revolt-delta"
|
||||
version = "0.6.8"
|
||||
version = "0.6.12"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"async-std",
|
||||
@@ -3615,7 +3703,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "revolt-models"
|
||||
version = "0.6.8"
|
||||
version = "0.6.12"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"iso8601-timestamp 0.2.11",
|
||||
@@ -3631,7 +3719,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "revolt-permissions"
|
||||
version = "0.6.8"
|
||||
version = "0.6.12"
|
||||
dependencies = [
|
||||
"async-std",
|
||||
"async-trait",
|
||||
@@ -3646,7 +3734,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "revolt-presence"
|
||||
version = "0.6.8"
|
||||
version = "0.6.12"
|
||||
dependencies = [
|
||||
"async-std",
|
||||
"log",
|
||||
@@ -3657,7 +3745,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "revolt-quark"
|
||||
version = "0.6.8"
|
||||
version = "0.6.12"
|
||||
dependencies = [
|
||||
"async-lock",
|
||||
"async-recursion",
|
||||
@@ -3710,7 +3798,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "revolt-result"
|
||||
version = "0.6.8"
|
||||
version = "0.6.12"
|
||||
dependencies = [
|
||||
"revolt_okapi",
|
||||
"revolt_rocket_okapi",
|
||||
@@ -3830,7 +3918,7 @@ dependencies = [
|
||||
"atomic",
|
||||
"atty",
|
||||
"binascii",
|
||||
"bytes 1.1.0",
|
||||
"bytes 1.5.0",
|
||||
"either",
|
||||
"figment",
|
||||
"futures",
|
||||
@@ -3840,7 +3928,7 @@ dependencies = [
|
||||
"multer",
|
||||
"num_cpus",
|
||||
"parking_lot",
|
||||
"pin-project-lite 0.2.9",
|
||||
"pin-project-lite 0.2.13",
|
||||
"rand 0.8.5",
|
||||
"ref-cast",
|
||||
"rocket_codegen",
|
||||
@@ -3850,7 +3938,7 @@ dependencies = [
|
||||
"state",
|
||||
"tempfile",
|
||||
"time 0.3.17",
|
||||
"tokio 1.18.2",
|
||||
"tokio 1.35.1",
|
||||
"tokio-stream",
|
||||
"tokio-util 0.7.2",
|
||||
"ubyte",
|
||||
@@ -3933,14 +4021,14 @@ dependencies = [
|
||||
"memchr",
|
||||
"pear",
|
||||
"percent-encoding",
|
||||
"pin-project-lite 0.2.9",
|
||||
"pin-project-lite 0.2.13",
|
||||
"ref-cast",
|
||||
"serde",
|
||||
"smallvec",
|
||||
"stable-pattern",
|
||||
"state",
|
||||
"time 0.3.17",
|
||||
"tokio 1.18.2",
|
||||
"tokio 1.35.1",
|
||||
"uncased",
|
||||
]
|
||||
|
||||
@@ -4088,7 +4176,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"windows-sys",
|
||||
"windows-sys 0.36.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4222,7 +4310,7 @@ dependencies = [
|
||||
"sentry-debug-images",
|
||||
"sentry-panic",
|
||||
"sentry-tracing",
|
||||
"tokio 1.18.2",
|
||||
"tokio 1.35.1",
|
||||
"ureq",
|
||||
]
|
||||
|
||||
@@ -4523,6 +4611,16 @@ dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.5.2"
|
||||
@@ -4783,33 +4881,32 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.18.2"
|
||||
version = "1.35.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4903bf0427cf68dddd5aa6a93220756f8be0c34fcfa9f5e6191e103e15a31395"
|
||||
checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104"
|
||||
dependencies = [
|
||||
"bytes 1.1.0",
|
||||
"backtrace",
|
||||
"bytes 1.5.0",
|
||||
"libc",
|
||||
"memchr",
|
||||
"mio 0.8.3",
|
||||
"mio 0.8.10",
|
||||
"num_cpus",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"pin-project-lite 0.2.9",
|
||||
"pin-project-lite 0.2.13",
|
||||
"signal-hook-registry",
|
||||
"socket2 0.4.4",
|
||||
"socket2 0.5.5",
|
||||
"tokio-macros",
|
||||
"winapi 0.3.9",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "1.7.0"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7"
|
||||
checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.26",
|
||||
"syn 1.0.107",
|
||||
"syn 2.0.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4819,7 +4916,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b"
|
||||
dependencies = [
|
||||
"native-tls",
|
||||
"tokio 1.18.2",
|
||||
"tokio 1.35.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4829,7 +4926,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59"
|
||||
dependencies = [
|
||||
"rustls",
|
||||
"tokio 1.18.2",
|
||||
"tokio 1.35.1",
|
||||
"webpki",
|
||||
]
|
||||
|
||||
@@ -4840,8 +4937,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"pin-project-lite 0.2.9",
|
||||
"tokio 1.18.2",
|
||||
"pin-project-lite 0.2.13",
|
||||
"tokio 1.35.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4874,12 +4971,12 @@ version = "0.6.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507"
|
||||
dependencies = [
|
||||
"bytes 1.1.0",
|
||||
"bytes 1.5.0",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"log",
|
||||
"pin-project-lite 0.2.9",
|
||||
"tokio 1.18.2",
|
||||
"pin-project-lite 0.2.13",
|
||||
"tokio 1.35.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4888,12 +4985,12 @@ version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f988a1a1adc2fb21f9c12aa96441da33a1728193ae0b95d2be22dbd17fcb4e5c"
|
||||
dependencies = [
|
||||
"bytes 1.1.0",
|
||||
"bytes 1.5.0",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"pin-project-lite 0.2.9",
|
||||
"tokio 1.18.2",
|
||||
"pin-project-lite 0.2.13",
|
||||
"tokio 1.35.1",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
@@ -4932,7 +5029,7 @@ checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"log",
|
||||
"pin-project-lite 0.2.9",
|
||||
"pin-project-lite 0.2.13",
|
||||
"tracing-attributes",
|
||||
"tracing-core",
|
||||
]
|
||||
@@ -5010,7 +5107,7 @@ dependencies = [
|
||||
"futures-channel",
|
||||
"futures-io",
|
||||
"futures-util",
|
||||
"idna",
|
||||
"idna 0.2.3",
|
||||
"ipnet",
|
||||
"lazy_static",
|
||||
"log",
|
||||
@@ -5018,7 +5115,7 @@ dependencies = [
|
||||
"smallvec",
|
||||
"thiserror",
|
||||
"tinyvec",
|
||||
"tokio 1.18.2",
|
||||
"tokio 1.35.1",
|
||||
"url",
|
||||
]
|
||||
|
||||
@@ -5038,7 +5135,7 @@ dependencies = [
|
||||
"resolv-conf",
|
||||
"smallvec",
|
||||
"thiserror",
|
||||
"tokio 1.18.2",
|
||||
"tokio 1.35.1",
|
||||
"trust-dns-proto",
|
||||
]
|
||||
|
||||
@@ -5056,7 +5153,7 @@ checksum = "d96a2dea40e7570482f28eb57afbe42d97551905da6a9400acc5c328d24004f5"
|
||||
dependencies = [
|
||||
"base64 0.13.0",
|
||||
"byteorder",
|
||||
"bytes 1.1.0",
|
||||
"bytes 1.5.0",
|
||||
"http",
|
||||
"httparse",
|
||||
"log",
|
||||
@@ -5169,9 +5266,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.8"
|
||||
version = "0.3.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
|
||||
checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
@@ -5181,9 +5278,9 @@ checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
version = "0.1.19"
|
||||
version = "0.1.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9"
|
||||
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
|
||||
dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
@@ -5243,13 +5340,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.2.2"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c"
|
||||
checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
"matches",
|
||||
"idna 0.5.0",
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
]
|
||||
@@ -5263,6 +5359,12 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urlencoding"
|
||||
version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
|
||||
|
||||
[[package]]
|
||||
name = "utf-8"
|
||||
version = "0.7.6"
|
||||
@@ -5295,7 +5397,7 @@ version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f07b0a1390e01c0fc35ebb26b28ced33c9a3808f7f9fbe94d3cc01e233bfeed5"
|
||||
dependencies = [
|
||||
"idna",
|
||||
"idna 0.2.3",
|
||||
"lazy_static",
|
||||
"regex",
|
||||
"serde",
|
||||
@@ -5310,7 +5412,7 @@ version = "0.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32ad5bf234c7d3ad1042e5252b7eddb2c4669ee23f32c7dd0e9b7705f07ef591"
|
||||
dependencies = [
|
||||
"idna",
|
||||
"idna 0.2.3",
|
||||
"lazy_static",
|
||||
"regex",
|
||||
"serde",
|
||||
@@ -5626,43 +5728,109 @@ version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
|
||||
dependencies = [
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_msvc",
|
||||
"windows_aarch64_msvc 0.36.1",
|
||||
"windows_i686_gnu 0.36.1",
|
||||
"windows_i686_msvc 0.36.1",
|
||||
"windows_x86_64_gnu 0.36.1",
|
||||
"windows_x86_64_msvc 0.36.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc 0.48.5",
|
||||
"windows_i686_gnu 0.48.5",
|
||||
"windows_i686_msvc 0.48.5",
|
||||
"windows_x86_64_gnu 0.48.5",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.7.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "revolt-bonfire"
|
||||
version = "0.6.8"
|
||||
version = "0.6.12"
|
||||
license = "AGPL-3.0-or-later"
|
||||
edition = "2021"
|
||||
|
||||
@@ -18,6 +18,7 @@ querystring = "1.1.0"
|
||||
revolt-quark = { path = "../quark" }
|
||||
|
||||
# serde
|
||||
bincode = "1.3.3"
|
||||
serde_json = "1.0.79"
|
||||
rmp-serde = "1.0.0"
|
||||
serde = "1.0.136"
|
||||
@@ -34,3 +35,6 @@ async-std = { version = "1.8.0", features = [
|
||||
# core
|
||||
revolt-presence = { path = "../core/presence", features = ["redis-is-patched"] }
|
||||
sentry = "0.31.5"
|
||||
|
||||
# redis
|
||||
fred = { version = "8.0.1", features = ["subscriber-client"] }
|
||||
@@ -29,6 +29,10 @@ async fn main() {
|
||||
|
||||
// Start accepting new connections and spawn a client for each connection.
|
||||
while let Ok((stream, addr)) = listener.accept().await {
|
||||
websocket::spawn_client(database::get_db(), stream, addr);
|
||||
async_std::task::spawn(async move {
|
||||
info!("User connected from {addr:?}");
|
||||
websocket::client(database::get_db(), stream, addr).await;
|
||||
info!("User disconnected from {addr:?}");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+238
-228
@@ -1,6 +1,16 @@
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use futures::{channel::oneshot, pin_mut, select, FutureExt, SinkExt, StreamExt, TryStreamExt};
|
||||
use async_tungstenite::WebSocketStream;
|
||||
use fred::{
|
||||
interfaces::{ClientLike, EventInterface, PubsubInterface},
|
||||
types::RedisConfig,
|
||||
};
|
||||
use futures::{
|
||||
channel::oneshot,
|
||||
pin_mut, select,
|
||||
stream::{SplitSink, SplitStream},
|
||||
FutureExt, SinkExt, StreamExt, TryStreamExt,
|
||||
};
|
||||
use revolt_presence::{create_session, delete_session};
|
||||
use revolt_quark::{
|
||||
events::{
|
||||
@@ -9,249 +19,249 @@ use revolt_quark::{
|
||||
state::{State, SubscriptionStateChange},
|
||||
},
|
||||
models::{user::UserHint, User},
|
||||
redis_kiss, Database,
|
||||
redis_kiss::{PayloadType, REDIS_PAYLOAD_TYPE, REDIS_URI},
|
||||
Database,
|
||||
};
|
||||
|
||||
use async_std::{net::TcpStream, sync::Mutex, task};
|
||||
use async_std::{net::TcpStream, sync::Mutex};
|
||||
|
||||
use crate::config::WebsocketHandshakeCallback;
|
||||
use crate::config::{ProtocolConfiguration, WebsocketHandshakeCallback};
|
||||
|
||||
/// Spawn a new WebSocket client worker given access to the database,
|
||||
type WsReader = SplitStream<WebSocketStream<TcpStream>>;
|
||||
type WsWriter = SplitSink<WebSocketStream<TcpStream>, async_tungstenite::tungstenite::Message>;
|
||||
|
||||
/// Start a new WebSocket client worker given access to the database,
|
||||
/// the relevant TCP stream and the remote address of the client.
|
||||
pub fn spawn_client(db: &'static Database, stream: TcpStream, addr: SocketAddr) {
|
||||
// Spawn a new Async task to work on.
|
||||
task::spawn(async move {
|
||||
info!("User connected from {addr:?}");
|
||||
pub async fn client(db: &'static Database, stream: TcpStream, addr: SocketAddr) {
|
||||
// Upgrade the TCP connection to a WebSocket connection.
|
||||
// In this process, we also parse any additional parameters given.
|
||||
// e.g. wss://example.com?format=json&version=1
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
let Ok(ws) = async_tungstenite::accept_hdr_async_with_config(
|
||||
stream,
|
||||
WebsocketHandshakeCallback::from(sender),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
else {
|
||||
return;
|
||||
};
|
||||
// Verify we've received a valid config, otherwise we should just drop the connection.
|
||||
let Ok(mut config) = receiver.await else {
|
||||
return;
|
||||
};
|
||||
info!(
|
||||
"User {addr:?} provided protocol configuration (version = {}, format = {:?})",
|
||||
config.get_protocol_version(),
|
||||
config.get_protocol_format()
|
||||
);
|
||||
|
||||
// Upgrade the TCP connection to a WebSocket connection.
|
||||
// In this process, we also parse any additional parameters given.
|
||||
// e.g. wss://example.com?format=json&version=1
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
if let Ok(ws) = async_tungstenite::accept_hdr_async_with_config(
|
||||
stream,
|
||||
WebsocketHandshakeCallback::from(sender),
|
||||
None,
|
||||
)
|
||||
// Split the socket for simultaneously read and write.
|
||||
let (mut write, mut read) = ws.split();
|
||||
|
||||
// If the user has not provided authentication, request information.
|
||||
if config.get_session_token().is_none() {
|
||||
while let Ok(message) = read.try_next().await {
|
||||
if let Ok(ClientMessage::Authenticate { token }) =
|
||||
config.decode(message.as_ref().unwrap())
|
||||
{
|
||||
config.set_session_token(token);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try to authenticate the user.
|
||||
let Some(token) = config.get_session_token().as_ref() else {
|
||||
return;
|
||||
};
|
||||
let user = match User::from_token(db, token, UserHint::Any).await {
|
||||
Ok(user) => user,
|
||||
Err(err) => {
|
||||
write.send(config.encode(&err)).await.ok();
|
||||
return;
|
||||
}
|
||||
};
|
||||
info!("User {addr:?} authenticated as @{}", user.username);
|
||||
|
||||
// Create local state.
|
||||
let mut state = State::from(user);
|
||||
let user_id = state.cache.user_id.clone();
|
||||
|
||||
// Notify socket we have authenticated.
|
||||
if write
|
||||
.send(config.encode(&EventV1::Authenticated))
|
||||
.await
|
||||
{
|
||||
// Verify we've received a valid config, otherwise we should just drop the connection.
|
||||
if let Ok(mut config) = receiver.await {
|
||||
info!(
|
||||
"User {addr:?} provided protocol configuration (version = {}, format = {:?})",
|
||||
config.get_protocol_version(),
|
||||
config.get_protocol_format()
|
||||
);
|
||||
.is_err()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Split the socket for simultaneously read and write.
|
||||
let (write, mut read) = ws.split();
|
||||
let write = Mutex::new(write);
|
||||
// Download required data to local cache and send Ready payload.
|
||||
let Ok(ready_payload) = state.generate_ready_payload(db).await else {
|
||||
return;
|
||||
};
|
||||
if write.send(config.encode(&ready_payload)).await.is_err() {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the user has not provided authentication, request information.
|
||||
if config.get_session_token().is_none() {
|
||||
'outer: while let Ok(message) = read.try_next().await {
|
||||
if let Ok(ClientMessage::Authenticate { token }) =
|
||||
config.decode(message.as_ref().unwrap())
|
||||
{
|
||||
config.set_session_token(token);
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
// Create presence session.
|
||||
let (first_session, session_id) = create_session(&user_id, 0).await;
|
||||
|
||||
// If this was the first session, notify other users that we just went online.
|
||||
if first_session {
|
||||
state.broadcast_presence_change(true).await;
|
||||
}
|
||||
|
||||
{
|
||||
let write = Mutex::new(write);
|
||||
// Create a PubSub connection to poll on.
|
||||
let listener = listener(db, &mut state, addr, &config, &write).fuse();
|
||||
// Read from WebSocket stream.
|
||||
let worker = worker(user_id.clone(), &config, read, &write).fuse();
|
||||
|
||||
// Pin both tasks.
|
||||
pin_mut!(listener, worker);
|
||||
|
||||
// Wait for either disconnect or for listener to die.
|
||||
select!(
|
||||
() = listener => {},
|
||||
() = worker => {}
|
||||
);
|
||||
}
|
||||
// Clean up presence session.
|
||||
let last_session = delete_session(&user_id, session_id).await;
|
||||
|
||||
// If this was the last session, notify other users that we just went offline.
|
||||
if last_session {
|
||||
state.broadcast_presence_change(false).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn listener(
|
||||
db: &'static Database,
|
||||
state: &mut State,
|
||||
addr: SocketAddr,
|
||||
config: &ProtocolConfiguration,
|
||||
write: &Mutex<WsWriter>,
|
||||
) {
|
||||
let redis_config = RedisConfig::from_url(&REDIS_URI).unwrap();
|
||||
let Ok(subscriber) = fred::types::Builder::from_config(redis_config).build_subscriber_client()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if subscriber.init().await.is_err() {
|
||||
return;
|
||||
};
|
||||
let mut message_rx = subscriber.message_rx();
|
||||
loop {
|
||||
// Check for state changes for subscriptions.
|
||||
match state.apply_state() {
|
||||
SubscriptionStateChange::Reset => {
|
||||
subscriber.unsubscribe_all().await.unwrap();
|
||||
for id in state.iter_subscriptions() {
|
||||
subscriber.subscribe(id).await.unwrap();
|
||||
}
|
||||
|
||||
// Try to authenticate the user.
|
||||
if let Some(token) = config.get_session_token().as_ref() {
|
||||
match User::from_token(db, token, UserHint::Any).await {
|
||||
Ok(user) => {
|
||||
info!("User {addr:?} authenticated as @{}", user.username);
|
||||
#[cfg(debug_assertions)]
|
||||
info!("{addr:?} has reset their subscriptions");
|
||||
}
|
||||
SubscriptionStateChange::Change { add, remove } => {
|
||||
for id in remove {
|
||||
#[cfg(debug_assertions)]
|
||||
info!("{addr:?} unsubscribing from {id}");
|
||||
|
||||
// Create local state.
|
||||
let mut state = State::from(user);
|
||||
let user_id = state.cache.user_id.clone();
|
||||
subscriber.unsubscribe(id).await.unwrap();
|
||||
}
|
||||
|
||||
// Create presence session.
|
||||
let (first_session, session_id) = create_session(&user_id, 0).await;
|
||||
for id in add {
|
||||
#[cfg(debug_assertions)]
|
||||
info!("{addr:?} subscribing to {id}");
|
||||
|
||||
// Notify socket we have authenticated.
|
||||
write
|
||||
.lock()
|
||||
.await
|
||||
.send(config.encode(&EventV1::Authenticated))
|
||||
.await
|
||||
.ok();
|
||||
|
||||
// Download required data to local cache and send Ready payload.
|
||||
if let Ok(ready_payload) = state.generate_ready_payload(db).await {
|
||||
write
|
||||
.lock()
|
||||
.await
|
||||
.send(config.encode(&ready_payload))
|
||||
.await
|
||||
.ok();
|
||||
|
||||
// If this was the first session, notify other users that we just went online.
|
||||
if first_session {
|
||||
state.broadcast_presence_change(true).await;
|
||||
}
|
||||
|
||||
// Create a PubSub connection to poll on.
|
||||
let listener = async {
|
||||
if let Ok(mut conn) = redis_kiss::open_pubsub_connection().await
|
||||
{
|
||||
loop {
|
||||
// Check for state changes for subscriptions.
|
||||
match state.apply_state() {
|
||||
SubscriptionStateChange::Reset => {
|
||||
for id in state.iter_subscriptions() {
|
||||
conn.subscribe(id).await.unwrap();
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
info!("{addr:?} has reset their subscriptions");
|
||||
}
|
||||
SubscriptionStateChange::Change { add, remove } => {
|
||||
for id in remove {
|
||||
#[cfg(debug_assertions)]
|
||||
info!("{addr:?} unsubscribing from {id}");
|
||||
|
||||
conn.unsubscribe(id).await.unwrap();
|
||||
}
|
||||
|
||||
for id in add {
|
||||
#[cfg(debug_assertions)]
|
||||
info!("{addr:?} subscribing to {id}");
|
||||
|
||||
conn.subscribe(id).await.unwrap();
|
||||
}
|
||||
}
|
||||
SubscriptionStateChange::None => {}
|
||||
}
|
||||
|
||||
// * Debug logging of current subscriptions.
|
||||
/*#[cfg(debug_assertions)]
|
||||
info!(
|
||||
"User {addr:?} is subscribed to {:?}",
|
||||
state
|
||||
.iter_subscriptions()
|
||||
.collect::<Vec<&String>>()
|
||||
);*/
|
||||
|
||||
// Handle incoming events.
|
||||
match conn.on_message().next().await.map(|res| {
|
||||
res.map(|item|(
|
||||
item.get_channel_name().to_string(),
|
||||
redis_kiss::decode_payload::<EventV1>(&item),
|
||||
))
|
||||
}) {
|
||||
Some(Ok((channel, item))) => {
|
||||
if let Ok(mut event) = item {
|
||||
if state
|
||||
.handle_incoming_event_v1(
|
||||
db, &mut event,
|
||||
)
|
||||
.await
|
||||
&& write.lock().await
|
||||
.send(config.encode(&event))
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
warn!("Failed to deserialise an event for {channel}!");
|
||||
}
|
||||
}
|
||||
Some(Err(e)) => {
|
||||
info!("Error while consuming pub/sub messages: {e:?}");
|
||||
sentry::capture_error(&e);
|
||||
break
|
||||
}
|
||||
// No more data, assume we disconnected or otherwise
|
||||
// something bad occurred, so disconnect user.
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.fuse();
|
||||
|
||||
// Read from WebSocket stream.
|
||||
let worker =
|
||||
async {
|
||||
while let Ok(Some(msg)) = read.try_next().await {
|
||||
if let Ok(payload) = config.decode(&msg) {
|
||||
match payload {
|
||||
ClientMessage::BeginTyping { channel } => {
|
||||
EventV1::ChannelStartTyping {
|
||||
id: channel.clone(),
|
||||
user: user_id.clone(),
|
||||
}
|
||||
.p(channel.clone())
|
||||
.await;
|
||||
}
|
||||
ClientMessage::EndTyping { channel } => {
|
||||
EventV1::ChannelStopTyping {
|
||||
id: channel.clone(),
|
||||
user: user_id.clone(),
|
||||
}
|
||||
.p(channel.clone())
|
||||
.await;
|
||||
}
|
||||
ClientMessage::Ping { data, responded } => {
|
||||
if responded.is_none() {
|
||||
write
|
||||
.lock()
|
||||
.await
|
||||
.send(config.encode(
|
||||
&EventV1::Pong { data },
|
||||
))
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.fuse();
|
||||
|
||||
// Pin both tasks.
|
||||
pin_mut!(listener, worker);
|
||||
|
||||
// Wait for either disconnect or for listener to die.
|
||||
select!(
|
||||
() = listener => {},
|
||||
() = worker => {}
|
||||
);
|
||||
|
||||
// * Combine the streams back once we are ready to disconnect.
|
||||
/* ws = read.reunite(write).unwrap(); */
|
||||
}
|
||||
|
||||
// Clean up presence session.
|
||||
let last_session = delete_session(&user_id, session_id).await;
|
||||
|
||||
// If this was the last session, notify other users that we just went offline.
|
||||
if last_session {
|
||||
state.broadcast_presence_change(false).await;
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
write.lock().await.send(config.encode(&err)).await.ok();
|
||||
}
|
||||
}
|
||||
subscriber.subscribe(id).await.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
// * Disconnect the WebSocket if it isn't already.
|
||||
/*ws.close(Some(CloseFrame {
|
||||
code: CloseCode::Normal,
|
||||
reason: std::borrow::Cow::from(""),
|
||||
}))
|
||||
.await
|
||||
.unwrap();*/
|
||||
SubscriptionStateChange::None => {}
|
||||
}
|
||||
|
||||
info!("User disconnected from {addr:?}");
|
||||
});
|
||||
// Handle incoming events.
|
||||
let Ok(message) = message_rx.recv().await.map_err(|e| {
|
||||
info!("Error while consuming pub/sub messages: {e:?}");
|
||||
sentry::capture_error(&e);
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
let event = match *REDIS_PAYLOAD_TYPE {
|
||||
PayloadType::Json => message
|
||||
.value
|
||||
.as_str()
|
||||
.and_then(|s| serde_json::from_str::<EventV1>(s.as_ref()).ok()),
|
||||
PayloadType::Msgpack => message
|
||||
.value
|
||||
.as_bytes()
|
||||
.and_then(|b| rmp_serde::from_slice::<EventV1>(b).ok()),
|
||||
PayloadType::Bincode => message
|
||||
.value
|
||||
.as_bytes()
|
||||
.and_then(|b| bincode::deserialize::<EventV1>(b).ok()),
|
||||
};
|
||||
let Some(mut event) = event else {
|
||||
warn!("Failed to deserialise an event for {}!", message.channel);
|
||||
return;
|
||||
};
|
||||
let should_send = state.handle_incoming_event_v1(db, &mut event).await;
|
||||
|
||||
if should_send
|
||||
&& write
|
||||
.lock()
|
||||
.await
|
||||
.send(config.encode(&event))
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn worker(
|
||||
user_id: String,
|
||||
config: &ProtocolConfiguration,
|
||||
mut read: WsReader,
|
||||
write: &Mutex<WsWriter>,
|
||||
) {
|
||||
while let Ok(Some(msg)) = read.try_next().await {
|
||||
let Ok(payload) = config.decode(&msg) else {
|
||||
continue;
|
||||
};
|
||||
match payload {
|
||||
ClientMessage::BeginTyping { channel } => {
|
||||
EventV1::ChannelStartTyping {
|
||||
id: channel.clone(),
|
||||
user: user_id.clone(),
|
||||
}
|
||||
.p(channel.clone())
|
||||
.await;
|
||||
}
|
||||
ClientMessage::EndTyping { channel } => {
|
||||
EventV1::ChannelStopTyping {
|
||||
id: channel.clone(),
|
||||
user: user_id.clone(),
|
||||
}
|
||||
.p(channel.clone())
|
||||
.await;
|
||||
}
|
||||
ClientMessage::Ping { data, responded } => {
|
||||
if responded.is_none() {
|
||||
write
|
||||
.lock()
|
||||
.await
|
||||
.send(config.encode(&EventV1::Pong { data }))
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "revolt-config"
|
||||
version = "0.6.8"
|
||||
version = "0.6.12"
|
||||
edition = "2021"
|
||||
license = "AGPL-3.0-or-later"
|
||||
authors = ["Paul Makles <me@insrt.uk>"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "revolt-database"
|
||||
version = "0.6.8"
|
||||
version = "0.6.12"
|
||||
edition = "2021"
|
||||
license = "AGPL-3.0-or-later"
|
||||
authors = ["Paul Makles <me@insrt.uk>"]
|
||||
@@ -23,11 +23,11 @@ default = ["mongodb", "async-std-runtime", "tasks"]
|
||||
|
||||
[dependencies]
|
||||
# Core
|
||||
revolt-config = { version = "0.6.8", path = "../config" }
|
||||
revolt-result = { version = "0.6.8", path = "../result" }
|
||||
revolt-models = { version = "0.6.8", path = "../models" }
|
||||
revolt-presence = { version = "0.6.8", path = "../presence" }
|
||||
revolt-permissions = { version = "0.6.8", path = "../permissions", features = [
|
||||
revolt-config = { version = "0.6.12", path = "../config" }
|
||||
revolt-result = { version = "0.6.12", path = "../result" }
|
||||
revolt-models = { version = "0.6.12", path = "../models" }
|
||||
revolt-presence = { version = "0.6.12", path = "../presence" }
|
||||
revolt-permissions = { version = "0.6.12", path = "../permissions", features = [
|
||||
"serde",
|
||||
"bson",
|
||||
] }
|
||||
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
@@ -110,6 +110,7 @@ pub enum EventV1 {
|
||||
id: String,
|
||||
server: Server,
|
||||
channels: Vec<Channel>,
|
||||
emojis: Vec<Emoji>,
|
||||
},
|
||||
|
||||
/// Update existing server
|
||||
@@ -243,8 +244,7 @@ impl EventV1 {
|
||||
pub async fn p_user(self, id: String, db: &Database) {
|
||||
self.clone().p(id.clone()).await;
|
||||
|
||||
// ! FIXME: this should be captured by member list in the future
|
||||
// ! and not immediately fanned out to users
|
||||
// TODO: this should be captured by member list in the future and not immediately fanned out to users
|
||||
if let Ok(members) = db.fetch_all_memberships(&id).await {
|
||||
for member in members {
|
||||
self.clone().p(member.id.server).await;
|
||||
|
||||
@@ -6,7 +6,10 @@ use revolt_permissions::OverrideField;
|
||||
use revolt_result::Result;
|
||||
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!(
|
||||
#[serde(tag = "channel_type")]
|
||||
@@ -189,6 +192,67 @@ impl Channel {
|
||||
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
|
||||
pub async fn create_group(
|
||||
db: &Database,
|
||||
@@ -627,130 +691,18 @@ impl IntoDocumentPath for FieldsChannel {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::HashMap;
|
||||
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
|
||||
|
||||
use revolt_permissions::{calculate_channel_permissions, ChannelPermission, OverrideField};
|
||||
|
||||
use crate::{
|
||||
util::permissions::DatabasePermissionQuery, Channel, Member, MemberCompositeKey, Role,
|
||||
Server, User,
|
||||
};
|
||||
use crate::{fixture, util::permissions::DatabasePermissionQuery};
|
||||
|
||||
#[async_std::test]
|
||||
async fn permissions_text_channel() {
|
||||
database_test!(|db| async move {
|
||||
let owner = User::create(&db, "Owner".to_string(), None, None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let moderator = User::create(&db, "Moderator".to_string(), None, None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let user = User::create(&db, "User".to_string(), None, None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let server_id = ulid::Ulid::new().to_string();
|
||||
|
||||
let channel = Channel::TextChannel {
|
||||
id: ulid::Ulid::new().to_string(),
|
||||
server: server_id.clone(),
|
||||
name: "Channel".to_string(),
|
||||
description: None,
|
||||
icon: None,
|
||||
last_message_id: None,
|
||||
default_permissions: Some(OverrideField {
|
||||
d: 1048576, // TODO: bitfield
|
||||
..Default::default()
|
||||
}),
|
||||
role_permissions: HashMap::from([(
|
||||
"01F9HFTSBWTNA2F4TMSV7VM3FG".to_string(),
|
||||
OverrideField {
|
||||
a: 1048576, // TODO: bitfield
|
||||
..Default::default()
|
||||
},
|
||||
)]),
|
||||
nsfw: false,
|
||||
};
|
||||
|
||||
let server = Server {
|
||||
id: server_id,
|
||||
owner: owner.id.clone(),
|
||||
name: "My Server".to_string(),
|
||||
description: None,
|
||||
channels: vec![channel.id()],
|
||||
categories: None,
|
||||
system_messages: None,
|
||||
roles: HashMap::from([
|
||||
(
|
||||
"01F9HFTSBWTNA2F4TMSV7VM3FG".to_string(),
|
||||
Role {
|
||||
name: "Moderator".to_string(),
|
||||
permissions: OverrideField {
|
||||
a: 545270208, // TODO: explicit
|
||||
..Default::default()
|
||||
},
|
||||
colour: None,
|
||||
hoist: true,
|
||||
rank: 3,
|
||||
},
|
||||
),
|
||||
(
|
||||
"01FBF9DNHSRPVTWFMNB3JNB8FK".to_string(),
|
||||
Role {
|
||||
name: "Owner".to_string(),
|
||||
permissions: Default::default(),
|
||||
colour: None,
|
||||
hoist: true,
|
||||
rank: 0,
|
||||
},
|
||||
),
|
||||
]),
|
||||
default_permissions: 4000322560, // TODO: use bitfield
|
||||
icon: None,
|
||||
banner: None,
|
||||
flags: None,
|
||||
nsfw: false,
|
||||
analytics: false,
|
||||
discoverable: false,
|
||||
};
|
||||
|
||||
// TODO: proper creation
|
||||
db.insert_channel(&channel).await.unwrap();
|
||||
server.create(&db).await.unwrap();
|
||||
|
||||
db.insert_member(&Member {
|
||||
id: MemberCompositeKey {
|
||||
user: owner.id.clone(),
|
||||
server: server.id.clone(),
|
||||
},
|
||||
roles: vec!["01FBF9DNHSRPVTWFMNB3JNB8FK".to_string()],
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
db.insert_member(&Member {
|
||||
id: MemberCompositeKey {
|
||||
user: moderator.id.clone(),
|
||||
server: server.id.clone(),
|
||||
},
|
||||
roles: vec!["01F9HFTSBWTNA2F4TMSV7VM3FG".to_string()],
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
db.insert_member(&Member {
|
||||
id: MemberCompositeKey {
|
||||
user: user.id.clone(),
|
||||
server: server.id.clone(),
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
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)
|
||||
|
||||
@@ -24,13 +24,12 @@ impl AbstractEmojis for MongoDb {
|
||||
async fn fetch_emoji_by_parent_id(&self, parent_id: &str) -> Result<Vec<Emoji>> {
|
||||
query!(
|
||||
self,
|
||||
find_one,
|
||||
find,
|
||||
COL,
|
||||
doc! {
|
||||
"parent.id": parent_id
|
||||
}
|
||||
)?
|
||||
.ok_or_else(|| create_error!(NotFound))
|
||||
)
|
||||
}
|
||||
|
||||
/// Fetch emoji by their parent ids
|
||||
|
||||
@@ -212,6 +212,7 @@ impl Message {
|
||||
author: MessageAuthor<'_>,
|
||||
mut idempotency: IdempotencyKey,
|
||||
generate_embeds: bool,
|
||||
allow_mentions: bool,
|
||||
) -> Result<Message> {
|
||||
let config = config().await;
|
||||
|
||||
@@ -272,10 +273,12 @@ impl Message {
|
||||
|
||||
// Parse mentions in message.
|
||||
let mut mentions = HashSet::new();
|
||||
if let Some(content) = &data.content {
|
||||
for capture in RE_MENTION.captures_iter(content) {
|
||||
if let Some(mention) = capture.get(1) {
|
||||
mentions.insert(mention.as_str().to_string());
|
||||
if allow_mentions {
|
||||
if let Some(content) = &data.content {
|
||||
for capture in RE_MENTION.captures_iter(content) {
|
||||
if let Some(mention) = capture.get(1) {
|
||||
mentions.insert(mention.as_str().to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -292,7 +295,7 @@ impl Message {
|
||||
for ReplyIntent { id, mention } in entries {
|
||||
let message = db.fetch_message(&id).await?;
|
||||
|
||||
if mention {
|
||||
if mention && allow_mentions {
|
||||
mentions.insert(message.author.to_owned());
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ impl AbstractMessages for ReferenceDb {
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
// TODO: sorting, etc (will be required for tests)
|
||||
// FIXME: sorting, etc (will be required for tests)
|
||||
|
||||
Ok(matched_messages)
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
|
||||
use revolt_result::{create_error, Result};
|
||||
|
||||
use crate::{
|
||||
events::client::EventV1, util::permissions::DatabasePermissionQuery, Database, File, Server,
|
||||
SystemMessage, User,
|
||||
events::client::EventV1, util::permissions::DatabasePermissionQuery, Channel, Database, File,
|
||||
Server, SystemMessage, User,
|
||||
};
|
||||
|
||||
auto_derived_partial!(
|
||||
@@ -80,9 +80,8 @@ impl Member {
|
||||
db: &Database,
|
||||
server: &Server,
|
||||
user: &User,
|
||||
// channels: Option<Vec<Channel>>,
|
||||
//) -> Result<Vec<Channel>> {
|
||||
) -> Result<()> {
|
||||
channels: Option<Vec<Channel>>,
|
||||
) -> Result<Vec<Channel>> {
|
||||
if db.fetch_ban(&server.id, &user.id).await.is_ok() {
|
||||
return Err(create_error!(Banned));
|
||||
}
|
||||
@@ -101,9 +100,10 @@ impl Member {
|
||||
|
||||
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 existing_channels = db.fetch_channels(&server.channels).await?;
|
||||
|
||||
@@ -119,6 +119,8 @@ impl Member {
|
||||
}
|
||||
}
|
||||
|
||||
let emojis = db.fetch_emoji_by_parent_id(&server.id).await?;
|
||||
|
||||
EventV1::ServerMemberJoin {
|
||||
id: server.id.clone(),
|
||||
user: user.id.clone(),
|
||||
@@ -134,6 +136,7 @@ impl Member {
|
||||
.into_iter()
|
||||
.map(|channel| channel.into())
|
||||
.collect(),
|
||||
emojis: emojis.into_iter().map(|emoji| emoji.into()).collect(),
|
||||
}
|
||||
.private(user.id.clone())
|
||||
.await;
|
||||
@@ -152,8 +155,7 @@ impl Member {
|
||||
.ok();
|
||||
}
|
||||
|
||||
// Ok(channels)
|
||||
Ok(())
|
||||
Ok(channels)
|
||||
}
|
||||
|
||||
/// Update member data
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
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 ulid::Ulid;
|
||||
|
||||
use crate::{events::client::EventV1, Database, File};
|
||||
use crate::{events::client::EventV1, Channel, Database, File, User};
|
||||
|
||||
auto_derived_partial!(
|
||||
/// Server
|
||||
@@ -22,7 +23,7 @@ auto_derived_partial!(
|
||||
pub description: Option<String>,
|
||||
|
||||
/// Channels within this server
|
||||
// ! FIXME: this may be redundant
|
||||
// TODO: investigate if this is redundant and can be removed
|
||||
pub channels: Vec<String>,
|
||||
/// Categories for this server
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@@ -131,8 +132,52 @@ auto_derived!(
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
impl Server {
|
||||
/// Create a server
|
||||
pub async fn create(&self, db: &Database) -> Result<()> {
|
||||
db.insert_server(self).await
|
||||
pub async fn create(
|
||||
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![]
|
||||
};
|
||||
|
||||
server.channels = channels.iter().map(|c| c.id()).collect();
|
||||
db.insert_server(&server).await?;
|
||||
Ok((server, channels))
|
||||
}
|
||||
|
||||
/// Update server data
|
||||
@@ -209,80 +254,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
|
||||
pub async fn remove_member(
|
||||
@@ -453,109 +425,18 @@ impl SystemMessageChannels {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::HashMap;
|
||||
use revolt_permissions::{calculate_server_permissions, ChannelPermission};
|
||||
|
||||
use revolt_permissions::{calculate_server_permissions, ChannelPermission, OverrideField};
|
||||
|
||||
use crate::{
|
||||
util::permissions::DatabasePermissionQuery, Member, MemberCompositeKey, Role, Server, User,
|
||||
};
|
||||
use crate::{fixture, util::permissions::DatabasePermissionQuery};
|
||||
|
||||
#[async_std::test]
|
||||
async fn permissions() {
|
||||
database_test!(|db| async move {
|
||||
let owner = User::create(&db, "Owner".to_string(), None, None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let moderator = User::create(&db, "Moderator".to_string(), None, None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let user = User::create(&db, "User".to_string(), None, None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let server_id = ulid::Ulid::new().to_string();
|
||||
|
||||
// TODO: seeder functions
|
||||
// e.g. seed!("channel", "file.json")
|
||||
let server = Server {
|
||||
id: server_id,
|
||||
owner: owner.id.clone(),
|
||||
name: "My Server".to_string(),
|
||||
description: None,
|
||||
channels: vec![],
|
||||
categories: None,
|
||||
system_messages: None,
|
||||
roles: HashMap::from([
|
||||
(
|
||||
"01F9HFTSBWTNA2F4TMSV7VM3FG".to_string(),
|
||||
Role {
|
||||
name: "Moderator".to_string(),
|
||||
permissions: OverrideField {
|
||||
a: 545270208, // TODO: explicit
|
||||
..Default::default()
|
||||
},
|
||||
colour: None,
|
||||
hoist: true,
|
||||
rank: 3,
|
||||
},
|
||||
),
|
||||
(
|
||||
"01FBF9DNHSRPVTWFMNB3JNB8FK".to_string(),
|
||||
Role {
|
||||
name: "Owner".to_string(),
|
||||
permissions: Default::default(),
|
||||
colour: None,
|
||||
hoist: true,
|
||||
rank: 0,
|
||||
},
|
||||
),
|
||||
]),
|
||||
default_permissions: 4000322560, // TODO: use bitfield
|
||||
icon: None,
|
||||
banner: None,
|
||||
flags: None,
|
||||
nsfw: false,
|
||||
analytics: false,
|
||||
discoverable: false,
|
||||
};
|
||||
|
||||
// TODO: proper creation
|
||||
server.create(&db).await.unwrap();
|
||||
|
||||
db.insert_member(&Member {
|
||||
id: MemberCompositeKey {
|
||||
user: owner.id.clone(),
|
||||
server: server.id.clone(),
|
||||
},
|
||||
roles: vec!["01FBF9DNHSRPVTWFMNB3JNB8FK".to_string()],
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
db.insert_member(&Member {
|
||||
id: MemberCompositeKey {
|
||||
user: moderator.id.clone(),
|
||||
server: server.id.clone(),
|
||||
},
|
||||
roles: vec!["01F9HFTSBWTNA2F4TMSV7VM3FG".to_string()],
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
db.insert_member(&Member {
|
||||
id: MemberCompositeKey {
|
||||
user: user.id.clone(),
|
||||
server: server.id.clone(),
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
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)
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::{events::client::EventV1, Database, File, RatelimitEvent};
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use rand::seq::SliceRandom;
|
||||
use revolt_config::config;
|
||||
use revolt_result::{create_error, Error, ErrorType, Result};
|
||||
use ulid::Ulid;
|
||||
|
||||
@@ -218,6 +219,18 @@ impl User {
|
||||
.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
|
||||
pub fn validate_username(username: String) -> Result<String> {
|
||||
// Copy the username for validation
|
||||
|
||||
@@ -132,7 +132,7 @@ pub async fn generate(
|
||||
return Err(create_error!(LabelMe));
|
||||
}
|
||||
|
||||
// ! FIXME: batch request to january
|
||||
// TODO: batch request to january
|
||||
let mut tasks = Vec::new();
|
||||
|
||||
for link in links {
|
||||
|
||||
@@ -341,11 +341,7 @@ impl From<crate::Message> for Message {
|
||||
embeds: value.embeds,
|
||||
mentions: value.mentions,
|
||||
replies: value.replies,
|
||||
reactions: value
|
||||
.reactions
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k, v.into_iter().collect()))
|
||||
.collect(),
|
||||
reactions: value.reactions,
|
||||
interactions: value.interactions.into(),
|
||||
masquerade: value.masquerade.map(|masq| masq.into()),
|
||||
}
|
||||
@@ -369,12 +365,7 @@ impl From<crate::PartialMessage> for PartialMessage {
|
||||
embeds: value.embeds,
|
||||
mentions: value.mentions,
|
||||
replies: value.replies,
|
||||
reactions: value.reactions.map(|reactions| {
|
||||
reactions
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k, v.into_iter().collect()))
|
||||
.collect()
|
||||
}),
|
||||
reactions: value.reactions,
|
||||
interactions: value.interactions.map(|interactions| interactions.into()),
|
||||
masquerade: value.masquerade.map(|masq| masq.into()),
|
||||
}
|
||||
|
||||
@@ -2,3 +2,6 @@ pub mod bridge;
|
||||
pub mod idempotency;
|
||||
pub mod permissions;
|
||||
pub mod reference;
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod test_fixtures;
|
||||
|
||||
@@ -344,7 +344,23 @@ impl PermissionQuery for DatabasePermissionQuery<'_> {
|
||||
| Cow::Owned(Channel::TextChannel { server, .. })
|
||||
| Cow::Borrowed(Channel::VoiceChannel { server, .. })
|
||||
| Cow::Owned(Channel::VoiceChannel { server, .. }) => {
|
||||
// FIXME: may double fetch
|
||||
if let Some(known_server) =
|
||||
// I'm not sure why I can't just pattern match both at once here?
|
||||
// It throws some weird error and the provided fix doesn't work :/
|
||||
if let Some(Cow::Borrowed(known_server)) = self.server {
|
||||
Some(known_server)
|
||||
} else if let Some(Cow::Owned(ref known_server)) = self.server {
|
||||
Some(known_server)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
{
|
||||
if server == &known_server.id {
|
||||
// Already cached, return early.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(server) = self.database.fetch_server(server).await {
|
||||
self.server.replace(Cow::Owned(server));
|
||||
}
|
||||
@@ -428,6 +444,26 @@ impl<'a> DatabasePermissionQuery<'a> {
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Access the underlying user
|
||||
pub fn user_ref(&self) -> &Option<Cow<User>> {
|
||||
&self.user
|
||||
}
|
||||
|
||||
/// Access the underlying server
|
||||
pub fn channel_ref(&self) -> &Option<Cow<Channel>> {
|
||||
&self.channel
|
||||
}
|
||||
|
||||
/// Access the underlying server
|
||||
pub fn server_ref(&self) -> &Option<Cow<Server>> {
|
||||
&self.server
|
||||
}
|
||||
|
||||
/// Access the underlying member
|
||||
pub fn member_ref(&self) -> &Option<Cow<Member>> {
|
||||
&self.member
|
||||
}
|
||||
}
|
||||
|
||||
/// Short-hand for creating a permission calculator
|
||||
|
||||
@@ -7,7 +7,7 @@ use schemars::{
|
||||
JsonSchema,
|
||||
};
|
||||
|
||||
use crate::{Bot, Channel, Database, Emoji, Message, User, Webhook};
|
||||
use crate::{Bot, Channel, Database, Emoji, Message, Server, User, Webhook};
|
||||
|
||||
/// Reference to some object in the database
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@@ -42,6 +42,11 @@ impl Reference {
|
||||
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
|
||||
|
||||
@@ -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]
|
||||
name = "revolt-models"
|
||||
version = "0.6.8"
|
||||
version = "0.6.12"
|
||||
edition = "2021"
|
||||
license = "AGPL-3.0-or-later"
|
||||
authors = ["Paul Makles <me@insrt.uk>"]
|
||||
@@ -18,8 +18,8 @@ default = ["serde", "partials"]
|
||||
|
||||
[dependencies]
|
||||
# Core
|
||||
revolt-config = { version = "0.6.8", path = "../config" }
|
||||
revolt-permissions = { version = "0.6.8", path = "../permissions" }
|
||||
revolt-config = { version = "0.6.12", path = "../config" }
|
||||
revolt-permissions = { version = "0.6.12", path = "../permissions" }
|
||||
|
||||
# Utility
|
||||
regex = "1"
|
||||
@@ -32,7 +32,7 @@ serde = { version = "1", features = ["derive"], optional = true }
|
||||
iso8601-timestamp = { version = "0.2.11", features = ["schema", "bson"] }
|
||||
|
||||
# Spec Generation
|
||||
schemars = { version = "0.8.8", optional = true }
|
||||
schemars = { version = "0.8.8", optional = true, features = ["indexmap1"] }
|
||||
|
||||
# Validation
|
||||
validator = { version = "0.16.0", optional = true, features = ["derive"] }
|
||||
|
||||
@@ -212,24 +212,52 @@ auto_derived!(
|
||||
#[cfg_attr(feature = "validator", derive(validator::Validate))]
|
||||
pub struct DataCreateGroup {
|
||||
/// Group name
|
||||
#[validate(length(min = 1, max = 32))]
|
||||
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 32)))]
|
||||
pub name: String,
|
||||
/// Group description
|
||||
#[validate(length(min = 0, max = 1024))]
|
||||
#[cfg_attr(feature = "validator", validate(length(min = 0, max = 1024)))]
|
||||
pub description: Option<String>,
|
||||
/// Group icon
|
||||
#[validate(length(min = 1, max = 128))]
|
||||
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 128)))]
|
||||
pub icon: Option<String>,
|
||||
/// Array of user IDs to add to the group
|
||||
///
|
||||
/// Must be friends with these users.
|
||||
#[validate(length(min = 0, max = 49))]
|
||||
#[cfg_attr(feature = "validator", validate(length(min = 0, max = 49)))]
|
||||
#[serde(default)]
|
||||
pub users: HashSet<String>,
|
||||
/// Whether this group is age-restricted
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
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 {
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
time::SystemTime,
|
||||
};
|
||||
use std::time::SystemTime;
|
||||
|
||||
use indexmap::{IndexMap, IndexSet};
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use revolt_config::config;
|
||||
@@ -55,8 +53,8 @@ auto_derived_partial!(
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub replies: Option<Vec<String>>,
|
||||
/// Hashmap of emoji IDs to array of user IDs
|
||||
#[serde(skip_serializing_if = "HashMap::is_empty", default)]
|
||||
pub reactions: HashMap<String, HashSet<String>>,
|
||||
#[serde(skip_serializing_if = "IndexMap::is_empty", default)]
|
||||
pub reactions: IndexMap<String, IndexSet<String>>,
|
||||
/// Information about how this message should be interacted with
|
||||
#[serde(skip_serializing_if = "Interactions::is_default", default)]
|
||||
pub interactions: Interactions,
|
||||
@@ -98,17 +96,19 @@ auto_derived!(
|
||||
/// Name and / or avatar override information
|
||||
#[cfg_attr(feature = "validator", derive(Validate))]
|
||||
pub struct Masquerade {
|
||||
// FIXME: missing validation
|
||||
/// Replace the display name shown on this message
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[validate(length(min = 1, max = 32))]
|
||||
pub name: Option<String>,
|
||||
/// Replace the avatar shown on this message (URL to image file)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[validate(length(min = 1, max = 256))]
|
||||
pub avatar: Option<String>,
|
||||
/// Replace the display role colour shown on this message
|
||||
///
|
||||
/// Must have `ManageRole` permission to use
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[validate(length(min = 1, max = 128), regex = "RE_COLOUR")]
|
||||
pub colour: Option<String>,
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ auto_derived!(
|
||||
pub struct Interactions {
|
||||
/// Reactions which should always appear and be distinct
|
||||
#[serde(skip_serializing_if = "Option::is_none", default)]
|
||||
pub reactions: Option<HashSet<String>>,
|
||||
pub reactions: Option<IndexSet<String>>,
|
||||
/// Whether reactions should be restricted to the given list
|
||||
///
|
||||
/// Can only be set to true if reactions list is of at least length 1
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
use super::File;
|
||||
use super::{Channel, File};
|
||||
|
||||
use revolt_permissions::OverrideField;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[cfg(feature = "validator")]
|
||||
use validator::Validate;
|
||||
|
||||
auto_derived_partial!(
|
||||
/// Server
|
||||
pub struct Server {
|
||||
@@ -19,7 +22,7 @@ auto_derived_partial!(
|
||||
pub description: Option<String>,
|
||||
|
||||
/// Channels within this server
|
||||
// ! FIXME: this may be redundant
|
||||
// TODO: investigate if this is redundant and can be removed
|
||||
pub channels: Vec<String>,
|
||||
/// Categories for this server
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
@@ -141,4 +144,27 @@ auto_derived!(
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
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]
|
||||
name = "revolt-permissions"
|
||||
version = "0.6.8"
|
||||
version = "0.6.12"
|
||||
edition = "2021"
|
||||
license = "AGPL-3.0-or-later"
|
||||
authors = ["Paul Makles <me@insrt.uk>"]
|
||||
|
||||
@@ -37,6 +37,12 @@ pub async fn calculate_user_permissions<P: PermissionQuery>(query: &mut P) -> Pe
|
||||
} else {
|
||||
permissions.into()
|
||||
}
|
||||
|
||||
// TODO: add boolean switch for permission for users to globally message a user
|
||||
// maybe an enum?
|
||||
// PrivacyLevel { Private, Friends, Mutual, Public, Global }
|
||||
|
||||
// TODO: add boolean switch for permission for users to mutually DM a user
|
||||
}
|
||||
|
||||
/// Calculate permissions against a server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "revolt-presence"
|
||||
version = "0.6.8"
|
||||
version = "0.6.12"
|
||||
edition = "2021"
|
||||
license = "AGPL-3.0-or-later"
|
||||
authors = ["Paul Makles <me@insrt.uk>"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "revolt-result"
|
||||
version = "0.6.8"
|
||||
version = "0.6.12"
|
||||
edition = "2021"
|
||||
license = "AGPL-3.0-or-later"
|
||||
authors = ["Paul Makles <me@insrt.uk>"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "revolt-delta"
|
||||
version = "0.6.8"
|
||||
version = "0.6.12"
|
||||
license = "AGPL-3.0-or-later"
|
||||
authors = ["Paul Makles <paulmakles@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
@@ -42,7 +42,7 @@ pub async fn invite_bot(
|
||||
.await
|
||||
.throw_if_lacking_channel_permission(ChannelPermission::ManageServer)?;
|
||||
|
||||
Member::create(db, &server, &bot_user)
|
||||
Member::create(db, &server, &bot_user, None)
|
||||
.await
|
||||
.map(|_| EmptyResponse)
|
||||
}
|
||||
@@ -66,7 +66,7 @@ pub async fn invite_bot(
|
||||
mod test {
|
||||
use crate::{rocket, util::test::TestHarness};
|
||||
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};
|
||||
|
||||
#[rocket::async_test]
|
||||
@@ -125,26 +125,17 @@ mod test {
|
||||
.await
|
||||
.expect("`Bot`");
|
||||
|
||||
// FIXME: Server::create_server
|
||||
let server = Server {
|
||||
id: ulid::Ulid::new().to_string(),
|
||||
name: TestHarness::rand_string(),
|
||||
owner: user.id.to_string(),
|
||||
analytics: false,
|
||||
discoverable: false,
|
||||
nsfw: false,
|
||||
banner: None,
|
||||
icon: None,
|
||||
categories: None,
|
||||
channels: vec![],
|
||||
default_permissions: 0,
|
||||
description: None,
|
||||
flags: None,
|
||||
roles: Default::default(),
|
||||
system_messages: None,
|
||||
};
|
||||
|
||||
server.create(&harness.db).await.unwrap();
|
||||
let (server, _) = Server::create(
|
||||
&harness.db,
|
||||
DataCreateServer {
|
||||
name: TestHarness::rand_string(),
|
||||
..Default::default()
|
||||
},
|
||||
&user,
|
||||
false,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let response = harness
|
||||
.client
|
||||
|
||||
@@ -31,8 +31,7 @@ pub async fn req(
|
||||
|
||||
match &channel {
|
||||
Channel::Group { .. } => {
|
||||
// FIXME: use permissions here?
|
||||
// interesting if users could block new group invites
|
||||
// TODO: use permissions here? interesting if users could block new group invites
|
||||
let member = member_id.as_user(db).await?;
|
||||
if !user.is_friends_with(&member.id) {
|
||||
return Err(create_error!(NotFriends));
|
||||
|
||||
@@ -148,7 +148,7 @@ mod test {
|
||||
.await;
|
||||
|
||||
dbg!(response.into_string().await);
|
||||
// TODO: finish impl
|
||||
// FIXME: finish impl
|
||||
// assert_eq!(response.status(), Status::NotFound);
|
||||
}
|
||||
|
||||
@@ -185,6 +185,6 @@ mod test {
|
||||
.dispatch()
|
||||
.await;
|
||||
|
||||
// TODO: impl assert_eq!(response.status(), Status::Forbidden);
|
||||
// FIXME: impl assert_eq!(response.status(), Status::Forbidden);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ pub async fn req(
|
||||
})
|
||||
.await?;
|
||||
|
||||
BulkMessageResponse::transform(db, Some(&channel), messages, include_users)
|
||||
BulkMessageResponse::transform(db, Some(&channel), messages, &user, include_users)
|
||||
.await
|
||||
.map(Json)
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ pub async fn req(
|
||||
})
|
||||
.await?;
|
||||
|
||||
BulkMessageResponse::transform(db, Some(&channel), messages, include_users)
|
||||
BulkMessageResponse::transform(db, Some(&channel), messages, &user, include_users)
|
||||
.await
|
||||
.map(Json)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use chrono::{Duration, Utc};
|
||||
use revolt_database::util::permissions::DatabasePermissionQuery;
|
||||
use revolt_database::{
|
||||
util::idempotency::IdempotencyKey, util::reference::Reference, Database, User,
|
||||
@@ -61,6 +62,23 @@ pub async fn message_send(
|
||||
interactions.validate(db, &permissions).await?;
|
||||
}
|
||||
|
||||
// Disallow mentions for new users (TRUST-0: <12 hours age) in public servers
|
||||
let allow_mentions = if let Some(server) = query.server_ref() {
|
||||
if server.discoverable {
|
||||
if (Utc::now() - ulid::Ulid::from_string(&user.id).unwrap().datetime())
|
||||
< Duration::hours(12)
|
||||
{
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
} else {
|
||||
true
|
||||
}
|
||||
} else {
|
||||
true
|
||||
};
|
||||
|
||||
// Create the message
|
||||
let author: v0::User = user.clone().into(db, Some(&user)).await;
|
||||
Ok(Json(
|
||||
@@ -71,6 +89,7 @@ pub async fn message_send(
|
||||
v0::MessageAuthor::User(&author),
|
||||
idempotency,
|
||||
permissions.has_channel_permission(ChannelPermission::SendEmbeds),
|
||||
allow_mentions,
|
||||
)
|
||||
.await?
|
||||
.into(),
|
||||
|
||||
@@ -45,7 +45,7 @@ pub async fn create_emoji(
|
||||
|
||||
// Check that we haven't hit the emoji limit
|
||||
let emojis = db.fetch_emoji_by_parent_id(&server.id).await?;
|
||||
if emojis.len() > config.features.limits.default.server_emoji {
|
||||
if emojis.len() >= config.features.limits.default.server_emoji {
|
||||
return Err(create_error!(TooManyEmoji {
|
||||
max: config.features.limits.default.server_emoji,
|
||||
}));
|
||||
|
||||
@@ -1,119 +1,39 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use revolt_quark::{
|
||||
models::{server::PartialServer, Channel, User},
|
||||
perms, Db, Error, Permission, Ref, Result, variables::delta::MAX_CHANNEL_COUNT,
|
||||
};
|
||||
use revolt_database::util::permissions::DatabasePermissionQuery;
|
||||
use revolt_database::{util::reference::Reference, Channel, Database, User};
|
||||
use revolt_models::v0;
|
||||
use revolt_permissions::{calculate_server_permissions, ChannelPermission};
|
||||
use revolt_result::{create_error, Result};
|
||||
|
||||
use rocket::serde::json::Json;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ulid::Ulid;
|
||||
use rocket::State;
|
||||
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 a new Text or Voice channel.
|
||||
#[openapi(tag = "Server Information")]
|
||||
#[post("/<target>/channels", data = "<info>")]
|
||||
pub async fn req(
|
||||
db: &Db,
|
||||
#[post("/<server>/channels", data = "<data>")]
|
||||
pub async fn create_server_channel(
|
||||
db: &State<Database>,
|
||||
user: User,
|
||||
target: Ref,
|
||||
info: Json<DataCreateChannel>,
|
||||
) -> Result<Json<Channel>> {
|
||||
let info = info.into_inner();
|
||||
info.validate()
|
||||
.map_err(|error| Error::FailedValidation { error })?;
|
||||
server: Reference,
|
||||
data: Json<v0::DataCreateServerChannel>,
|
||||
) -> Result<Json<v0::Channel>> {
|
||||
let data = data.into_inner();
|
||||
data.validate().map_err(|error| {
|
||||
create_error!(FailedValidation {
|
||||
error: error.to_string()
|
||||
})
|
||||
})?;
|
||||
|
||||
let mut server = target.as_server(db).await?;
|
||||
perms(&user)
|
||||
.server(&server)
|
||||
.throw_permission(db, Permission::ManageChannel)
|
||||
.await?;
|
||||
let mut server = server.as_server(db).await?;
|
||||
let mut query = DatabasePermissionQuery::new(db, &user).server(&server);
|
||||
calculate_server_permissions(&mut query)
|
||||
.await
|
||||
.throw_if_lacking_channel_permission(ChannelPermission::ManageChannel)?;
|
||||
|
||||
if server.channels.len() > *MAX_CHANNEL_COUNT {
|
||||
return Err(Error::TooManyChannels { max: *MAX_CHANNEL_COUNT })
|
||||
};
|
||||
|
||||
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))
|
||||
Channel::create_server_channel(db, &mut server, data, true)
|
||||
.await
|
||||
.map(|channel| channel.into())
|
||||
.map(Json)
|
||||
}
|
||||
|
||||
@@ -1,17 +1,25 @@
|
||||
use revolt_quark::{
|
||||
models::{Member, User},
|
||||
perms, Db, Ref, Result,
|
||||
};
|
||||
use revolt_quark::models::server_member::MemberResponse;
|
||||
use revolt_quark::{models::User, perms, Db, Ref, Result};
|
||||
use rocket::serde::json::Json;
|
||||
|
||||
/// # Fetch Member
|
||||
///
|
||||
/// Retrieve a member.
|
||||
#[openapi(tag = "Server Members")]
|
||||
#[get("/<target>/members/<member>")]
|
||||
pub async fn req(db: &Db, user: User, target: Ref, member: Ref) -> Result<Json<Member>> {
|
||||
#[get("/<target>/members/<member>?<roles>")]
|
||||
pub async fn req(
|
||||
db: &Db,
|
||||
user: User,
|
||||
target: Ref,
|
||||
member: Ref,
|
||||
roles: Option<bool>,
|
||||
) -> Result<Json<MemberResponse>> {
|
||||
let server = target.as_server(db).await?;
|
||||
perms(&user).server(&server).calc(db).await?;
|
||||
|
||||
member.as_member(db, &server.id).await.map(Json)
|
||||
let member_response: MemberResponse = match roles {
|
||||
Some(true) => member.as_member_with_roles(db, &server.id).await?.into(),
|
||||
_ => member.as_member(db, &server.id).await?.into(),
|
||||
};
|
||||
|
||||
Ok(Json(member_response))
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ mod permissions_set_default;
|
||||
mod roles_create;
|
||||
mod roles_delete;
|
||||
mod roles_edit;
|
||||
mod roles_fetch;
|
||||
mod server_ack;
|
||||
mod server_create;
|
||||
mod server_delete;
|
||||
@@ -25,12 +26,12 @@ mod server_fetch;
|
||||
|
||||
pub fn routes() -> (Vec<Route>, OpenApi) {
|
||||
openapi_get_routes_spec![
|
||||
server_create::req,
|
||||
server_create::create_server,
|
||||
server_delete::req,
|
||||
server_fetch::req,
|
||||
server_edit::req,
|
||||
server_ack::req,
|
||||
channel_create::req,
|
||||
channel_create::create_server_channel,
|
||||
member_fetch_all::req,
|
||||
member_remove::req,
|
||||
member_fetch::req,
|
||||
@@ -42,6 +43,7 @@ pub fn routes() -> (Vec<Route>, OpenApi) {
|
||||
invites_fetch::req,
|
||||
roles_create::req,
|
||||
roles_edit::req,
|
||||
roles_fetch::req,
|
||||
roles_delete::req,
|
||||
permissions_set::req,
|
||||
permissions_set_default::req,
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
use revolt_quark::{models::server::Role, Db, Error, Ref, Result};
|
||||
use rocket::serde::json::Json;
|
||||
|
||||
/// # Fetch Role
|
||||
///
|
||||
/// Fetch a role by its id.
|
||||
#[openapi(tag = "Server Permissions")]
|
||||
#[get("/<target>/roles/<role_id>")]
|
||||
pub async fn req(db: &Db, target: Ref, role_id: String) -> Result<Json<Role>> {
|
||||
let server = target.as_server(db).await?;
|
||||
|
||||
let role = server.roles.get(&role_id);
|
||||
|
||||
if let Some(role) = role {
|
||||
Ok(Json(role.clone()))
|
||||
} else {
|
||||
Err(Error::NotFound)
|
||||
}
|
||||
}
|
||||
@@ -1,102 +1,39 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use revolt_quark::{
|
||||
models::{Channel, Server, User},
|
||||
variables::delta::MAX_SERVER_COUNT,
|
||||
Db, Error, Result, DEFAULT_PERMISSION_SERVER,
|
||||
};
|
||||
use revolt_database::{Database, Member, Server, User};
|
||||
use revolt_models::v0;
|
||||
use revolt_result::{create_error, Result};
|
||||
|
||||
use rocket::serde::json::Json;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ulid::Ulid;
|
||||
use rocket::State;
|
||||
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 a new server.
|
||||
#[openapi(tag = "Server Information")]
|
||||
#[post("/create", data = "<info>")]
|
||||
pub async fn req(
|
||||
db: &Db,
|
||||
#[post("/create", data = "<data>")]
|
||||
pub async fn create_server(
|
||||
db: &State<Database>,
|
||||
user: User,
|
||||
info: Json<DataCreateServer>,
|
||||
) -> Result<Json<CreateServerResponse>> {
|
||||
data: Json<v0::DataCreateServer>,
|
||||
) -> Result<Json<v0::CreateServerLegacyResponse>> {
|
||||
if user.bot.is_some() {
|
||||
return Err(Error::IsBot);
|
||||
return Err(create_error!(IsBot));
|
||||
}
|
||||
|
||||
let info = info.into_inner();
|
||||
info.validate()
|
||||
.map_err(|error| Error::FailedValidation { error })?;
|
||||
let data = data.into_inner();
|
||||
data.validate().map_err(|error| {
|
||||
create_error!(FailedValidation {
|
||||
error: error.to_string()
|
||||
})
|
||||
})?;
|
||||
|
||||
if !user.can_acquire_server(db).await? {
|
||||
return Err(Error::TooManyServers {
|
||||
max: *MAX_SERVER_COUNT,
|
||||
});
|
||||
}
|
||||
user.can_acquire_server(db).await?;
|
||||
|
||||
let DataCreateServer {
|
||||
name,
|
||||
description,
|
||||
nsfw,
|
||||
} = info;
|
||||
let (server, channels) = Server::create(db, data, &user, true).await?;
|
||||
let channels = Member::create(db, &server, &user, Some(channels)).await?;
|
||||
|
||||
let channel_id = Ulid::new().to_string();
|
||||
let server_id = Ulid::new().to_string();
|
||||
|
||||
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 }))
|
||||
Ok(Json(v0::CreateServerLegacyResponse {
|
||||
server: server.into(),
|
||||
channels: channels.into_iter().map(|channel| channel.into()).collect(),
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ pub async fn webhook_execute(
|
||||
v0::MessageAuthor::Webhook(&webhook.into()),
|
||||
idempotency,
|
||||
true,
|
||||
true,
|
||||
)
|
||||
.await?
|
||||
.into(),
|
||||
|
||||
@@ -180,7 +180,7 @@ impl Ratelimiter {
|
||||
pub fn from(
|
||||
identifier: &str,
|
||||
(bucket, resource): (&str, Option<&str>),
|
||||
) -> Result<Ratelimiter, u128> {
|
||||
) -> Result<Ratelimiter, Ratelimiter> {
|
||||
let mut key = DefaultHasher::new();
|
||||
key.write(identifier.as_bytes());
|
||||
key.write(bucket.as_bytes());
|
||||
@@ -194,27 +194,29 @@ impl Ratelimiter {
|
||||
let mut entry = Entry::from(key);
|
||||
|
||||
let remaining = entry.get_remaining(limit);
|
||||
if remaining > 0 {
|
||||
entry.deduct();
|
||||
|
||||
let reset = entry.left_until_reset();
|
||||
entry.save(key);
|
||||
|
||||
Ok(Ratelimiter {
|
||||
key,
|
||||
limit,
|
||||
remaining: remaining - 1,
|
||||
reset,
|
||||
})
|
||||
} else {
|
||||
Err(entry.left_until_reset())
|
||||
let reset = entry.left_until_reset();
|
||||
let mut ratelimiter = Ratelimiter {
|
||||
key,
|
||||
limit,
|
||||
remaining,
|
||||
reset,
|
||||
};
|
||||
if remaining == 0 {
|
||||
return Err(ratelimiter);
|
||||
}
|
||||
|
||||
entry.deduct();
|
||||
entry.save(key);
|
||||
ratelimiter.remaining -= 1;
|
||||
ratelimiter.reset = entry.left_until_reset();
|
||||
|
||||
Ok(ratelimiter)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<'r> FromRequest<'r> for Ratelimiter {
|
||||
type Error = u128;
|
||||
type Error = Ratelimiter;
|
||||
|
||||
async fn from_request(request: &'r rocket::Request<'_>) -> Outcome<Self, Self::Error> {
|
||||
let ratelimiter = request
|
||||
@@ -233,7 +235,7 @@ impl<'r> FromRequest<'r> for Ratelimiter {
|
||||
|
||||
match ratelimiter {
|
||||
Ok(ratelimiter) => Outcome::Success(*ratelimiter),
|
||||
Err(retry_after) => Outcome::Failure((Status::TooManyRequests, *retry_after)),
|
||||
Err(ratelimiter) => Outcome::Failure((Status::TooManyRequests, *ratelimiter)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -275,23 +277,24 @@ impl Fairing for RatelimitFairing {
|
||||
}
|
||||
|
||||
async fn on_response<'r>(&self, request: &'r Request<'_>, response: &mut Response<'r>) {
|
||||
use rocket::outcome::Outcome;
|
||||
match request.guard::<Ratelimiter>().await {
|
||||
Outcome::Success(ratelimiter) => {
|
||||
let Ratelimiter {
|
||||
key,
|
||||
limit,
|
||||
remaining,
|
||||
reset,
|
||||
} = ratelimiter;
|
||||
let guard = request.guard::<Ratelimiter>().await;
|
||||
let (Outcome::Success(ratelimiter) | Outcome::Failure((_, ratelimiter))) = guard else {
|
||||
unreachable!()
|
||||
};
|
||||
let Ratelimiter {
|
||||
key,
|
||||
limit,
|
||||
remaining,
|
||||
reset,
|
||||
} = ratelimiter;
|
||||
|
||||
response.set_raw_header("X-RateLimit-Limit", limit.to_string());
|
||||
response.set_raw_header("X-RateLimit-Bucket", key.to_string());
|
||||
response.set_raw_header("X-RateLimit-Remaining", remaining.to_string());
|
||||
response.set_raw_header("X-RateLimit-Reset-After", reset.to_string());
|
||||
}
|
||||
Outcome::Failure(_) => response.set_status(Status::TooManyRequests),
|
||||
Outcome::Forward(_) => unreachable!(),
|
||||
response.set_raw_header("X-RateLimit-Limit", limit.to_string());
|
||||
response.set_raw_header("X-RateLimit-Bucket", key.to_string());
|
||||
response.set_raw_header("X-RateLimit-Remaining", remaining.to_string());
|
||||
response.set_raw_header("X-RateLimit-Reset-After", reset.to_string());
|
||||
|
||||
if guard.is_failure() {
|
||||
response.set_status(Status::TooManyRequests);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -308,11 +311,14 @@ impl<'r> FromRequest<'r> for RatelimitInformation {
|
||||
type Error = u128;
|
||||
|
||||
async fn from_request(request: &'r rocket::Request<'_>) -> Outcome<Self, Self::Error> {
|
||||
Outcome::Success(match request.guard::<Ratelimiter>().await {
|
||||
let info = match request.guard::<Ratelimiter>().await {
|
||||
Outcome::Success(ratelimiter) => RatelimitInformation::Success(ratelimiter),
|
||||
Outcome::Failure((_, retry_after)) => RatelimitInformation::Failure { retry_after },
|
||||
Outcome::Failure((_, ratelimiter)) => RatelimitInformation::Failure {
|
||||
retry_after: ratelimiter.reset,
|
||||
},
|
||||
_ => unreachable!(),
|
||||
})
|
||||
};
|
||||
Outcome::Success(info)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "revolt-quark"
|
||||
version = "0.6.8"
|
||||
version = "0.6.12"
|
||||
edition = "2021"
|
||||
license = "AGPL-3.0-or-later"
|
||||
|
||||
|
||||
@@ -143,6 +143,7 @@ pub enum EventV1 {
|
||||
id: String,
|
||||
server: Server,
|
||||
channels: Vec<Channel>,
|
||||
emojis: Vec<Emoji>,
|
||||
},
|
||||
|
||||
/// Update existing server
|
||||
|
||||
@@ -401,6 +401,7 @@ impl State {
|
||||
id,
|
||||
server,
|
||||
channels,
|
||||
emojis: _,
|
||||
} => {
|
||||
self.insert_subscription(id.clone());
|
||||
self.cache.servers.insert(id.clone(), server.clone());
|
||||
@@ -568,8 +569,7 @@ impl EventV1 {
|
||||
pub async fn p_user(self, id: String, db: &Database) {
|
||||
self.clone().p(id.clone()).await;
|
||||
|
||||
// ! FIXME: this should be captured by member list in the future
|
||||
// ! and not immediately fanned out to users
|
||||
// TODO: this should be captured by member list in the future and not immediately fanned out to users
|
||||
if let Ok(members) = db.fetch_all_memberships(&id).await {
|
||||
for member in members {
|
||||
self.clone().p(member.id.server).await;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use crate::models::server_member::{FieldsMember, Member, MemberCompositeKey, PartialMember};
|
||||
use crate::models::server_member::{
|
||||
FieldsMember, Member, MemberCompositeKey, MemberWithRoles, PartialMember,
|
||||
};
|
||||
use crate::{AbstractServerMember, Result};
|
||||
|
||||
use super::super::DummyDb;
|
||||
@@ -8,6 +10,9 @@ impl AbstractServerMember for DummyDb {
|
||||
async fn fetch_member(&self, server: &str, user: &str) -> Result<Member> {
|
||||
Ok(Member::new(server.into(), user.into()))
|
||||
}
|
||||
async fn fetch_member_with_roles(&self, server: &str, user: &str) -> Result<MemberWithRoles> {
|
||||
Ok(MemberWithRoles::new(server.into(), user.into()))
|
||||
}
|
||||
|
||||
async fn insert_member(&self, member: &Member) -> Result<()> {
|
||||
info!("Create {member:?}");
|
||||
|
||||
@@ -192,7 +192,7 @@ impl Message {
|
||||
/// Add a reaction to a message
|
||||
pub async fn add_reaction(&self, db: &Database, user: &User, emoji: &str) -> Result<()> {
|
||||
// Check how many reactions are already on the message
|
||||
if self.reactions.len() >= 20 {
|
||||
if self.reactions.len() >= 20 && !self.reactions.contains_key(emoji) {
|
||||
return Err(Error::InvalidOperation);
|
||||
}
|
||||
|
||||
@@ -382,11 +382,16 @@ impl BulkMessageResponse {
|
||||
db: &Database,
|
||||
channel: Option<&Channel>,
|
||||
messages: Vec<Message>,
|
||||
user: &User,
|
||||
include_users: Option<bool>,
|
||||
) -> Result<BulkMessageResponse> {
|
||||
if let Some(true) = include_users {
|
||||
let user_ids = messages.get_user_ids();
|
||||
let users = User::fetch_foreign_users(db, &user_ids).await?;
|
||||
let users = User::fetch_foreign_users(db, &user_ids)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|x| x.with_relationship(user))
|
||||
.collect();
|
||||
|
||||
Ok(match channel {
|
||||
Some(Channel::TextChannel { server, .. })
|
||||
|
||||
@@ -206,6 +206,8 @@ impl Server {
|
||||
}
|
||||
}
|
||||
|
||||
let emojis = db.fetch_emoji_by_parent_id(&self.id).await?;
|
||||
|
||||
EventV1::ServerMemberJoin {
|
||||
id: self.id.clone(),
|
||||
user: user.id.clone(),
|
||||
@@ -217,6 +219,7 @@ impl Server {
|
||||
id: self.id.clone(),
|
||||
server: self.clone(),
|
||||
channels: channels.clone(),
|
||||
emojis,
|
||||
}
|
||||
.private(user.id.clone())
|
||||
.await;
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use iso8601_timestamp::Timestamp;
|
||||
|
||||
use crate::{
|
||||
events::client::EventV1,
|
||||
models::{
|
||||
server_member::{FieldsMember, MemberCompositeKey, PartialMember},
|
||||
server_member::{FieldsMember, MemberCompositeKey, MemberWithRoles, PartialMember},
|
||||
Member, Server,
|
||||
},
|
||||
Database, Result,
|
||||
@@ -82,3 +84,12 @@ impl Member {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MemberWithRoles {
|
||||
pub fn new(server_id: String, user_id: String) -> Self {
|
||||
Self {
|
||||
member: Member::new(server_id, user_id),
|
||||
roles: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,7 +170,8 @@ impl User {
|
||||
|
||||
/// Check if this user can acquire another server
|
||||
pub async fn can_acquire_server(&self, db: &Database) -> Result<bool> {
|
||||
// ! FIXME: hardcoded max server count
|
||||
// hardcoded max server count
|
||||
// NB. fixed in new crate
|
||||
Ok(db.fetch_server_count(&self.id).await? <= 100)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use bson::Document;
|
||||
|
||||
use super::super::MongoDb;
|
||||
use crate::models::server_member::MemberWithRoles;
|
||||
use crate::models::server_member::{FieldsMember, Member, MemberCompositeKey, PartialMember};
|
||||
use crate::r#impl::mongo::IntoDocumentPath;
|
||||
use crate::{AbstractServerMember, Error, Result};
|
||||
|
||||
use super::super::MongoDb;
|
||||
use crate::{AbstractServer, AbstractServerMember, Error, Result};
|
||||
|
||||
static COL: &str = "server_members";
|
||||
|
||||
@@ -21,6 +21,17 @@ impl AbstractServerMember for MongoDb {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn fetch_member_with_roles(&self, server: &str, user: &str) -> Result<MemberWithRoles> {
|
||||
let member = self.fetch_member(server, user).await?;
|
||||
let server_roles = self.fetch_server(server).await?.roles;
|
||||
let roles = member
|
||||
.roles
|
||||
.iter()
|
||||
.filter_map(|id| server_roles.get(id).map(|r| (id.clone(), r.clone())))
|
||||
.collect();
|
||||
Ok(MemberWithRoles { member, roles })
|
||||
}
|
||||
|
||||
async fn insert_member(&self, member: &Member) -> Result<()> {
|
||||
self.insert_one(COL, member).await.map(|_| ())
|
||||
}
|
||||
|
||||
@@ -93,7 +93,6 @@ pub struct Server {
|
||||
pub description: Option<String>,
|
||||
|
||||
/// Channels within this server
|
||||
// ! FIXME: this may be redundant
|
||||
pub channels: Vec<String>,
|
||||
/// Categories for this server
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use iso8601_timestamp::Timestamp;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::models::attachment::File;
|
||||
|
||||
use super::server::Role;
|
||||
|
||||
/// Composite primary key consisting of server and user id
|
||||
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, Default)]
|
||||
pub struct MemberCompositeKey {
|
||||
@@ -41,6 +46,30 @@ pub struct Member {
|
||||
pub timeout: Option<Timestamp>,
|
||||
}
|
||||
|
||||
/// Representation of a member of a server on Revolt With Role Data
|
||||
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)]
|
||||
pub struct MemberWithRoles {
|
||||
pub member: Member,
|
||||
pub roles: HashMap<String, Role>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)]
|
||||
#[serde(untagged)]
|
||||
pub enum MemberResponse {
|
||||
Member(Member),
|
||||
MemberWithRoles(MemberWithRoles),
|
||||
}
|
||||
|
||||
impl From<Member> for MemberResponse {
|
||||
fn from(value: Member) -> Self {
|
||||
Self::Member(value)
|
||||
}
|
||||
}
|
||||
impl From<MemberWithRoles> for MemberResponse {
|
||||
fn from(value: MemberWithRoles) -> Self {
|
||||
Self::MemberWithRoles(value)
|
||||
}
|
||||
}
|
||||
/// Optional fields on server member object
|
||||
#[derive(Serialize, Deserialize, JsonSchema, Debug, PartialEq, Eq, Clone)]
|
||||
pub enum FieldsMember {
|
||||
|
||||
@@ -78,12 +78,6 @@ async fn calculate_permission(data: &mut PermissionCalculator<'_>, db: &crate::D
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// ! FIXME: add boolean switch for permission for users to globally message a user
|
||||
// maybe an enum?
|
||||
// PrivacyLevel { Private, Friends, Mutual, Public, Global }
|
||||
|
||||
// ! FIXME: add boolean switch for permission for users to mutually DM a user
|
||||
|
||||
if data.flag_has_mutual_connection
|
||||
|| data
|
||||
.perspective
|
||||
|
||||
@@ -43,7 +43,7 @@ pub trait AbstractChannel: Sync + Send {
|
||||
async fn remove_user_from_group(&self, channel: &str, user: &str) -> Result<()>;
|
||||
|
||||
/// Set role permission for a channel
|
||||
/// ! FIXME: may want to refactor to just use normal updates
|
||||
/// TODO: may want to refactor to just use normal updates
|
||||
async fn set_channel_role_permission(
|
||||
&self,
|
||||
channel: &str,
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use crate::models::server_member::{FieldsMember, Member, MemberCompositeKey, PartialMember};
|
||||
use crate::models::server_member::{
|
||||
FieldsMember, Member, MemberCompositeKey, MemberWithRoles, PartialMember,
|
||||
};
|
||||
use crate::Result;
|
||||
|
||||
#[async_trait]
|
||||
@@ -6,6 +8,8 @@ pub trait AbstractServerMember: Sync + Send {
|
||||
/// Fetch a server member by their id
|
||||
async fn fetch_member(&self, server: &str, user: &str) -> Result<Member>;
|
||||
|
||||
async fn fetch_member_with_roles(&self, server: &str, user: &str) -> Result<MemberWithRoles>;
|
||||
|
||||
/// Insert a new server member into the database
|
||||
async fn insert_member(&self, member: &Member) -> Result<()>;
|
||||
|
||||
|
||||
@@ -2,10 +2,10 @@ use async_lock::Semaphore;
|
||||
use async_std::task::spawn;
|
||||
use futures::future::join_all;
|
||||
use linkify::{LinkFinder, LinkKind};
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{collections::HashSet, sync::Arc};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use crate::{models::attachment::File, Error, Result};
|
||||
|
||||
@@ -187,8 +187,6 @@ impl Embed {
|
||||
max_embeds: usize,
|
||||
semaphore: Arc<Semaphore>,
|
||||
) -> Result<Vec<Embed>> {
|
||||
|
||||
|
||||
// Ignore code blocks.
|
||||
let content = RE_CODE.replace_all(&content, "");
|
||||
|
||||
@@ -233,7 +231,6 @@ impl Embed {
|
||||
return Err(Error::LabelMe);
|
||||
}
|
||||
|
||||
// ! FIXME: batch request to january?
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
let mut tasks = Vec::new();
|
||||
|
||||
@@ -5,9 +5,8 @@ use schemars::schema::{InstanceType, Schema, SchemaObject, SingleOrVec};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::models::{
|
||||
Channel, Emoji, Invite, Member, Message, Report, Server, ServerBan, User,
|
||||
};
|
||||
use crate::models::server_member::MemberWithRoles;
|
||||
use crate::models::{Channel, Emoji, Invite, Member, Message, Report, Server, ServerBan, User};
|
||||
use crate::{Database, Error, Result};
|
||||
|
||||
/// Reference to some object in the database
|
||||
@@ -66,6 +65,14 @@ impl Ref {
|
||||
db.fetch_member(server, &self.id).await
|
||||
}
|
||||
|
||||
pub async fn as_member_with_roles(
|
||||
&self,
|
||||
db: &Database,
|
||||
server: &str,
|
||||
) -> Result<MemberWithRoles> {
|
||||
db.fetch_member_with_roles(server, &self.id).await
|
||||
}
|
||||
|
||||
/// Fetch ban from Ref
|
||||
pub async fn as_ban(&self, db: &Database, server: &str) -> Result<ServerBan> {
|
||||
db.fetch_ban(server, &self.id).await
|
||||
|
||||
Reference in New Issue
Block a user