merge: branch 'refactor/split-project-into-core-crates'

This commit is contained in:
Paul Makles
2023-04-22 21:13:45 +01:00
56 changed files with 1624 additions and 272 deletions

View File

@@ -2,7 +2,6 @@ name: Rust build, test, and generate specification
on:
push:
branches: [master]
pull_request:
branches: [master]
@@ -27,34 +26,40 @@ jobs:
with:
command: build
- name: Run services in background
run: |
docker-compose -f docker-compose.db.yml up -d
- name: Run cargo test
uses: actions-rs/cargo@v1
with:
command: test
- name: Run cargo test (with MongoDB)
uses: actions-rs/cargo@v1
env:
MONGODB: mongodb://localhost
with:
command: test
- name: Copy .env.example
if: github.event_name != 'pull_request'
if: github.event_name != 'pull_request' && github.ref_name == 'master'
run: |
cp .env.example .env
- name: Run services in background
if: github.event_name != 'pull_request'
run: |
docker-compose -f docker-compose.db.yml up -d
- name: Start API in background
if: github.event_name != 'pull_request'
if: github.event_name != 'pull_request' && github.ref_name == 'master'
run: |
cargo run --bin revolt-delta &
- name: Wait for API to go up
if: github.event_name != 'pull_request'
if: github.event_name != 'pull_request' && github.ref_name == 'master'
uses: nev7n/wait_for_response@v1
with:
url: "http://localhost:8000/"
- name: Checkout API repository
if: github.event_name != 'pull_request'
if: github.event_name != 'pull_request' && github.ref_name == 'master'
uses: actions/checkout@v3
with:
repository: revoltchat/api
@@ -62,11 +67,11 @@ jobs:
token: ${{ secrets.PAT }}
- name: Download OpenAPI specification
if: github.event_name != 'pull_request'
if: github.event_name != 'pull_request' && github.ref_name == 'master'
run: curl http://localhost:8000/openapi.json -o api/OpenAPI.json
- name: Commit changes
if: github.event_name != 'pull_request'
if: github.event_name != 'pull_request' && github.ref_name == 'master'
uses: EndBug/add-and-commit@v4
with:
cwd: "api"

194
Cargo.lock generated
View File

