Compare commits
16
Commits
20230704-1
...
20230810-3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
430df03b20 | ||
|
|
f5f70287e7 | ||
|
|
a0580c5f5a | ||
|
|
121a9cd87c | ||
|
|
d87d608d9e | ||
|
|
bd9ecc1519 | ||
|
|
f5a113da47 | ||
|
|
11fdb0c1dc | ||
|
|
e0033ceb12 | ||
|
|
7318ec6ef6 | ||
|
|
a516c7adcf | ||
|
|
c48109ca66 | ||
|
|
f0ef781cd7 | ||
|
|
d9dd040db4 | ||
|
|
fec947b4c8 | ||
|
|
ec6df36c25 |
@@ -4,3 +4,4 @@ target
|
||||
.env
|
||||
|
||||
.vercel
|
||||
.DS_Store
|
||||
|
||||
Generated
+111
-37
@@ -421,6 +421,12 @@ version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.21.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d"
|
||||
|
||||
[[package]]
|
||||
name = "binascii"
|
||||
version = "0.1.4"
|
||||
@@ -519,7 +525,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_bytes",
|
||||
"serde_json",
|
||||
"uuid",
|
||||
"uuid 0.8.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -798,12 +804,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "debugid"
|
||||
version = "0.7.3"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6ee87af31d84ef885378aebca32be3d682b0e0dc119d5b4860a2c5bb5046730"
|
||||
checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"uuid",
|
||||
"uuid 1.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1001,6 +1007,18 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "findshlibs"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40b9e59cd0f7e0806cca4be089683ecb6434e602038df21fe6bf6711b2f07f64"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
@@ -1548,9 +1566,9 @@ checksum = "90f97a5f38dd3ccfbe7aa80f4a0c00930f21b922c74195be0201c51028f22dcf"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.1"
|
||||
version = "1.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
|
||||
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||
dependencies = [
|
||||
"autocfg 1.1.0",
|
||||
"hashbrown 0.12.1",
|
||||
@@ -1613,11 +1631,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "iso8601-timestamp"
|
||||
version = "0.2.10"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "368d05885f276773b79b4578869a60ab91e880415d17fb2a8d193aea74188a29"
|
||||
checksum = "3bd7663ae5b511b3d13c7d374dbe73b03da3da8e66b83950390173bc7c7325cc"
|
||||
dependencies = [
|
||||
"generic-array 0.14.5",
|
||||
"schemars",
|
||||
"serde",
|
||||
"time 0.3.17",
|
||||
]
|
||||
@@ -2005,7 +2024,7 @@ dependencies = [
|
||||
"trust-dns-proto",
|
||||
"trust-dns-resolver",
|
||||
"typed-builder",
|
||||
"uuid",
|
||||
"uuid 0.8.2",
|
||||
"webpki-roots",
|
||||
]
|
||||
|
||||
@@ -2231,11 +2250,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "os_info"
|
||||
version = "3.4.0"
|
||||
version = "3.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eca3ecae1481e12c3d9379ec541b238a16f0b75c9a409942daa8ec20dbfdb62"
|
||||
checksum = "006e42d5b888366f1880eda20371fedde764ed2213dc8496f49622fa0c99cd5e"
|
||||
dependencies = [
|
||||
"log",
|
||||
"serde",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
@@ -2857,7 +2877,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "revolt-bonfire"
|
||||
version = "0.6.5"
|
||||
version = "0.6.6"
|
||||
dependencies = [
|
||||
"async-std",
|
||||
"async-tungstenite",
|
||||
@@ -2874,7 +2894,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "revolt-database"
|
||||
version = "0.6.5"
|
||||
version = "0.6.6"
|
||||
dependencies = [
|
||||
"async-recursion",
|
||||
"async-std",
|
||||
@@ -2882,7 +2902,8 @@ dependencies = [
|
||||
"authifier",
|
||||
"bson",
|
||||
"futures",
|
||||
"iso8601-timestamp 0.2.10",
|
||||
"indexmap",
|
||||
"iso8601-timestamp 0.2.11",
|
||||
"log",
|
||||
"mongodb",
|
||||
"nanoid",
|
||||
@@ -2905,7 +2926,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "revolt-delta"
|
||||
version = "0.6.5"
|
||||
version = "0.6.6"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"async-std",
|
||||
@@ -2946,8 +2967,10 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "revolt-models"
|
||||
version = "0.6.5"
|
||||
version = "0.6.6"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"iso8601-timestamp 0.2.11",
|
||||
"revolt-permissions",
|
||||
"revolt_optional_struct",
|
||||
"schemars",
|
||||
@@ -2957,7 +2980,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "revolt-permissions"
|
||||
version = "0.6.5"
|
||||
version = "0.6.6"
|
||||
dependencies = [
|
||||
"async-std",
|
||||
"async-trait",
|
||||
@@ -2971,7 +2994,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "revolt-presence"
|
||||
version = "0.6.5"
|
||||
version = "0.6.6"
|
||||
dependencies = [
|
||||
"async-std",
|
||||
"log",
|
||||
@@ -2982,7 +3005,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "revolt-quark"
|
||||
version = "0.6.5"
|
||||
version = "0.6.6"
|
||||
dependencies = [
|
||||
"async-lock",
|
||||
"async-recursion",
|
||||
@@ -3035,7 +3058,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "revolt-result"
|
||||
version = "0.6.5"
|
||||
version = "0.6.6"
|
||||
dependencies = [
|
||||
"revolt_okapi",
|
||||
"revolt_rocket_okapi",
|
||||
@@ -3456,39 +3479,44 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
||||
|
||||
[[package]]
|
||||
name = "sentry"
|
||||
version = "0.25.0"
|
||||
version = "0.31.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2d23af89cf3e40dffb53f974e9a21653353b3e21cf51633aa58006f2a0caf8a"
|
||||
checksum = "01b0ad16faa5d12372f914ed40d00bda21a6d1bdcc99264c5e5e1c9495cf3654"
|
||||
dependencies = [
|
||||
"httpdate 1.0.2",
|
||||
"native-tls",
|
||||
"reqwest",
|
||||
"sentry-backtrace",
|
||||
"sentry-contexts",
|
||||
"sentry-core",
|
||||
"sentry-debug-images",
|
||||
"sentry-panic",
|
||||
"sentry-tracing",
|
||||
"tokio 1.18.2",
|
||||
"ureq",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sentry-backtrace"
|
||||
version = "0.25.0"
|
||||
version = "0.31.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8158a446429420acdf6a4f75192ee8929da16a0c41c89a1c34b2e0f1eaebcc02"
|
||||
checksum = "11f2ee8f147bb5f22ac59b5c35754a759b9a6f6722402e2a14750b2a63fc59bd"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"lazy_static",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"sentry-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sentry-contexts"
|
||||
version = "0.25.0"
|
||||
version = "0.31.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a3bda8a1e3213f1944da2d42f3081ea9f3717105bb2a6b0a8fe4f5e603010a3"
|
||||
checksum = "dcd133362c745151eeba0ac61e3ba8350f034e9fe7509877d08059fe1d7720c6"
|
||||
dependencies = [
|
||||
"hostname",
|
||||
"libc",
|
||||
"os_info",
|
||||
"rustc_version 0.4.0",
|
||||
"sentry-core",
|
||||
"uname",
|
||||
@@ -3496,11 +3524,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sentry-core"
|
||||
version = "0.25.0"
|
||||
version = "0.31.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56333f11be3a78131c67637f7611339df8af7ad9af831226585a457df75f9e3b"
|
||||
checksum = "7163491708804a74446642ff2c80b3acd668d4b9e9f497f85621f3d250fd012b"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"once_cell",
|
||||
"rand 0.8.5",
|
||||
"sentry-types",
|
||||
"serde",
|
||||
@@ -3508,20 +3536,43 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sentry-panic"
|
||||
version = "0.25.0"
|
||||
name = "sentry-debug-images"
|
||||
version = "0.31.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b957b1965c450acd220a27806fe1f2dec998d393973ebae797936b12df1c7416"
|
||||
checksum = "6a5003d7ff08aa3b2b76994080b183e8cfa06c083e280737c9cee02ca1c70f5e"
|
||||
dependencies = [
|
||||
"findshlibs",
|
||||
"once_cell",
|
||||
"sentry-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sentry-panic"
|
||||
version = "0.31.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4dfe8371c9b2e126a8b64f6fefa54cef716ff2a50e63b5558a48b899265bccd"
|
||||
dependencies = [
|
||||
"sentry-backtrace",
|
||||
"sentry-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sentry-types"
|
||||
version = "0.25.0"
|
||||
name = "sentry-tracing"
|
||||
version = "0.31.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "825fd3382e2397007499a910e0184e55f7837cb0df4af30ae62bd2123e2ebcd6"
|
||||
checksum = "5aca8b88978677a27ee1a91beafe4052306c474c06f582321fde72d2e2cc2f7f"
|
||||
dependencies = [
|
||||
"sentry-backtrace",
|
||||
"sentry-core",
|
||||
"tracing-core",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sentry-types"
|
||||
version = "0.31.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e7a88e0c1922d19b3efee12a8215f6a8a806e442e665ada71cc222cab72985f"
|
||||
dependencies = [
|
||||
"debugid",
|
||||
"getrandom",
|
||||
@@ -3531,7 +3582,7 @@ dependencies = [
|
||||
"thiserror",
|
||||
"time 0.3.17",
|
||||
"url",
|
||||
"uuid",
|
||||
"uuid 1.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4403,6 +4454,19 @@ version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
|
||||
|
||||
[[package]]
|
||||
name = "ureq"
|
||||
version = "2.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b11c96ac7ee530603dcdf68ed1557050f374ce55a5a07193ebf8cbc9f8927e9"
|
||||
dependencies = [
|
||||
"base64 0.21.2",
|
||||
"log",
|
||||
"native-tls",
|
||||
"once_cell",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.2.2"
|
||||
@@ -4432,6 +4496,16 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "validator"
|
||||
version = "0.15.0"
|
||||
|
||||
@@ -21,6 +21,10 @@ Note: `january`, `autumn`, and `vortex` are yet to be moved into this monorepo.
|
||||
- [Revolt Testers Server](https://app.revolt.chat/invite/Testers)
|
||||
- [Contribution Guide](https://developers.revolt.chat/contributing)
|
||||
|
||||
## Minimum Supported Rust Version
|
||||
|
||||
Rust 1.70 or higher.
|
||||
|
||||
## Contributing
|
||||
|
||||
The contribution guide is located at [developers.revolt.chat/contributing](https://developers.revolt.chat/contributing).
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
Beyond using Cargo format and Clippy, there are some specific code style guidelines laid out in this document for different parts of the project.
|
||||
|
||||
## Writing Style
|
||||
|
||||
- Shorten "identifier" to "Id" with that exact casing, i.e. Server Id.
|
||||
|
||||
## `core/database` crate
|
||||
|
||||
w.r.t. `model.rs` files
|
||||
@@ -22,6 +26,7 @@ w.r.t. `model.rs` files
|
||||
);
|
||||
```
|
||||
- `auto_derived!` macro accepts multiple entries and should be used as such:
|
||||
|
||||
```rust
|
||||
auto_derived!(
|
||||
/// Optional fields on server object
|
||||
@@ -31,6 +36,7 @@ w.r.t. `model.rs` files
|
||||
pub enum FieldsRole { .. }
|
||||
);
|
||||
```
|
||||
|
||||
- If special serialisation conditions are required, such as checking if a boolean is false, use the existing definitions for these functions from the crate root:
|
||||
```rust
|
||||
#[serde(skip_serializing_if = "crate::if_false", default)]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "revolt-bonfire"
|
||||
version = "0.6.5"
|
||||
version = "0.6.6"
|
||||
license = "AGPL-3.0-or-later"
|
||||
edition = "2021"
|
||||
|
||||
@@ -25,7 +25,11 @@ serde = "1.0.136"
|
||||
# async
|
||||
futures = "0.3.21"
|
||||
async-tungstenite = { version = "0.17.0", features = ["async-std-runtime"] }
|
||||
async-std = { version = "1.8.0", features = ["tokio1", "tokio02", "attributes"] }
|
||||
async-std = { version = "1.8.0", features = [
|
||||
"tokio1",
|
||||
"tokio02",
|
||||
"attributes",
|
||||
] }
|
||||
|
||||
# core
|
||||
revolt-presence = { path = "../core/presence", features = [ "redis-is-patched" ] }
|
||||
revolt-presence = { path = "../core/presence", features = ["redis-is-patched"] }
|
||||
|
||||
@@ -1,31 +1,34 @@
|
||||
[package]
|
||||
name = "revolt-database"
|
||||
version = "0.6.5"
|
||||
version = "0.6.6"
|
||||
edition = "2021"
|
||||
license = "AGPL-3.0-or-later"
|
||||
authors = [ "Paul Makles <me@insrt.uk>" ]
|
||||
authors = ["Paul Makles <me@insrt.uk>"]
|
||||
description = "Revolt Backend: Database Implementation"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[features]
|
||||
# Databases
|
||||
mongodb = [ "dep:mongodb", "bson" ]
|
||||
mongodb = ["dep:mongodb", "bson"]
|
||||
|
||||
# ... Other
|
||||
async-std-runtime = [ "async-std" ]
|
||||
rocket-impl = [ "rocket", "schemars" ]
|
||||
redis-is-patched = [ "revolt-presence/redis-is-patched" ]
|
||||
async-std-runtime = ["async-std"]
|
||||
rocket-impl = ["rocket", "schemars"]
|
||||
redis-is-patched = ["revolt-presence/redis-is-patched"]
|
||||
|
||||
# Default Features
|
||||
default = [ "mongodb", "async-std-runtime" ]
|
||||
default = ["mongodb", "async-std-runtime"]
|
||||
|
||||
[dependencies]
|
||||
# Core
|
||||
revolt-result = { version = "0.6.5", path = "../result" }
|
||||
revolt-models = { version = "0.6.5", path = "../models" }
|
||||
revolt-presence = { version = "0.6.5", path = "../presence" }
|
||||
revolt-permissions = { version = "0.6.5", path = "../permissions", features = [ "serde", "bson" ] }
|
||||
revolt-result = { version = "0.6.6", path = "../result" }
|
||||
revolt-models = { version = "0.6.6", path = "../models" }
|
||||
revolt-presence = { version = "0.6.6", path = "../presence" }
|
||||
revolt-permissions = { version = "0.6.6", path = "../permissions", features = [
|
||||
"serde",
|
||||
"bson",
|
||||
] }
|
||||
|
||||
# Utility
|
||||
log = "0.4"
|
||||
@@ -33,6 +36,7 @@ rand = "0.8.5"
|
||||
ulid = "1.0.0"
|
||||
nanoid = "0.4.0"
|
||||
once_cell = "1.17"
|
||||
indexmap = "1.9.1"
|
||||
|
||||
# Serialisation
|
||||
serde_json = "1"
|
||||
@@ -61,7 +65,9 @@ async-std = { version = "1.8.0", features = ["attributes"], optional = true }
|
||||
|
||||
# Rocket Impl
|
||||
schemars = { version = "0.8.8", optional = true }
|
||||
rocket = { version = "0.5.0-rc.2", default-features = false, features = ["json"], optional = true }
|
||||
rocket = { version = "0.5.0-rc.2", default-features = false, features = [
|
||||
"json",
|
||||
], optional = true }
|
||||
|
||||
# Authifier
|
||||
authifier = { version = "1.0" }
|
||||
|
||||
@@ -3,29 +3,28 @@ use std::{collections::HashMap, sync::Arc};
|
||||
use futures::lock::Mutex;
|
||||
|
||||
use crate::{
|
||||
AccountStrike, Bot, Channel, File, Member, MemberCompositeKey, Server, User, UserSettings,
|
||||
Webhook,
|
||||
Bot, Channel, ChannelCompositeKey, ChannelUnread, Emoji, File, Invite, Member,
|
||||
MemberCompositeKey, Server, ServerBan, User, UserSettings, Webhook,
|
||||
};
|
||||
|
||||
database_derived!(
|
||||
/// Reference implementation
|
||||
#[derive(Default)]
|
||||
pub struct ReferenceDb {
|
||||
pub account_strikes: Arc<Mutex<HashMap<String, AccountStrike>>>,
|
||||
pub bots: Arc<Mutex<HashMap<String, Bot>>>,
|
||||
pub channels: Arc<Mutex<HashMap<String, Channel>>>,
|
||||
pub channel_invites: Arc<Mutex<HashMap<String, Invite>>>,
|
||||
pub channel_unreads: Arc<Mutex<HashMap<ChannelCompositeKey, ChannelUnread>>>,
|
||||
pub channel_webhooks: Arc<Mutex<HashMap<String, Webhook>>>,
|
||||
pub emojis: Arc<Mutex<HashMap<String, Emoji>>>,
|
||||
pub user_settings: Arc<Mutex<HashMap<String, UserSettings>>>,
|
||||
pub users: Arc<Mutex<HashMap<String, User>>>,
|
||||
pub server_bans: Arc<Mutex<HashMap<MemberCompositeKey, ServerBan>>>,
|
||||
pub server_members: Arc<Mutex<HashMap<MemberCompositeKey, Member>>>,
|
||||
pub servers: Arc<Mutex<HashMap<String, Server>>>,
|
||||
pub files: Arc<Mutex<HashMap<String, File>>>,
|
||||
pub server_bans: Arc<Mutex<HashMap<String, ()>>>,
|
||||
pub safety_reports: Arc<Mutex<HashMap<String, ()>>>,
|
||||
pub safety_snapshots: Arc<Mutex<HashMap<String, ()>>>,
|
||||
pub emoji: Arc<Mutex<HashMap<String, ()>>>,
|
||||
pub messages: Arc<Mutex<HashMap<String, ()>>>,
|
||||
pub channels: Arc<Mutex<HashMap<String, Channel>>>,
|
||||
pub channel_invites: Arc<Mutex<HashMap<String, ()>>>,
|
||||
pub channel_unreads: Arc<Mutex<HashMap<String, ()>>>,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -2,7 +2,9 @@ use authifier::AuthifierEvent;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use revolt_models::v0::{
|
||||
Channel, FieldsChannel, FieldsWebhook, PartialChannel, PartialWebhook, Webhook,
|
||||
Channel, Emoji, FieldsChannel, FieldsMember, FieldsRole, FieldsServer, FieldsWebhook,
|
||||
MemberCompositeKey, PartialChannel, PartialMember, PartialRole, PartialServer, PartialWebhook,
|
||||
Server, UserSettings, Webhook,
|
||||
};
|
||||
use revolt_result::Error;
|
||||
|
||||
@@ -101,8 +103,7 @@ pub enum EventV1 {
|
||||
},
|
||||
|
||||
/// Bulk delete messages
|
||||
BulkMessageDelete { channel: String, ids: Vec<String> },
|
||||
|
||||
BulkMessageDelete { channel: String, ids: Vec<String> },*/
|
||||
/// New server
|
||||
ServerCreate {
|
||||
id: String,
|
||||
@@ -144,7 +145,7 @@ pub enum EventV1 {
|
||||
/// Server role deleted
|
||||
ServerRoleDelete { id: String, role_id: String },
|
||||
|
||||
/// Update existing user
|
||||
/*/// Update existing user
|
||||
UserUpdate {
|
||||
id: String,
|
||||
data: PartialUser,
|
||||
@@ -158,12 +159,11 @@ pub enum EventV1 {
|
||||
user: User,
|
||||
// ! this field can be deprecated
|
||||
status: RelationshipStatus,
|
||||
},
|
||||
|
||||
},*/
|
||||
/// Settings updated remotely
|
||||
UserSettingsUpdate { id: String, update: UserSettings },
|
||||
|
||||
/// User has been platform banned or deleted their account
|
||||
/*/// User has been platform banned or deleted their account
|
||||
///
|
||||
/// Clients should remove the following associated data:
|
||||
/// - Messages
|
||||
@@ -172,15 +172,14 @@ pub enum EventV1 {
|
||||
/// - Server Memberships
|
||||
///
|
||||
/// User flags are specified to explain why a wipe is occurring though not all reasons will necessarily ever appear.
|
||||
UserPlatformWipe { user_id: String, flags: i32 },
|
||||
|
||||
UserPlatformWipe { user_id: String, flags: i32 }, */
|
||||
/// New emoji
|
||||
EmojiCreate(Emoji),
|
||||
|
||||
/// Delete emoji
|
||||
EmojiDelete { id: String },
|
||||
|
||||
/// New report
|
||||
/*/// New report
|
||||
ReportCreate(Report), */
|
||||
/// New channel
|
||||
ChannelCreate(Channel),
|
||||
|
||||
@@ -697,7 +697,9 @@ pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 {
|
||||
}
|
||||
|
||||
if revision <= 19 {
|
||||
info!("Running migration [revision 19 / 27-02-2023]: Create report / snapshot collections, migrate to new model if applicable.");
|
||||
info!(
|
||||
"Running migration [revision 19 / 27-02-2023]: Create report / snapshot collections."
|
||||
);
|
||||
|
||||
db.db()
|
||||
.create_collection("safety_reports", None)
|
||||
@@ -708,19 +710,6 @@ pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 {
|
||||
.create_collection("safety_snapshots", None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
db.col::<Document>("safety_reports")
|
||||
.update_many(
|
||||
doc! {},
|
||||
doc! {
|
||||
"$set": {
|
||||
"status": "Created"
|
||||
}
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
if revision <= 20 {
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
use revolt_result::{create_error, Result};
|
||||
|
||||
use crate::Database;
|
||||
|
||||
/* static ALPHABET: [char; 54] = [
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J',
|
||||
'K', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
|
||||
'g', 'h', 'j', 'k', 'm', 'n', 'p', 'q', 'r', 's', 't', 'v', 'w', 'x', 'y', 'z',
|
||||
]; */
|
||||
|
||||
auto_derived!(
|
||||
/// Invite
|
||||
pub enum Invite {
|
||||
/// Invite to a specific server channel
|
||||
Server {
|
||||
/// Invite code
|
||||
#[serde(rename = "_id")]
|
||||
code: String,
|
||||
/// Id of the server this invite points to
|
||||
server: String,
|
||||
/// Id of user who created this invite
|
||||
creator: String,
|
||||
/// Id of the server channel this invite points to
|
||||
channel: String,
|
||||
},
|
||||
/// Invite to a group channel
|
||||
Group {
|
||||
/// Invite code
|
||||
#[serde(rename = "_id")]
|
||||
code: String,
|
||||
/// Id of user who created this invite
|
||||
creator: String,
|
||||
/// Id of the group channel this invite points to
|
||||
channel: String,
|
||||
}, /* User {
|
||||
code: String,
|
||||
user: String
|
||||
} */
|
||||
}
|
||||
);
|
||||
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
impl Invite {
|
||||
/// Get the invite code for this invite
|
||||
pub fn code(&'_ self) -> &'_ str {
|
||||
match self {
|
||||
Invite::Server { code, .. } | Invite::Group { code, .. } => code,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the ID of the user who created this invite
|
||||
pub fn creator(&'_ self) -> &'_ str {
|
||||
match self {
|
||||
Invite::Server { creator, .. } | Invite::Group { creator, .. } => creator,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new invite from given information
|
||||
/*pub async fn create_channel_invite(db: &Database, creator_id: String, target: &Channel) -> Result<Invite> {
|
||||
let code = nanoid::nanoid!(8, &ALPHABET);
|
||||
let invite = match &target {
|
||||
Channel::Group { id, .. } => Ok(Invite::Group {
|
||||
code,
|
||||
creator: creator.id.clone(),
|
||||
channel: id.clone(),
|
||||
}),
|
||||
Channel::TextChannel { id, server, .. } | Channel::VoiceChannel { id, server, .. } => {
|
||||
Ok(Invite::Server {
|
||||
code,
|
||||
creator: creator.id.clone(),
|
||||
server: server.clone(),
|
||||
channel: id.clone(),
|
||||
})
|
||||
}
|
||||
_ => Err(Error::InvalidOperation),
|
||||
}?;
|
||||
|
||||
db.insert_invite(&invite).await?;
|
||||
Ok(invite)
|
||||
}*/
|
||||
|
||||
/// Resolve an invite by its ID or by a public server ID
|
||||
pub async fn find(db: &Database, code: &str) -> Result<Invite> {
|
||||
if let Ok(invite) = db.fetch_invite(code).await {
|
||||
return Ok(invite);
|
||||
} else if let Ok(server) = db.fetch_server(code).await {
|
||||
if server.discoverable {
|
||||
if let Some(channel) = server.channels.into_iter().next() {
|
||||
return Ok(Invite::Server {
|
||||
code: code.to_string(),
|
||||
server: server.id,
|
||||
creator: server.owner,
|
||||
channel,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(create_error!(NotFound))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
use revolt_result::Result;
|
||||
|
||||
use crate::Invite;
|
||||
|
||||
mod mongodb;
|
||||
mod reference;
|
||||
|
||||
#[async_trait]
|
||||
pub trait AbstractChannelInvites: Sync + Send {
|
||||
/// Insert a new invite into the database
|
||||
async fn insert_invite(&self, invite: &Invite) -> Result<()>;
|
||||
|
||||
/// Fetch an invite by its id
|
||||
async fn fetch_invite(&self, code: &str) -> Result<Invite>;
|
||||
|
||||
/// Fetch all invites for a server
|
||||
async fn fetch_invites_for_server(&self, server_id: &str) -> Result<Vec<Invite>>;
|
||||
|
||||
/// Delete an invite by its id
|
||||
async fn delete_invite(&self, code: &str) -> Result<()>;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
use futures::StreamExt;
|
||||
use revolt_result::Result;
|
||||
|
||||
use crate::Invite;
|
||||
use crate::MongoDb;
|
||||
|
||||
use super::AbstractChannelInvites;
|
||||
|
||||
static COL: &str = "channel_invites";
|
||||
|
||||
#[async_trait]
|
||||
impl AbstractChannelInvites for MongoDb {
|
||||
/// Insert a new invite into the database
|
||||
async fn insert_invite(&self, invite: &Invite) -> Result<()> {
|
||||
query!(self, insert_one, COL, &invite).map(|_| ())
|
||||
}
|
||||
|
||||
/// Fetch an invite by the code
|
||||
async fn fetch_invite(&self, code: &str) -> Result<Invite> {
|
||||
query!(self, find_one_by_id, COL, code)?.ok_or_else(|| create_error!(NotFound))
|
||||
}
|
||||
|
||||
/// Fetch all invites for a server
|
||||
async fn fetch_invites_for_server(&self, server_id: &str) -> Result<Vec<Invite>> {
|
||||
Ok(self
|
||||
.col::<Invite>(COL)
|
||||
.find(
|
||||
doc! {
|
||||
"server": server_id,
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map_err(|_| create_database_error!("find", COL))?
|
||||
.filter_map(|s| async {
|
||||
if cfg!(debug_assertions) {
|
||||
Some(s.unwrap())
|
||||
} else {
|
||||
s.ok()
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
.await)
|
||||
}
|
||||
|
||||
/// Delete an invite by its code
|
||||
async fn delete_invite(&self, code: &str) -> Result<()> {
|
||||
query!(self, delete_one_by_id, COL, code).map(|_| ())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
use revolt_result::Result;
|
||||
|
||||
use crate::Invite;
|
||||
use crate::ReferenceDb;
|
||||
|
||||
use super::AbstractChannelInvites;
|
||||
|
||||
#[async_trait]
|
||||
impl AbstractChannelInvites for ReferenceDb {
|
||||
/// Insert a new invite into the database
|
||||
async fn insert_invite(&self, invite: &Invite) -> Result<()> {
|
||||
let mut invites = self.channel_invites.lock().await;
|
||||
if invites.contains_key(invite.code()) {
|
||||
Err(create_database_error!("insert", "invite"))
|
||||
} else {
|
||||
invites.insert(invite.code().to_string(), invite.clone());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetch an invite by the code
|
||||
async fn fetch_invite(&self, code: &str) -> Result<Invite> {
|
||||
let invites = self.channel_invites.lock().await;
|
||||
invites
|
||||
.get(code)
|
||||
.cloned()
|
||||
.ok_or_else(|| create_error!(NotFound))
|
||||
}
|
||||
|
||||
/// Fetch all invites for a server
|
||||
async fn fetch_invites_for_server(&self, server_id: &str) -> Result<Vec<Invite>> {
|
||||
let invites = self.channel_invites.lock().await;
|
||||
Ok(invites
|
||||
.values()
|
||||
.filter(|invite| match invite {
|
||||
Invite::Server { server, .. } => server == server_id,
|
||||
_ => false,
|
||||
})
|
||||
.cloned()
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Delete an invite by its code
|
||||
async fn delete_invite(&self, code: &str) -> Result<()> {
|
||||
let mut invites = self.channel_invites.lock().await;
|
||||
if invites.remove(code).is_some() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(create_error!(NotFound))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
mod model;
|
||||
mod ops;
|
||||
|
||||
pub use model::*;
|
||||
pub use ops::*;
|
||||
@@ -0,0 +1,24 @@
|
||||
auto_derived!(
|
||||
/// Channel Unread
|
||||
pub struct ChannelUnread {
|
||||
/// Composite key pointing to a user's view of a channel
|
||||
#[serde(rename = "_id")]
|
||||
pub id: ChannelCompositeKey,
|
||||
|
||||
/// Id of the last message read in this channel by a user
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub last_id: Option<String>,
|
||||
/// Array of message ids that mention the user
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub mentions: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
/// Composite primary key consisting of channel and user id
|
||||
#[derive(Hash)]
|
||||
pub struct ChannelCompositeKey {
|
||||
/// Channel Id
|
||||
pub channel: String,
|
||||
/// User Id
|
||||
pub user: String,
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,31 @@
|
||||
use revolt_result::Result;
|
||||
|
||||
use crate::ChannelUnread;
|
||||
|
||||
mod mongodb;
|
||||
mod reference;
|
||||
|
||||
#[async_trait]
|
||||
pub trait AbstractChannelUnreads: Sync + Send {
|
||||
/// Acknowledge a message.
|
||||
async fn acknowledge_message(
|
||||
&self,
|
||||
channel_id: &str,
|
||||
user_id: &str,
|
||||
message_id: &str,
|
||||
) -> Result<()>;
|
||||
|
||||
/// Acknowledge many channels.
|
||||
async fn acknowledge_channels(&self, user_id: &str, channel_ids: &[String]) -> Result<()>;
|
||||
|
||||
/// Add a mention.
|
||||
async fn add_mention_to_unread<'a>(
|
||||
&self,
|
||||
channel_id: &str,
|
||||
user_id: &str,
|
||||
message_ids: &[String],
|
||||
) -> Result<()>;
|
||||
|
||||
/// Fetch all channel unreads for a user.
|
||||
async fn fetch_unreads(&self, user_id: &str) -> Result<Vec<ChannelUnread>>;
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
use bson::Document;
|
||||
use mongodb::options::UpdateOptions;
|
||||
use revolt_result::Result;
|
||||
use ulid::Ulid;
|
||||
|
||||
use crate::ChannelUnread;
|
||||
use crate::MongoDb;
|
||||
|
||||
use super::AbstractChannelUnreads;
|
||||
|
||||
static COL: &str = "channel_unreads";
|
||||
|
||||
#[async_trait]
|
||||
impl AbstractChannelUnreads for MongoDb {
|
||||
/// Acknowledge a message.
|
||||
async fn acknowledge_message(
|
||||
&self,
|
||||
channel_id: &str,
|
||||
user_id: &str,
|
||||
message_id: &str,
|
||||
) -> Result<()> {
|
||||
self.col::<Document>(COL)
|
||||
.update_one(
|
||||
doc! {
|
||||
"_id.channel": channel_id,
|
||||
"_id.user": user_id,
|
||||
},
|
||||
doc! {
|
||||
"$unset": {
|
||||
"mentions": 1_i32
|
||||
},
|
||||
"$set": {
|
||||
"last_id": message_id
|
||||
}
|
||||
},
|
||||
UpdateOptions::builder().upsert(true).build(),
|
||||
)
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|_| create_database_error!("update_one", COL))
|
||||
}
|
||||
|
||||
/// Acknowledge many channels.
|
||||
async fn acknowledge_channels(&self, user_id: &str, channel_ids: &[String]) -> Result<()> {
|
||||
let current_time = Ulid::new().to_string();
|
||||
|
||||
self.col::<Document>(COL)
|
||||
.delete_many(
|
||||
doc! {
|
||||
"_id.channel": {
|
||||
"$in": channel_ids
|
||||
},
|
||||
"_id.user": user_id
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map_err(|_| create_database_error!("delete_many", COL))?;
|
||||
|
||||
self.col::<Document>(COL)
|
||||
.insert_many(
|
||||
channel_ids
|
||||
.iter()
|
||||
.map(|channel_id| {
|
||||
doc! {
|
||||
"_id": {
|
||||
"channel": channel_id,
|
||||
"user": user_id
|
||||
},
|
||||
"last_id": ¤t_time
|
||||
}
|
||||
})
|
||||
.collect::<Vec<Document>>(),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|_| create_database_error!("update_many", COL))
|
||||
}
|
||||
|
||||
/// Add a mention.
|
||||
async fn add_mention_to_unread<'a>(
|
||||
&self,
|
||||
channel_id: &str,
|
||||
user_id: &str,
|
||||
message_ids: &[String],
|
||||
) -> Result<()> {
|
||||
self.col::<Document>(COL)
|
||||
.update_one(
|
||||
doc! {
|
||||
"_id.channel": channel_id,
|
||||
"_id.user": user_id,
|
||||
},
|
||||
doc! {
|
||||
"$push": {
|
||||
"mentions": {
|
||||
"$each": message_ids
|
||||
}
|
||||
}
|
||||
},
|
||||
UpdateOptions::builder().upsert(true).build(),
|
||||
)
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|_| create_database_error!("update_one", COL))
|
||||
}
|
||||
|
||||
/// Fetch all channel unreads for a user.
|
||||
async fn fetch_unreads(&self, user_id: &str) -> Result<Vec<ChannelUnread>> {
|
||||
query!(
|
||||
self,
|
||||
find,
|
||||
COL,
|
||||
doc! {
|
||||
"_id.user": user_id
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
use revolt_result::Result;
|
||||
use ulid::Ulid;
|
||||
|
||||
use crate::{ChannelCompositeKey, ChannelUnread, ReferenceDb};
|
||||
|
||||
use super::AbstractChannelUnreads;
|
||||
|
||||
#[async_trait]
|
||||
impl AbstractChannelUnreads for ReferenceDb {
|
||||
/// Acknowledge a message.
|
||||
async fn acknowledge_message(
|
||||
&self,
|
||||
channel_id: &str,
|
||||
user_id: &str,
|
||||
message_id: &str,
|
||||
) -> Result<()> {
|
||||
let mut unreads = self.channel_unreads.lock().await;
|
||||
let key = ChannelCompositeKey {
|
||||
channel: channel_id.to_string(),
|
||||
user: user_id.to_string(),
|
||||
};
|
||||
|
||||
if let Some(mut unread) = unreads.get_mut(&key) {
|
||||
unread.mentions = None;
|
||||
unread.last_id.replace(message_id.to_string());
|
||||
} else {
|
||||
unreads.insert(
|
||||
key.clone(),
|
||||
ChannelUnread {
|
||||
id: key,
|
||||
last_id: Some(message_id.to_string()),
|
||||
mentions: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Acknowledge many channels.
|
||||
async fn acknowledge_channels(&self, user_id: &str, channel_ids: &[String]) -> Result<()> {
|
||||
let current_time = Ulid::new().to_string();
|
||||
for channel_id in channel_ids {
|
||||
self.acknowledge_message(channel_id, user_id, ¤t_time)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add a mention.
|
||||
async fn add_mention_to_unread<'a>(
|
||||
&self,
|
||||
channel_id: &str,
|
||||
user_id: &str,
|
||||
message_ids: &[String],
|
||||
) -> Result<()> {
|
||||
let mut unreads = self.channel_unreads.lock().await;
|
||||
let key = ChannelCompositeKey {
|
||||
channel: channel_id.to_string(),
|
||||
user: user_id.to_string(),
|
||||
};
|
||||
|
||||
if let Some(unread) = unreads.get_mut(&key) {
|
||||
unread.mentions.replace(message_ids.to_vec());
|
||||
} else {
|
||||
unreads.insert(
|
||||
key.clone(),
|
||||
ChannelUnread {
|
||||
id: key,
|
||||
last_id: None,
|
||||
mentions: Some(message_ids.to_vec()),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Fetch all channel unreads for a user.
|
||||
async fn fetch_unreads(&self, user_id: &str) -> Result<Vec<ChannelUnread>> {
|
||||
let unreads = self.channel_unreads.lock().await;
|
||||
Ok(unreads
|
||||
.values()
|
||||
.filter(|unread| unread.id.user == user_id)
|
||||
.cloned()
|
||||
.collect())
|
||||
}
|
||||
}
|
||||
@@ -169,6 +169,19 @@ impl Channel {
|
||||
pub async fn create(&self, db: &Database) -> Result<()> {
|
||||
db.insert_channel(self).await?;
|
||||
|
||||
let event = EventV1::ChannelCreate(self.clone().into());
|
||||
match self {
|
||||
Self::SavedMessages { user, .. } => event.private(user.clone()).await,
|
||||
Self::DirectMessage { recipients, .. } | Self::Group { recipients, .. } => {
|
||||
for recipient in recipients {
|
||||
event.clone().private(recipient.clone()).await;
|
||||
}
|
||||
}
|
||||
Self::TextChannel { server, .. } | Self::VoiceChannel { server, .. } => {
|
||||
event.p(server.clone()).await;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -293,7 +306,7 @@ impl Channel {
|
||||
}
|
||||
|
||||
/// Update channel data
|
||||
pub async fn update<'a>(
|
||||
pub async fn update(
|
||||
&mut self,
|
||||
db: &Database,
|
||||
partial: PartialChannel,
|
||||
@@ -305,8 +318,19 @@ impl Channel {
|
||||
|
||||
self.apply_options(partial.clone());
|
||||
|
||||
db.update_channel(&self.id(), &partial, remove.clone())
|
||||
.await?;
|
||||
let id = self.id().to_string();
|
||||
db.update_channel(&id, &partial, remove.clone()).await?;
|
||||
|
||||
EventV1::ChannelUpdate {
|
||||
id: id.clone(),
|
||||
data: partial.into(),
|
||||
clear: remove.into_iter().map(|v| v.into()).collect(),
|
||||
}
|
||||
.p(match self {
|
||||
Self::TextChannel { server, .. } | Self::VoiceChannel { server, .. } => server.clone(),
|
||||
_ => id,
|
||||
})
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -514,6 +538,8 @@ impl Channel {
|
||||
|
||||
/// Delete a channel
|
||||
pub async fn delete(&self, db: &Database) -> Result<()> {
|
||||
let id = self.id().to_string();
|
||||
EventV1::ChannelDelete { id: id.clone() }.p(id).await;
|
||||
db.delete_channel(self).await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
mod model;
|
||||
mod ops;
|
||||
|
||||
pub use model::*;
|
||||
pub use ops::*;
|
||||
@@ -0,0 +1,87 @@
|
||||
use std::collections::HashSet;
|
||||
use std::str::FromStr;
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use revolt_result::Result;
|
||||
use ulid::Ulid;
|
||||
|
||||
use crate::events::client::EventV1;
|
||||
use crate::Database;
|
||||
|
||||
static PERMISSIBLE_EMOJIS: Lazy<HashSet<String>> = Lazy::new(|| {
|
||||
include_str!("unicode_emoji.txt")
|
||||
.split('\n')
|
||||
.map(|x| x.into())
|
||||
.collect()
|
||||
});
|
||||
|
||||
auto_derived!(
|
||||
/// Emoji
|
||||
pub struct Emoji {
|
||||
/// Unique Id
|
||||
#[serde(rename = "_id")]
|
||||
pub id: String,
|
||||
/// What owns this emoji
|
||||
pub parent: EmojiParent,
|
||||
/// Uploader user id
|
||||
pub creator_id: String,
|
||||
/// Emoji name
|
||||
pub name: String,
|
||||
/// Whether the emoji is animated
|
||||
#[serde(skip_serializing_if = "crate::if_false", default)]
|
||||
pub animated: bool,
|
||||
/// Whether the emoji is marked as nsfw
|
||||
#[serde(skip_serializing_if = "crate::if_false", default)]
|
||||
pub nsfw: bool,
|
||||
}
|
||||
|
||||
/// Parent Id of the emoji
|
||||
#[serde(tag = "type")]
|
||||
pub enum EmojiParent {
|
||||
Server { id: String },
|
||||
Detached,
|
||||
}
|
||||
);
|
||||
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
impl Emoji {
|
||||
/// Get parent id
|
||||
fn parent(&self) -> &str {
|
||||
match &self.parent {
|
||||
EmojiParent::Server { id } => id,
|
||||
EmojiParent::Detached => "",
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an emoji
|
||||
pub async fn create(&self, db: &Database) -> Result<()> {
|
||||
db.insert_emoji(self).await?;
|
||||
|
||||
EventV1::EmojiCreate(self.clone().into())
|
||||
.p(self.parent().to_string())
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Delete an emoji
|
||||
pub async fn delete(self, db: &Database) -> Result<()> {
|
||||
EventV1::EmojiDelete {
|
||||
id: self.id.to_string(),
|
||||
}
|
||||
.p(self.parent().to_string())
|
||||
.await;
|
||||
|
||||
db.detach_emoji(&self).await
|
||||
}
|
||||
|
||||
/// Check whether we can use a given emoji
|
||||
pub async fn can_use(db: &Database, emoji: &str) -> Result<bool> {
|
||||
if Ulid::from_str(emoji).is_ok() {
|
||||
db.fetch_emoji(emoji).await?;
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(PERMISSIBLE_EMOJIS.contains(emoji))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
use revolt_result::Result;
|
||||
|
||||
use crate::Emoji;
|
||||
|
||||
mod mongodb;
|
||||
mod reference;
|
||||
|
||||
#[async_trait]
|
||||
pub trait AbstractEmojis: Sync + Send {
|
||||
/// Insert emoji into database.
|
||||
async fn insert_emoji(&self, emoji: &Emoji) -> Result<()>;
|
||||
|
||||
/// Fetch an emoji by its id
|
||||
async fn fetch_emoji(&self, id: &str) -> Result<Emoji>;
|
||||
|
||||
/// Fetch emoji by their parent id
|
||||
async fn fetch_emoji_by_parent_id(&self, parent_id: &str) -> Result<Vec<Emoji>>;
|
||||
|
||||
/// Fetch emoji by their parent ids
|
||||
async fn fetch_emoji_by_parent_ids(&self, parent_ids: &[String]) -> Result<Vec<Emoji>>;
|
||||
|
||||
/// Detach an emoji by its id
|
||||
async fn detach_emoji(&self, emoji: &Emoji) -> Result<()>;
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
use bson::Document;
|
||||
use revolt_result::Result;
|
||||
|
||||
use crate::Emoji;
|
||||
use crate::MongoDb;
|
||||
|
||||
use super::AbstractEmojis;
|
||||
|
||||
static COL: &str = "emojis";
|
||||
|
||||
#[async_trait]
|
||||
impl AbstractEmojis for MongoDb {
|
||||
/// Insert emoji into database.
|
||||
async fn insert_emoji(&self, emoji: &Emoji) -> Result<()> {
|
||||
query!(self, insert_one, COL, &emoji).map(|_| ())
|
||||
}
|
||||
|
||||
/// Fetch an emoji by its id
|
||||
async fn fetch_emoji(&self, id: &str) -> Result<Emoji> {
|
||||
query!(self, find_one_by_id, COL, id)?.ok_or_else(|| create_error!(NotFound))
|
||||
}
|
||||
|
||||
/// Fetch emoji by their parent id
|
||||
async fn fetch_emoji_by_parent_id(&self, parent_id: &str) -> Result<Vec<Emoji>> {
|
||||
query!(
|
||||
self,
|
||||
find_one,
|
||||
COL,
|
||||
doc! {
|
||||
"parent.id": parent_id
|
||||
}
|
||||
)?
|
||||
.ok_or_else(|| create_error!(NotFound))
|
||||
}
|
||||
|
||||
/// Fetch emoji by their parent ids
|
||||
async fn fetch_emoji_by_parent_ids(&self, parent_ids: &[String]) -> Result<Vec<Emoji>> {
|
||||
query!(
|
||||
self,
|
||||
find,
|
||||
COL,
|
||||
doc! {
|
||||
"parent.id": {
|
||||
"$in": parent_ids
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/// Detach an emoji by its id
|
||||
async fn detach_emoji(&self, emoji: &Emoji) -> Result<()> {
|
||||
self.col::<Document>(COL)
|
||||
.update_one(
|
||||
doc! {
|
||||
"_id": &emoji.id
|
||||
},
|
||||
doc! {
|
||||
"$set": {
|
||||
"parent": {
|
||||
"type": "Detached"
|
||||
}
|
||||
}
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|_| create_database_error!("update_one", COL))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
use revolt_result::Result;
|
||||
|
||||
use crate::Emoji;
|
||||
use crate::EmojiParent;
|
||||
use crate::ReferenceDb;
|
||||
|
||||
use super::AbstractEmojis;
|
||||
|
||||
#[async_trait]
|
||||
impl AbstractEmojis for ReferenceDb {
|
||||
/// Insert emoji into database.
|
||||
async fn insert_emoji(&self, emoji: &Emoji) -> Result<()> {
|
||||
let mut emojis = self.emojis.lock().await;
|
||||
if emojis.contains_key(&emoji.id) {
|
||||
Err(create_database_error!("insert", "emoji"))
|
||||
} else {
|
||||
emojis.insert(emoji.id.to_string(), emoji.clone());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetch an emoji by its id
|
||||
async fn fetch_emoji(&self, id: &str) -> Result<Emoji> {
|
||||
let emojis = self.emojis.lock().await;
|
||||
emojis
|
||||
.get(id)
|
||||
.cloned()
|
||||
.ok_or_else(|| create_error!(NotFound))
|
||||
}
|
||||
|
||||
/// Fetch emoji by their parent id
|
||||
async fn fetch_emoji_by_parent_id(&self, parent_id: &str) -> Result<Vec<Emoji>> {
|
||||
let emojis = self.emojis.lock().await;
|
||||
Ok(emojis
|
||||
.values()
|
||||
.filter(|emoji| match &emoji.parent {
|
||||
EmojiParent::Server { id } => id == parent_id,
|
||||
_ => false,
|
||||
})
|
||||
.cloned()
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Fetch emoji by their parent ids
|
||||
async fn fetch_emoji_by_parent_ids(&self, parent_ids: &[String]) -> Result<Vec<Emoji>> {
|
||||
let emojis = self.emojis.lock().await;
|
||||
Ok(emojis
|
||||
.values()
|
||||
.filter(|emoji| match &emoji.parent {
|
||||
EmojiParent::Server { id } => parent_ids.contains(id),
|
||||
_ => false,
|
||||
})
|
||||
.cloned()
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Detach an emoji by its id
|
||||
async fn detach_emoji(&self, emoji: &Emoji) -> Result<()> {
|
||||
let mut emojis = self.emojis.lock().await;
|
||||
if let Some(bot) = emojis.get_mut(&emoji.id) {
|
||||
bot.parent = EmojiParent::Detached;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(create_error!(NotFound))
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,5 @@
|
||||
mod model;
|
||||
mod ops;
|
||||
|
||||
pub use model::*;
|
||||
pub use ops::*;
|
||||
@@ -0,0 +1,212 @@
|
||||
use indexmap::{IndexMap, IndexSet};
|
||||
use iso8601_timestamp::Timestamp;
|
||||
use revolt_models::v0::{Embed, MessageSort, MessageWebhook};
|
||||
|
||||
use crate::File;
|
||||
|
||||
auto_derived_partial!(
|
||||
/// Message
|
||||
pub struct Message {
|
||||
/// Unique Id
|
||||
#[serde(rename = "_id")]
|
||||
pub id: String,
|
||||
/// Unique value generated by client sending this message
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub nonce: Option<String>,
|
||||
/// Id of the channel this message was sent in
|
||||
pub channel: String,
|
||||
/// Id of the user or webhook that sent this message
|
||||
pub author: String,
|
||||
/// The webhook that sent this message
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub webhook: Option<MessageWebhook>,
|
||||
/// Message content
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub content: Option<String>,
|
||||
/// System message
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub system: Option<SystemMessage>,
|
||||
/// Array of attachments
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub attachments: Option<Vec<File>>,
|
||||
/// Time at which this message was last edited
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub edited: Option<Timestamp>,
|
||||
/// Attached embeds to this message
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub embeds: Option<Vec<Embed>>,
|
||||
/// Array of user ids mentioned in this message
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub mentions: Option<Vec<String>>,
|
||||
/// Array of message ids this message is replying to
|
||||
#[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 = "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,
|
||||
/// Name and / or avatar overrides for this message
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub masquerade: Option<Masquerade>,
|
||||
},
|
||||
"PartialMessage"
|
||||
);
|
||||
|
||||
auto_derived!(
|
||||
/// System Event
|
||||
#[serde(tag = "type")]
|
||||
pub enum SystemMessage {
|
||||
#[serde(rename = "text")]
|
||||
Text { content: String },
|
||||
#[serde(rename = "user_added")]
|
||||
UserAdded { id: String, by: String },
|
||||
#[serde(rename = "user_remove")]
|
||||
UserRemove { id: String, by: String },
|
||||
#[serde(rename = "user_joined")]
|
||||
UserJoined { id: String },
|
||||
#[serde(rename = "user_left")]
|
||||
UserLeft { id: String },
|
||||
#[serde(rename = "user_kicked")]
|
||||
UserKicked { id: String },
|
||||
#[serde(rename = "user_banned")]
|
||||
UserBanned { id: String },
|
||||
#[serde(rename = "channel_renamed")]
|
||||
ChannelRenamed { name: String, by: String },
|
||||
#[serde(rename = "channel_description_changed")]
|
||||
ChannelDescriptionChanged { by: String },
|
||||
#[serde(rename = "channel_icon_changed")]
|
||||
ChannelIconChanged { by: String },
|
||||
#[serde(rename = "channel_ownership_changed")]
|
||||
ChannelOwnershipChanged { from: String, to: String },
|
||||
}
|
||||
|
||||
/// Name and / or avatar override information
|
||||
pub struct Masquerade {
|
||||
/// Replace the display name shown on this message
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub name: Option<String>,
|
||||
/// Replace the avatar shown on this message (URL to image file)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
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")]
|
||||
pub colour: Option<String>,
|
||||
}
|
||||
|
||||
/// Information to guide interactions on this message
|
||||
#[derive(Default)]
|
||||
pub struct Interactions {
|
||||
/// Reactions which should always appear and be distinct
|
||||
#[serde(skip_serializing_if = "Option::is_none", default)]
|
||||
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
|
||||
#[serde(skip_serializing_if = "crate::if_false", default)]
|
||||
pub restrict_reactions: bool,
|
||||
}
|
||||
|
||||
/// Appended Information
|
||||
pub struct AppendMessage {
|
||||
/// Additional embeds to include in this message
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub embeds: Option<Vec<Embed>>,
|
||||
}
|
||||
|
||||
/// Message Time Period
|
||||
///
|
||||
/// Filter and sort messages by time
|
||||
#[serde(untagged)]
|
||||
pub enum MessageTimePeriod {
|
||||
Relative {
|
||||
/// Message id to search around
|
||||
///
|
||||
/// Specifying 'nearby' ignores 'before', 'after' and 'sort'.
|
||||
/// It will also take half of limit rounded as the limits to each side.
|
||||
/// It also fetches the message ID specified.
|
||||
nearby: String,
|
||||
},
|
||||
Absolute {
|
||||
/// Message id before which messages should be fetched
|
||||
before: Option<String>,
|
||||
/// Message id after which messages should be fetched
|
||||
after: Option<String>,
|
||||
/// Message sort direction
|
||||
sort: Option<MessageSort>,
|
||||
},
|
||||
}
|
||||
|
||||
/// Message Filter
|
||||
pub struct MessageFilter {
|
||||
/// Parent channel ID
|
||||
pub channel: Option<String>,
|
||||
/// Message author ID
|
||||
pub author: Option<String>,
|
||||
/// Search query
|
||||
pub query: Option<String>,
|
||||
}
|
||||
|
||||
/// Message Query
|
||||
pub struct MessageQuery {
|
||||
/// Maximum number of messages to fetch
|
||||
///
|
||||
/// For fetching nearby messages, this is \`(limit + 1)\`.
|
||||
pub limit: Option<i64>,
|
||||
/// Filter to apply
|
||||
#[serde(flatten)]
|
||||
pub filter: MessageFilter,
|
||||
/// Time period to fetch
|
||||
#[serde(flatten)]
|
||||
pub time_period: MessageTimePeriod,
|
||||
}
|
||||
);
|
||||
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
impl Message {}
|
||||
|
||||
impl Interactions {
|
||||
/// Validate interactions info is correct
|
||||
/* pub async fn validate(
|
||||
&self,
|
||||
db: &Database,
|
||||
permissions: &mut PermissionCalculator<'_>,
|
||||
) -> Result<()> {
|
||||
if let Some(reactions) = &self.reactions {
|
||||
permissions.throw_permission(db, Permission::React).await?;
|
||||
|
||||
if reactions.len() > 20 {
|
||||
return Err(Error::InvalidOperation);
|
||||
}
|
||||
|
||||
for reaction in reactions {
|
||||
if !Emoji::can_use(db, reaction).await? {
|
||||
return Err(Error::InvalidOperation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}*/
|
||||
|
||||
/// Check if we can use a given emoji to react
|
||||
pub fn can_use(&self, emoji: &str) -> bool {
|
||||
if self.restrict_reactions {
|
||||
if let Some(reactions) = &self.reactions {
|
||||
reactions.contains(emoji)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if default initialisation of fields
|
||||
pub fn is_default(&self) -> bool {
|
||||
!self.restrict_reactions && self.reactions.is_none()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
use revolt_result::Result;
|
||||
|
||||
use crate::{AppendMessage, Message, MessageQuery, PartialMessage};
|
||||
|
||||
// mod mongodb;
|
||||
// mod reference;
|
||||
|
||||
#[async_trait]
|
||||
pub trait AbstractMessages: Sync + Send {
|
||||
/// Insert a new message into the database
|
||||
async fn insert_message(&self, message: &Message) -> Result<()>;
|
||||
|
||||
/// Fetch a message by its id
|
||||
async fn fetch_message(&self, id: &str) -> Result<Message>;
|
||||
|
||||
/// Fetch multiple messages by given query
|
||||
async fn fetch_messages(&self, query: MessageQuery) -> Result<Vec<Message>>;
|
||||
|
||||
/// Update a given message with new information
|
||||
async fn update_message(&self, id: &str, message: &PartialMessage) -> Result<()>;
|
||||
|
||||
/// Append information to a given message
|
||||
async fn append_message(&self, id: &str, append: &AppendMessage) -> Result<()>;
|
||||
|
||||
/// Add a new reaction to a message
|
||||
async fn add_reaction(&self, id: &str, emoji: &str, user: &str) -> Result<()>;
|
||||
|
||||
/// Remove a reaction from a message
|
||||
async fn remove_reaction(&self, id: &str, emoji: &str, user: &str) -> Result<()>;
|
||||
|
||||
/// Remove reaction from a message
|
||||
async fn clear_reaction(&self, id: &str, emoji: &str) -> Result<()>;
|
||||
|
||||
/// Delete a message from the database by its id
|
||||
async fn delete_message(&self, id: &str) -> Result<()>;
|
||||
|
||||
/// Delete messages from a channel by their ids and corresponding channel id
|
||||
async fn delete_messages(&self, channel: &str, ids: Vec<String>) -> Result<()>;
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
use bson::Document;
|
||||
use revolt_result::Result;
|
||||
|
||||
use crate::Emoji;
|
||||
use crate::MongoDb;
|
||||
|
||||
use super::AbstractEmojis;
|
||||
|
||||
static COL: &str = "emojis";
|
||||
|
||||
#[async_trait]
|
||||
impl AbstractEmojis for MongoDb {
|
||||
/// Insert emoji into database.
|
||||
async fn insert_emoji(&self, emoji: &Emoji) -> Result<()> {
|
||||
query!(self, insert_one, COL, &emoji).map(|_| ())
|
||||
}
|
||||
|
||||
/// Fetch an emoji by its id
|
||||
async fn fetch_emoji(&self, id: &str) -> Result<Emoji> {
|
||||
query!(self, find_one_by_id, COL, id)?.ok_or_else(|| create_error!(NotFound))
|
||||
}
|
||||
|
||||
/// Fetch emoji by their parent id
|
||||
async fn fetch_emoji_by_parent_id(&self, parent_id: &str) -> Result<Vec<Emoji>> {
|
||||
query!(
|
||||
self,
|
||||
find_one,
|
||||
COL,
|
||||
doc! {
|
||||
"parent.id": parent_id
|
||||
}
|
||||
)?
|
||||
.ok_or_else(|| create_error!(NotFound))
|
||||
}
|
||||
|
||||
/// Fetch emoji by their parent ids
|
||||
async fn fetch_emoji_by_parent_ids(&self, parent_ids: &[String]) -> Result<Vec<Emoji>> {
|
||||
query!(
|
||||
self,
|
||||
find,
|
||||
COL,
|
||||
doc! {
|
||||
"parent.id": {
|
||||
"$in": parent_ids
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/// Detach an emoji by its id
|
||||
async fn detach_emoji(&self, emoji: &Emoji) -> Result<()> {
|
||||
self.col::<Document>(COL)
|
||||
.update_one(
|
||||
doc! {
|
||||
"_id": &emoji.id
|
||||
},
|
||||
doc! {
|
||||
"$set": {
|
||||
"parent": {
|
||||
"type": "Detached"
|
||||
}
|
||||
}
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|_| create_database_error!("update_one", COL))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
use revolt_result::Result;
|
||||
|
||||
use crate::Emoji;
|
||||
use crate::EmojiParent;
|
||||
use crate::ReferenceDb;
|
||||
|
||||
use super::AbstractEmojis;
|
||||
|
||||
#[async_trait]
|
||||
impl AbstractEmojis for ReferenceDb {
|
||||
/// Insert emoji into database.
|
||||
async fn insert_emoji(&self, emoji: &Emoji) -> Result<()> {
|
||||
let mut emojis = self.emojis.lock().await;
|
||||
if emojis.contains_key(&emoji.id) {
|
||||
Err(create_database_error!("insert", "emoji"))
|
||||
} else {
|
||||
emojis.insert(emoji.id.to_string(), emoji.clone());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetch an emoji by its id
|
||||
async fn fetch_emoji(&self, id: &str) -> Result<Emoji> {
|
||||
let emojis = self.emojis.lock().await;
|
||||
emojis
|
||||
.get(id)
|
||||
.cloned()
|
||||
.ok_or_else(|| create_error!(NotFound))
|
||||
}
|
||||
|
||||
/// Fetch emoji by their parent id
|
||||
async fn fetch_emoji_by_parent_id(&self, parent_id: &str) -> Result<Vec<Emoji>> {
|
||||
let emojis = self.emojis.lock().await;
|
||||
Ok(emojis
|
||||
.values()
|
||||
.filter(|emoji| match &emoji.parent {
|
||||
EmojiParent::Server { id } => id == parent_id,
|
||||
_ => false,
|
||||
})
|
||||
.cloned()
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Fetch emoji by their parent ids
|
||||
async fn fetch_emoji_by_parent_ids(&self, parent_ids: &[String]) -> Result<Vec<Emoji>> {
|
||||
let emojis = self.emojis.lock().await;
|
||||
Ok(emojis
|
||||
.values()
|
||||
.filter(|emoji| match &emoji.parent {
|
||||
EmojiParent::Server { id } => parent_ids.contains(id),
|
||||
_ => false,
|
||||
})
|
||||
.cloned()
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Detach an emoji by its id
|
||||
async fn detach_emoji(&self, emoji: &Emoji) -> Result<()> {
|
||||
let mut emojis = self.emojis.lock().await;
|
||||
if let Some(bot) = emojis.get_mut(&emoji.id) {
|
||||
bot.parent = EmojiParent::Detached;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(create_error!(NotFound))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,14 @@
|
||||
mod admin_migrations;
|
||||
mod bots;
|
||||
mod channel_invites;
|
||||
mod channel_unreads;
|
||||
mod channel_webhooks;
|
||||
mod channels;
|
||||
mod emojis;
|
||||
mod files;
|
||||
mod messages;
|
||||
mod ratelimit_events;
|
||||
mod safety_strikes;
|
||||
mod server_bans;
|
||||
mod server_members;
|
||||
mod servers;
|
||||
mod user_settings;
|
||||
@@ -12,11 +16,15 @@ mod users;
|
||||
|
||||
pub use admin_migrations::*;
|
||||
pub use bots::*;
|
||||
pub use channel_invites::*;
|
||||
pub use channel_unreads::*;
|
||||
pub use channel_webhooks::*;
|
||||
pub use channels::*;
|
||||
pub use emojis::*;
|
||||
pub use files::*;
|
||||
pub use messages::*;
|
||||
pub use ratelimit_events::*;
|
||||
pub use safety_strikes::*;
|
||||
pub use server_bans::*;
|
||||
pub use server_members::*;
|
||||
pub use servers::*;
|
||||
pub use user_settings::*;
|
||||
@@ -30,10 +38,13 @@ pub trait AbstractDatabase:
|
||||
+ admin_migrations::AbstractMigrations
|
||||
+ bots::AbstractBots
|
||||
+ channels::AbstractChannels
|
||||
+ channel_invites::AbstractChannelInvites
|
||||
+ channel_unreads::AbstractChannelUnreads
|
||||
+ channel_webhooks::AbstractWebhooks
|
||||
+ emojis::AbstractEmojis
|
||||
+ files::AbstractAttachments
|
||||
+ ratelimit_events::AbstractRatelimitEvents
|
||||
+ safety_strikes::AbstractAccountStrikes
|
||||
+ server_bans::AbstractServerBans
|
||||
+ server_members::AbstractServerMembers
|
||||
+ servers::AbstractServers
|
||||
+ user_settings::AbstractUserSettings
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
use revolt_result::Result;
|
||||
|
||||
use crate::Database;
|
||||
|
||||
auto_derived_partial!(
|
||||
/// Account Strike
|
||||
pub struct AccountStrike {
|
||||
/// Strike Id
|
||||
#[serde(rename = "_id")]
|
||||
pub id: String,
|
||||
/// Id of reported user
|
||||
pub user_id: String,
|
||||
/// Id of moderator
|
||||
pub moderator_id: String,
|
||||
|
||||
/// Attached reason
|
||||
pub reason: String,
|
||||
},
|
||||
"PartialAccountStrike"
|
||||
);
|
||||
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
impl AccountStrike {
|
||||
pub async fn create(
|
||||
db: &Database,
|
||||
user_id: String,
|
||||
reason: String,
|
||||
moderator_id: String,
|
||||
) -> Result<AccountStrike> {
|
||||
let strike = AccountStrike {
|
||||
id: ulid::Ulid::new().to_string(),
|
||||
user_id,
|
||||
moderator_id,
|
||||
reason,
|
||||
};
|
||||
|
||||
db.insert_account_strike(&strike).await?;
|
||||
Ok(strike)
|
||||
}
|
||||
|
||||
/// Update this strike
|
||||
pub async fn update(&mut self, db: &Database, partial: PartialAccountStrike) -> Result<()> {
|
||||
db.update_account_strike(&self.id, &partial).await?;
|
||||
self.apply_options(partial);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Delete this strike
|
||||
pub async fn delete(&self, db: &Database) -> Result<()> {
|
||||
db.delete_account_strike(&self.id).await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::{AccountStrike, PartialAccountStrike};
|
||||
|
||||
#[async_std::test]
|
||||
async fn crud() {
|
||||
database_test!(|db| async move {
|
||||
let user_id = "user";
|
||||
|
||||
let strike = AccountStrike::create(
|
||||
&db,
|
||||
user_id.to_string(),
|
||||
"reason 1".to_string(),
|
||||
"moderator_id".to_string(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut updated_strike = strike.clone();
|
||||
updated_strike
|
||||
.update(
|
||||
&db,
|
||||
PartialAccountStrike {
|
||||
reason: Some("new reason".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let strike2 = AccountStrike::create(
|
||||
&db,
|
||||
user_id.to_string(),
|
||||
"reason 2".to_string(),
|
||||
"moderator_id".to_string(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let strikes = db.fetch_account_strikes_by_user(user_id).await.unwrap();
|
||||
|
||||
let ids = strikes
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|strike| strike.id)
|
||||
.collect::<HashSet<String>>();
|
||||
|
||||
assert!(ids.contains(&strike.id));
|
||||
assert!(ids.contains(&strike2.id));
|
||||
|
||||
let fetched_strike = strikes
|
||||
.into_iter()
|
||||
.find(|entry| entry.id == strike.id)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(fetched_strike, updated_strike);
|
||||
assert_ne!(fetched_strike, strike);
|
||||
|
||||
strike.delete(&db).await.unwrap();
|
||||
assert_eq!(
|
||||
1,
|
||||
db.fetch_account_strikes_by_user(user_id)
|
||||
.await
|
||||
.unwrap()
|
||||
.len()
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
use revolt_result::Result;
|
||||
|
||||
use crate::{AccountStrike, PartialAccountStrike};
|
||||
|
||||
mod mongodb;
|
||||
mod reference;
|
||||
|
||||
#[async_trait]
|
||||
pub trait AbstractAccountStrikes: Sync + Send {
|
||||
/// Insert new strike into the database
|
||||
async fn insert_account_strike(&self, strike: &AccountStrike) -> Result<()>;
|
||||
|
||||
/// Fetch strike by id
|
||||
async fn fetch_account_strike(&self, id: &str) -> Result<AccountStrike>;
|
||||
|
||||
/// Fetch strikes by user id
|
||||
async fn fetch_account_strikes_by_user(&self, user_id: &str) -> Result<Vec<AccountStrike>>;
|
||||
|
||||
/// Update strike with new information
|
||||
async fn update_account_strike(&self, id: &str, partial: &PartialAccountStrike) -> Result<()>;
|
||||
|
||||
/// Delete a strike from the database
|
||||
async fn delete_account_strike(&self, id: &str) -> Result<()>;
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
use futures::StreamExt;
|
||||
use revolt_result::Result;
|
||||
|
||||
use crate::MongoDb;
|
||||
use crate::{AccountStrike, PartialAccountStrike};
|
||||
|
||||
use super::AbstractAccountStrikes;
|
||||
|
||||
static COL: &str = "safety_strikes";
|
||||
|
||||
#[async_trait]
|
||||
impl AbstractAccountStrikes for MongoDb {
|
||||
/// Insert new strike into the database
|
||||
async fn insert_account_strike(&self, strike: &AccountStrike) -> Result<()> {
|
||||
query!(self, insert_one, COL, &strike).map(|_| ())
|
||||
}
|
||||
|
||||
/// Fetch strike by id
|
||||
async fn fetch_account_strike(&self, id: &str) -> Result<AccountStrike> {
|
||||
query!(self, find_one_by_id, COL, id)?.ok_or_else(|| create_error!(NotFound))
|
||||
}
|
||||
|
||||
/// Fetch strikes by user id
|
||||
async fn fetch_account_strikes_by_user(&self, user_id: &str) -> Result<Vec<AccountStrike>> {
|
||||
Ok(self
|
||||
.col::<AccountStrike>(COL)
|
||||
.find(
|
||||
doc! {
|
||||
"user_id": user_id,
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map_err(|_| create_database_error!("find", COL))?
|
||||
.filter_map(|s| async {
|
||||
if cfg!(debug_assertions) {
|
||||
Some(s.unwrap())
|
||||
} else {
|
||||
s.ok()
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
.await)
|
||||
}
|
||||
|
||||
/// Update strike with new information
|
||||
async fn update_account_strike(&self, id: &str, partial: &PartialAccountStrike) -> Result<()> {
|
||||
query!(self, update_one_by_id, COL, id, partial, vec![], None).map(|_| ())
|
||||
}
|
||||
|
||||
/// Delete a strike from the database
|
||||
async fn delete_account_strike(&self, id: &str) -> Result<()> {
|
||||
query!(self, delete_one_by_id, COL, id).map(|_| ())
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
use revolt_result::Result;
|
||||
|
||||
use crate::ReferenceDb;
|
||||
use crate::{AccountStrike, PartialAccountStrike};
|
||||
|
||||
use super::AbstractAccountStrikes;
|
||||
|
||||
#[async_trait]
|
||||
impl AbstractAccountStrikes for ReferenceDb {
|
||||
/// Insert new strike into the database
|
||||
async fn insert_account_strike(&self, strike: &AccountStrike) -> Result<()> {
|
||||
let mut strikes = self.account_strikes.lock().await;
|
||||
if strikes.contains_key(&strike.id) {
|
||||
Err(create_database_error!("insert", "strike"))
|
||||
} else {
|
||||
strikes.insert(strike.id.to_string(), strike.clone());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetch strike by id
|
||||
async fn fetch_account_strike(&self, id: &str) -> Result<AccountStrike> {
|
||||
let strikes = self.account_strikes.lock().await;
|
||||
strikes
|
||||
.get(id)
|
||||
.cloned()
|
||||
.ok_or_else(|| create_error!(NotFound))
|
||||
}
|
||||
|
||||
/// Fetch strikes by user id
|
||||
async fn fetch_account_strikes_by_user(&self, user_id: &str) -> Result<Vec<AccountStrike>> {
|
||||
let strikes = self.account_strikes.lock().await;
|
||||
Ok(strikes
|
||||
.values()
|
||||
.filter(|strike| strike.user_id == user_id)
|
||||
.cloned()
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Update strike with new information
|
||||
async fn update_account_strike(&self, id: &str, partial: &PartialAccountStrike) -> Result<()> {
|
||||
let mut strikes = self.account_strikes.lock().await;
|
||||
if let Some(strike) = strikes.get_mut(id) {
|
||||
strike.apply_options(partial.clone());
|
||||
Ok(())
|
||||
} else {
|
||||
Err(create_error!(NotFound))
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete a strike from the database
|
||||
async fn delete_account_strike(&self, id: &str) -> Result<()> {
|
||||
let mut strikes = self.account_strikes.lock().await;
|
||||
if strikes.remove(id).is_some() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(create_error!(NotFound))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
mod model;
|
||||
mod ops;
|
||||
|
||||
pub use model::*;
|
||||
pub use ops::*;
|
||||
@@ -0,0 +1,12 @@
|
||||
use crate::MemberCompositeKey;
|
||||
|
||||
auto_derived!(
|
||||
/// Server Ban
|
||||
pub struct ServerBan {
|
||||
/// Unique member id
|
||||
#[serde(rename = "_id")]
|
||||
pub id: MemberCompositeKey,
|
||||
/// Reason for ban creation
|
||||
pub reason: Option<String>,
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,21 @@
|
||||
use revolt_result::Result;
|
||||
|
||||
use crate::{MemberCompositeKey, ServerBan};
|
||||
|
||||
mod mongodb;
|
||||
mod reference;
|
||||
|
||||
#[async_trait]
|
||||
pub trait AbstractServerBans: Sync + Send {
|
||||
/// Insert new ban into database
|
||||
async fn insert_ban(&self, ban: &ServerBan) -> Result<()>;
|
||||
|
||||
/// Fetch a server ban by server and user id
|
||||
async fn fetch_ban(&self, server_id: &str, user_id: &str) -> Result<ServerBan>;
|
||||
|
||||
/// Fetch all bans in a server
|
||||
async fn fetch_bans(&self, server_id: &str) -> Result<Vec<ServerBan>>;
|
||||
|
||||
/// Delete a ban from the database
|
||||
async fn delete_ban(&self, id: &MemberCompositeKey) -> Result<()>;
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
use revolt_result::Result;
|
||||
|
||||
use crate::MongoDb;
|
||||
use crate::{MemberCompositeKey, ServerBan};
|
||||
|
||||
use super::AbstractServerBans;
|
||||
|
||||
static COL: &str = "server_bans";
|
||||
|
||||
#[async_trait]
|
||||
impl AbstractServerBans for MongoDb {
|
||||
/// Insert new ban into database
|
||||
async fn insert_ban(&self, ban: &ServerBan) -> Result<()> {
|
||||
query!(self, insert_one, COL, &ban).map(|_| ())
|
||||
}
|
||||
|
||||
/// Fetch a server ban by server and user id
|
||||
async fn fetch_ban(&self, server_id: &str, user_id: &str) -> Result<ServerBan> {
|
||||
query!(
|
||||
self,
|
||||
find_one,
|
||||
COL,
|
||||
doc! {
|
||||
"_id.server": server_id,
|
||||
"_id.user": user_id
|
||||
}
|
||||
)?
|
||||
.ok_or_else(|| create_error!(NotFound))
|
||||
}
|
||||
|
||||
/// Fetch all bans in a server
|
||||
async fn fetch_bans(&self, server_id: &str) -> Result<Vec<ServerBan>> {
|
||||
query!(
|
||||
self,
|
||||
find,
|
||||
COL,
|
||||
doc! {
|
||||
"_id.server": server_id
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/// Delete a ban from the database
|
||||
async fn delete_ban(&self, id: &MemberCompositeKey) -> Result<()> {
|
||||
query!(
|
||||
self,
|
||||
delete_one,
|
||||
COL,
|
||||
doc! {
|
||||
"_id.server": &id.server,
|
||||
"_id.user": &id.user
|
||||
}
|
||||
)
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
use revolt_result::Result;
|
||||
|
||||
use crate::ReferenceDb;
|
||||
use crate::{MemberCompositeKey, ServerBan};
|
||||
|
||||
use super::AbstractServerBans;
|
||||
|
||||
#[async_trait]
|
||||
impl AbstractServerBans for ReferenceDb {
|
||||
/// Insert new ban into database
|
||||
async fn insert_ban(&self, ban: &ServerBan) -> Result<()> {
|
||||
let mut server_bans = self.server_bans.lock().await;
|
||||
if server_bans.contains_key(&ban.id) {
|
||||
Err(create_database_error!("insert", "ban"))
|
||||
} else {
|
||||
server_bans.insert(ban.id.clone(), ban.clone());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetch a server ban by server and user id
|
||||
async fn fetch_ban(&self, server_id: &str, user_id: &str) -> Result<ServerBan> {
|
||||
let server_bans = self.server_bans.lock().await;
|
||||
server_bans
|
||||
.get(&MemberCompositeKey {
|
||||
server: server_id.to_string(),
|
||||
user: user_id.to_string(),
|
||||
})
|
||||
.cloned()
|
||||
.ok_or_else(|| create_error!(NotFound))
|
||||
}
|
||||
|
||||
/// Fetch all bans in a server
|
||||
async fn fetch_bans(&self, server_id: &str) -> Result<Vec<ServerBan>> {
|
||||
let server_bans = self.server_bans.lock().await;
|
||||
Ok(server_bans
|
||||
.values()
|
||||
.filter(|member| member.id.server == server_id)
|
||||
.cloned()
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Delete a ban from the database
|
||||
async fn delete_ban(&self, id: &MemberCompositeKey) -> Result<()> {
|
||||
let mut server_bans = self.server_bans.lock().await;
|
||||
if server_bans.remove(id).is_some() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(create_error!(NotFound))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ use revolt_permissions::OverrideField;
|
||||
use revolt_result::Result;
|
||||
use ulid::Ulid;
|
||||
|
||||
use crate::{Database, File};
|
||||
use crate::{events::client::EventV1, Database, File};
|
||||
|
||||
auto_derived_partial!(
|
||||
/// Server
|
||||
@@ -149,24 +149,24 @@ impl Server {
|
||||
|
||||
db.update_server(&self.id, &partial, remove.clone()).await?;
|
||||
|
||||
/* // TODO: EventV1::ServerUpdate {
|
||||
EventV1::ServerUpdate {
|
||||
id: self.id.clone(),
|
||||
data: partial,
|
||||
clear: remove,
|
||||
data: partial.into(),
|
||||
clear: remove.into_iter().map(|v| v.into()).collect(),
|
||||
}
|
||||
.p(self.id.clone())
|
||||
.await; */
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Delete a server
|
||||
pub async fn delete(self, db: &Database) -> Result<()> {
|
||||
/* // TODO: EventV1::ServerDelete {
|
||||
EventV1::ServerDelete {
|
||||
id: self.id.clone(),
|
||||
}
|
||||
.p(self.id.clone())
|
||||
.await; */
|
||||
.await;
|
||||
|
||||
db.delete_server(&self.id).await
|
||||
}
|
||||
@@ -364,14 +364,14 @@ impl Role {
|
||||
let role_id = Ulid::new().to_string();
|
||||
db.insert_role(server_id, &role_id, self).await?;
|
||||
|
||||
/* // TODO: EventV1::ServerRoleUpdate {
|
||||
EventV1::ServerRoleUpdate {
|
||||
id: server_id.to_string(),
|
||||
role_id: role_id.to_string(),
|
||||
data: self.clone().into_optional(),
|
||||
data: self.clone().into_optional().into(),
|
||||
clear: vec![],
|
||||
}
|
||||
.p(server_id.to_string())
|
||||
.await; */
|
||||
.await;
|
||||
|
||||
Ok(role_id)
|
||||
}
|
||||
@@ -394,14 +394,14 @@ impl Role {
|
||||
db.update_role(server_id, role_id, &partial, remove.clone())
|
||||
.await?;
|
||||
|
||||
/* // TODO: EventV1::ServerRoleUpdate {
|
||||
EventV1::ServerRoleUpdate {
|
||||
id: server_id.to_string(),
|
||||
role_id: role_id.to_string(),
|
||||
data: partial,
|
||||
data: partial.into(),
|
||||
clear: vec![],
|
||||
}
|
||||
.p(server_id.to_string())
|
||||
.await; */
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -415,12 +415,12 @@ impl Role {
|
||||
|
||||
/// Delete a role
|
||||
pub async fn delete(self, db: &Database, server_id: &str, role_id: &str) -> Result<()> {
|
||||
/* // TODO: EventV1::ServerRoleDelete {
|
||||
EventV1::ServerRoleDelete {
|
||||
id: server_id.to_string(),
|
||||
role_id: role_id.to_string(),
|
||||
}
|
||||
.p(server_id.to_string())
|
||||
.await; */
|
||||
.await;
|
||||
|
||||
db.delete_role(server_id, role_id).await
|
||||
}
|
||||
|
||||
@@ -1,15 +1,5 @@
|
||||
use revolt_models::v0::*;
|
||||
|
||||
impl From<crate::AccountStrike> for AccountStrike {
|
||||
fn from(value: crate::AccountStrike) -> Self {
|
||||
AccountStrike {
|
||||
id: value.id,
|
||||
user_id: value.user_id,
|
||||
reason: value.reason,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::Bot {
|
||||
pub fn into_public_bot(self, user: crate::User) -> PublicBot {
|
||||
#[cfg(debug_assertions)]
|
||||
@@ -44,6 +34,52 @@ impl From<crate::Bot> for Bot {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::Invite> for Invite {
|
||||
fn from(value: crate::Invite) -> Self {
|
||||
match value {
|
||||
crate::Invite::Group {
|
||||
code,
|
||||
creator,
|
||||
channel,
|
||||
} => Invite::Group {
|
||||
code,
|
||||
creator,
|
||||
channel,
|
||||
},
|
||||
crate::Invite::Server {
|
||||
code,
|
||||
server,
|
||||
creator,
|
||||
channel,
|
||||
} => Invite::Server {
|
||||
code,
|
||||
server,
|
||||
creator,
|
||||
channel,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::ChannelUnread> for ChannelUnread {
|
||||
fn from(value: crate::ChannelUnread) -> Self {
|
||||
ChannelUnread {
|
||||
id: value.id.into(),
|
||||
last_id: value.last_id,
|
||||
mentions: value.mentions.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::ChannelCompositeKey> for ChannelCompositeKey {
|
||||
fn from(value: crate::ChannelCompositeKey) -> Self {
|
||||
ChannelCompositeKey {
|
||||
channel: value.channel,
|
||||
user: value.user,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::Webhook> for Webhook {
|
||||
fn from(value: crate::Webhook) -> Self {
|
||||
Webhook {
|
||||
@@ -52,7 +88,7 @@ impl From<crate::Webhook> for Webhook {
|
||||
avatar: value.avatar.map(|file| file.into()),
|
||||
channel_id: value.channel_id,
|
||||
token: value.token,
|
||||
permissions: value.permissions
|
||||
permissions: value.permissions,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,7 +101,7 @@ impl From<crate::PartialWebhook> for PartialWebhook {
|
||||
avatar: value.avatar.map(|file| file.into()),
|
||||
channel_id: value.channel_id,
|
||||
token: value.token,
|
||||
permissions: value.permissions
|
||||
permissions: value.permissions,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -199,6 +235,28 @@ impl From<crate::FieldsChannel> for FieldsChannel {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::Emoji> for Emoji {
|
||||
fn from(value: crate::Emoji) -> Self {
|
||||
Emoji {
|
||||
id: value.id,
|
||||
parent: value.parent.into(),
|
||||
creator_id: value.creator_id,
|
||||
name: value.name,
|
||||
animated: value.animated,
|
||||
nsfw: value.nsfw,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::EmojiParent> for EmojiParent {
|
||||
fn from(value: crate::EmojiParent) -> Self {
|
||||
match value {
|
||||
crate::EmojiParent::Detached => EmojiParent::Detached,
|
||||
crate::EmojiParent::Server { id } => EmojiParent::Server { id },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::File> for File {
|
||||
fn from(value: crate::File) -> Self {
|
||||
File {
|
||||
@@ -236,6 +294,279 @@ impl From<crate::Metadata> for Metadata {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::Message> for Message {
|
||||
fn from(value: crate::Message) -> Self {
|
||||
Message {
|
||||
id: value.id,
|
||||
nonce: value.nonce,
|
||||
channel: value.channel,
|
||||
author: value.author,
|
||||
webhook: value.webhook,
|
||||
content: value.content,
|
||||
system: value.system.map(|system| system.into()),
|
||||
attachments: value
|
||||
.attachments
|
||||
.map(|v| v.into_iter().map(|f| f.into()).collect()),
|
||||
edited: value.edited,
|
||||
embeds: value.embeds,
|
||||
mentions: value.mentions,
|
||||
replies: value.replies,
|
||||
reactions: value.reactions,
|
||||
interactions: value.interactions.into(),
|
||||
masquerade: value.masquerade.map(|masq| masq.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::PartialMessage> for PartialMessage {
|
||||
fn from(value: crate::PartialMessage) -> Self {
|
||||
PartialMessage {
|
||||
id: value.id,
|
||||
nonce: value.nonce,
|
||||
channel: value.channel,
|
||||
author: value.author,
|
||||
webhook: value.webhook,
|
||||
content: value.content,
|
||||
system: value.system.map(|system| system.into()),
|
||||
attachments: value
|
||||
.attachments
|
||||
.map(|v| v.into_iter().map(|f| f.into()).collect()),
|
||||
edited: value.edited,
|
||||
embeds: value.embeds,
|
||||
mentions: value.mentions,
|
||||
replies: value.replies,
|
||||
reactions: value.reactions,
|
||||
interactions: value.interactions.map(|interactions| interactions.into()),
|
||||
masquerade: value.masquerade.map(|masq| masq.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::SystemMessage> for SystemMessage {
|
||||
fn from(value: crate::SystemMessage) -> Self {
|
||||
match value {
|
||||
crate::SystemMessage::ChannelDescriptionChanged { by } => {
|
||||
Self::ChannelDescriptionChanged { by }
|
||||
}
|
||||
crate::SystemMessage::ChannelIconChanged { by } => Self::ChannelIconChanged { by },
|
||||
crate::SystemMessage::ChannelOwnershipChanged { from, to } => {
|
||||
Self::ChannelOwnershipChanged { from, to }
|
||||
}
|
||||
crate::SystemMessage::ChannelRenamed { name, by } => Self::ChannelRenamed { name, by },
|
||||
crate::SystemMessage::Text { content } => Self::Text { content },
|
||||
crate::SystemMessage::UserAdded { id, by } => Self::UserAdded { id, by },
|
||||
crate::SystemMessage::UserBanned { id } => Self::UserBanned { id },
|
||||
crate::SystemMessage::UserJoined { id } => Self::UserJoined { id },
|
||||
crate::SystemMessage::UserKicked { id } => Self::UserKicked { id },
|
||||
crate::SystemMessage::UserLeft { id } => Self::UserLeft { id },
|
||||
crate::SystemMessage::UserRemove { id, by } => Self::UserRemove { id, by },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::Interactions> for Interactions {
|
||||
fn from(value: crate::Interactions) -> Self {
|
||||
Interactions {
|
||||
reactions: value.reactions,
|
||||
restrict_reactions: value.restrict_reactions,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::Masquerade> for Masquerade {
|
||||
fn from(value: crate::Masquerade) -> Self {
|
||||
Masquerade {
|
||||
name: value.name,
|
||||
avatar: value.avatar,
|
||||
colour: value.colour,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::ServerBan> for ServerBan {
|
||||
fn from(value: crate::ServerBan) -> Self {
|
||||
ServerBan {
|
||||
id: value.id.into(),
|
||||
reason: value.reason,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::Member> for Member {
|
||||
fn from(value: crate::Member) -> Self {
|
||||
Member {
|
||||
id: value.id.into(),
|
||||
joined_at: value.joined_at,
|
||||
nickname: value.nickname,
|
||||
avatar: value.avatar.map(|f| f.into()),
|
||||
roles: value.roles,
|
||||
timeout: value.timeout,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::PartialMember> for PartialMember {
|
||||
fn from(value: crate::PartialMember) -> Self {
|
||||
PartialMember {
|
||||
id: value.id.map(|id| id.into()),
|
||||
joined_at: value.joined_at,
|
||||
nickname: value.nickname,
|
||||
avatar: value.avatar.map(|f| f.into()),
|
||||
roles: value.roles,
|
||||
timeout: value.timeout,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::MemberCompositeKey> for MemberCompositeKey {
|
||||
fn from(value: crate::MemberCompositeKey) -> Self {
|
||||
MemberCompositeKey {
|
||||
server: value.server,
|
||||
user: value.user,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::FieldsMember> for FieldsMember {
|
||||
fn from(value: crate::FieldsMember) -> Self {
|
||||
match value {
|
||||
crate::FieldsMember::Avatar => FieldsMember::Avatar,
|
||||
crate::FieldsMember::Nickname => FieldsMember::Nickname,
|
||||
crate::FieldsMember::Roles => FieldsMember::Roles,
|
||||
crate::FieldsMember::Timeout => FieldsMember::Timeout,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::RemovalIntention> for RemovalIntention {
|
||||
fn from(value: crate::RemovalIntention) -> Self {
|
||||
match value {
|
||||
crate::RemovalIntention::Ban => RemovalIntention::Ban,
|
||||
crate::RemovalIntention::Kick => RemovalIntention::Kick,
|
||||
crate::RemovalIntention::Leave => RemovalIntention::Leave,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::Server> for Server {
|
||||
fn from(value: crate::Server) -> Self {
|
||||
Server {
|
||||
id: value.id,
|
||||
owner: value.owner,
|
||||
name: value.name,
|
||||
description: value.description,
|
||||
channels: value.channels,
|
||||
categories: value
|
||||
.categories
|
||||
.map(|categories| categories.into_iter().map(|v| v.into()).collect()),
|
||||
system_messages: value.system_messages.map(|v| v.into()),
|
||||
roles: value
|
||||
.roles
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k, v.into()))
|
||||
.collect(),
|
||||
default_permissions: value.default_permissions,
|
||||
icon: value.icon.map(|f| f.into()),
|
||||
banner: value.banner.map(|f| f.into()),
|
||||
flags: value.flags.unwrap_or_default() as u32,
|
||||
nsfw: value.nsfw,
|
||||
analytics: value.analytics,
|
||||
discoverable: value.discoverable,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::PartialServer> for PartialServer {
|
||||
fn from(value: crate::PartialServer) -> Self {
|
||||
PartialServer {
|
||||
id: value.id,
|
||||
owner: value.owner,
|
||||
name: value.name,
|
||||
description: value.description,
|
||||
channels: value.channels,
|
||||
categories: value
|
||||
.categories
|
||||
.map(|categories| categories.into_iter().map(|v| v.into()).collect()),
|
||||
system_messages: value.system_messages.map(|v| v.into()),
|
||||
roles: value
|
||||
.roles
|
||||
.map(|roles| roles.into_iter().map(|(k, v)| (k, v.into())).collect()),
|
||||
default_permissions: value.default_permissions,
|
||||
icon: value.icon.map(|f| f.into()),
|
||||
banner: value.banner.map(|f| f.into()),
|
||||
flags: value.flags.map(|v| v as u32),
|
||||
nsfw: value.nsfw,
|
||||
analytics: value.analytics,
|
||||
discoverable: value.discoverable,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::FieldsServer> for FieldsServer {
|
||||
fn from(value: crate::FieldsServer) -> Self {
|
||||
match value {
|
||||
crate::FieldsServer::Banner => FieldsServer::Banner,
|
||||
crate::FieldsServer::Categories => FieldsServer::Categories,
|
||||
crate::FieldsServer::Description => FieldsServer::Description,
|
||||
crate::FieldsServer::Icon => FieldsServer::Icon,
|
||||
crate::FieldsServer::SystemMessages => FieldsServer::SystemMessages,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::Category> for Category {
|
||||
fn from(value: crate::Category) -> Self {
|
||||
Category {
|
||||
id: value.id,
|
||||
title: value.title,
|
||||
channels: value.channels,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::SystemMessageChannels> for SystemMessageChannels {
|
||||
fn from(value: crate::SystemMessageChannels) -> Self {
|
||||
SystemMessageChannels {
|
||||
user_joined: value.user_joined,
|
||||
user_left: value.user_left,
|
||||
user_kicked: value.user_kicked,
|
||||
user_banned: value.user_banned,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::Role> for Role {
|
||||
fn from(value: crate::Role) -> Self {
|
||||
Role {
|
||||
name: value.name,
|
||||
permissions: value.permissions,
|
||||
colour: value.colour,
|
||||
hoist: value.hoist,
|
||||
rank: value.rank,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::PartialRole> for PartialRole {
|
||||
fn from(value: crate::PartialRole) -> Self {
|
||||
PartialRole {
|
||||
name: value.name,
|
||||
permissions: value.permissions,
|
||||
colour: value.colour,
|
||||
hoist: value.hoist,
|
||||
rank: value.rank,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::FieldsRole> for FieldsRole {
|
||||
fn from(value: crate::FieldsRole) -> Self {
|
||||
match value {
|
||||
crate::FieldsRole::Colour => FieldsRole::Colour,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::User {
|
||||
pub async fn into<P>(self, perspective: P) -> User
|
||||
where
|
||||
|
||||
@@ -1,28 +1,30 @@
|
||||
[package]
|
||||
name = "revolt-models"
|
||||
version = "0.6.5"
|
||||
version = "0.6.6"
|
||||
edition = "2021"
|
||||
license = "AGPL-3.0-or-later"
|
||||
authors = [ "Paul Makles <me@insrt.uk>" ]
|
||||
authors = ["Paul Makles <me@insrt.uk>"]
|
||||
description = "Revolt Backend: API Models"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[features]
|
||||
serde = [ "dep:serde", "revolt-permissions/serde" ]
|
||||
schemas = [ "dep:schemars", "revolt-permissions/schemas" ]
|
||||
validator = [ "dep:validator" ]
|
||||
partials = [ "dep:revolt_optional_struct", "serde", "schemas" ]
|
||||
serde = ["dep:serde", "revolt-permissions/serde"]
|
||||
schemas = ["dep:schemars", "revolt-permissions/schemas"]
|
||||
validator = ["dep:validator"]
|
||||
partials = ["dep:revolt_optional_struct", "serde", "schemas"]
|
||||
|
||||
default = [ "serde", "partials" ]
|
||||
default = ["serde", "partials"]
|
||||
|
||||
[dependencies]
|
||||
# Core
|
||||
revolt-permissions = { version = "0.6.5", path = "../permissions" }
|
||||
revolt-permissions = { version = "0.6.6", path = "../permissions" }
|
||||
|
||||
# Serialisation
|
||||
indexmap = "1.9.3"
|
||||
revolt_optional_struct = { version = "0.2.0", optional = true }
|
||||
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 }
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
auto_derived!(
|
||||
/// Account Strike
|
||||
pub struct AccountStrike {
|
||||
/// Strike Id
|
||||
#[cfg_attr(feature = "serde", serde(rename = "_id"))]
|
||||
pub id: String,
|
||||
/// Id of reported user
|
||||
pub user_id: String,
|
||||
|
||||
/// Attached reason
|
||||
pub reason: String,
|
||||
}
|
||||
|
||||
/// New strike information
|
||||
pub struct DataCreateStrike {
|
||||
/// Id of reported user
|
||||
pub user_id: String,
|
||||
|
||||
/// Attached reason
|
||||
pub reason: String,
|
||||
}
|
||||
|
||||
/// New strike information
|
||||
pub struct DataEditAccountStrike {
|
||||
/// New attached reason
|
||||
pub reason: String,
|
||||
}
|
||||
);
|
||||
@@ -65,16 +65,16 @@ auto_derived!(
|
||||
/// Public Bot
|
||||
pub struct PublicBot {
|
||||
/// Bot Id
|
||||
#[serde(rename = "_id")]
|
||||
#[cfg_attr(feature = "serde", serde(rename = "_id"))]
|
||||
pub id: String,
|
||||
|
||||
/// Bot Username
|
||||
pub username: String,
|
||||
/// Profile Avatar
|
||||
#[serde(skip_serializing_if = "String::is_empty")]
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "String::is_empty"))]
|
||||
pub avatar: String,
|
||||
/// Profile Description
|
||||
#[serde(skip_serializing_if = "String::is_empty")]
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "String::is_empty"))]
|
||||
pub description: String,
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
auto_derived!(
|
||||
/// Invite
|
||||
pub enum Invite {
|
||||
/// Invite to a specific server channel
|
||||
Server {
|
||||
/// Invite code
|
||||
#[cfg_attr(feature = "serde", serde(rename = "_id"))]
|
||||
code: String,
|
||||
/// Id of the server this invite points to
|
||||
server: String,
|
||||
/// Id of user who created this invite
|
||||
creator: String,
|
||||
/// Id of the server channel this invite points to
|
||||
channel: String,
|
||||
},
|
||||
/// Invite to a group channel
|
||||
Group {
|
||||
/// Invite code
|
||||
#[cfg_attr(feature = "serde", serde(rename = "_id"))]
|
||||
code: String,
|
||||
/// Id of user who created this invite
|
||||
creator: String,
|
||||
/// Id of the group channel this invite points to
|
||||
channel: String,
|
||||
},
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,27 @@
|
||||
auto_derived!(
|
||||
/// Channel Unread
|
||||
pub struct ChannelUnread {
|
||||
/// Composite key pointing to a user's view of a channel
|
||||
#[serde(rename = "_id")]
|
||||
pub id: ChannelCompositeKey,
|
||||
|
||||
/// Id of the last message read in this channel by a user
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub last_id: Option<String>,
|
||||
/// Array of message ids that mention the user
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
serde(skip_serializing_if = "Vec::is_empty", default)
|
||||
)]
|
||||
pub mentions: Vec<String>,
|
||||
}
|
||||
|
||||
/// Composite primary key consisting of channel and user id
|
||||
#[derive(Hash)]
|
||||
pub struct ChannelCompositeKey {
|
||||
/// Channel Id
|
||||
pub channel: String,
|
||||
/// User Id
|
||||
pub user: String,
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,160 @@
|
||||
use super::File;
|
||||
|
||||
auto_derived!(
|
||||
/// Image positioning and size
|
||||
pub enum ImageSize {
|
||||
/// Show large preview at the bottom of the embed
|
||||
Large,
|
||||
/// Show small preview to the side of the embed
|
||||
Preview,
|
||||
}
|
||||
|
||||
/// Image
|
||||
pub struct Image {
|
||||
/// URL to the original image
|
||||
pub url: String,
|
||||
/// Width of the image
|
||||
pub width: isize,
|
||||
/// Height of the image
|
||||
pub height: isize,
|
||||
/// Positioning and size
|
||||
pub size: ImageSize,
|
||||
}
|
||||
|
||||
/// Video
|
||||
pub struct Video {
|
||||
/// URL to the original video
|
||||
pub url: String,
|
||||
/// Width of the video
|
||||
pub width: isize,
|
||||
/// Height of the video
|
||||
pub height: isize,
|
||||
}
|
||||
|
||||
/// Type of remote Twitch content
|
||||
pub enum TwitchType {
|
||||
Channel,
|
||||
Video,
|
||||
Clip,
|
||||
}
|
||||
|
||||
/// Type of remote Lightspeed.tv content
|
||||
pub enum LightspeedType {
|
||||
Channel,
|
||||
}
|
||||
|
||||
/// Type of remote Bandcamp content
|
||||
pub enum BandcampType {
|
||||
Album,
|
||||
Track,
|
||||
}
|
||||
|
||||
/// Information about special remote content
|
||||
#[serde(tag = "type")]
|
||||
pub enum Special {
|
||||
/// No remote content
|
||||
None,
|
||||
/// Content hint that this contains a GIF
|
||||
///
|
||||
/// Use metadata to find video or image to play
|
||||
GIF,
|
||||
/// YouTube video
|
||||
YouTube {
|
||||
id: String,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
timestamp: Option<String>,
|
||||
},
|
||||
/// Lightspeed.tv stream
|
||||
Lightspeed {
|
||||
content_type: LightspeedType,
|
||||
id: String,
|
||||
},
|
||||
/// Twitch stream or clip
|
||||
Twitch {
|
||||
content_type: TwitchType,
|
||||
id: String,
|
||||
},
|
||||
/// Spotify track
|
||||
Spotify { content_type: String, id: String },
|
||||
/// Soundcloud track
|
||||
Soundcloud,
|
||||
/// Bandcamp track
|
||||
Bandcamp {
|
||||
content_type: BandcampType,
|
||||
id: String,
|
||||
},
|
||||
/// Streamable Video
|
||||
Streamable { id: String },
|
||||
}
|
||||
|
||||
/// Website metadata
|
||||
pub struct WebsiteMetadata {
|
||||
/// Direct URL to web page
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
url: Option<String>,
|
||||
/// Original direct URL
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
original_url: Option<String>,
|
||||
/// Remote content
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
special: Option<Special>,
|
||||
|
||||
/// Title of website
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
title: Option<String>,
|
||||
/// Description of website
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
description: Option<String>,
|
||||
/// Embedded image
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
image: Option<Image>,
|
||||
/// Embedded video
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
video: Option<Video>,
|
||||
|
||||
// #[serde(skip_serializing_if = "Option::is_none")]
|
||||
// opengraph_type: Option<String>,
|
||||
/// Site name
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
site_name: Option<String>,
|
||||
/// URL to site icon
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
icon_url: Option<String>,
|
||||
/// CSS Colour
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
colour: Option<String>,
|
||||
}
|
||||
|
||||
/// Text Embed
|
||||
pub struct Text {
|
||||
/// URL to icon
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub icon_url: Option<String>,
|
||||
/// URL for title
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub url: Option<String>,
|
||||
/// Title of text embed
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub title: Option<String>,
|
||||
/// Description of text embed
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub description: Option<String>,
|
||||
/// ID of uploaded autumn file
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub media: Option<File>,
|
||||
/// CSS Colour
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub colour: Option<String>,
|
||||
}
|
||||
|
||||
/// Embed
|
||||
#[serde(tag = "type")]
|
||||
pub enum Embed {
|
||||
Website(WebsiteMetadata),
|
||||
Image(Image),
|
||||
Video(Video),
|
||||
Text(Text),
|
||||
None,
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,33 @@
|
||||
auto_derived!(
|
||||
/// Emoji
|
||||
pub struct Emoji {
|
||||
/// Unique Id
|
||||
#[cfg_attr(feature = "serde", serde(rename = "_id"))]
|
||||
pub id: String,
|
||||
/// What owns this emoji
|
||||
pub parent: EmojiParent,
|
||||
/// Uploader user id
|
||||
pub creator_id: String,
|
||||
/// Emoji name
|
||||
pub name: String,
|
||||
/// Whether the emoji is animated
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
serde(skip_serializing_if = "crate::if_false", default)
|
||||
)]
|
||||
pub animated: bool,
|
||||
/// Whether the emoji is marked as nsfw
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
serde(skip_serializing_if = "crate::if_false", default)
|
||||
)]
|
||||
pub nsfw: bool,
|
||||
}
|
||||
|
||||
/// Parent Id of the emoji
|
||||
#[serde(tag = "type")]
|
||||
pub enum EmojiParent {
|
||||
Server { id: String },
|
||||
Detached,
|
||||
}
|
||||
);
|
||||
@@ -2,7 +2,7 @@ auto_derived!(
|
||||
/// File
|
||||
pub struct File {
|
||||
/// Unique Id
|
||||
#[serde(rename = "_id")]
|
||||
#[cfg_attr(feature = "serde", serde(rename = "_id"))]
|
||||
pub id: String,
|
||||
/// Tag / bucket this file was uploaded to
|
||||
pub tag: String,
|
||||
@@ -16,29 +16,29 @@ auto_derived!(
|
||||
pub size: isize,
|
||||
|
||||
/// Whether this file was deleted
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub deleted: Option<bool>,
|
||||
/// Whether this file was reported
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub reported: Option<bool>,
|
||||
|
||||
// TODO: migrate this mess to having:
|
||||
// - author_id
|
||||
// - parent: Parent { Message(id), User(id), etc }
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub message_id: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub user_id: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub server_id: Option<String>,
|
||||
|
||||
/// Id of the object this file is associated with
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub object_id: Option<String>,
|
||||
}
|
||||
|
||||
/// Metadata associated with a file
|
||||
#[serde(tag = "type")]
|
||||
#[cfg_attr(feature = "serde", serde(tag = "type"))]
|
||||
#[derive(Default)]
|
||||
pub enum Metadata {
|
||||
/// File is just a generic uncategorised file
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
use indexmap::{IndexMap, IndexSet};
|
||||
use iso8601_timestamp::Timestamp;
|
||||
|
||||
use super::{Embed, File, MessageWebhook};
|
||||
|
||||
auto_derived_partial!(
|
||||
/// Message
|
||||
pub struct Message {
|
||||
/// Unique Id
|
||||
#[serde(rename = "_id")]
|
||||
pub id: String,
|
||||
/// Unique value generated by client sending this message
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub nonce: Option<String>,
|
||||
/// Id of the channel this message was sent in
|
||||
pub channel: String,
|
||||
/// Id of the user or webhook that sent this message
|
||||
pub author: String,
|
||||
/// The webhook that sent this message
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub webhook: Option<MessageWebhook>,
|
||||
/// Message content
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub content: Option<String>,
|
||||
/// System message
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub system: Option<SystemMessage>,
|
||||
/// Array of attachments
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub attachments: Option<Vec<File>>,
|
||||
/// Time at which this message was last edited
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub edited: Option<Timestamp>,
|
||||
/// Attached embeds to this message
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub embeds: Option<Vec<Embed>>,
|
||||
/// Array of user ids mentioned in this message
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub mentions: Option<Vec<String>>,
|
||||
/// Array of message ids this message is replying to
|
||||
#[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 = "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,
|
||||
/// Name and / or avatar overrides for this message
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub masquerade: Option<Masquerade>,
|
||||
},
|
||||
"PartialMessage"
|
||||
);
|
||||
|
||||
auto_derived!(
|
||||
/// System Event
|
||||
#[serde(tag = "type")]
|
||||
pub enum SystemMessage {
|
||||
#[serde(rename = "text")]
|
||||
Text { content: String },
|
||||
#[serde(rename = "user_added")]
|
||||
UserAdded { id: String, by: String },
|
||||
#[serde(rename = "user_remove")]
|
||||
UserRemove { id: String, by: String },
|
||||
#[serde(rename = "user_joined")]
|
||||
UserJoined { id: String },
|
||||
#[serde(rename = "user_left")]
|
||||
UserLeft { id: String },
|
||||
#[serde(rename = "user_kicked")]
|
||||
UserKicked { id: String },
|
||||
#[serde(rename = "user_banned")]
|
||||
UserBanned { id: String },
|
||||
#[serde(rename = "channel_renamed")]
|
||||
ChannelRenamed { name: String, by: String },
|
||||
#[serde(rename = "channel_description_changed")]
|
||||
ChannelDescriptionChanged { by: String },
|
||||
#[serde(rename = "channel_icon_changed")]
|
||||
ChannelIconChanged { by: String },
|
||||
#[serde(rename = "channel_ownership_changed")]
|
||||
ChannelOwnershipChanged { from: String, to: String },
|
||||
}
|
||||
|
||||
/// Name and / or avatar override information
|
||||
pub struct Masquerade {
|
||||
/// Replace the display name shown on this message
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub name: Option<String>,
|
||||
/// Replace the avatar shown on this message (URL to image file)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
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")]
|
||||
pub colour: Option<String>,
|
||||
}
|
||||
|
||||
/// Information to guide interactions on this message
|
||||
#[derive(Default)]
|
||||
pub struct Interactions {
|
||||
/// Reactions which should always appear and be distinct
|
||||
#[serde(skip_serializing_if = "Option::is_none", default)]
|
||||
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
|
||||
#[serde(skip_serializing_if = "crate::if_false", default)]
|
||||
pub restrict_reactions: bool,
|
||||
}
|
||||
|
||||
/// Appended Information
|
||||
pub struct AppendMessage {
|
||||
/// Additional embeds to include in this message
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub embeds: Option<Vec<Embed>>,
|
||||
}
|
||||
|
||||
/// Message Sort
|
||||
///
|
||||
/// Sort used for retrieving messages
|
||||
#[derive(Default)]
|
||||
pub enum MessageSort {
|
||||
/// Sort by the most relevant messages
|
||||
#[default]
|
||||
Relevance,
|
||||
/// Sort by the newest messages first
|
||||
Latest,
|
||||
/// Sort by the oldest messages first
|
||||
Oldest,
|
||||
}
|
||||
);
|
||||
|
||||
impl Interactions {
|
||||
/// Check if default initialisation of fields
|
||||
pub fn is_default(&self) -> bool {
|
||||
!self.restrict_reactions && self.reactions.is_none()
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,29 @@
|
||||
mod account_strikes;
|
||||
mod bots;
|
||||
mod channel_invites;
|
||||
mod channel_unreads;
|
||||
mod channel_webhooks;
|
||||
mod channels;
|
||||
mod embeds;
|
||||
mod emojis;
|
||||
mod files;
|
||||
mod messages;
|
||||
mod server_bans;
|
||||
mod server_members;
|
||||
mod servers;
|
||||
mod user_settings;
|
||||
mod users;
|
||||
|
||||
pub use account_strikes::*;
|
||||
pub use bots::*;
|
||||
pub use channel_invites::*;
|
||||
pub use channel_unreads::*;
|
||||
pub use channel_webhooks::*;
|
||||
pub use channels::*;
|
||||
pub use embeds::*;
|
||||
pub use emojis::*;
|
||||
pub use files::*;
|
||||
pub use messages::*;
|
||||
pub use server_bans::*;
|
||||
pub use server_members::*;
|
||||
pub use servers::*;
|
||||
pub use user_settings::*;
|
||||
pub use users::*;
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
use super::MemberCompositeKey;
|
||||
|
||||
auto_derived!(
|
||||
/// Server Ban
|
||||
pub struct ServerBan {
|
||||
/// Unique member id
|
||||
#[cfg_attr(feature = "serde", serde(rename = "_id"))]
|
||||
pub id: MemberCompositeKey,
|
||||
/// Reason for ban creation
|
||||
pub reason: Option<String>,
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,60 @@
|
||||
use super::File;
|
||||
|
||||
use iso8601_timestamp::Timestamp;
|
||||
|
||||
auto_derived_partial!(
|
||||
/// Server Member
|
||||
pub struct Member {
|
||||
/// Unique member id
|
||||
#[cfg_attr(feature = "serde", serde(rename = "_id"))]
|
||||
pub id: MemberCompositeKey,
|
||||
|
||||
/// Time at which this user joined the server
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub joined_at: Option<Timestamp>,
|
||||
|
||||
/// Member's nickname
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub nickname: Option<String>,
|
||||
/// Avatar attachment
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub avatar: Option<File>,
|
||||
|
||||
/// Member's roles
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
serde(skip_serializing_if = "Vec::is_empty", default)
|
||||
)]
|
||||
pub roles: Vec<String>,
|
||||
/// Timestamp this member is timed out until
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub timeout: Option<Timestamp>,
|
||||
},
|
||||
"PartialMember"
|
||||
);
|
||||
|
||||
auto_derived!(
|
||||
/// Composite primary key consisting of server and user id
|
||||
#[derive(Hash, Default)]
|
||||
pub struct MemberCompositeKey {
|
||||
/// Server Id
|
||||
pub server: String,
|
||||
/// User Id
|
||||
pub user: String,
|
||||
}
|
||||
|
||||
/// Optional fields on server member object
|
||||
pub enum FieldsMember {
|
||||
Nickname,
|
||||
Avatar,
|
||||
Roles,
|
||||
Timeout,
|
||||
}
|
||||
|
||||
/// Member removal intention
|
||||
pub enum RemovalIntention {
|
||||
Leave,
|
||||
Kick,
|
||||
Ban,
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,144 @@
|
||||
use super::File;
|
||||
|
||||
use revolt_permissions::OverrideField;
|
||||
use std::collections::HashMap;
|
||||
|
||||
auto_derived_partial!(
|
||||
/// Server
|
||||
pub struct Server {
|
||||
/// Unique Id
|
||||
#[cfg_attr(feature = "serde", serde(rename = "_id"))]
|
||||
pub id: String,
|
||||
/// User id of the owner
|
||||
pub owner: String,
|
||||
|
||||
/// Name of the server
|
||||
pub name: String,
|
||||
/// Description for the server
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub description: Option<String>,
|
||||
|
||||
/// Channels within this server
|
||||
// ! FIXME: this may be redundant
|
||||
pub channels: Vec<String>,
|
||||
/// Categories for this server
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub categories: Option<Vec<Category>>,
|
||||
/// Configuration for sending system event messages
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub system_messages: Option<SystemMessageChannels>,
|
||||
|
||||
/// Roles for this server
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
serde(
|
||||
default = "HashMap::<String, Role>::new",
|
||||
skip_serializing_if = "HashMap::<String, Role>::is_empty"
|
||||
)
|
||||
)]
|
||||
pub roles: HashMap<String, Role>,
|
||||
/// Default set of server and channel permissions
|
||||
pub default_permissions: i64,
|
||||
|
||||
/// Icon attachment
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub icon: Option<File>,
|
||||
/// Banner attachment
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub banner: Option<File>,
|
||||
|
||||
/// Bitfield of server flags
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
serde(skip_serializing_if = "crate::if_zero_u32", default)
|
||||
)]
|
||||
pub flags: u32,
|
||||
|
||||
/// Whether this server is flagged as not safe for work
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
serde(skip_serializing_if = "crate::if_false", default)
|
||||
)]
|
||||
pub nsfw: bool,
|
||||
/// Whether to enable analytics
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
serde(skip_serializing_if = "crate::if_false", default)
|
||||
)]
|
||||
pub analytics: bool,
|
||||
/// Whether this server should be publicly discoverable
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
serde(skip_serializing_if = "crate::if_false", default)
|
||||
)]
|
||||
pub discoverable: bool,
|
||||
},
|
||||
"PartialServer"
|
||||
);
|
||||
|
||||
auto_derived_partial!(
|
||||
/// Role
|
||||
pub struct Role {
|
||||
/// Role name
|
||||
pub name: String,
|
||||
/// Permissions available to this role
|
||||
pub permissions: OverrideField,
|
||||
/// Colour used for this role
|
||||
///
|
||||
/// This can be any valid CSS colour
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub colour: Option<String>,
|
||||
/// Whether this role should be shown separately on the member sidebar
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
serde(skip_serializing_if = "crate::if_false", default)
|
||||
)]
|
||||
pub hoist: bool,
|
||||
/// Ranking of this role
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
pub rank: i64,
|
||||
},
|
||||
"PartialRole"
|
||||
);
|
||||
|
||||
auto_derived!(
|
||||
/// Optional fields on server object
|
||||
pub enum FieldsServer {
|
||||
Description,
|
||||
Categories,
|
||||
SystemMessages,
|
||||
Icon,
|
||||
Banner,
|
||||
}
|
||||
|
||||
/// Optional fields on server object
|
||||
pub enum FieldsRole {
|
||||
Colour,
|
||||
}
|
||||
|
||||
/// Channel category
|
||||
pub struct Category {
|
||||
/// Unique ID for this category
|
||||
pub id: String,
|
||||
/// Title for this category
|
||||
pub title: String,
|
||||
/// Channels in this category
|
||||
pub channels: Vec<String>,
|
||||
}
|
||||
|
||||
/// System message channel assignments
|
||||
pub struct SystemMessageChannels {
|
||||
/// ID of channel to send user join messages in
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub user_joined: Option<String>,
|
||||
/// ID of channel to send user left messages in
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub user_left: Option<String>,
|
||||
/// ID of channel to send user kicked messages in
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub user_kicked: Option<String>,
|
||||
/// ID of channel to send user banned messages in
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub user_banned: Option<String>,
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,6 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// HashMap of user settings
|
||||
/// Each key is mapped to a tuple consisting of the
|
||||
/// revision timestamp and serialised data (in JSON format)
|
||||
pub type UserSettings = HashMap<String, (i64, String)>;
|
||||
@@ -4,40 +4,52 @@ auto_derived!(
|
||||
/// User
|
||||
pub struct User {
|
||||
/// Unique Id
|
||||
#[serde(rename = "_id")]
|
||||
#[cfg_attr(feature = "serde", serde(rename = "_id"))]
|
||||
pub id: String,
|
||||
/// Username
|
||||
pub username: String,
|
||||
/// Discriminator
|
||||
pub discriminator: String,
|
||||
/// Display name
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub display_name: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
/// Avatar attachment
|
||||
pub avatar: Option<File>,
|
||||
/// Relationships with other users
|
||||
#[serde(skip_serializing_if = "Vec::is_empty", default)]
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
serde(skip_serializing_if = "Vec::is_empty", default)
|
||||
)]
|
||||
pub relations: Vec<Relationship>,
|
||||
|
||||
/// Bitfield of user badges
|
||||
#[serde(skip_serializing_if = "crate::if_zero_u32", default)]
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
serde(skip_serializing_if = "crate::if_zero_u32", default)
|
||||
)]
|
||||
pub badges: u32,
|
||||
/// User's current status
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub status: Option<UserStatus>,
|
||||
/// User's profile page
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub profile: Option<UserProfile>,
|
||||
|
||||
/// Enum of user flags
|
||||
#[serde(skip_serializing_if = "crate::if_zero_u32", default)]
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
serde(skip_serializing_if = "crate::if_zero_u32", default)
|
||||
)]
|
||||
pub flags: u32,
|
||||
/// Whether this user is privileged
|
||||
#[serde(skip_serializing_if = "crate::if_false", default)]
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
serde(skip_serializing_if = "crate::if_false", default)
|
||||
)]
|
||||
pub privileged: bool,
|
||||
/// Bot information
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub bot: Option<BotInformation>,
|
||||
|
||||
/// Current session user's relationship with this user
|
||||
@@ -49,20 +61,29 @@ auto_derived!(
|
||||
/// User's relationship with another user (or themselves)
|
||||
#[derive(Default)]
|
||||
pub enum RelationshipStatus {
|
||||
/// No relationship with other user
|
||||
#[default]
|
||||
None,
|
||||
/// Other user is us
|
||||
User,
|
||||
/// Friends with the other user
|
||||
Friend,
|
||||
/// Pending friend request to user
|
||||
Outgoing,
|
||||
/// Incoming friend request from user
|
||||
Incoming,
|
||||
/// Blocked this user
|
||||
Blocked,
|
||||
/// Blocked by this user
|
||||
BlockedOther,
|
||||
}
|
||||
|
||||
/// Relationship entry indicating current status with other user
|
||||
pub struct Relationship {
|
||||
#[serde(rename = "_id")]
|
||||
/// Other user's Id
|
||||
#[cfg_attr(feature = "serde", serde(rename = "_id"))]
|
||||
pub user_id: String,
|
||||
/// Relationship status with them
|
||||
pub status: RelationshipStatus,
|
||||
}
|
||||
|
||||
@@ -83,20 +104,20 @@ auto_derived!(
|
||||
/// User's active status
|
||||
pub struct UserStatus {
|
||||
/// Custom status text
|
||||
#[serde(skip_serializing_if = "String::is_empty")]
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "String::is_empty"))]
|
||||
pub text: String,
|
||||
/// Current presence option
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub presence: Option<Presence>,
|
||||
}
|
||||
|
||||
/// User's profile
|
||||
pub struct UserProfile {
|
||||
/// Text content on user's profile
|
||||
#[serde(skip_serializing_if = "String::is_empty")]
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "String::is_empty"))]
|
||||
pub content: String,
|
||||
/// Background visible on user's profile
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub background: Option<File>,
|
||||
}
|
||||
|
||||
@@ -143,7 +164,7 @@ auto_derived!(
|
||||
/// Bot information for if the user is a bot
|
||||
pub struct BotInformation {
|
||||
/// Id of the owner of this bot
|
||||
#[serde(rename = "owner")]
|
||||
#[cfg_attr(feature = "serde", serde(rename = "owner"))]
|
||||
pub owner_id: String,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
[package]
|
||||
name = "revolt-permissions"
|
||||
version = "0.6.5"
|
||||
version = "0.6.6"
|
||||
edition = "2021"
|
||||
license = "AGPL-3.0-or-later"
|
||||
authors = [ "Paul Makles <me@insrt.uk>" ]
|
||||
authors = ["Paul Makles <me@insrt.uk>"]
|
||||
description = "Revolt Backend: Permission Logic"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[features]
|
||||
bson = ["dep:bson"]
|
||||
serde = [ "dep:serde" ]
|
||||
schemas = [ "dep:schemars" ]
|
||||
try-from-primitive = [ "dep:num_enum" ]
|
||||
serde = ["dep:serde"]
|
||||
schemas = ["dep:schemars"]
|
||||
try-from-primitive = ["dep:num_enum"]
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
@@ -30,7 +30,7 @@ async-trait = "0.1.51"
|
||||
|
||||
# Serialisation
|
||||
serde = { version = "1", features = ["derive"], optional = true }
|
||||
bson = { version = "2.1.0", optional = true}
|
||||
bson = { version = "2.1.0", optional = true }
|
||||
|
||||
# Spec Generation
|
||||
schemars = { version = "0.8.8", optional = true }
|
||||
schemars = { version = "0.8.8", optional = true }
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
[package]
|
||||
name = "revolt-presence"
|
||||
version = "0.6.5"
|
||||
version = "0.6.6"
|
||||
edition = "2021"
|
||||
license = "AGPL-3.0-or-later"
|
||||
authors = [ "Paul Makles <me@insrt.uk>" ]
|
||||
authors = ["Paul Makles <me@insrt.uk>"]
|
||||
description = "Revolt Backend: User Presence"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
[package]
|
||||
name = "revolt-result"
|
||||
version = "0.6.5"
|
||||
version = "0.6.6"
|
||||
edition = "2021"
|
||||
license = "AGPL-3.0-or-later"
|
||||
authors = [ "Paul Makles <me@insrt.uk>" ]
|
||||
authors = ["Paul Makles <me@insrt.uk>"]
|
||||
description = "Revolt Backend: Result and Error types"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[features]
|
||||
serde = [ "dep:serde" ]
|
||||
schemas = [ "dep:schemars" ]
|
||||
rocket = [ "dep:rocket", "dep:serde_json" ]
|
||||
okapi = [ "dep:revolt_rocket_okapi", "dep:revolt_okapi" ]
|
||||
serde = ["dep:serde"]
|
||||
schemas = ["dep:schemars"]
|
||||
rocket = ["dep:rocket", "dep:serde_json"]
|
||||
okapi = ["dep:revolt_rocket_okapi", "dep:revolt_okapi"]
|
||||
|
||||
default = [ "serde" ]
|
||||
default = ["serde"]
|
||||
|
||||
[dependencies]
|
||||
# Serialisation
|
||||
|
||||
+17
-8
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "revolt-delta"
|
||||
version = "0.6.5"
|
||||
version = "0.6.6"
|
||||
license = "AGPL-3.0-or-later"
|
||||
authors = ["Paul Makles <paulmakles@gmail.com>"]
|
||||
edition = "2018"
|
||||
@@ -38,29 +38,38 @@ futures = "0.3.8"
|
||||
chrono = "0.4.15"
|
||||
async-channel = "1.6.1"
|
||||
reqwest = { version = "0.11.4", features = ["json"] }
|
||||
async-std = { version = "1.8.0", features = ["tokio1", "tokio02", "attributes"] }
|
||||
async-std = { version = "1.8.0", features = [
|
||||
"tokio1",
|
||||
"tokio02",
|
||||
"attributes",
|
||||
] }
|
||||
|
||||
# internal util
|
||||
lettre = "0.10.0-alpha.4"
|
||||
|
||||
# web
|
||||
rocket = { version = "0.5.0-rc.2", default-features = false, features = ["json"] }
|
||||
rocket = { version = "0.5.0-rc.2", default-features = false, features = [
|
||||
"json",
|
||||
] }
|
||||
rocket_empty = { version = "0.1.1", features = ["schema"] }
|
||||
rocket_authifier = { version = "1.0.7" }
|
||||
rocket_prometheus = "0.10.0-rc.3"
|
||||
|
||||
# spec generation
|
||||
schemars = "0.8.8"
|
||||
revolt_rocket_okapi = { version = "0.9.1", features = [ "swagger" ] }
|
||||
revolt_rocket_okapi = { version = "0.9.1", features = ["swagger"] }
|
||||
|
||||
# quark
|
||||
revolt-quark = { path = "../quark" }
|
||||
|
||||
# core
|
||||
revolt-database = { path = "../core/database", features = [ "rocket-impl", "redis-is-patched" ] }
|
||||
revolt-models = { path = "../core/models", features = [ "schemas", "validator" ] }
|
||||
revolt-result = { path = "../core/result", features = [ "rocket", "okapi" ] }
|
||||
revolt-permissions = { path = "../core/permissions", features = [ "schemas" ] }
|
||||
revolt-database = { path = "../core/database", features = [
|
||||
"rocket-impl",
|
||||
"redis-is-patched",
|
||||
] }
|
||||
revolt-models = { path = "../core/models", features = ["schemas", "validator"] }
|
||||
revolt-result = { path = "../core/result", features = ["rocket", "okapi"] }
|
||||
revolt-permissions = { path = "../core/permissions", features = ["schemas"] }
|
||||
|
||||
[build-dependencies]
|
||||
vergen = "7.5.0"
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
use revolt_quark::{
|
||||
models::{
|
||||
message::{BulkMessageResponse, MessageQuery},
|
||||
User,
|
||||
},
|
||||
Db, Error, Result,
|
||||
};
|
||||
use rocket::serde::json::Json;
|
||||
|
||||
/// # Globally Fetch Messages
|
||||
///
|
||||
/// This is a privileged route to globally fetch messages.
|
||||
#[openapi(tag = "Admin")]
|
||||
#[post("/messages", data = "<data>")]
|
||||
pub async fn message_query(
|
||||
db: &Db,
|
||||
user: User,
|
||||
data: Json<MessageQuery>,
|
||||
) -> Result<Json<BulkMessageResponse>> {
|
||||
// Must be privileged for this route
|
||||
if !user.privileged {
|
||||
return Err(Error::NotPrivileged);
|
||||
}
|
||||
|
||||
// Fetch data using query
|
||||
let data = data.into_inner();
|
||||
let messages = db.fetch_messages(data).await?;
|
||||
BulkMessageResponse::transform(db, None, messages, Some(true))
|
||||
.await
|
||||
.map(Json)
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
use revolt_rocket_okapi::revolt_okapi::openapi3::OpenApi;
|
||||
use rocket::Route;
|
||||
|
||||
mod message_query;
|
||||
mod stats;
|
||||
|
||||
pub fn routes() -> (Vec<Route>, OpenApi) {
|
||||
openapi_get_routes_spec![stats::stats, message_query::message_query]
|
||||
openapi_get_routes_spec![stats::stats]
|
||||
}
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
use revolt_database::{AccountStrike, Database};
|
||||
use revolt_models::v0::{AccountStrike as AccountStrikeModel, DataCreateStrike};
|
||||
use revolt_quark::models::User;
|
||||
use revolt_quark::{Error, Result};
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::State;
|
||||
|
||||
/// # Create Strike
|
||||
///
|
||||
/// Create a new account strike
|
||||
#[openapi(tag = "User Safety")]
|
||||
#[post("/strikes", data = "<data>")]
|
||||
pub async fn create_strike(
|
||||
db: &State<Database>,
|
||||
user: User,
|
||||
data: Json<DataCreateStrike>,
|
||||
) -> Result<Json<AccountStrikeModel>> {
|
||||
// Must be privileged for this route
|
||||
if !user.privileged {
|
||||
return Err(Error::NotPrivileged);
|
||||
}
|
||||
|
||||
let data = data.into_inner();
|
||||
let target = db
|
||||
.fetch_user(&data.user_id)
|
||||
.await
|
||||
.map_err(Error::from_core)?;
|
||||
|
||||
AccountStrike::create(db, target.id, data.reason, user.id)
|
||||
.await
|
||||
.map(|strike| strike.into())
|
||||
.map(Json)
|
||||
.map_err(Error::from_core)
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
use revolt_database::Database;
|
||||
use revolt_quark::models::User;
|
||||
use revolt_quark::{Error, Result};
|
||||
use rocket::State;
|
||||
|
||||
/// # Delete Strike
|
||||
///
|
||||
/// Delete a strike by its ID
|
||||
#[openapi(tag = "User Safety")]
|
||||
#[delete("/strikes/<strike_id>")]
|
||||
pub async fn delete_strike(db: &State<Database>, user: User, strike_id: String) -> Result<()> {
|
||||
// Must be privileged for this route
|
||||
if !user.privileged {
|
||||
return Err(Error::NotPrivileged);
|
||||
}
|
||||
|
||||
let strike = db
|
||||
.fetch_account_strike(&strike_id)
|
||||
.await
|
||||
.map_err(Error::from_core)?;
|
||||
|
||||
strike.delete(db).await.map_err(Error::from_core)
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
use revolt_quark::{
|
||||
models::{
|
||||
report::{PartialReport, ReportStatus},
|
||||
Report, User,
|
||||
},
|
||||
Db, Error, Ref, Result,
|
||||
};
|
||||
use rocket::serde::json::Json;
|
||||
use serde::Deserialize;
|
||||
use validator::Validate;
|
||||
|
||||
/// # Report Data
|
||||
#[derive(Validate, Deserialize, JsonSchema)]
|
||||
pub struct DataEditReport {
|
||||
/// New report status
|
||||
status: Option<ReportStatus>,
|
||||
/// Report notes
|
||||
notes: Option<String>,
|
||||
}
|
||||
|
||||
/// # Edit Report
|
||||
///
|
||||
/// Edit a report.
|
||||
#[openapi(tag = "User Safety")]
|
||||
#[patch("/reports/<report>", data = "<edit>")]
|
||||
pub async fn edit_report(
|
||||
db: &Db,
|
||||
user: User,
|
||||
report: Ref,
|
||||
edit: Json<DataEditReport>,
|
||||
) -> Result<Json<Report>> {
|
||||
// Must be privileged for this route
|
||||
if !user.privileged {
|
||||
return Err(Error::NotPrivileged);
|
||||
}
|
||||
|
||||
// Validate data
|
||||
let edit = edit.into_inner();
|
||||
edit.validate()
|
||||
.map_err(|error| Error::FailedValidation { error })?;
|
||||
|
||||
// Create and apply update to report
|
||||
let mut report = report.as_report(db).await?;
|
||||
report
|
||||
.update(
|
||||
db,
|
||||
PartialReport {
|
||||
status: edit.status,
|
||||
notes: edit.notes,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(Json(report))
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
use revolt_database::{Database, PartialAccountStrike};
|
||||
use revolt_models::v0::DataEditAccountStrike;
|
||||
use revolt_quark::models::User;
|
||||
use revolt_quark::{Error, Result};
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::State;
|
||||
|
||||
/// # Edit Strike
|
||||
///
|
||||
/// Edit a strike by its ID
|
||||
#[openapi(tag = "User Safety")]
|
||||
#[post("/strikes/<strike_id>", data = "<data>")]
|
||||
pub async fn edit_strike(
|
||||
db: &State<Database>,
|
||||
user: User,
|
||||
strike_id: String,
|
||||
data: Json<DataEditAccountStrike>,
|
||||
) -> Result<()> {
|
||||
// Must be privileged for this route
|
||||
if !user.privileged {
|
||||
return Err(Error::NotPrivileged);
|
||||
}
|
||||
|
||||
let mut strike = db
|
||||
.fetch_account_strike(&strike_id)
|
||||
.await
|
||||
.map_err(Error::from_core)?;
|
||||
|
||||
strike
|
||||
.update(
|
||||
db,
|
||||
PartialAccountStrike {
|
||||
reason: Some(data.0.reason),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
.map_err(Error::from_core)
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
use revolt_quark::models::{Report, User};
|
||||
use revolt_quark::{Db, Error, Result};
|
||||
use rocket::serde::json::Json;
|
||||
|
||||
/// # Fetch Report
|
||||
///
|
||||
/// Fetch a report by its ID
|
||||
#[openapi(tag = "User Safety")]
|
||||
#[get("/report/<id>")]
|
||||
pub async fn fetch_report(db: &Db, user: User, id: String) -> Result<Json<Report>> {
|
||||
// Must be privileged for this route
|
||||
if !user.privileged {
|
||||
return Err(Error::NotPrivileged);
|
||||
}
|
||||
|
||||
db.fetch_report(&id).await.map(Json)
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
use revolt_quark::models::report::{ReportStatus, ReportStatusString, ReportedContent};
|
||||
use revolt_quark::models::{Report, User};
|
||||
use revolt_quark::{Db, Error, Result};
|
||||
use rocket::serde::json::Json;
|
||||
use serde::Deserialize;
|
||||
|
||||
/// # Query Parameters
|
||||
#[derive(Deserialize, JsonSchema, FromForm)]
|
||||
pub struct OptionsFetchReports {
|
||||
/// Find reports against messages, servers, or users
|
||||
content_id: Option<String>,
|
||||
|
||||
/// Find reports created by user
|
||||
author_id: Option<String>,
|
||||
|
||||
/// Report status to include in search
|
||||
status: Option<ReportStatusString>,
|
||||
}
|
||||
|
||||
/// # Fetch Reports
|
||||
///
|
||||
/// Fetch all available reports
|
||||
#[openapi(tag = "User Safety")]
|
||||
#[get("/reports?<options..>")]
|
||||
pub async fn fetch_reports(
|
||||
db: &Db,
|
||||
user: User,
|
||||
options: OptionsFetchReports,
|
||||
) -> Result<Json<Vec<Report>>> {
|
||||
// Must be privileged for this route
|
||||
if !user.privileged {
|
||||
return Err(Error::NotPrivileged);
|
||||
}
|
||||
|
||||
let mut reports = db.fetch_reports().await?;
|
||||
|
||||
if let Some(content_id) = options.content_id {
|
||||
reports.retain(|report| match &report.content {
|
||||
ReportedContent::Message { id, .. }
|
||||
| ReportedContent::Server { id, .. }
|
||||
| ReportedContent::User { id, .. } => id == &content_id,
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(author_id) = options.author_id {
|
||||
reports.retain(|report| report.author_id == author_id);
|
||||
}
|
||||
|
||||
if let Some(status) = options.status {
|
||||
reports.retain(|report| {
|
||||
matches!(
|
||||
(&status, &report.status),
|
||||
(ReportStatusString::Created, ReportStatus::Created { .. })
|
||||
| (ReportStatusString::Rejected, ReportStatus::Rejected { .. })
|
||||
| (ReportStatusString::Resolved, ReportStatus::Resolved { .. })
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
Ok(Json(reports))
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use revolt_quark::models::snapshot::{SnapshotContent, SnapshotWithContext};
|
||||
use revolt_quark::models::{Channel, User};
|
||||
use revolt_quark::{Db, Error, Result};
|
||||
use rocket::serde::json::Json;
|
||||
|
||||
/// # Fetch Snapshots
|
||||
///
|
||||
/// Fetch a snapshots for a given report
|
||||
#[openapi(tag = "User Safety")]
|
||||
#[get("/snapshot/<report_id>")]
|
||||
pub async fn fetch_snapshots(
|
||||
db: &Db,
|
||||
user: User,
|
||||
report_id: String,
|
||||
) -> Result<Json<Vec<SnapshotWithContext>>> {
|
||||
// Must be privileged for this route
|
||||
if !user.privileged {
|
||||
return Err(Error::NotPrivileged);
|
||||
}
|
||||
|
||||
// Fetch snapshots
|
||||
let snapshots = db.fetch_snapshots(&report_id).await?;
|
||||
let mut result = vec![];
|
||||
|
||||
for snapshot in snapshots {
|
||||
// Resolve and fetch IDs of associated content
|
||||
let mut user_ids: HashSet<&str> = HashSet::new();
|
||||
let mut channel_ids: HashSet<&str> = HashSet::new();
|
||||
|
||||
match &snapshot.content {
|
||||
SnapshotContent::Message {
|
||||
prior_context,
|
||||
leading_context,
|
||||
message,
|
||||
} => {
|
||||
for msg in prior_context {
|
||||
user_ids.insert(&msg.author);
|
||||
}
|
||||
|
||||
for msg in leading_context {
|
||||
user_ids.insert(&msg.author);
|
||||
}
|
||||
|
||||
user_ids.insert(&message.author);
|
||||
channel_ids.insert(&message.channel);
|
||||
}
|
||||
SnapshotContent::User(user) => {
|
||||
user_ids.insert(&user.id);
|
||||
}
|
||||
SnapshotContent::Server(server) => {
|
||||
for channel in &server.channels {
|
||||
channel_ids.insert(channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Collect user and channel IDs
|
||||
let user_ids: Vec<String> = user_ids.into_iter().map(|s| s.to_owned()).collect();
|
||||
let channel_ids: Vec<String> = channel_ids.into_iter().map(|s| s.to_owned()).collect();
|
||||
|
||||
// Fetch users and channels
|
||||
let users = db.fetch_users(&user_ids).await?;
|
||||
let channels = db.fetch_channels(&channel_ids).await?;
|
||||
|
||||
// Pull out first server from channels if possible
|
||||
let server = if let Some(server_id) = channels.iter().find_map(|channel| match channel {
|
||||
Channel::TextChannel { server, .. } => Some(server),
|
||||
_ => None,
|
||||
}) {
|
||||
Some(db.fetch_server(server_id).await?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Return snapshot with context
|
||||
result.push(SnapshotWithContext {
|
||||
snapshot,
|
||||
users,
|
||||
channels,
|
||||
server,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(Json(result))
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
use revolt_database::Database;
|
||||
use revolt_models::v0::AccountStrike;
|
||||
use revolt_quark::models::User;
|
||||
use revolt_quark::{Error, Result};
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::State;
|
||||
|
||||
/// # Fetch Strikes
|
||||
///
|
||||
/// Fetch strikes for a user by their ID
|
||||
#[openapi(tag = "User Safety")]
|
||||
#[get("/strikes/<user_id>")]
|
||||
pub async fn fetch_strikes(
|
||||
db: &State<Database>,
|
||||
user: User,
|
||||
user_id: String,
|
||||
) -> Result<Json<Vec<AccountStrike>>> {
|
||||
// Must be privileged for this route
|
||||
if !user.privileged {
|
||||
return Err(Error::NotPrivileged);
|
||||
}
|
||||
|
||||
db.fetch_account_strikes_by_user(&user_id)
|
||||
.await
|
||||
.map(|v| v.into_iter().map(|e| e.into()).collect())
|
||||
.map(Json)
|
||||
.map_err(Error::from_core)
|
||||
}
|
||||
@@ -1,31 +1,11 @@
|
||||
use revolt_rocket_okapi::revolt_okapi::openapi3::OpenApi;
|
||||
use rocket::Route;
|
||||
|
||||
mod edit_report;
|
||||
mod fetch_report;
|
||||
mod fetch_reports;
|
||||
mod report_content;
|
||||
|
||||
mod fetch_snapshots;
|
||||
|
||||
mod create_strike;
|
||||
mod delete_strike;
|
||||
mod edit_strike;
|
||||
mod fetch_strikes;
|
||||
|
||||
pub fn routes() -> (Vec<Route>, OpenApi) {
|
||||
openapi_get_routes_spec![
|
||||
// Reports
|
||||
edit_report::edit_report,
|
||||
fetch_report::fetch_report,
|
||||
fetch_reports::fetch_reports,
|
||||
report_content::report_content,
|
||||
// Snapshots
|
||||
fetch_snapshots::fetch_snapshots,
|
||||
// Strikes
|
||||
create_strike::create_strike,
|
||||
fetch_strikes::fetch_strikes,
|
||||
edit_strike::edit_strike,
|
||||
delete_strike::delete_strike
|
||||
]
|
||||
}
|
||||
|
||||
+14
-12
@@ -1,13 +1,13 @@
|
||||
[package]
|
||||
name = "revolt-quark"
|
||||
version = "0.6.5"
|
||||
version = "0.6.6"
|
||||
edition = "2021"
|
||||
license = "AGPL-3.0-or-later"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[features]
|
||||
mongo = [ "mongodb" ]
|
||||
mongo = ["mongodb"]
|
||||
rocket_impl = [
|
||||
"rocket",
|
||||
"rocket_empty",
|
||||
@@ -18,11 +18,11 @@ rocket_impl = [
|
||||
|
||||
"authifier/database-mongodb",
|
||||
"authifier/rocket_impl",
|
||||
"authifier/okapi_impl"
|
||||
"authifier/okapi_impl",
|
||||
]
|
||||
|
||||
test = [ "async-std", "mongo", "mongodb/async-std-runtime", "rocket_impl" ]
|
||||
default = [ "test" ]
|
||||
test = ["async-std", "mongo", "mongodb/async-std-runtime", "rocket_impl"]
|
||||
default = ["test"]
|
||||
|
||||
[dependencies]
|
||||
# Serialisation
|
||||
@@ -39,7 +39,7 @@ bson = { version = "2.1.0", features = ["chrono-0_4"] }
|
||||
# Spec Generation
|
||||
schemars = "0.8.8"
|
||||
revolt_okapi = "0.9.1"
|
||||
revolt_rocket_okapi = { version = "0.9.1", features = [ "swagger" ] }
|
||||
revolt_rocket_okapi = { version = "0.9.1", features = ["swagger"] }
|
||||
|
||||
# Databases
|
||||
redis-kiss = { version = "0.1.4" }
|
||||
@@ -81,18 +81,20 @@ web-push = "0.7.2"
|
||||
|
||||
# Implementations
|
||||
rocket_http = { optional = true, version = "0.5.0-rc.2" }
|
||||
rocket = { optional = true, version = "0.5.0-rc.2", default-features = false, features = ["json"] }
|
||||
rocket_empty = { version = "0.1.1", optional = true, features = [ "schema" ] }
|
||||
rocket = { optional = true, version = "0.5.0-rc.2", default-features = false, features = [
|
||||
"json",
|
||||
] }
|
||||
rocket_empty = { version = "0.1.1", optional = true, features = ["schema"] }
|
||||
rocket_cors = { optional = true, git = "https://github.com/lawliet89/rocket_cors", rev = "c17e8145baa4790319fdb6a473e465b960f55e7c" }
|
||||
|
||||
# Authifier
|
||||
authifier = { version = "1.0.7", features = [ "async-std-runtime" ] }
|
||||
authifier = { version = "1.0.7", features = ["async-std-runtime"] }
|
||||
|
||||
# Sentry
|
||||
sentry = "0.25.0"
|
||||
sentry = "0.31.5"
|
||||
|
||||
# Core
|
||||
revolt-result = { path = "../core/result", features = [ "serde", "schemas" ] }
|
||||
revolt-presence = { path = "../core/presence", features = [ "redis-is-patched" ] }
|
||||
revolt-result = { path = "../core/result", features = ["serde", "schemas"] }
|
||||
revolt-presence = { path = "../core/presence", features = ["redis-is-patched"] }
|
||||
revolt-database = { path = "../core/database" }
|
||||
revolt-models = { path = "../core/models" }
|
||||
|
||||
@@ -269,7 +269,6 @@ pub struct DataMessageSend {
|
||||
#[validate(length(min = 0, max = 2000))]
|
||||
pub content: Option<String>,
|
||||
/// Attachments to include in message
|
||||
|
||||
pub attachments: Option<Vec<String>>,
|
||||
/// Messages to reply to
|
||||
pub replies: Option<Vec<Reply>>,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/// Configure logging and common Rust variables
|
||||
pub fn setup_logging(release: &'static str) -> sentry::ClientInitGuard {
|
||||
pub fn setup_logging(release: &'static str) -> Option<sentry::ClientInitGuard> {
|
||||
dotenv::dotenv().ok();
|
||||
|
||||
if std::env::var("RUST_LOG").is_err() {
|
||||
@@ -13,13 +13,17 @@ pub fn setup_logging(release: &'static str) -> sentry::ClientInitGuard {
|
||||
pretty_env_logger::init();
|
||||
info!("Starting {release}");
|
||||
|
||||
sentry::init((
|
||||
"https://d1d2a6f15c6245a987c532bbbcb30a04@glitchtip.insert.moe/2",
|
||||
sentry::ClientOptions {
|
||||
release: Some(release.into()),
|
||||
..Default::default()
|
||||
},
|
||||
))
|
||||
if let Ok(dsn) = std::env::var("SENTRY_DSN") {
|
||||
Some(sentry::init((
|
||||
dsn,
|
||||
sentry::ClientOptions {
|
||||
release: Some(release.into()),
|
||||
..Default::default()
|
||||
},
|
||||
)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
|
||||
Reference in New Issue
Block a user