@@ -105,7 +105,7 @@ version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5"
dependencies = [
"quote 1.0.18",
"quote 1.0.26",
"syn 1.0.107",
]
@@ -199,20 +199,20 @@ dependencies = [
[[package]]
name = "async-recursion"
version = "1.0.0"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cda8f4bcc10624c4e85bc66b3f452cca98cfa5ca002dc83a16aad2367641bea"
checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba"
dependencies = [
"proc-macro2",
"quote 1.0.18",
"syn 1.0.107",
"quote 1.0.26",
"syn 2.0.15",
]
[[package]]
name = "async-std"
version = "1.11.0"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52580991739c5cdb36cde8b2a516371c0a3b70dda36d916cc08b82372916808c"
checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d"
dependencies = [
"async-attributes",
"async-channel",
@@ -229,7 +229,6 @@ dependencies = [
"kv-log-macro",
"log",
"memchr",
"num_cpus",
"once_cell",
"pin-project-lite 0.2.9",
"pin-utils",
@@ -269,7 +268,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27"
dependencies = [
"proc-macro2",
"quote 1.0.18",
"quote 1.0.26",
"syn 1.0.107",
]
@@ -286,7 +285,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96cf8829f67d2eab0b2dfa42c5d0ef737e0724e4a82b01b3e292456202b19716"
dependencies = [
"proc-macro2",
"quote 1.0.18",
"quote 1.0.26",
"syn 1.0.107",
]
@@ -715,7 +714,7 @@ version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f877be4f7c9f246b183111634f75baa039715e3f46ce860677d3b19a69fb229c"
dependencies = [
"quote 1.0.18",
"quote 1.0.26",
"syn 1.0.107",
]
@@ -747,7 +746,7 @@ dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote 1.0.18",
"quote 1.0.26",
"strsim",
"syn 1.0.107",
]
@@ -759,7 +758,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835"
dependencies = [
"darling_core",
"quote 1.0.18",
"quote 1.0.26",
"syn 1.0.107",
]
@@ -808,7 +807,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
dependencies = [
"proc-macro2",
"quote 1.0.18",
"quote 1.0.26",
"syn 1.0.107",
]
@@ -829,7 +828,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "123c73e7a6e51b05c75fe1a1b2f4e241399ea5740ed810b0e3e6cacd9db5e7b2"
dependencies = [
"devise_core",
"quote 1.0.18",
"quote 1.0.26",
]
[[package]]
@@ -841,7 +840,7 @@ dependencies = [
"bitflags",
"proc-macro2",
"proc-macro2-diagnostics",
"quote 1.0.18",
"quote 1.0.26",
"syn 1.0.107",
]
@@ -909,7 +908,7 @@ checksum = "21cdad81446a7f7dc43f6a77409efeb9733d2fa65553efef6018ef257c959b73"
dependencies = [
"heck",
"proc-macro2",
"quote 1.0.18",
"quote 1.0.26",
"syn 1.0.107",
]
@@ -929,7 +928,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "828de45d0ca18782232dfb8f3ea9cc428e8ced380eb26a520baaacfc70de39ce"
dependencies = [
"proc-macro2",
"quote 1.0.18",
"quote 1.0.26",
"syn 1.0.107",
]
@@ -1113,7 +1112,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512"
dependencies = [
"proc-macro2",
"quote 1.0.18",
"quote 1.0.26",
"syn 1.0.107",
]
@@ -1204,7 +1203,7 @@ checksum = "e45727250e75cc04ff2846a66397da8ef2b3db8e40e0cef4df67950a07621eb9"
dependencies = [
"proc-macro-error",
"proc-macro2",
"quote 1.0.18",
"quote 1.0.26",
"syn 1.0.107",
]
@@ -1842,7 +1841,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49e30813093f757be5cf21e50389a24dc7dbb22c49f23b7e8f51d69b508a5ffa"
dependencies = [
"proc-macro2",
"quote 1.0.18",
"quote 1.0.26",
"syn 1.0.107",
]
@@ -2110,7 +2109,7 @@ checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce"
dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote 1.0.18",
"quote 1.0.26",
"syn 1.0.107",
]
@@ -2163,7 +2162,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c"
dependencies = [
"proc-macro2",
"quote 1.0.18",
"quote 1.0.26",
"syn 1.0.107",
]
@@ -2186,15 +2185,6 @@ dependencies = [
"vcpkg",
]
[[package]]
name = "optional_struct"
version = "0.2.0"
source = "git+https://github.com/insertish/OptionalStruct?rev=ee56427cee1f007839825d93d07fffd5a5e038c7#ee56427cee1f007839825d93d07fffd5a5e038c7"
dependencies = [
"quote 0.3.15",
"syn 0.11.11",
]
[[package]]
name = "os_info"
version = "3.4.0"
@@ -2268,7 +2258,7 @@ checksum = "82a5ca643c2303ecb740d506539deba189e16f2754040a42901cd8105d0282d0"
dependencies = [
"proc-macro2",
"proc-macro2-diagnostics",
"quote 1.0.18",
"quote 1.0.26",
"syn 1.0.107",
]
@@ -2306,7 +2296,7 @@ dependencies = [
"pest",
"pest_meta",
"proc-macro2",
"quote 1.0.18",
"quote 1.0.26",
"syn 1.0.107",
]
@@ -2337,7 +2327,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb"
dependencies = [
"proc-macro2",
"quote 1.0.18",
"quote 1.0.26",
"syn 1.0.107",
]
@@ -2424,7 +2414,7 @@ checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote 1.0.18",
"quote 1.0.26",
"syn 1.0.107",
"version_check",
]
@@ -2436,15 +2426,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote 1.0.18",
"quote 1.0.26",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.50"
version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2"
checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
dependencies = [
"unicode-ident",
]
@@ -2456,7 +2446,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bf29726d67464d49fa6224a1d07936a8c08bb3fba727c7493f6cf1616fdaada"
dependencies = [
"proc-macro2",
"quote 1.0.18",
"quote 1.0.26",
"syn 1.0.107",
"version_check",
"yansi",
@@ -2482,9 +2472,9 @@ checksum = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
[[package]]
name = "quote"
version = "1.0.18"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1"
checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
dependencies = [
"proc-macro2",
]
@@ -2722,7 +2712,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a043824e29c94169374ac5183ac0ed43f5724dc4556b19568007486bd840fa1f"
dependencies = [
"proc-macro2",
"quote 1.0.18",
"quote 1.0.26",
"syn 1.0.107",
]
@@ -2817,12 +2807,31 @@ dependencies = [
"log",
"once_cell",
"querystring",
"revolt-presence",
"revolt-quark",
"rmp-serde",
"serde",
"serde_json",
]
[[package]]
name = "revolt-database"
version = "0.0.1"
dependencies = [
"async-recursion",
"async-std",
"async-trait",
"authifier",
"futures",
"log",
"mongodb",
"nanoid",
"revolt-result",
"revolt_optional_struct",
"serde",
"serde_json",
]
[[package]]
name = "revolt-delta"
version = "0.5.19"
@@ -2845,6 +2854,8 @@ dependencies = [
"once_cell",
"regex",
"reqwest",
"revolt-database",
"revolt-models",
"revolt-quark",
"revolt_rocket_okapi",
"rocket",
@@ -2859,6 +2870,26 @@ dependencies = [
"vergen",
]
[[package]]
name = "revolt-models"
version = "0.0.1"
dependencies = [
"revolt-database",
"schemars",
"serde",
]
[[package]]
name = "revolt-presence"
version = "0.0.1"
dependencies = [
"async-std",
"log",
"once_cell",
"rand 0.8.5",
"redis-kiss",
]
[[package]]
name = "revolt-quark"
version = "0.5.19"
@@ -2886,13 +2917,15 @@ dependencies = [
"nanoid",
"num_enum",
"once_cell",
"optional_struct",
"pretty_env_logger",
"rand 0.8.5",
"redis-kiss",
"regex",
"reqwest",
"revolt-presence",
"revolt-result",
"revolt_okapi",
"revolt_optional_struct",
"revolt_rocket_okapi",
"rocket",
"rocket_cors",
@@ -2907,6 +2940,14 @@ dependencies = [
"web-push",
]
[[package]]
name = "revolt-result"
version = "0.0.1"
dependencies = [
"schemars",
"serde",
]
[[package]]
name = "revolt_okapi"
version = "0.9.1"
@@ -2919,6 +2960,16 @@ dependencies = [
"serde_json",
]
[[package]]
name = "revolt_optional_struct"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d342739658623fe9d72b42f7a36ba19094d1d9bf9b423c3e337ab203c15d53e"
dependencies = [
"quote 0.3.15",
"syn 0.11.11",
]
[[package]]
name = "revolt_rocket_okapi"
version = "0.9.1"
@@ -2943,7 +2994,7 @@ checksum = "cc6620569d8ac8f0a1690fcca13f488503807a60e96ebf729749b59aca1dbef9"
dependencies = [
"darling",
"proc-macro2",
"quote 1.0.18",
"quote 1.0.26",
"rocket_http",
"syn 1.0.107",
]
@@ -3050,7 +3101,7 @@ dependencies = [
"glob",
"indexmap",
"proc-macro2",
"quote 1.0.18",
"quote 1.0.26",
"rocket_http",
"syn 1.0.107",
"unicode-xid 0.2.3",
@@ -3225,7 +3276,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af4d7e1b012cb3d9129567661a63755ea4b8a7386d339dc945ae187e403c6743"
dependencies = [
"proc-macro2",
"quote 1.0.18",
"quote 1.0.26",
"serde_derive_internals",
"syn 1.0.107",
]
@@ -3378,9 +3429,9 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.152"
version = "1.0.160"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c"
dependencies = [
"serde_derive",
]
@@ -3396,13 +3447,13 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.152"
version = "1.0.160"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df"
dependencies = [
"proc-macro2",
"quote 1.0.18",
"syn 1.0.107",
"quote 1.0.26",
"syn 2.0.15",
]
[[package]]
@@ -3412,15 +3463,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c"
dependencies = [
"proc-macro2",
"quote 1.0.18",
"quote 1.0.26",
"syn 1.0.107",
]
[[package]]
name = "serde_json"
version = "1.0.91"
version = "1.0.96"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883"
checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1"
dependencies = [
"indexmap",
"itoa 1.0.2",
@@ -3458,7 +3509,7 @@ checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082"
dependencies = [
"darling",
"proc-macro2",
"quote 1.0.18",
"quote 1.0.26",
"syn 1.0.107",
]
@@ -3633,7 +3684,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
dependencies = [
"proc-macro2",
"quote 1.0.18",
"quote 1.0.26",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
dependencies = [
"proc-macro2",
"quote 1.0.26",
"unicode-ident",
]
@@ -3705,7 +3767,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
dependencies = [
"proc-macro2",
"quote 1.0.18",
"quote 1.0.26",
"syn 1.0.107",
]
@@ -3814,7 +3876,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7"
dependencies = [
"proc-macro2",
"quote 1.0.18",
"quote 1.0.26",
"syn 1.0.107",
]
@@ -3950,7 +4012,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc6b8ad3567499f98a1db7a752b07a7c8c7c7c34c332ec00effb2b0027974b7c"
dependencies = [
"proc-macro2",
"quote 1.0.18",
"quote 1.0.26",
"syn 1.0.107",
]
@@ -4080,7 +4142,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89851716b67b937e393b3daa8423e67ddfc4bbbf1654bcf05488e95e0828db0c"
dependencies = [
"proc-macro2",
"quote 1.0.18",
"quote 1.0.26",
"syn 1.0.107",
]
@@ -4290,7 +4352,7 @@ dependencies = [
"lazy_static",
"proc-macro-error",
"proc-macro2",
"quote 1.0.18",
"quote 1.0.26",
"regex",
"syn 1.0.107",
"validator_types",
@@ -4400,7 +4462,7 @@ dependencies = [
"lazy_static",
"log",
"proc-macro2",
"quote 1.0.18",
"quote 1.0.26",
"syn 1.0.107",
"wasm-bindgen-shared",
]
@@ -4423,7 +4485,7 @@ version = "0.2.80"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5"
dependencies = [
"quote 1.0.18",
"quote 1.0.26",
"wasm-bindgen-macro-support",
]
@@ -4434,7 +4496,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b"
dependencies = [
"proc-macro2",
"quote 1.0.18",
"quote 1.0.26",
"syn 1.0.107",
"wasm-bindgen-backend",
"wasm-bindgen-shared",

View File

@@ -1,5 +1,5 @@
[workspace]
members = ["crates/*"]
members = ["crates/delta", "crates/bonfire", "crates/quark", "crates/core/*"]
[patch.crates-io]
# mobc-redis = { git = "https://github.com/insertish/mobc", rev = "8b880bb59f2ba80b4c7bc40c649c113d8857a186" }

7
clippy.toml Normal file
View File

@@ -0,0 +1,7 @@
disallowed-methods = [
# Shouldn't need to access these directly
"revolt_database::models::bots::model::Bot::remove_field",
# Prefer to use Object::delete()
"revolt_database::models::bots::ops::AbstractBots::update_bot",
]

View File

@@ -26,3 +26,6 @@ serde = "1.0.136"
futures = "0.3.21"
async-tungstenite = { version = "0.17.0", features = ["async-std-runtime"] }
async-std = { version = "1.8.0", features = ["tokio1", "tokio02", "attributes"] }
# core
revolt-presence = { path = "../core/presence" }

View File

@@ -1,7 +1,7 @@
use std::env;
use async_std::net::TcpListener;
use revolt_quark::presence::presence_clear_region;
use revolt_presence::clear_region;
#[macro_use]
extern crate log;
@@ -18,7 +18,7 @@ async fn main() {
database::connect().await;
// Clean up the current region information.
presence_clear_region(None).await;
clear_region(None).await;
// Setup a TCP listener to accept WebSocket connections on.
// By default, we bind to port 9000 on all interfaces.

View File

@@ -1,6 +1,7 @@
use std::net::SocketAddr;
use futures::{channel::oneshot, pin_mut, select, FutureExt, SinkExt, StreamExt, TryStreamExt};
use revolt_presence::{create_session, delete_session};
use revolt_quark::{
events::{
client::EventV1,
@@ -8,7 +9,6 @@ use revolt_quark::{
state::{State, SubscriptionStateChange},
},
models::{user::UserHint, User},
presence::{presence_create_session, presence_delete_session},
redis_kiss, Database,
};
@@ -69,8 +69,7 @@ pub fn spawn_client(db: &'static Database, stream: TcpStream, addr: SocketAddr)
let user_id = state.cache.user_id.clone();
// Create presence session.
let (first_session, session_id) =
presence_create_session(&user_id, 0).await;
let (first_session, session_id) = create_session(&user_id, 0).await;
// Notify socket we have authenticated.
write
@@ -225,7 +224,7 @@ pub fn spawn_client(db: &'static Database, stream: TcpStream, addr: SocketAddr)
}
// Clean up presence session.
let last_session = presence_delete_session(&user_id, session_id).await;
let last_session = delete_session(&user_id, session_id).await;
// If this was the last session, notify other users that we just went offline.
if last_session {

View File

@@ -0,0 +1,46 @@
[package]
name = "revolt-database"
version = "0.0.1"
edition = "2021"
license = "AGPL-3.0-or-later"
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" ]
# ... Other
async-std-runtime = [ "async-std" ]
# Default Features
default = [ "mongodb", "async-std-runtime" ]
[dependencies]
# Core
revolt-result = { version = "0.0.1", path = "../result" }
# Utility
log = "0.4"
nanoid = "0.4.0"
# Serialisation
serde_json = "1"
revolt_optional_struct = "0.2.0"
serde = { version = "1", features = ["derive"] }
# Database
mongodb = { optional = true, version = "2.1.0", default-features = false }
# Async Language Features
futures = "0.3.19"
async-trait = "0.1.51"
async-recursion = "1.0.4"
# Async
async-std = { version = "1.8.0", features = ["attributes"], optional = true }
# Authifier
authifier = { version = "1.0" }

View File

@@ -0,0 +1,67 @@
mod mongodb;
mod reference;
pub use self::mongodb::*;
pub use self::reference::*;
/// Database information to use to create a client
pub enum DatabaseInfo {
/// Auto-detect the database in use
Auto,
/// Auto-detect the database in use and create an empty testing database
Test(String),
/// Use the mock database
Reference,
/// Connect to MongoDB
MongoDb { uri: String, database_name: String },
/// Use existing MongoDB connection
MongoDbFromClient(::mongodb::Client, String),
}
/// Database
#[derive(Clone)]
pub enum Database {
/// Mock database
Reference(ReferenceDb),
/// MongoDB database
MongoDb(MongoDb),
}
impl DatabaseInfo {
/// Create a database client from the given database information
#[async_recursion]
pub async fn connect(self) -> Result<Database, String> {
Ok(match self {
DatabaseInfo::Auto => {
if let Ok(uri) = std::env::var("MONGODB") {
return DatabaseInfo::MongoDb {
uri,
database_name: "revolt".to_string(),
}
.connect()
.await;
}
DatabaseInfo::Reference.connect().await?
}
DatabaseInfo::Test(database_name) => {
if let Ok(uri) = std::env::var("MONGODB") {
return DatabaseInfo::MongoDb { uri, database_name }.connect().await;
}
DatabaseInfo::Reference.connect().await?
}
DatabaseInfo::Reference => Database::Reference(Default::default()),
DatabaseInfo::MongoDb { uri, database_name } => {
let client = ::mongodb::Client::with_uri_str(uri)
.await
.map_err(|_| "Failed to init db connection.".to_string())?;
Database::MongoDb(MongoDb(client, database_name))
}
DatabaseInfo::MongoDbFromClient(client, database_name) => {
Database::MongoDb(MongoDb(client, database_name))
}
})
}
}

View File

@@ -0,0 +1,243 @@
use std::collections::HashMap;
use std::ops::Deref;
use futures::StreamExt;
use mongodb::bson::{doc, to_document, Document};
use mongodb::error::Result;
use mongodb::options::{FindOneOptions, FindOptions};
use mongodb::results::{DeleteResult, InsertOneResult, UpdateResult};
use serde::de::DeserializeOwned;
use serde::Serialize;
database_derived!(
#[cfg(feature = "mongodb")]
/// MongoDB implementation
pub struct MongoDb(pub ::mongodb::Client, pub String);
);
impl Deref for MongoDb {
type Target = mongodb::Client;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[allow(dead_code)]
impl MongoDb {
/// Get the Revolt database
pub fn db(&self) -> mongodb::Database {
self.database(&self.1)
}
/// Get a collection by its name
pub fn col<T>(&self, collection: &str) -> mongodb::Collection<T> {
self.db().collection(collection)
}
/// Insert one document into a collection
pub async fn insert_one<T: Serialize>(
&self,
collection: &'static str,
document: T,
) -> Result<InsertOneResult> {
self.col::<T>(collection).insert_one(document, None).await
}
/// Count documents by projection
pub async fn count_documents(
&self,
collection: &'static str,
projection: Document,
) -> Result<u64> {
self.col::<Document>(collection)
.count_documents(projection, None)
.await
}
/// Find multiple documents in a collection with options
pub async fn find_with_options<O, T: DeserializeOwned + Unpin + Send + Sync>(
&self,
collection: &'static str,
projection: Document,
options: O,
) -> Result<Vec<T>>
where
O: Into<Option<FindOptions>>,
{
Ok(self
.col::<T>(collection)
.find(projection, options)
.await?
.filter_map(|s| async {
if cfg!(debug_assertions) {
// Hard fail on invalid documents
Some(s.unwrap())
} else {
s.ok()
}
})
.collect::<Vec<T>>()
.await)
}
/// Find multiple documents in a collection
pub async fn find<T: DeserializeOwned + Unpin + Send + Sync>(
&self,
collection: &'static str,
projection: Document,
) -> Result<Vec<T>> {
self.find_with_options(collection, projection, None).await
}
/// Find one document with options
pub async fn find_one_with_options<O, T: DeserializeOwned + Unpin + Send + Sync>(
&self,
collection: &'static str,
projection: Document,
options: O,
) -> Result<Option<T>>
where
O: Into<Option<FindOneOptions>>,
{
self.col::<T>(collection)
.find_one(projection, options)
.await
}
/// Find one document
pub async fn find_one<T: DeserializeOwned + Unpin + Send + Sync>(
&self,
collection: &'static str,
projection: Document,
) -> Result<Option<T>> {
self.find_one_with_options(collection, projection, None)
.await
}
/// Find one document by its ID
pub async fn find_one_by_id<T: DeserializeOwned + Unpin + Send + Sync>(
&self,
collection: &'static str,
id: &str,
) -> Result<Option<T>> {
self.find_one(
collection,
doc! {
"_id": id
},
)
.await
}
/// Update one document given a projection, partial document, and list of paths to unset
pub async fn update_one<P, T: Serialize>(
&self,
collection: &'static str,
projection: Document,
partial: T,
remove: Vec<&dyn IntoDocumentPath>,
prefix: P,
) -> Result<UpdateResult>
where
P: Into<Option<String>>,
{
let prefix = prefix.into();
let mut unset = doc! {};
for field in remove {
if let Some(path) = field.as_path() {
if let Some(prefix) = &prefix {
unset.insert(prefix.to_owned() + path, 1_i32);
} else {
unset.insert(path, 1_i32);
}
}
}
let query = doc! {
"$unset": unset,
"$set": if let Some(prefix) = &prefix {
to_document(&prefix_keys(&partial, prefix))
} else {
to_document(&partial)
}?
};
self.col::<Document>(collection)
.update_one(projection, query, None)
.await
}
/// Update one document given an ID, partial document, and list of paths to unset
pub async fn update_one_by_id<P, T: Serialize>(
&self,
collection: &'static str,
id: &str,
partial: T,
remove: Vec<&dyn IntoDocumentPath>,
prefix: P,
) -> Result<UpdateResult>
where
P: Into<Option<String>>,
{
self.update_one(
collection,
doc! {
"_id": id
},
partial,
remove,
prefix,
)
.await
}
/// Delete one document by the given projection
pub async fn delete_one(
&self,
collection: &'static str,
projection: Document,
) -> Result<DeleteResult> {
self.col::<Document>(collection)
.delete_one(projection, None)
.await
}
/// Delete one document by the given ID
pub async fn delete_one_by_id(
&self,
collection: &'static str,
id: &str,
) -> Result<DeleteResult> {
self.delete_one(
collection,
doc! {
"_id": id
},
)
.await
}
}
/// Just a string ID struct
#[derive(Deserialize)]
pub struct DocumentId {
#[serde(rename = "_id")]
pub id: String,
}
pub trait IntoDocumentPath: Send + Sync {
/// Create JSON key path
fn as_path(&self) -> Option<&'static str>;
}
/// Prefix keys on an arbitrary object
pub fn prefix_keys<T: Serialize>(t: &T, prefix: &str) -> HashMap<String, serde_json::Value> {
let v: String = serde_json::to_string(t).unwrap();
let v: HashMap<String, serde_json::Value> = serde_json::from_str(&v).unwrap();
v.into_iter()
.filter(|(_k, v)| !v.is_null())
.map(|(k, v)| (prefix.to_owned() + &k, v))
.collect()
}

View File

@@ -0,0 +1,13 @@
use std::{collections::HashMap, sync::Arc};
use futures::lock::Mutex;
use crate::Bot;
database_derived!(
/// Reference implementation
#[derive(Default)]
pub struct ReferenceDb {
pub bots: Arc<Mutex<HashMap<String, Bot>>>,
}
);

View File

@@ -0,0 +1,81 @@
#[macro_use]
extern crate serde;
#[macro_use]
extern crate async_recursion;
#[macro_use]
extern crate async_trait;
#[macro_use]
extern crate log;
#[macro_use]
extern crate revolt_optional_struct;
#[macro_use]
extern crate revolt_result;
#[cfg(feature = "mongodb")]
pub use mongodb;
macro_rules! database_derived {
( $( $item:item )+ ) => {
$(
#[derive(Clone)]
$item
)+
};
}
macro_rules! auto_derived {
( $( $item:item )+ ) => {
$(
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
$item
)+
};
}
macro_rules! auto_derived_partial {
( $item:item, $name:expr ) => {
#[derive(OptionalStruct, Serialize, Deserialize, Debug, Clone, Default, Eq, PartialEq)]
#[optional_derive(Serialize, Deserialize, Debug, Clone, Default, Eq, PartialEq)]
#[optional_name = $name]
#[opt_skip_serializing_none]
#[opt_some_priority]
$item
};
}
mod drivers;
pub use drivers::*;
#[cfg(test)]
macro_rules! database_test {
( | $db: ident | $test:expr ) => {
let db = $crate::DatabaseInfo::Test(format!(
"{}:{}",
file!().replace('/', "_").replace(".rs", ""),
line!()
))
.connect()
.await
.expect("Database connection failed.");
db.drop_database().await;
#[allow(clippy::redundant_closure_call)]
(|$db: $crate::Database| $test)(db.clone()).await;
db.drop_database().await
};
}
mod models;
pub use models::*;
/// Utility function to check if a boolean value is false
pub fn if_false(t: &bool) -> bool {
!t
}

View File

@@ -0,0 +1,5 @@
mod model;
mod ops;
pub use model::*;
pub use ops::*;

View File

@@ -0,0 +1,24 @@
auto_derived!(
/// Document representing migration information
pub struct MigrationInfo {
/// Unique Id
#[serde(rename = "_id")]
pub id: i32,
/// Current database revision
pub revision: i32,
}
);
#[cfg(test)]
mod tests {
#[async_std::test]
async fn migrate() {
database_test!(|db| async move {
// Initialise the database
db.migrate_database().await.unwrap();
// Migrate the existing database
db.migrate_database().await.unwrap()
});
}
}

View File

@@ -0,0 +1,12 @@
mod mongodb;
mod reference;
#[async_trait]
pub trait AbstractMigrations: Sync + Send {
#[cfg(test)]
/// Drop the database
async fn drop_database(&self);
/// Migrate the database
async fn migrate_database(&self) -> Result<(), ()>;
}

View File

@@ -1,13 +1,20 @@
use crate::{AbstractMigrations, Result};
use crate::MongoDb;
use super::super::MongoDb;
use super::AbstractMigrations;
mod init;
mod scripts;
#[async_trait]
impl AbstractMigrations for MongoDb {
async fn migrate_database(&self) -> Result<()> {
#[cfg(test)]
/// Drop the database
async fn drop_database(&self) {
self.db().drop(None).await.ok();
}
/// Migrate the database
async fn migrate_database(&self) -> Result<(), ()> {
info!("Migrating the database.");
let list = self
@@ -15,7 +22,7 @@ impl AbstractMigrations for MongoDb {
.await
.expect("Failed to fetch database names.");
if list.iter().any(|x| x == "revolt") {
if list.iter().any(|x| x == &self.1) {
scripts::migrate_database(self).await;
} else {
init::create_database(self).await;

View File

@@ -1,9 +1,8 @@
use crate::r#impl::mongo::MongoDb;
use super::scripts::LATEST_REVISION;
use mongodb::bson::doc;
use mongodb::options::CreateCollectionOptions;
use crate::mongodb::bson::doc;
use crate::mongodb::options::CreateCollectionOptions;
use crate::MongoDb;
pub async fn create_database(db: &MongoDb) {
info!("Creating database.");

View File

@@ -1,15 +1,15 @@
use std::{time::Duration, ops::BitXor};
use std::{ops::BitXor, time::Duration};
use bson::{Bson, DateTime};
use futures::StreamExt;
use mongodb::{
bson::{doc, from_bson, from_document, to_document, Document},
options::FindOptions,
use crate::{
mongodb::{
bson::{doc, from_bson, from_document, to_document, Bson, DateTime, Document},
options::FindOptions,
},
MongoDb,
};
use futures::StreamExt;
use serde::{Deserialize, Serialize};
use crate::{r#impl::mongo::MongoDb, Permission, DEFAULT_PERMISSION_SERVER};
#[derive(Serialize, Deserialize)]
struct MigrationInfo {
_id: i32,
@@ -504,7 +504,7 @@ pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 {
update.insert(
"default_permissions",
// Remove Send Message permission if it wasn't originally granted
DEFAULT_PERMISSION_SERVER.bitxor(if has_send { 0 } else { Permission::SendMessage as u64}) as i64,
(4000323584).bitxor(if has_send { 0 } else { (1 << 22) as u64 }) as i64,
);
if let Some(Bson::Document(mut roles)) = document.remove("roles") {
@@ -563,7 +563,7 @@ pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 {
doc! {
"default_permissions": {
"a": 0_i64,
"d": Permission::SendMessage as i64
"d": (1 << 22) as i64
}
},
);

View File

@@ -0,0 +1,16 @@
use crate::ReferenceDb;
use super::AbstractMigrations;
#[async_trait]
impl AbstractMigrations for ReferenceDb {
#[cfg(test)]
/// Drop the database
async fn drop_database(&self) {}
/// Migrate the database
async fn migrate_database(&self) -> Result<(), ()> {
// Here you would do your typical migrations if this was a real database.
Ok(())
}
}

View File

@@ -0,0 +1,5 @@
mod model;
mod ops;
pub use model::*;
pub use ops::*;

View File

@@ -0,0 +1,152 @@
use revolt_result::Result;
use crate::Database;
auto_derived_partial!(
/// Bot
pub struct Bot {
/// Bot Id
///
/// This equals the associated bot user's id.
#[serde(rename = "_id")]
pub id: String,
/// User Id of the bot owner
pub owner: String,
/// Token used to authenticate requests for this bot
pub token: String,
/// Whether the bot is public
/// (may be invited by anyone)
pub public: bool,
/// Whether to enable analytics
#[serde(skip_serializing_if = "crate::if_false", default)]
pub analytics: bool,
/// Whether this bot should be publicly discoverable
#[serde(skip_serializing_if = "crate::if_false", default)]
pub discoverable: bool,
/// Reserved; URL for handling interactions
#[serde(skip_serializing_if = "String::is_empty", default)]
pub interactions_url: String,
/// URL for terms of service
#[serde(skip_serializing_if = "String::is_empty", default)]
pub terms_of_service_url: String,
/// URL for privacy policy
#[serde(skip_serializing_if = "String::is_empty", default)]
pub privacy_policy_url: String,
/// Enum of bot flags
#[serde(skip_serializing_if = "Option::is_none")]
pub flags: Option<i32>,
},
"PartialBot"
);
auto_derived!(
/// Flags that may be attributed to a bot
#[repr(i32)]
pub enum BotFlags {
Verified = 1,
Official = 2,
}
/// Optional fields on bot object
pub enum FieldsBot {
Token,
InteractionsURL,
}
);
#[allow(clippy::disallowed_methods)]
impl Bot {
/// Remove a field from this object
pub fn remove_field(&mut self, field: &FieldsBot) {
match field {
FieldsBot::Token => self.token = nanoid::nanoid!(64),
FieldsBot::InteractionsURL => {
self.interactions_url = String::new();
}
}
}
/// Update this bot
pub async fn update(
&mut self,
db: &Database,
mut partial: PartialBot,
remove: Vec<FieldsBot>,
) -> Result<()> {
if remove.contains(&FieldsBot::Token) {
partial.token = Some(nanoid::nanoid!(64));
}
for field in &remove {
self.remove_field(field);
}
db.update_bot(&self.id, &partial, remove).await?;
self.apply_options(partial);
Ok(())
}
/// Delete this bot
pub async fn delete(&self, db: &Database) -> Result<()> {
// db.fetch_user(&self.id).await?.mark_deleted(db).await?;
db.delete_bot(&self.id).await
}
}
#[cfg(test)]
mod tests {
use crate::{Bot, FieldsBot, PartialBot};
#[async_std::test]
async fn crud() {
database_test!(|db| async move {
let bot_id = "bot";
let user_id = "user";
let token = "my_token";
let bot = Bot {
id: bot_id.to_string(),
owner: user_id.to_string(),
token: token.to_string(),
interactions_url: "some url".to_string(),
..Default::default()
};
db.insert_bot(&bot).await.unwrap();
let mut updated_bot = bot.clone();
updated_bot
.update(
&db,
PartialBot {
public: Some(true),
..Default::default()
},
vec![FieldsBot::Token, FieldsBot::InteractionsURL],
)
.await
.unwrap();
let fetched_bot1 = db.fetch_bot(bot_id).await.unwrap();
let fetched_bot2 = db.fetch_bot_by_token(&fetched_bot1.token).await.unwrap();
let fetched_bots = db.fetch_bots_by_user(user_id).await.unwrap();
assert!(!bot.public);
assert!(fetched_bot1.public);
assert!(!bot.interactions_url.is_empty());
assert!(fetched_bot1.interactions_url.is_empty());
assert_ne!(bot.token, fetched_bot1.token);
assert_eq!(updated_bot, fetched_bot1);
assert_eq!(fetched_bot1, fetched_bot2);
assert_eq!(fetched_bot1, fetched_bots[0]);
assert_eq!(1, db.get_number_of_bots_by_user(user_id).await.unwrap());
bot.delete(&db).await.unwrap();
assert!(db.fetch_bot(bot_id).await.is_err());
assert_eq!(0, db.get_number_of_bots_by_user(user_id).await.unwrap())
});
}
}

View File

@@ -0,0 +1,35 @@
use revolt_result::Result;
use crate::{Bot, FieldsBot, PartialBot};
mod mongodb;
mod reference;
#[async_trait]
pub trait AbstractBots: Sync + Send {
/// Fetch a bot by its id
async fn fetch_bot(&self, id: &str) -> Result<Bot>;
/// Fetch a bot by its token
async fn fetch_bot_by_token(&self, token: &str) -> Result<Bot>;
/// Insert new bot into the database
async fn insert_bot(&self, bot: &Bot) -> Result<()>;
/// Update bot with new information
async fn update_bot(
&self,
id: &str,
partial: &PartialBot,
remove: Vec<FieldsBot>,
) -> Result<()>;
/// Delete a bot from the database
async fn delete_bot(&self, id: &str) -> Result<()>;
/// Fetch bots owned by a user
async fn fetch_bots_by_user(&self, user_id: &str) -> Result<Vec<Bot>>;
/// Get the number of bots owned by a user
async fn get_number_of_bots_by_user(&self, user_id: &str) -> Result<usize>;
}

View File

@@ -0,0 +1,93 @@
use ::mongodb::bson::doc;
use revolt_result::Result;
use crate::{Bot, FieldsBot, PartialBot};
use crate::{IntoDocumentPath, MongoDb};
use super::AbstractBots;
static COL: &str = "bots";
#[async_trait]
impl AbstractBots for MongoDb {
/// Fetch a bot by its id
async fn fetch_bot(&self, id: &str) -> Result<Bot> {
query!(self, find_one_by_id, COL, id)?.ok_or_else(|| create_error!(NotFound))
}
/// Fetch a bot by its token
async fn fetch_bot_by_token(&self, token: &str) -> Result<Bot> {
query!(
self,
find_one,
COL,
doc! {
"token": token
}
)?
.ok_or_else(|| create_error!(NotFound))
}
/// Insert new bot into the database
async fn insert_bot(&self, bot: &Bot) -> Result<()> {
query!(self, insert_one, COL, &bot).map(|_| ())
}
/// Update bot with new information
async fn update_bot(
&self,
id: &str,
partial: &PartialBot,
remove: Vec<FieldsBot>,
) -> Result<()> {
query!(
self,
update_one_by_id,
COL,
id,
partial,
remove.iter().map(|x| x as &dyn IntoDocumentPath).collect(),
None
)
.map(|_| ())
}
/// Delete a bot from the database
async fn delete_bot(&self, id: &str) -> Result<()> {
query!(self, delete_one_by_id, COL, id).map(|_| ())
}
/// Fetch bots owned by a user
async fn fetch_bots_by_user(&self, user_id: &str) -> Result<Vec<Bot>> {
query!(
self,
find,
COL,
doc! {
"owner": user_id
}
)
}
/// Get the number of bots owned by a user
async fn get_number_of_bots_by_user(&self, user_id: &str) -> Result<usize> {
query!(
self,
count_documents,
COL,
doc! {
"owner": user_id
}
)
.map(|v| v as usize)
}
}
impl IntoDocumentPath for FieldsBot {
fn as_path(&self) -> Option<&'static str> {
match self {
FieldsBot::InteractionsURL => Some("interactions_url"),
FieldsBot::Token => None,
}
}
}

View File

@@ -0,0 +1,82 @@
use revolt_result::Result;
use crate::ReferenceDb;
use crate::{Bot, FieldsBot, PartialBot};
use super::AbstractBots;
#[async_trait]
impl AbstractBots for ReferenceDb {
/// Fetch a bot by its id
async fn fetch_bot(&self, id: &str) -> Result<Bot> {
let bots = self.bots.lock().await;
bots.get(id).cloned().ok_or_else(|| create_error!(NotFound))
}
/// Fetch a bot by its token
async fn fetch_bot_by_token(&self, token: &str) -> Result<Bot> {
let bots = self.bots.lock().await;
bots.values()
.find(|bot| bot.token == token)
.cloned()
.ok_or_else(|| create_error!(NotFound))
}
/// Insert new bot into the database
async fn insert_bot(&self, bot: &Bot) -> Result<()> {
let mut bots = self.bots.lock().await;
if bots.contains_key(&bot.id) {
Err(create_database_error!("insert", "bot"))
} else {
bots.insert(bot.id.to_string(), bot.clone());
Ok(())
}
}
/// Update bot with new information
async fn update_bot(
&self,
id: &str,
partial: &PartialBot,
remove: Vec<FieldsBot>,
) -> Result<()> {
let mut bots = self.bots.lock().await;
if let Some(bot) = bots.get_mut(id) {
for field in remove {
#[allow(clippy::disallowed_methods)]
bot.remove_field(&field);
}
bot.apply_options(partial.clone());
Ok(())
} else {
Err(create_error!(NotFound))
}
}
/// Delete a bot from the database
async fn delete_bot(&self, id: &str) -> Result<()> {
let mut bots = self.bots.lock().await;
if bots.remove(id).is_some() {
Ok(())
} else {
Err(create_error!(NotFound))
}
}
/// Fetch bots owned by a user
async fn fetch_bots_by_user(&self, user_id: &str) -> Result<Vec<Bot>> {
let bots = self.bots.lock().await;
Ok(bots
.values()
.filter(|bot| bot.owner == user_id)
.cloned()
.collect())
}
/// Get the number of bots owned by a user
async fn get_number_of_bots_by_user(&self, user_id: &str) -> Result<usize> {
let bots = self.bots.lock().await;
Ok(bots.values().filter(|bot| bot.owner == user_id).count())
}
}

View File

@@ -0,0 +1,26 @@
mod admin_migrations;
mod bots;
pub use admin_migrations::*;
pub use bots::*;
use crate::{Database, MongoDb, ReferenceDb};
pub trait AbstractDatabase:
Sync + Send + admin_migrations::AbstractMigrations + bots::AbstractBots
{
}
impl AbstractDatabase for ReferenceDb {}
impl AbstractDatabase for MongoDb {}
impl std::ops::Deref for Database {
type Target = dyn AbstractDatabase;
fn deref(&self) -> &Self::Target {
match &self {
Database::Reference(dummy) => dummy,
Database::MongoDb(mongo) => mongo,
}
}
}

View File

@@ -0,0 +1,26 @@
[package]
name = "revolt-models"
version = "0.0.1"
edition = "2021"
license = "AGPL-3.0-or-later"
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" ]
schemas = [ "dep:schemars" ]
from_database = [ "revolt-database" ]
default = [ "serde", "from_database" ]
[dependencies]
# Repo
revolt-database = { version = "0.0.1", path = "../database", optional = true }
# Serialisation
serde = { version = "1", features = ["derive"], optional = true }
# Spec Generation
schemars = { version = "0.8.8", optional = true }

View File

@@ -0,0 +1,103 @@
auto_derived!(
/// # Bot
pub struct Bot {
/// Bot Id
#[serde(rename = "_id")]
pub id: String,
/// User Id of the bot owner
#[serde(rename = "owner")]
pub owner_id: String,
/// Token used to authenticate requests for this bot
pub token: String,
/// Whether the bot is public
/// (may be invited by anyone)
pub public: bool,
/// Whether to enable analytics
#[cfg_attr(
feature = "serde",
serde(skip_serializing_if = "crate::if_false", default)
)]
pub analytics: bool,
/// Whether this bot should be publicly discoverable
#[cfg_attr(
feature = "serde",
serde(skip_serializing_if = "crate::if_false", default)
)]
pub discoverable: bool,
/// Reserved; URL for handling interactions
#[cfg_attr(
feature = "serde",
serde(skip_serializing_if = "String::is_empty", default)
)]
pub interactions_url: String,
/// URL for terms of service
#[cfg_attr(
feature = "serde",
serde(skip_serializing_if = "String::is_empty", default)
)]
pub terms_of_service_url: String,
/// URL for privacy policy
#[cfg_attr(
feature = "serde",
serde(skip_serializing_if = "String::is_empty", default)
)]
pub privacy_policy_url: String,
/// Enum of bot flags
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub flags: Option<i32>,
}
/// # Public Bot
pub struct PublicBot {
/// Bot Id
#[serde(rename = "_id")]
id: String,
/// Bot Username
username: String,
/// Profile Avatar
#[serde(skip_serializing_if = "String::is_empty")]
avatar: String,
/// Profile Description
#[serde(skip_serializing_if = "String::is_empty")]
description: String,
}
);
#[cfg(feature = "from_database")]
impl PublicBot {
pub fn from(
bot: revolt_database::Bot,
username: String,
avatar: Option<String>,
description: Option<String>,
) -> Self {
PublicBot {
id: bot.id,
username,
avatar: avatar.unwrap_or_default(),
description: description.unwrap_or_default(),
}
}
}
#[cfg(feature = "from_database")]
impl From<revolt_database::Bot> for Bot {
fn from(value: revolt_database::Bot) -> Self {
Bot {
id: value.id,
owner_id: value.owner,
token: value.token,
public: value.public,
analytics: value.analytics,
discoverable: value.discoverable,
interactions_url: value.interactions_url,
terms_of_service_url: value.terms_of_service_url,
privacy_policy_url: value.privacy_policy_url,
flags: value.flags,
}
}
}

View File

@@ -0,0 +1,27 @@
#[cfg(feature = "serde")]
#[macro_use]
extern crate serde;
#[cfg(feature = "schemas")]
#[macro_use]
extern crate schemars;
macro_rules! auto_derived {
( $( $item:item )+ ) => {
$(
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "schemas", derive(JsonSchema))]
#[derive(Debug, Clone, Eq, PartialEq)]
$item
)+
};
}
mod bots;
pub use bots::*;
/// Utility function to check if a boolean value is false
pub fn if_false(t: &bool) -> bool {
!t
}

View File

@@ -0,0 +1,19 @@
[package]
name = "revolt-presence"
version = "0.0.1"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dev-dependencies]
# Async
async-std = { version = "1.8.0", features = ["attributes"] }
[dependencies]
# Utility
log = "0.4.17"
rand = "0.8.5"
once_cell = "1.17.1"
# Redis
redis-kiss = "0.1.4"

View File

@@ -1,20 +1,31 @@
#[macro_use]
extern crate log;
use once_cell::sync::Lazy;
use rand::Rng;
use redis_kiss::{get_connection, AsyncCommands};
use std::collections::HashSet;
use redis_kiss::{get_connection, AsyncCommands};
use rand::Rng;
mod entry;
mod operations;
use operations::{
__add_to_set_string, __add_to_set_u32, __delete_key, __get_set_members_as_string,
__get_set_size, __remove_from_set_string, __remove_from_set_u32,
};
use self::entry::{ONLINE_SET, REGION_KEY};
pub static REGION_ID: Lazy<u16> = Lazy::new(|| {
std::env::var("REGION_ID")
.unwrap_or_else(|_| "0".to_string())
.parse()
.unwrap()
});
pub static REGION_KEY: Lazy<String> = Lazy::new(|| format!("region{}", &*REGION_ID));
pub static ONLINE_SET: &str = "online";
pub static FLAG_BITS: u32 = 0b1;
/// Create a new presence session, returns the ID of this session
pub async fn presence_create_session(user_id: &str, flags: u8) -> (bool, u32) {
pub async fn create_session(user_id: &str, flags: u8) -> (bool, u32) {
info!("Creating a presence session for {user_id} with flags {flags}");
if let Ok(mut conn) = get_connection().await {
@@ -24,7 +35,7 @@ pub async fn presence_create_session(user_id: &str, flags: u8) -> (bool, u32) {
// A session ID is comprised of random data and any flags ORed to the end
let session_id = {
let mut rng = rand::thread_rng();
(rng.gen::<u32>() ^ 1) | (flags as u32 & 1)
(rng.gen::<u32>() & !FLAG_BITS) | (flags as u32 & FLAG_BITS)
};
// Add session to user's sessions and to the region
@@ -41,16 +52,12 @@ pub async fn presence_create_session(user_id: &str, flags: u8) -> (bool, u32) {
}
/// Delete existing presence session
pub async fn presence_delete_session(user_id: &str, session_id: u32) -> bool {
presence_delete_session_internal(user_id, session_id, false).await
pub async fn delete_session(user_id: &str, session_id: u32) -> bool {
delete_session_internal(user_id, session_id, false).await
}
/// Delete existing presence session (but also choose whether to skip region)
async fn presence_delete_session_internal(
user_id: &str,
session_id: u32,
skip_region: bool,
) -> bool {
async fn delete_session_internal(user_id: &str, session_id: u32, skip_region: bool) -> bool {
info!("Deleting presence session for {user_id} with id {session_id}");
if let Ok(mut conn) = get_connection().await {
@@ -78,7 +85,7 @@ async fn presence_delete_session_internal(
}
/// Check whether a given user ID is online
pub async fn presence_is_online(user_id: &str) -> bool {
pub async fn is_online(user_id: &str) -> bool {
if let Ok(mut conn) = get_connection().await {
conn.exists(user_id).await.unwrap_or(false)
} else {
@@ -87,7 +94,7 @@ pub async fn presence_is_online(user_id: &str) -> bool {
}
/// Check whether a set of users is online, returns a set of the online user IDs
pub async fn presence_filter_online(user_ids: &'_ [String]) -> HashSet<String> {
pub async fn filter_online(user_ids: &'_ [String]) -> HashSet<String> {
// Ignore empty list immediately, to save time.
let mut set = HashSet::new();
if user_ids.is_empty() {
@@ -102,7 +109,7 @@ pub async fn presence_filter_online(user_ids: &'_ [String]) -> HashSet<String> {
// as for some reason or another, Redis does not like us sending
// a list of just one ID to the server.
if user_ids.len() == 1 {
if presence_is_online(&user_ids[0]).await {
if is_online(&user_ids[0]).await {
set.insert(user_ids[0].to_string());
}
@@ -133,7 +140,7 @@ pub async fn presence_filter_online(user_ids: &'_ [String]) -> HashSet<String> {
}
/// Reset any stale presence data
pub async fn presence_clear_region(region_id: Option<&str>) {
pub async fn clear_region(region_id: Option<&str>) {
let region_id = region_id.unwrap_or(&*REGION_KEY);
let mut conn = get_connection().await.expect("Redis connection");
@@ -150,7 +157,7 @@ pub async fn presence_clear_region(region_id: Option<&str>) {
let parts = session.split(':').collect::<Vec<&str>>();
if let (Some(user_id), Some(session_id)) = (parts.first(), parts.get(1)) {
if let Ok(session_id) = session_id.parse() {
presence_delete_session_internal(user_id, session_id, true).await;
delete_session_internal(user_id, session_id, true).await;
}
}
}
@@ -161,3 +168,59 @@ pub async fn presence_clear_region(region_id: Option<&str>) {
info!("Clean up complete.");
}
}
#[cfg(test)]
mod tests {
use crate::{clear_region, create_session, delete_session, filter_online, is_online};
use rand::Rng;
#[async_std::test]
async fn it_works() {
// Clear the region before we start the tests:
clear_region(None).await;
// Generate some data we'll use:
let user_id = rand::thread_rng().gen::<u32>().to_string();
let other_id = rand::thread_rng().gen::<u32>().to_string();
let flags = 1;
// Create a session
let (first_session, session_id) = create_session(&user_id, flags).await;
assert!(first_session);
assert_ne!(session_id, 0);
assert_eq!(session_id as u8 & flags, flags);
// Check if the user is online
assert!(is_online(&user_id).await);
let user_ids = filter_online(&[user_id.to_string()]).await;
assert_eq!(user_ids.len(), 1);
assert!(user_ids.contains(&user_id));
// Create a few more sessions
let (first_session, second_session_id) = create_session(&user_id, 0).await;
assert!(!first_session);
dbg!(second_session_id);
assert_eq!(second_session_id as u8 & 1, 0);
let (first_session, other_session_id) = create_session(&other_id, 0).await;
assert!(first_session);
let user_ids = filter_online(&[user_id.to_string(), other_id.to_string()]).await;
assert_eq!(user_ids.len(), 2);
assert!(user_ids.contains(&user_id));
assert!(user_ids.contains(&other_id));
// Remove sessions
delete_session(&user_id, session_id).await;
delete_session(&other_id, other_session_id).await;
assert!(!is_online(&other_id).await);
// Check if we can wipe everything too
clear_region(None).await;
assert!(!is_online(&user_id).await);
let user_ids = filter_online(&[user_id.to_string(), other_id.to_string()]).await;
assert!(user_ids.is_empty())
}
}

View File

@@ -0,0 +1,22 @@
[package]
name = "revolt-result"
version = "0.0.1"
edition = "2021"
license = "AGPL-3.0-or-later"
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" ]
default = [ "serde" ]
[dependencies]
# Serialisation
serde = { version = "1", features = ["derive"], optional = true }
# Spec Generation
schemars = { version = "0.8.8", optional = true }

View File

@@ -0,0 +1,178 @@
#[cfg(feature = "serde")]
#[macro_use]
extern crate serde;
#[cfg(feature = "schemas")]
#[macro_use]
extern crate schemars;
/// Result type with custom Error
pub type Result<T, E = Error> = std::result::Result<T, E>;
/// Error information
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "schemas", derive(JsonSchema))]
#[derive(Debug, Clone)]
pub struct Error {
/// Type of error and additional information
#[cfg_attr(feature = "serde", serde(flatten))]
pub error_type: ErrorType,
/// Where this error occurred
pub location: String,
}
/// Possible error types
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "type"))]
#[cfg_attr(feature = "schemas", derive(JsonSchema))]
#[derive(Debug, Clone)]
pub enum ErrorType {
/// This error was not labeled :(
LabelMe,
// ? Onboarding related errors
AlreadyOnboarded,
// ? User related errors
UsernameTaken,
InvalidUsername,
UnknownUser,
AlreadyFriends,
AlreadySentRequest,
Blocked,
BlockedByOther,
NotFriends,
// ? Channel related errors
UnknownChannel,
UnknownAttachment,
UnknownMessage,
CannotEditMessage,
CannotJoinCall,
TooManyAttachments {
max: usize,
},
TooManyReplies {
max: usize,
},
TooManyChannels {
max: usize,
},
EmptyMessage,
PayloadTooLarge,
CannotRemoveYourself,
GroupTooLarge {
max: usize,
},
AlreadyInGroup,
NotInGroup,
// ? Server related errors
UnknownServer,
InvalidRole,
Banned,
TooManyServers {
max: usize,
},
TooManyEmoji {
max: usize,
},
TooManyRoles {
max: usize,
},
// ? Bot related errors
ReachedMaximumBots,
IsBot,
BotIsPrivate,
// ? User safety related errors
CannotReportYourself,
// ? Permission errors
MissingPermission {
permission: String,
},
MissingUserPermission {
permission: String,
},
NotElevated,
NotPrivileged,
CannotGiveMissingPermissions,
NotOwner,
// ? General errors
DatabaseError {
operation: String,
collection: String,
},
InternalError,
InvalidOperation,
InvalidCredentials,
InvalidProperty,
InvalidSession,
DuplicateNonce,
NotFound,
NoEffect,
FailedValidation {
error: String,
},
// ? Legacy errors
VosoUnavailable,
}
#[macro_export]
macro_rules! create_error {
( $error: ident $( $tt:tt )? ) => {
$crate::Error {
error_type: $crate::ErrorType::$error $( $tt )?,
location: format!("{}:{}:{}", file!(), line!(), column!()),
}
};
}
#[macro_export]
macro_rules! create_database_error {
( $operation: expr, $collection: expr ) => {
create_error!(DatabaseError {
operation: $operation.to_string(),
collection: $collection.to_string()
})
};
}
#[macro_export]
#[cfg(debug_assertions)]
macro_rules! query {
( $self: ident, $type: ident, $collection: expr, $($rest:expr),+ ) => {
Ok($self.$type($collection, $($rest),+).await.unwrap())
};
}
#[macro_export]
#[cfg(not(debug_assertions))]
macro_rules! query {
( $self: ident, $type: ident, $collection: expr, $($rest:expr),+ ) => {
$self.$type($collection, $($rest),+).await
.map_err(|_| create_database_error!(stringify!($type), $collection))
};
}
#[cfg(test)]
mod tests {
use crate::ErrorType;
#[test]
fn use_macro_to_construct_error() {
let error = create_error!(LabelMe);
assert!(matches!(error.error_type, ErrorType::LabelMe));
}
#[test]
fn use_macro_to_construct_complex_error() {
let error = create_error!(LabelMe);
assert!(matches!(error.error_type, ErrorType::LabelMe));
}
}

View File

@@ -55,5 +55,9 @@ revolt_rocket_okapi = { version = "0.9.1", features = [ "swagger" ] }
# quark
revolt-quark = { path = "../quark" }
# core
revolt-database = { path = "../core/database" }
revolt-models = { path = "../core/models", features = [ "schemas" ] }
[build-dependencies]
vergen = "7.5.0"

View File

@@ -22,15 +22,18 @@ async fn rocket() -> _ {
revolt_quark::variables::delta::preflight_checks();
// Setup database
let db = DatabaseInfo::Auto.connect().await.unwrap();
let db = revolt_database::DatabaseInfo::Auto.connect().await.unwrap();
db.migrate_database().await.unwrap();
// Legacy database setup from quark
let legacy_db = DatabaseInfo::Auto.connect().await.unwrap();
// Setup Authifier event channel
let (sender, receiver) = unbounded();
// Setup Authifier
let authifier = Authifier {
database: db.clone().into(),
database: legacy_db.clone().into(),
config: revolt_quark::util::authifier::config(),
event_channel: Some(sender),
};
@@ -52,7 +55,7 @@ async fn rocket() -> _ {
});
// Launch background task workers
async_std::task::spawn(revolt_quark::tasks::start_workers(db.clone()));
async_std::task::spawn(revolt_quark::tasks::start_workers(legacy_db.clone()));
// Configure CORS
let cors = revolt_quark::web::cors::new();
@@ -65,6 +68,7 @@ async fn rocket() -> _ {
.mount("/swagger/", revolt_quark::web::swagger::routes())
.manage(authifier)
.manage(db)
.manage(legacy_db)
.manage(cors.clone())
.attach(revolt_quark::web::ratelimiter::RatelimitFairing)
.attach(cors)

View File

@@ -1,11 +1,11 @@
use revolt_quark::{
models::{Bot, User},
Db, Error, Ref, Result,
};
use rocket::serde::json::Json;
use revolt_database::Database;
use revolt_models::Bot;
use revolt_quark::{models::User, Db, Error, Ref, Result};
use rocket::{serde::json::Json, State};
use serde::Serialize;
/// # Bot Response
/// TODO: move to revolt-models
#[derive(Serialize, JsonSchema)]
pub struct BotResponse {
/// Bot object
@@ -19,18 +19,23 @@ pub struct BotResponse {
/// Fetch details of a bot you own by its id.
#[openapi(tag = "Bots")]
#[get("/<target>")]
pub async fn fetch_bot(db: &Db, user: User, target: Ref) -> Result<Json<BotResponse>> {
pub async fn fetch_bot(
legacy_db: &Db,
db: &State<Database>,
user: User,
target: Ref,
) -> Result<Json<BotResponse>> {
if user.bot.is_some() {
return Err(Error::IsBot);
}
let bot = target.as_bot(db).await?;
let bot = db.fetch_bot(&target.id).await.map_err(Error::from_core)?;
if bot.owner != user.id {
return Err(Error::NotFound);
}
Ok(Json(BotResponse {
user: db.fetch_user(&bot.id).await?.foreign(),
bot,
user: legacy_db.fetch_user(&bot.id).await?.foreign(),
bot: bot.into(),
}))
}

View File

@@ -1,44 +1,32 @@
use revolt_quark::{
models::{File, User},
Db, Error, Ref, Result,
};
use revolt_database::Database;
use revolt_models::PublicBot;
use revolt_quark::{models::User, Db, Error, Ref, Result};
use rocket::serde::json::Json;
use serde::{Deserialize, Serialize};
/// # Public Bot
#[derive(Serialize, Deserialize, JsonSchema)]
pub struct PublicBot {
/// Bot Id
#[serde(rename = "_id")]
id: String,
/// Bot Username
username: String,
/// Profile Avatar
#[serde(skip_serializing_if = "Option::is_none")]
avatar: Option<File>,
/// Profile Description
#[serde(skip_serializing_if = "Option::is_none")]
description: Option<String>,
}
use rocket::State;
/// # Fetch Public Bot
///
/// Fetch details of a public (or owned) bot by its id.
#[openapi(tag = "Bots")]
#[get("/<target>/invite")]
pub async fn fetch_public_bot(db: &Db, user: Option<User>, target: Ref) -> Result<Json<PublicBot>> {
let bot = target.as_bot(db).await?;
pub async fn fetch_public_bot(
legacy_db: &Db,
db: &State<Database>,
user: Option<User>,
target: Ref,
) -> Result<Json<PublicBot>> {
let bot = db.fetch_bot(&target.id).await.map_err(Error::from_core)?;
if !bot.public && user.map_or(true, |x| x.id != bot.owner) {
return Err(Error::NotFound);
}
let user = db.fetch_user(&bot.id).await?;
let user = legacy_db.fetch_user(&bot.id).await?;
Ok(Json(PublicBot {
id: bot.id,
username: user.username,
avatar: user.avatar,
description: user.profile.and_then(|p| p.content),
}))
Ok(Json(PublicBot::from(
bot,
user.username,
user.avatar.map(|f| f.id),
user.profile.and_then(|p| p.content),
)))
}

View File

@@ -1,8 +1,8 @@
[package]
name = "revolt-quark"
version = "0.5.19"
license = "AGPL-3.0-or-later"
edition = "2021"
license = "AGPL-3.0-or-later"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -26,10 +26,10 @@ default = [ "test" ]
[dependencies]
# Serialisation
revolt_optional_struct = "0.2.0"
serde = { version = "1", features = ["derive"] }
validator = { version = "0.14", features = ["derive"] }
iso8601-timestamp = { version = "0.1.8", features = ["schema", "bson"] }
optional_struct = { git = "https://github.com/insertish/OptionalStruct", rev = "ee56427cee1f007839825d93d07fffd5a5e038c7" }
# Formats
bincode = "1.3.3"
@@ -89,3 +89,7 @@ authifier = { version = "1.0.7", features = [ "async-std-runtime" ] }
# Sentry
sentry = "0.25.0"
# Core
revolt-result = { path = "../core/result", features = [ "serde", "schemas" ] }
revolt-presence = { path = "../core/presence" }

View File

@@ -1,34 +0,0 @@
use revolt_quark::models::user::PartialUser;
use revolt_quark::presence::{
presence_create_session, presence_delete_session, presence_filter_online, presence_is_online,
};
use revolt_quark::*;
#[async_std::main]
async fn main() {
let db = DatabaseInfo::Dummy.connect().await.unwrap();
let sus = PartialUser {
username: Some("neat".into()),
..Default::default()
};
db.update_user("user id", &sus, vec![]).await.unwrap();
dbg!(presence_create_session("entry", 0).await);
dbg!(presence_is_online("entry").await);
dbg!(presence_filter_online(&["a".into(), "b".into(), "entry".into()]).await);
dbg!(presence_delete_session("entry", 0).await);
dbg!(presence_is_online("entry").await);
dbg!(presence_create_session("entry", 0).await);
dbg!(presence_create_session("entry", 0).await);
dbg!(presence_delete_session("entry", 0).await);
dbg!(presence_is_online("entry").await);
dbg!(presence_delete_session("entry", 1).await);
dbg!(presence_is_online("entry").await);
// __set_key("dietz", vec![0xFF]).await;
// dbg!(presence_filter_online(&["dietz".into(), "nuts".into()]).await);
}

View File

@@ -7,11 +7,11 @@ use crate::{
user::{PartialUser, Presence, RelationshipStatus},
Channel, Member, User,
},
perms,
presence::presence_filter_online,
Database, Permission, Result,
perms, Database, Permission, Result,
};
use revolt_presence::filter_online;
use super::{
client::EventV1,
state::{Cache, State},
@@ -135,8 +135,7 @@ impl State {
}
// Fetch presence data for known users.
let online_ids =
presence_filter_online(&user_ids.iter().cloned().collect::<Vec<String>>()).await;
let online_ids = filter_online(&user_ids.iter().cloned().collect::<Vec<String>>()).await;
user.online = Some(true);
// Fetch user data.

View File

@@ -1,11 +0,0 @@
use crate::{AbstractMigrations, Result};
use super::super::DummyDb;
#[async_trait]
impl AbstractMigrations for DummyDb {
async fn migrate_database(&self) -> Result<()> {
info!("Migrating the database.");
Ok(())
}
}

View File

@@ -1,7 +1,6 @@
use crate::AbstractDatabase;
pub mod admin {
pub mod migrations;
pub mod stats;
}

View File

@@ -1,5 +1,6 @@
use std::collections::HashSet;
use revolt_presence::filter_online;
use serde_json::json;
use ulid::Ulid;
use validator::Validate;
@@ -14,7 +15,6 @@ use crate::{
Channel, Emoji, Message, User,
},
permissions::PermissionCalculator,
presence::presence_filter_online,
tasks::ack::AckEvent,
types::{
january::{Embed, Text},
@@ -79,7 +79,7 @@ impl Message {
Channel::DirectMessage { recipients, .. }
| Channel::Group { recipients, .. } => {
target_ids = (&recipients.iter().cloned().collect::<HashSet<String>>()
- &presence_filter_online(recipients).await)
- &filter_online(recipients).await)
.into_iter()
.collect::<Vec<String>>();
}

View File

@@ -1,9 +1,5 @@
//! Database agnostic implementations.
pub mod admin {
pub mod migrations;
}
pub mod media {
pub mod attachment;
pub mod emoji;

View File

@@ -4,11 +4,11 @@ use crate::models::user::{
};
use crate::permissions::defn::UserPerms;
use crate::permissions::r#impl::user::get_relationship;
use crate::presence::presence_filter_online;
use crate::{perms, Database, Error, Result};
use futures::try_join;
use impl_ops::impl_op_ex_commutative;
use revolt_presence::filter_online;
use std::ops;
impl_op_ex_commutative!(+ |a: &i32, b: &Badges| -> i32 { *a | *b as i32 });
@@ -98,7 +98,7 @@ impl User {
/// Fetch foreign users by a list of IDs
pub async fn fetch_foreign_users(db: &Database, user_ids: &[String]) -> Result<Vec<User>> {
let online_ids = presence_filter_online(user_ids).await;
let online_ids = filter_online(user_ids).await;
Ok(db
.fetch_users(user_ids)

View File

@@ -12,7 +12,6 @@ use serde::{Deserialize, Serialize};
use crate::{util::manipulation::prefix_keys, AbstractDatabase, Error, Result};
pub mod admin {
pub mod migrations;
pub mod stats;
}

View File

@@ -9,7 +9,7 @@ extern crate log;
#[macro_use]
extern crate impl_ops;
#[macro_use]
extern crate optional_struct;
extern crate revolt_optional_struct;
#[macro_use]
extern crate bitfield;
#[macro_use]
@@ -22,7 +22,6 @@ pub use redis_kiss;
pub mod events;
pub mod r#impl;
pub mod models;
pub mod presence;
pub mod tasks;
pub mod types;
pub mod util;

View File

@@ -1,11 +0,0 @@
use serde::{Deserialize, Serialize};
/// Document representing migration information
#[derive(Serialize, Deserialize)]
pub struct MigrationInfo {
/// Unique Id
#[serde(rename = "_id")]
id: i32,
/// Current database revision
revision: i32,
}

View File

@@ -1,5 +1,4 @@
mod admin {
pub mod migrations;
pub mod simple;
pub mod stats;
}
@@ -47,7 +46,6 @@ pub use channel_invite::Invite;
pub use channel_unread::ChannelUnread;
pub use emoji::Emoji;
pub use message::Message;
pub use migrations::MigrationInfo;
pub use report::Report;
pub use server::Server;
pub use server_ban::ServerBan;

View File

@@ -1,13 +0,0 @@
use std::env;
use once_cell::sync::Lazy;
pub static REGION_ID: Lazy<u16> = Lazy::new(|| {
env::var("REGION_ID")
.unwrap_or_else(|_| "0".to_string())
.parse()
.unwrap()
});
pub static REGION_KEY: Lazy<String> = Lazy::new(|| format!("region{}", &*REGION_ID));
pub static ONLINE_SET: &str = "online";

View File

@@ -1,6 +0,0 @@
use crate::Result;
#[async_trait]
pub trait AbstractMigrations: Sync + Send {
async fn migrate_database(&self) -> Result<()>;
}

View File

@@ -1,5 +1,4 @@
mod admin {
pub mod migrations;
pub mod stats;
}
@@ -32,7 +31,6 @@ mod safety {
pub mod snapshot;
}
pub use admin::migrations::AbstractMigrations;
pub use admin::stats::AbstractStats;
pub use media::attachment::AbstractAttachment;
@@ -57,7 +55,6 @@ pub use safety::snapshot::AbstractSnapshot;
pub trait AbstractDatabase:
Sync
+ Send
+ AbstractMigrations
+ AbstractStats
+ AbstractAttachment
+ AbstractEmoji

View File

@@ -1,4 +1,5 @@
use futures::future::join;
use revolt_presence::is_online;
use rocket::request::FromParam;
use schemars::schema::{InstanceType, Schema, SchemaObject, SingleOrVec};
use schemars::JsonSchema;
@@ -7,7 +8,6 @@ use serde::{Deserialize, Serialize};
use crate::models::{
Bot, Channel, Emoji, Invite, Member, Message, Report, Server, ServerBan, User,
};
use crate::presence::presence_is_online;
use crate::{Database, Error, Result};
/// Reference to some object in the database
@@ -25,7 +25,7 @@ impl Ref {
/// Fetch user from Ref
pub async fn as_user(&self, db: &Database) -> Result<User> {
let (user, online) = join(db.fetch_user(&self.id), presence_is_online(&self.id)).await;
let (user, online) = join(db.fetch_user(&self.id), is_online(&self.id)).await;
let mut user = user?;
user.online = Some(online);
Ok(user)

View File

@@ -19,6 +19,12 @@ pub enum Error {
/// This error was not labeled :(
LabelMe,
/// Core crate error
Core {
#[serde(flatten)]
error: revolt_result::Error,
},
// ? Onboarding related errors
AlreadyOnboarded,
@@ -39,13 +45,13 @@ pub enum Error {
CannotEditMessage,
CannotJoinCall,
TooManyAttachments {
max: usize
max: usize,
},
TooManyReplies {
max: usize
max: usize,
},
TooManyChannels {
max: usize
max: usize,
},
EmptyMessage,
PayloadTooLarge,
@@ -64,10 +70,10 @@ pub enum Error {
max: usize,
},
TooManyEmoji {
max: usize
max: usize,
},
TooManyRoles {
max: usize
max: usize,
},
// ? Bot related errors
@@ -135,6 +141,11 @@ impl Error {
error: validation_error,
})
}
/// Create a error from core error
pub fn from_core(error: revolt_result::Error) -> Error {
Error::Core { error }
}
}
/// Result type with custom Error
@@ -145,6 +156,7 @@ impl<'r> Responder<'r, 'static> for Error {
fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> {
let status = match self {
Error::LabelMe => Status::InternalServerError,
Error::Core { .. } => Status::InternalServerError,
Error::AlreadyOnboarded => Status::Forbidden,

4
justfile Normal file
View File

@@ -0,0 +1,4 @@
publish:
cargo publish --package revolt-result
cargo publish --package revolt-database
cargo publish --package revolt-models