Compare commits
27
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
95db641f8f | ||
|
|
43e45aef3f | ||
|
|
c5880596a4 | ||
|
|
59a644891d | ||
|
|
a326cdc736 | ||
|
|
03a28dbb3e | ||
|
|
32542a822e | ||
|
|
1df90ff53b | ||
|
|
487b979f0d | ||
|
|
b9d813d8f0 | ||
|
|
36dd128459 | ||
|
|
144f0d39c6 | ||
|
|
32a294a64a | ||
|
|
b7b70346b4 | ||
|
|
59832e6ba2 | ||
|
|
f309218573 | ||
|
|
0321eff62b | ||
|
|
4c8ea31d98 | ||
|
|
92ac86a6bd | ||
|
|
13ed69c82a | ||
|
|
b83f6da648 | ||
|
|
89f1167239 | ||
|
|
82d868751f | ||
|
|
31b9f18921 | ||
|
|
fd80823910 | ||
|
|
6bd8221eda | ||
|
|
ce77e926a5 |
@@ -20,47 +20,52 @@ on:
|
||||
- "Dockerfile"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
base:
|
||||
runs-on: ubuntu-latest
|
||||
name: Build base image (amd64)
|
||||
name: Build base image
|
||||
steps:
|
||||
# Configure build environment
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
# Authenticate with GHCR
|
||||
- name: Login to Github Container Registry
|
||||
uses: docker/login-action@v1
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# Build all projects and cache
|
||||
- name: Build Base Image
|
||||
uses: docker/build-push-action@v3
|
||||
- name: Build base image
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ghcr.io/revoltchat/base:latest
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: ghcr.io/${{ github.repository_owner }}/base:latest
|
||||
cache-from: type=gha,scope=buildx-base-multi-arch
|
||||
cache-to: type=gha,scope=buildx-base-multi-arch,mode=max
|
||||
|
||||
publish_amd64:
|
||||
publish:
|
||||
needs: [base]
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name != 'pull_request'
|
||||
strategy:
|
||||
matrix:
|
||||
project: [delta, bonfire]
|
||||
name: Build ${{ matrix.project }} image (amd64)
|
||||
name: Build ${{ matrix.project }} image
|
||||
steps:
|
||||
# Configure build environment
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
@@ -68,10 +73,11 @@ jobs:
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: docker.io
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Login to Github Container Registry
|
||||
uses: docker/login-action@v1
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
@@ -86,11 +92,11 @@ jobs:
|
||||
{
|
||||
"delta": {
|
||||
"path": "crates/delta",
|
||||
"tag": "revoltchat/server"
|
||||
"tag": "${{ github.repository_owner }}/server"
|
||||
},
|
||||
"bonfire": {
|
||||
"path": "crates/bonfire",
|
||||
"tag": "revoltchat/bonfire"
|
||||
"tag": "${{ github.repository_owner }}/bonfire"
|
||||
}
|
||||
}
|
||||
export_to: output
|
||||
@@ -98,19 +104,21 @@ jobs:
|
||||
# Configure metadata
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v3
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: ${{ steps.export.outputs.tag }}, ghcr.io/${{ steps.export.outputs.tag }}
|
||||
images: |
|
||||
docker.io/${{ steps.export.outputs.tag }}
|
||||
ghcr.io/${{ steps.export.outputs.tag }}
|
||||
|
||||
# Build crate image
|
||||
- name: Publish
|
||||
uses: docker/build-push-action@v3
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
platforms: linux/amd64
|
||||
platforms: linux/amd64,linux/arm64
|
||||
file: ${{ steps.export.outputs.path }}/Dockerfile
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
build-args: |
|
||||
BASE_IMAGE=ghcr.io/${{ github.repository_owner }}/base:latest
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
Generated
+50
-46
@@ -871,12 +871,6 @@ version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
|
||||
|
||||
[[package]]
|
||||
name = "dtoa"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0"
|
||||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.5"
|
||||
@@ -1831,6 +1825,27 @@ version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
|
||||
[[package]]
|
||||
name = "metrics"
|
||||
version = "0.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e52eb6380b6d2a10eb3434aec0885374490f5b82c8aaf5cd487a183c98be834"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"metrics-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "metrics-macros"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49e30813093f757be5cf21e50389a24dc7dbb22c49f23b7e8f51d69b508a5ffa"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.18",
|
||||
"syn 1.0.107",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
version = "0.3.16"
|
||||
@@ -1897,9 +1912,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mobc"
|
||||
version = "0.7.3"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f76d2f2e2dcbb00a8d3b2b09f026a74a82693ea52cd071647aa6cfa7f1ff37e"
|
||||
checksum = "fc79c4a77e312fee9c7bd4b957c12ad1196db73c4a81e5c0b13f02083c4f7f2f"
|
||||
dependencies = [
|
||||
"async-std",
|
||||
"async-trait",
|
||||
@@ -1908,17 +1923,21 @@ dependencies = [
|
||||
"futures-timer",
|
||||
"futures-util",
|
||||
"log",
|
||||
"metrics",
|
||||
"thiserror",
|
||||
"tokio 1.18.2",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mobc-redis"
|
||||
version = "0.7.0"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7b5db77b37c9224d5b9949b214041ea3e1c15b6b1e5dd24a5acb8e73975d6d6"
|
||||
checksum = "7bd8e2fd6bf7e35263b86662e663a9496a0352ceddd413b6c33313c36d5068fd"
|
||||
dependencies = [
|
||||
"mobc",
|
||||
"redis 0.19.0",
|
||||
"redis 0.22.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2106,9 +2125,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.13.0"
|
||||
version = "1.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1"
|
||||
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
@@ -2624,57 +2643,55 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "redis"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a6ddfecac9391fed21cce10e83c65fa4abafd77df05c98b1c647c65374ce9b3"
|
||||
version = "0.22.3"
|
||||
source = "git+https://github.com/insertish/redis-rs?rev=6575a6b1c09eb8c9cc7f0082d95fe6b8f903c4d7#6575a6b1c09eb8c9cc7f0082d95fe6b8f903c4d7"
|
||||
dependencies = [
|
||||
"async-std",
|
||||
"async-trait",
|
||||
"bytes 1.1.0",
|
||||
"combine",
|
||||
"dtoa",
|
||||
"futures-util",
|
||||
"itoa 0.4.8",
|
||||
"itoa 1.0.2",
|
||||
"percent-encoding",
|
||||
"pin-project-lite 0.2.9",
|
||||
"sha1",
|
||||
"ryu",
|
||||
"sha1_smol",
|
||||
"tokio 1.18.2",
|
||||
"tokio-util 0.6.10",
|
||||
"tokio-util 0.7.2",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redis"
|
||||
version = "0.21.5"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a80b5f38d7f5a020856a0e16e40a9cfabf88ae8f0e4c2dcd8a3114c1e470852"
|
||||
checksum = "3ea8c51b5dc1d8e5fd3350ec8167f464ec0995e79f2e90a075b63371500d557f"
|
||||
dependencies = [
|
||||
"async-std",
|
||||
"async-trait",
|
||||
"bytes 1.1.0",
|
||||
"combine",
|
||||
"dtoa",
|
||||
"futures-util",
|
||||
"itoa 0.4.8",
|
||||
"itoa 1.0.2",
|
||||
"percent-encoding",
|
||||
"pin-project-lite 0.2.9",
|
||||
"sha1",
|
||||
"ryu",
|
||||
"tokio 1.18.2",
|
||||
"tokio-util 0.6.10",
|
||||
"tokio-util 0.7.2",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redis-kiss"
|
||||
version = "0.1.3"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d4f2f48fb776a308331c4a1ecb3a7b7b99f4d46c6beb2fc03db2da0f63fcad6"
|
||||
checksum = "58605cfd83a2146161de13bb253f24db25eef6919b35403fc90f508ef89bec92"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"lazy_static",
|
||||
"mobc",
|
||||
"mobc-redis",
|
||||
"redis 0.21.5",
|
||||
"redis 0.23.0",
|
||||
"rmp-serde",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -2792,7 +2809,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "revolt-bonfire"
|
||||
version = "0.5.12"
|
||||
version = "0.5.19"
|
||||
dependencies = [
|
||||
"async-std",
|
||||
"async-tungstenite",
|
||||
@@ -2808,7 +2825,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "revolt-delta"
|
||||
version = "0.5.12"
|
||||
version = "0.5.19"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"async-std",
|
||||
@@ -2819,17 +2836,13 @@ dependencies = [
|
||||
"env_logger",
|
||||
"futures",
|
||||
"impl_ops",
|
||||
"lazy_static",
|
||||
"lettre",
|
||||
"linkify 0.6.0",
|
||||
"log",
|
||||
"lru",
|
||||
"mobc",
|
||||
"mobc-redis",
|
||||
"nanoid",
|
||||
"num_enum",
|
||||
"once_cell",
|
||||
"redis 0.21.5",
|
||||
"regex",
|
||||
"reqwest",
|
||||
"revolt-quark",
|
||||
@@ -2848,7 +2861,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "revolt-quark"
|
||||
version = "0.5.12"
|
||||
version = "0.5.19"
|
||||
dependencies = [
|
||||
"async-lock",
|
||||
"async-recursion",
|
||||
@@ -2866,7 +2879,6 @@ dependencies = [
|
||||
"impl_ops",
|
||||
"indexmap",
|
||||
"iso8601-timestamp",
|
||||
"lazy_static",
|
||||
"linkify 0.8.1",
|
||||
"log",
|
||||
"lru",
|
||||
@@ -2876,6 +2888,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"optional_struct",
|
||||
"pretty_env_logger",
|
||||
"rand 0.8.5",
|
||||
"redis-kiss",
|
||||
"regex",
|
||||
"reqwest",
|
||||
@@ -3472,15 +3485,6 @@ dependencies = [
|
||||
"digest 0.10.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770"
|
||||
dependencies = [
|
||||
"sha1_smol",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1_smol"
|
||||
version = "1.0.0"
|
||||
|
||||
+6
-1
@@ -1,2 +1,7 @@
|
||||
[workspace]
|
||||
members = ["crates/*"]
|
||||
members = ["crates/*", "crates/bridges/*"]
|
||||
exclude = ["crates/bridges"]
|
||||
|
||||
[patch.crates-io]
|
||||
# mobc-redis = { git = "https://github.com/insertish/mobc", rev = "8b880bb59f2ba80b4c7bc40c649c113d8857a186" }
|
||||
redis = { git = "https://github.com/insertish/redis-rs", rev = "6575a6b1c09eb8c9cc7f0082d95fe6b8f903c4d7" }
|
||||
|
||||
+20
-5
@@ -1,12 +1,27 @@
|
||||
# Build Stage
|
||||
FROM rustlang/rust:nightly-slim AS builder
|
||||
FROM --platform="${BUILDPLATFORM}" rustlang/rust:nightly-slim
|
||||
USER 0:0
|
||||
WORKDIR /home/rust/src
|
||||
|
||||
# Install build requirements
|
||||
RUN apt-get update && apt-get install -y libssl-dev pkg-config
|
||||
ARG TARGETARCH
|
||||
|
||||
# Build all crates
|
||||
# Install build requirements
|
||||
RUN dpkg --add-architecture "${TARGETARCH}"
|
||||
RUN apt-get update && \
|
||||
apt-get install -y \
|
||||
make \
|
||||
pkg-config \
|
||||
libssl-dev:"${TARGETARCH}"
|
||||
COPY scripts/build-image-layer.sh /tmp/
|
||||
RUN sh /tmp/build-image-layer.sh tools
|
||||
|
||||
# Build all dependencies
|
||||
COPY Cargo.toml Cargo.lock ./
|
||||
COPY crates/bonfire/Cargo.toml ./crates/bonfire/
|
||||
COPY crates/delta/Cargo.toml ./crates/delta/
|
||||
COPY crates/quark/Cargo.toml ./crates/quark/
|
||||
RUN sh /tmp/build-image-layer.sh deps
|
||||
|
||||
# Build all apps
|
||||
COPY crates ./crates
|
||||
RUN cargo build --locked --release
|
||||
RUN sh /tmp/build-image-layer.sh apps
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "revolt-bonfire"
|
||||
version = "0.5.12"
|
||||
version = "0.5.19"
|
||||
license = "AGPL-3.0-or-later"
|
||||
edition = "2021"
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
# Build Stage
|
||||
FROM ghcr.io/revoltchat/base:latest AS builder
|
||||
RUN cargo install --locked --path crates/bonfire
|
||||
|
||||
# Bundle Stage
|
||||
FROM debian:buster-slim
|
||||
RUN apt-get update && apt-get install -y ca-certificates
|
||||
COPY --from=builder /usr/local/cargo/bin/revolt-bonfire ./
|
||||
FROM debian:bullseye-slim
|
||||
RUN apt-get update && \
|
||||
apt-get install -y ca-certificates && \
|
||||
apt-get clean
|
||||
COPY --from=builder /home/rust/src/target/release/revolt-bonfire ./
|
||||
EXPOSE 9000
|
||||
CMD ["./revolt-bonfire"]
|
||||
|
||||
Generated
+1294
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "revolt-discord-bridge"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
serenity = { version = "0.11.5", default-features = false, features = ["client", "gateway", "rustls_backend", "model"] }
|
||||
tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
|
||||
@@ -0,0 +1,45 @@
|
||||
use std::env;
|
||||
|
||||
use serenity::async_trait;
|
||||
use serenity::model::channel::Message;
|
||||
use serenity::model::channel::Channel;
|
||||
use serenity::model::gateway::{GatewayIntents, Ready};
|
||||
use serenity::prelude::*;
|
||||
|
||||
struct Handler;
|
||||
|
||||
#[async_trait]
|
||||
impl EventHandler for Handler {
|
||||
async fn message(&self, ctx: Context, msg: Message) {
|
||||
println!("Received message: {}", msg.content);
|
||||
|
||||
if msg.content == "deez" {
|
||||
if let Ok(Channel::Guild(channel)) = msg.channel(&ctx).await {
|
||||
channel.send_message(&ctx, |m| {
|
||||
m.content("this is where a message would usually go")
|
||||
}).await.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn ready(&self, _: Context, ready: Ready) {
|
||||
println!("{} is connected!", ready.user.name);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let token = env::var("DISCORD_TOKEN").expect("Expected a token in the environment");
|
||||
|
||||
let intents =
|
||||
GatewayIntents::GUILDS | GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT;
|
||||
|
||||
let mut client = Client::builder(token, intents)
|
||||
.event_handler(Handler)
|
||||
.await
|
||||
.expect("Error creating client");
|
||||
|
||||
if let Err(why) = client.start().await {
|
||||
println!("Client error: {:?}", why);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "revolt-delta"
|
||||
version = "0.5.12"
|
||||
version = "0.5.19"
|
||||
license = "AGPL-3.0-or-later"
|
||||
authors = ["Paul Makles <paulmakles@gmail.com>"]
|
||||
edition = "2018"
|
||||
@@ -15,9 +15,8 @@ log = "0.4.11"
|
||||
dotenv = "0.15.0"
|
||||
dashmap = "5.2.0"
|
||||
linkify = "0.6.0"
|
||||
once_cell = "1.4.1"
|
||||
once_cell = "1.17.1"
|
||||
env_logger = "0.7.1"
|
||||
lazy_static = "1.4.0"
|
||||
|
||||
# Lang. Utilities
|
||||
regex = "1"
|
||||
@@ -44,11 +43,6 @@ async-std = { version = "1.8.0", features = ["tokio1", "tokio02", "attributes"]
|
||||
# internal util
|
||||
lettre = "0.10.0-alpha.4"
|
||||
|
||||
# redis
|
||||
redis = { version = "0.21.2", features = ["async-std-comp"] }
|
||||
mobc = { version = "0.7.3" }
|
||||
mobc-redis = { version = "0.7.0", default-features = false, features = ["async-std-comp"] }
|
||||
|
||||
# web
|
||||
rocket = { version = "0.5.0-rc.2", default-features = false, features = ["json"] }
|
||||
rocket_empty = { version = "0.1.1", features = ["schema"] }
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
# Build Stage
|
||||
FROM ghcr.io/revoltchat/base:latest AS builder
|
||||
RUN cargo install --locked --path crates/delta
|
||||
|
||||
# Bundle Stage
|
||||
FROM debian:buster-slim
|
||||
RUN apt-get update && apt-get install -y ca-certificates
|
||||
COPY --from=builder /usr/local/cargo/bin/revolt-delta ./
|
||||
FROM debian:bullseye-slim
|
||||
RUN apt-get update && \
|
||||
apt-get install -y ca-certificates && \
|
||||
apt-get clean
|
||||
COPY --from=builder /home/rust/src/target/release/revolt-delta ./
|
||||
|
||||
EXPOSE 8000
|
||||
ENV ROCKET_ADDRESS 0.0.0.0
|
||||
|
||||
@@ -8,7 +8,7 @@ fn main() {
|
||||
.output()
|
||||
{
|
||||
if let Ok(git_origin) = String::from_utf8(output.stdout) {
|
||||
println!("cargo:rustc-env=GIT_ORIGIN_URL={}", git_origin);
|
||||
println!("cargo:rustc-env=GIT_ORIGIN_URL={git_origin}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,6 @@ extern crate rocket;
|
||||
extern crate revolt_rocket_okapi;
|
||||
#[macro_use]
|
||||
extern crate serde_json;
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
pub mod routes;
|
||||
pub mod util;
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
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)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
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]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
use revolt_quark::models::stats::Stats;
|
||||
use revolt_quark::{Db, Result};
|
||||
|
||||
use rocket::serde::json::Json;
|
||||
|
||||
/// # Query Stats
|
||||
///
|
||||
/// Fetch various technical statistics.
|
||||
#[openapi(tag = "Admin")]
|
||||
#[get("/stats")]
|
||||
pub async fn stats(db: &Db) -> Result<Json<Stats>> {
|
||||
Ok(Json(db.generate_stats().await?))
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
use revolt_quark::{
|
||||
models::{Channel, User},
|
||||
get_relationship,
|
||||
models::{user::RelationshipStatus, Channel, User},
|
||||
perms, Db, EmptyResponse, Error, Permission, Ref, Result,
|
||||
};
|
||||
|
||||
@@ -22,6 +23,13 @@ pub async fn req(db: &Db, user: User, target: Ref, member: Ref) -> Result<EmptyR
|
||||
match &channel {
|
||||
Channel::Group { .. } => {
|
||||
let member = member.as_user(db).await?;
|
||||
if !matches!(
|
||||
get_relationship(&user, &member.id),
|
||||
RelationshipStatus::Friend
|
||||
) {
|
||||
return Err(Error::NotFriends);
|
||||
}
|
||||
|
||||
channel
|
||||
.add_user_to_group(db, &member.id, &user.id)
|
||||
.await
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use revolt_quark::{
|
||||
models::message::{PartialMessage, SendableEmbed},
|
||||
models::{Message, User},
|
||||
perms,
|
||||
types::january::Embed,
|
||||
Db, Error, Ref, Result, Timestamp,
|
||||
Db, Error, Permission, Ref, Result, Timestamp,
|
||||
};
|
||||
|
||||
use rocket::serde::json::Json;
|
||||
@@ -28,7 +29,7 @@ pub struct DataEditMessage {
|
||||
pub async fn req(
|
||||
db: &Db,
|
||||
user: User,
|
||||
target: String,
|
||||
target: Ref,
|
||||
msg: Ref,
|
||||
edit: Json<DataEditMessage>,
|
||||
) -> Result<Json<Message>> {
|
||||
@@ -36,10 +37,17 @@ pub async fn req(
|
||||
edit.validate()
|
||||
.map_err(|error| Error::FailedValidation { error })?;
|
||||
|
||||
// Ensure we have permissions to send a message
|
||||
let channel = target.as_channel(db).await?;
|
||||
let mut permissions = perms(&user).channel(&channel);
|
||||
permissions
|
||||
.throw_permission_and_view_channel(db, Permission::SendMessage)
|
||||
.await?;
|
||||
|
||||
Message::validate_sum(&edit.content, &edit.embeds)?;
|
||||
|
||||
let mut message = msg.as_message(db).await?;
|
||||
if message.channel != target {
|
||||
if message.channel != channel.id() {
|
||||
return Err(Error::NotFound);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use revolt_quark::{
|
||||
models::{
|
||||
message::{BulkMessageResponse, MessageSort},
|
||||
message::{
|
||||
BulkMessageResponse, MessageFilter, MessageQuery, MessageSort, MessageTimePeriod,
|
||||
},
|
||||
User,
|
||||
},
|
||||
perms, Db, Error, Permission, Ref, Result,
|
||||
@@ -68,14 +70,28 @@ pub async fn req(
|
||||
sort,
|
||||
nearby,
|
||||
include_users,
|
||||
..
|
||||
} = options;
|
||||
|
||||
let messages = db
|
||||
.fetch_messages(channel.id(), limit, before, after, sort, nearby)
|
||||
.fetch_messages(MessageQuery {
|
||||
filter: MessageFilter {
|
||||
channel: Some(channel.id().to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
time_period: if let Some(nearby) = nearby {
|
||||
MessageTimePeriod::Relative { nearby }
|
||||
} else {
|
||||
MessageTimePeriod::Absolute {
|
||||
before,
|
||||
after,
|
||||
sort,
|
||||
}
|
||||
},
|
||||
limit,
|
||||
})
|
||||
.await?;
|
||||
|
||||
BulkMessageResponse::transform(db, &channel, messages, include_users)
|
||||
BulkMessageResponse::transform(db, Some(&channel), messages, include_users)
|
||||
.await
|
||||
.map(Json)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use revolt_quark::{
|
||||
models::{
|
||||
message::{BulkMessageResponse, MessageSort},
|
||||
message::{
|
||||
BulkMessageResponse, MessageFilter, MessageQuery, MessageSort, MessageTimePeriod,
|
||||
},
|
||||
User,
|
||||
},
|
||||
perms, Db, Error, Permission, Ref, Result,
|
||||
@@ -30,7 +32,7 @@ pub struct OptionsMessageSearch {
|
||||
after: Option<String>,
|
||||
/// Message sort direction
|
||||
///
|
||||
/// By default, it will be sorted by relevance.
|
||||
/// By default, it will be sorted by latest.
|
||||
#[serde(default = "MessageSort::default")]
|
||||
sort: MessageSort,
|
||||
/// Whether to include user (and member, if server channel) objects
|
||||
@@ -73,10 +75,22 @@ pub async fn req(
|
||||
} = options;
|
||||
|
||||
let messages = db
|
||||
.search_messages(channel.id(), &query, limit, before, after, sort)
|
||||
.fetch_messages(MessageQuery {
|
||||
filter: MessageFilter {
|
||||
channel: Some(channel.id().to_string()),
|
||||
query: Some(query),
|
||||
..Default::default()
|
||||
},
|
||||
time_period: MessageTimePeriod::Absolute {
|
||||
before,
|
||||
after,
|
||||
sort: Some(sort),
|
||||
},
|
||||
limit,
|
||||
})
|
||||
.await?;
|
||||
|
||||
BulkMessageResponse::transform(db, &channel, messages, include_users)
|
||||
BulkMessageResponse::transform(db, Some(&channel), messages, include_users)
|
||||
.await
|
||||
.map(Json)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use revolt_quark::{
|
||||
},
|
||||
perms,
|
||||
web::idempotency::IdempotencyKey,
|
||||
Db, Error, Permission, Ref, Result,
|
||||
Db, Error, Permission, Ref, Result, variables::delta::{MAX_ATTACHMENT_COUNT, MAX_REPLY_COUNT},
|
||||
};
|
||||
|
||||
use regex::Regex;
|
||||
@@ -15,6 +15,7 @@ use rocket::serde::json::Json;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ulid::Ulid;
|
||||
use validator::Validate;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
#[derive(Validate, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct DataMessageSend {
|
||||
@@ -44,10 +45,7 @@ pub struct DataMessageSend {
|
||||
interactions: Option<Interactions>,
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
// ignoring I L O and U is intentional
|
||||
static ref RE_MENTION: Regex = Regex::new(r"<@([0-9A-HJKMNP-TV-Z]{26})>").unwrap();
|
||||
}
|
||||
static RE_MENTION: Lazy<Regex> = Lazy::new(|| Regex::new(r"<@([0-9A-HJKMNP-TV-Z]{26})>").unwrap());
|
||||
|
||||
/// # Send Message
|
||||
///
|
||||
@@ -141,8 +139,8 @@ pub async fn message_send(
|
||||
// 4. Verify replies are valid.
|
||||
let mut replies = HashSet::new();
|
||||
if let Some(entries) = data.replies {
|
||||
if entries.len() > 5 {
|
||||
return Err(Error::TooManyReplies);
|
||||
if entries.len() > *MAX_REPLY_COUNT {
|
||||
return Err(Error::TooManyReplies { max: *MAX_REPLY_COUNT });
|
||||
}
|
||||
|
||||
for Reply { id, mention } in entries {
|
||||
@@ -193,8 +191,8 @@ pub async fn message_send(
|
||||
}
|
||||
|
||||
// ! FIXME: move this to app config
|
||||
if ids.len() > 5 {
|
||||
return Err(Error::TooManyAttachments);
|
||||
if ids.len() > *MAX_ATTACHMENT_COUNT {
|
||||
return Err(Error::TooManyAttachments { max: *MAX_ATTACHMENT_COUNT} );
|
||||
}
|
||||
|
||||
for attachment_id in ids {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use revolt_quark::models::emoji::EmojiParent;
|
||||
use revolt_quark::models::{Emoji, File, User};
|
||||
use revolt_quark::variables::delta::MAX_EMOJI_COUNT;
|
||||
use revolt_quark::{perms, Db, Error, Permission, Result};
|
||||
use serde::Deserialize;
|
||||
use validator::Validate;
|
||||
@@ -55,8 +56,8 @@ pub async fn create_emoji(
|
||||
// Check that there are no more than 100 emoji
|
||||
// ! FIXME: hardcoded upper limit
|
||||
let emojis = db.fetch_emoji_by_parent_id(&server.id).await?;
|
||||
if emojis.len() > 99 {
|
||||
return Err(Error::TooManyEmoji);
|
||||
if emojis.len() > *MAX_EMOJI_COUNT {
|
||||
return Err(Error::TooManyEmoji { max: *MAX_EMOJI_COUNT });
|
||||
}
|
||||
}
|
||||
EmojiParent::Detached => return Err(Error::InvalidOperation),
|
||||
|
||||
@@ -3,6 +3,7 @@ pub use rocket::http::Status;
|
||||
pub use rocket::response::Redirect;
|
||||
use rocket::{Build, Rocket};
|
||||
|
||||
mod admin;
|
||||
mod bots;
|
||||
mod channels;
|
||||
mod customisation;
|
||||
@@ -22,6 +23,7 @@ pub fn mount(mut rocket: Rocket<Build>) -> Rocket<Build> {
|
||||
rocket, "/".to_owned(), settings,
|
||||
"/" => (vec![], custom_openapi_spec()),
|
||||
"" => openapi_get_routes_spec![root::root, root::ping],
|
||||
"/admin" => admin::routes(),
|
||||
"/users" => users::routes(),
|
||||
"/bots" => bots::routes(),
|
||||
"/channels" => channels::routes(),
|
||||
@@ -108,8 +110,9 @@ fn custom_openapi_spec() -> OpenApi {
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Platform Moderation",
|
||||
"name": "Platform Administration",
|
||||
"tags": [
|
||||
"Admin",
|
||||
"User Safety"
|
||||
]
|
||||
},
|
||||
|
||||
@@ -44,8 +44,13 @@ pub async fn fetch_snapshot(
|
||||
user_ids.insert(&message.author);
|
||||
channel_ids.insert(&message.channel);
|
||||
}
|
||||
_ => {
|
||||
todo!()
|
||||
SnapshotContent::User(user) => {
|
||||
user_ids.insert(&user.id);
|
||||
}
|
||||
SnapshotContent::Server(server) => {
|
||||
for channel in &server.channels {
|
||||
channel_ids.insert(channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use revolt_quark::events::client::EventV1;
|
||||
use revolt_quark::models::message::{MessageFilter, MessageQuery, MessageSort, MessageTimePeriod};
|
||||
use revolt_quark::models::report::{ReportStatus, ReportedContent};
|
||||
use revolt_quark::models::snapshot::{Snapshot, SnapshotContent};
|
||||
use revolt_quark::models::{Report, User};
|
||||
@@ -55,26 +56,34 @@ pub async fn report_content(db: &Db, user: User, data: Json<DataReportContent>)
|
||||
|
||||
// Collect prior context
|
||||
let prior_context = db
|
||||
.fetch_messages(
|
||||
&message.channel,
|
||||
Some(15),
|
||||
Some(message.id.to_string()),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.fetch_messages(MessageQuery {
|
||||
filter: MessageFilter {
|
||||
channel: Some(message.channel.to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
limit: Some(15),
|
||||
time_period: MessageTimePeriod::Absolute {
|
||||
before: Some(message.id.to_string()),
|
||||
after: None,
|
||||
sort: Some(MessageSort::Latest),
|
||||
},
|
||||
})
|
||||
.await?;
|
||||
|
||||
// Collect leading context
|
||||
let leading_context = db
|
||||
.fetch_messages(
|
||||
&message.channel,
|
||||
Some(15),
|
||||
None,
|
||||
Some(message.id.to_string()),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.fetch_messages(MessageQuery {
|
||||
filter: MessageFilter {
|
||||
channel: Some(message.channel.to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
limit: Some(15),
|
||||
time_period: MessageTimePeriod::Absolute {
|
||||
before: None,
|
||||
after: Some(message.id.to_string()),
|
||||
sort: Some(MessageSort::Oldest),
|
||||
},
|
||||
})
|
||||
.await?;
|
||||
|
||||
(
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::collections::HashMap;
|
||||
|
||||
use revolt_quark::{
|
||||
models::{server::PartialServer, Channel, User},
|
||||
perms, Db, Error, Permission, Ref, Result,
|
||||
perms, Db, Error, Permission, Ref, Result, variables::delta::MAX_CHANNEL_COUNT,
|
||||
};
|
||||
|
||||
use rocket::serde::json::Json;
|
||||
@@ -11,20 +11,15 @@ use ulid::Ulid;
|
||||
use validator::Validate;
|
||||
|
||||
/// # Channel Type
|
||||
#[derive(Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Serialize, Deserialize, JsonSchema, Default)]
|
||||
enum ChannelType {
|
||||
/// Text Channel
|
||||
#[default]
|
||||
Text,
|
||||
/// Voice Channel
|
||||
Voice,
|
||||
}
|
||||
|
||||
impl Default for ChannelType {
|
||||
fn default() -> Self {
|
||||
ChannelType::Text
|
||||
}
|
||||
}
|
||||
|
||||
/// # Channel Data
|
||||
#[derive(Validate, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct DataCreateChannel {
|
||||
@@ -63,6 +58,10 @@ pub async fn req(
|
||||
.throw_permission(db, Permission::ManageChannel)
|
||||
.await?;
|
||||
|
||||
if server.channels.len() > *MAX_CHANNEL_COUNT {
|
||||
return Err(Error::TooManyChannels { max: *MAX_CHANNEL_COUNT })
|
||||
};
|
||||
|
||||
let id = Ulid::new().to_string();
|
||||
let mut channels = server.channels.clone();
|
||||
channels.push(id.clone());
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use revolt_quark::{
|
||||
models::{Invite, User},
|
||||
perms, Db, Error, Permission, Ref, Result,
|
||||
perms, Db, Permission, Ref, Result,
|
||||
};
|
||||
|
||||
use rocket::serde::json::Json;
|
||||
@@ -11,10 +11,6 @@ use rocket::serde::json::Json;
|
||||
#[openapi(tag = "Server Members")]
|
||||
#[get("/<target>/invites")]
|
||||
pub async fn req(db: &Db, user: User, target: Ref) -> Result<Json<Vec<Invite>>> {
|
||||
if user.bot.is_some() {
|
||||
return Err(Error::IsBot);
|
||||
}
|
||||
|
||||
let server = target.as_server(db).await?;
|
||||
perms(&user)
|
||||
.server(&server)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use revolt_quark::{
|
||||
models::{server::Role, User},
|
||||
perms, Db, Error, Permission, Ref, Result,
|
||||
perms, Db, Error, Permission, Ref, Result, variables::delta::MAX_ROLE_COUNT,
|
||||
};
|
||||
|
||||
use rocket::serde::json::Json;
|
||||
@@ -50,6 +50,10 @@ pub async fn req(
|
||||
.throw_permission(db, Permission::ManageRole)
|
||||
.await?;
|
||||
|
||||
if server.roles.len() > *MAX_ROLE_COUNT {
|
||||
return Err(Error::TooManyRoles { max: *MAX_ROLE_COUNT })
|
||||
};
|
||||
|
||||
let member_rank = permissions.get_member_rank();
|
||||
let rank = if let Some(given_rank) = data.rank {
|
||||
if given_rank <= member_rank.unwrap_or(i64::MIN) {
|
||||
|
||||
@@ -44,6 +44,10 @@ pub async fn req(
|
||||
user: User,
|
||||
info: Json<DataCreateServer>,
|
||||
) -> Result<Json<CreateServerResponse>> {
|
||||
if user.bot.is_some() {
|
||||
return Err(Error::IsBot);
|
||||
}
|
||||
|
||||
let info = info.into_inner();
|
||||
info.validate()
|
||||
.map_err(|error| Error::FailedValidation { error })?;
|
||||
|
||||
@@ -33,6 +33,10 @@ pub struct DataEditServer {
|
||||
/// System message configuration
|
||||
system_messages: Option<SystemMessageChannels>,
|
||||
|
||||
/// Bitfield of server flags
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub flags: Option<i32>,
|
||||
|
||||
// Whether this server is age-restricted
|
||||
// nsfw: Option<bool>,
|
||||
/// Whether this server is public and should show up on [Revolt Discover](https://rvlt.gg)
|
||||
@@ -92,6 +96,11 @@ pub async fn req(
|
||||
.await?;
|
||||
}
|
||||
|
||||
// Check we are privileged if changing sensitive fields
|
||||
if data.flags.is_some() && !user.privileged {
|
||||
return Err(Error::NotPrivileged);
|
||||
}
|
||||
|
||||
if data.categories.is_some() {
|
||||
permissions
|
||||
.throw_permission(db, Permission::ManageChannel)
|
||||
@@ -105,6 +114,7 @@ pub async fn req(
|
||||
banner,
|
||||
categories,
|
||||
system_messages,
|
||||
flags,
|
||||
// nsfw,
|
||||
discoverable,
|
||||
analytics,
|
||||
@@ -116,6 +126,7 @@ pub async fn req(
|
||||
description,
|
||||
categories,
|
||||
system_messages,
|
||||
flags,
|
||||
// nsfw,
|
||||
discoverable,
|
||||
analytics,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use revolt_quark::models::user::{FieldsUser, PartialUser, User};
|
||||
use revolt_quark::models::File;
|
||||
use revolt_quark::{Database, Error, Result};
|
||||
use revolt_quark::{Database, Error, Ref, Result};
|
||||
|
||||
use revolt_quark::models::user::UserStatus;
|
||||
use rocket::serde::json::Json;
|
||||
@@ -24,6 +24,10 @@ pub struct UserProfileData {
|
||||
/// # User Data
|
||||
#[derive(Validate, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct DataEditUser {
|
||||
/// Attachment Id for avatar
|
||||
#[validate(length(min = 1, max = 128))]
|
||||
avatar: Option<String>,
|
||||
|
||||
/// New user status
|
||||
#[validate]
|
||||
status: Option<UserStatus>,
|
||||
@@ -32,9 +36,14 @@ pub struct DataEditUser {
|
||||
/// This is applied as a partial.
|
||||
#[validate]
|
||||
profile: Option<UserProfileData>,
|
||||
/// Attachment Id for avatar
|
||||
#[validate(length(min = 1, max = 128))]
|
||||
avatar: Option<String>,
|
||||
|
||||
/// Bitfield of user badges
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
badges: Option<i32>,
|
||||
/// Enum of user flags
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
flags: Option<i32>,
|
||||
|
||||
/// Fields to remove from user object
|
||||
#[validate(length(min = 1))]
|
||||
remove: Option<Vec<FieldsUser>>,
|
||||
@@ -44,19 +53,36 @@ pub struct DataEditUser {
|
||||
///
|
||||
/// Edit currently authenticated user.
|
||||
#[openapi(tag = "User Information")]
|
||||
#[patch("/@me", data = "<data>")]
|
||||
#[patch("/<target>", data = "<data>")]
|
||||
pub async fn req(
|
||||
db: &State<Database>,
|
||||
mut user: User,
|
||||
target: Ref,
|
||||
data: Json<DataEditUser>,
|
||||
) -> Result<Json<User>> {
|
||||
let data = data.into_inner();
|
||||
data.validate()
|
||||
.map_err(|error| Error::FailedValidation { error })?;
|
||||
|
||||
// If we want to edit a different user than self, ensure we have
|
||||
// permissions and subsequently replace the user in question
|
||||
if target.id != "@me" {
|
||||
if !user.privileged {
|
||||
return Err(Error::NotPrivileged);
|
||||
}
|
||||
|
||||
user = target.as_user(db).await?;
|
||||
// Otherwise, filter out invalid edit fields
|
||||
} else if data.badges.is_some() || data.flags.is_some() {
|
||||
return Err(Error::NotPrivileged);
|
||||
}
|
||||
|
||||
// Exit out early if nothing is changed
|
||||
if data.status.is_none()
|
||||
&& data.profile.is_none()
|
||||
&& data.avatar.is_none()
|
||||
&& data.badges.is_none()
|
||||
&& data.flags.is_none()
|
||||
&& data.remove.is_none()
|
||||
{
|
||||
return Ok(Json(user));
|
||||
@@ -83,7 +109,11 @@ pub async fn req(
|
||||
}
|
||||
}
|
||||
|
||||
let mut partial: PartialUser = Default::default();
|
||||
let mut partial: PartialUser = PartialUser {
|
||||
badges: data.badges,
|
||||
flags: data.flags,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// 2. Apply new avatar
|
||||
if let Some(avatar) = data.avatar {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "revolt-quark"
|
||||
version = "0.5.12"
|
||||
version = "0.5.19"
|
||||
license = "AGPL-3.0-or-later"
|
||||
edition = "2021"
|
||||
|
||||
@@ -42,7 +42,7 @@ revolt_okapi = "0.9.1"
|
||||
revolt_rocket_okapi = { version = "0.9.1", features = [ "swagger" ] }
|
||||
|
||||
# Databases
|
||||
redis-kiss = { version = "0.1.3" }
|
||||
redis-kiss = { version = "0.1.4" }
|
||||
mongodb = { optional = true, version = "2.1.0", default-features = false }
|
||||
|
||||
# Async
|
||||
@@ -57,6 +57,7 @@ log = "0.4.14"
|
||||
pretty_env_logger = "0.4.0"
|
||||
|
||||
# Util
|
||||
rand = "0.8.5"
|
||||
ulid = "0.5.0"
|
||||
regex = "1.5.5"
|
||||
nanoid = "0.4.0"
|
||||
@@ -67,8 +68,7 @@ impl_ops = "0.1.1"
|
||||
num_enum = "0.5.6"
|
||||
reqwest = "0.11.10"
|
||||
bitfield = "0.13.2"
|
||||
once_cell = "1.13.0"
|
||||
lazy_static = "1.4.0"
|
||||
once_cell = "1.17.1"
|
||||
async-lock = "2.6.0"
|
||||
|
||||
lru = { version = "0.7.6", optional = true }
|
||||
|
||||
@@ -99,7 +99,7 @@ impl State {
|
||||
let mut user = self.clone_user();
|
||||
|
||||
// Find all relationships to the user.
|
||||
let mut user_ids: Vec<String> = user
|
||||
let mut user_ids: HashSet<String> = user
|
||||
.relations
|
||||
.as_ref()
|
||||
.map(|arr| arr.iter().map(|x| x.id.to_string()).collect())
|
||||
@@ -128,14 +128,15 @@ impl State {
|
||||
for channel in &channels {
|
||||
match channel {
|
||||
Channel::DirectMessage { recipients, .. } | Channel::Group { recipients, .. } => {
|
||||
user_ids.append(&mut recipients.clone());
|
||||
user_ids.extend(&mut recipients.clone().into_iter());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch presence data for known users.
|
||||
let online_ids = presence_filter_online(&user_ids).await;
|
||||
let online_ids =
|
||||
presence_filter_online(&user_ids.iter().cloned().collect::<Vec<String>>()).await;
|
||||
user.online = Some(true);
|
||||
|
||||
// Fetch user data.
|
||||
@@ -557,7 +558,7 @@ impl EventV1 {
|
||||
|
||||
/// Publish private event
|
||||
pub async fn private(self, id: String) {
|
||||
self.p(format!("{}!", id)).await;
|
||||
self.p(format!("{id}!")).await;
|
||||
}
|
||||
|
||||
/// Publish internal global event
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
use crate::{models::stats::Stats, AbstractStats, Result};
|
||||
|
||||
use super::super::DummyDb;
|
||||
|
||||
#[async_trait]
|
||||
impl AbstractStats for DummyDb {
|
||||
async fn generate_stats(&self) -> Result<Stats> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::models::message::{AppendMessage, Message, MessageSort, PartialMessage};
|
||||
use crate::models::message::{AppendMessage, Message, MessageQuery, PartialMessage};
|
||||
use crate::{AbstractMessage, Result};
|
||||
|
||||
use super::super::DummyDb;
|
||||
@@ -41,28 +41,8 @@ impl AbstractMessage for DummyDb {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn fetch_messages(
|
||||
&self,
|
||||
channel: &str,
|
||||
_limit: Option<i64>,
|
||||
_before: Option<String>,
|
||||
_after: Option<String>,
|
||||
_sort: Option<MessageSort>,
|
||||
_nearby: Option<String>,
|
||||
) -> Result<Vec<Message>> {
|
||||
Ok(vec![self.fetch_message(channel).await.unwrap()])
|
||||
}
|
||||
|
||||
async fn search_messages(
|
||||
&self,
|
||||
channel: &str,
|
||||
_query: &str,
|
||||
_limit: Option<i64>,
|
||||
_before: Option<String>,
|
||||
_after: Option<String>,
|
||||
_sort: MessageSort,
|
||||
) -> Result<Vec<Message>> {
|
||||
Ok(vec![self.fetch_message(channel).await.unwrap()])
|
||||
async fn fetch_messages(&self, _query: MessageQuery) -> Result<Vec<Message>> {
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
/// Add a new reaction to a message
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::AbstractDatabase;
|
||||
|
||||
pub mod admin {
|
||||
pub mod migrations;
|
||||
pub mod stats;
|
||||
}
|
||||
|
||||
pub mod media {
|
||||
|
||||
@@ -5,13 +5,11 @@ use crate::{
|
||||
Database, Error, Result,
|
||||
};
|
||||
|
||||
lazy_static! {
|
||||
static ref 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'
|
||||
];
|
||||
}
|
||||
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'
|
||||
];
|
||||
|
||||
impl Invite {
|
||||
/// Get the invite code for this invite
|
||||
@@ -30,7 +28,7 @@ impl Invite {
|
||||
|
||||
/// Create a new invite from given information
|
||||
pub async fn create(db: &Database, creator: &User, target: &Channel) -> Result<Invite> {
|
||||
let code = nanoid!(8, &*ALPHABET);
|
||||
let code = nanoid!(8, &ALPHABET);
|
||||
let invite = match &target {
|
||||
Channel::Group { id, .. } => Ok(Invite::Group {
|
||||
code,
|
||||
|
||||
@@ -305,12 +305,12 @@ impl IntoUsers for Message {
|
||||
|
||||
impl IntoUsers for Vec<Message> {
|
||||
fn get_user_ids(&self) -> Vec<String> {
|
||||
let mut ids = vec![];
|
||||
let mut ids = HashSet::new();
|
||||
for message in self {
|
||||
ids.append(&mut message.get_user_ids());
|
||||
ids.extend(&mut message.get_user_ids().into_iter());
|
||||
}
|
||||
|
||||
ids
|
||||
ids.into_iter().collect()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,7 +377,7 @@ impl SendableEmbed {
|
||||
impl BulkMessageResponse {
|
||||
pub async fn transform(
|
||||
db: &Database,
|
||||
channel: &Channel,
|
||||
channel: Option<&Channel>,
|
||||
messages: Vec<Message>,
|
||||
include_users: Option<bool>,
|
||||
) -> Result<BulkMessageResponse> {
|
||||
@@ -386,7 +386,8 @@ impl BulkMessageResponse {
|
||||
let users = User::fetch_foreign_users(db, &user_ids).await?;
|
||||
|
||||
Ok(match channel {
|
||||
Channel::TextChannel { server, .. } | Channel::VoiceChannel { server, .. } => {
|
||||
Some(Channel::TextChannel { server, .. })
|
||||
| Some(Channel::VoiceChannel { server, .. }) => {
|
||||
BulkMessageResponse::MessagesAndUsers {
|
||||
messages,
|
||||
users,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::{collections::HashSet, str::FromStr};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use ulid::Ulid;
|
||||
|
||||
@@ -8,13 +9,10 @@ use crate::{
|
||||
Database, Result,
|
||||
};
|
||||
|
||||
lazy_static! {
|
||||
/// Permissible emojis
|
||||
static ref PERMISSIBLE_EMOJIS: HashSet<String> = include_str!(crate::asset!("emojis.txt"))
|
||||
.split('\n')
|
||||
.map(|x| x.into())
|
||||
.collect();
|
||||
}
|
||||
static PERMISSIBLE_EMOJIS: Lazy<HashSet<String>> = Lazy::new(|| include_str!(crate::asset!("emojis.txt"))
|
||||
.split('\n')
|
||||
.map(|x| x.into())
|
||||
.collect());
|
||||
|
||||
impl Emoji {
|
||||
/// Get parent id
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::time::Duration;
|
||||
use std::{time::Duration, ops::BitXor};
|
||||
|
||||
use bson::{Bson, DateTime};
|
||||
use futures::StreamExt;
|
||||
@@ -503,13 +503,8 @@ pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 {
|
||||
|
||||
update.insert(
|
||||
"default_permissions",
|
||||
(*DEFAULT_PERMISSION_SERVER
|
||||
// Remove Send Message permission if it wasn't originally granted
|
||||
^ (if has_send {
|
||||
0
|
||||
} else {
|
||||
Permission::SendMessage as u64
|
||||
})) as i64,
|
||||
// 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,
|
||||
);
|
||||
|
||||
if let Some(Bson::Document(mut roles)) = document.remove("roles") {
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use bson::{from_document, Document};
|
||||
use futures::StreamExt;
|
||||
|
||||
use crate::{
|
||||
models::stats::{Index, Stats},
|
||||
AbstractStats, Error, Result,
|
||||
};
|
||||
|
||||
use super::super::MongoDb;
|
||||
|
||||
#[async_trait]
|
||||
impl AbstractStats for MongoDb {
|
||||
async fn generate_stats(&self) -> Result<Stats> {
|
||||
let mut indices = HashMap::new();
|
||||
let mut coll_stats = HashMap::new();
|
||||
|
||||
let collection_names =
|
||||
self.db()
|
||||
.list_collection_names(None)
|
||||
.await
|
||||
.map_err(|_| Error::DatabaseError {
|
||||
operation: "list_collection_names",
|
||||
with: "database",
|
||||
})?;
|
||||
|
||||
for collection in collection_names {
|
||||
indices.insert(
|
||||
collection.to_string(),
|
||||
self.col::<Document>(&collection)
|
||||
.aggregate(
|
||||
vec![doc! {
|
||||
"$indexStats": { }
|
||||
}],
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map_err(|_| Error::DatabaseError {
|
||||
operation: "aggregate",
|
||||
with: "col",
|
||||
})?
|
||||
.filter_map(|s| async { s.ok() })
|
||||
.collect::<Vec<Document>>()
|
||||
.await
|
||||
.into_iter()
|
||||
.filter_map(|doc| from_document(doc).ok())
|
||||
.collect::<Vec<Index>>(),
|
||||
);
|
||||
|
||||
coll_stats.insert(
|
||||
collection.to_string(),
|
||||
self.col::<Document>(&collection)
|
||||
.aggregate(
|
||||
vec![doc! {
|
||||
"$collStats": {
|
||||
"latencyStats": {
|
||||
"histograms": true
|
||||
},
|
||||
"storageStats": {},
|
||||
"count": {},
|
||||
"queryExecStats": {}
|
||||
}
|
||||
}],
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map_err(|_| Error::DatabaseError {
|
||||
operation: "aggregate",
|
||||
with: "col",
|
||||
})?
|
||||
.filter_map(|s| async { s.ok() })
|
||||
.collect::<Vec<Document>>()
|
||||
.await
|
||||
.into_iter()
|
||||
.filter_map(|doc| from_document(doc).ok())
|
||||
.next()
|
||||
.ok_or(Error::DatabaseError {
|
||||
operation: "next aggregation",
|
||||
with: "col",
|
||||
})?,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(Stats {
|
||||
indices,
|
||||
coll_stats,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,9 @@ use bson::{to_bson, Document};
|
||||
use futures::try_join;
|
||||
use mongodb::options::FindOptions;
|
||||
|
||||
use crate::models::message::{AppendMessage, Message, MessageSort, PartialMessage};
|
||||
use crate::models::message::{
|
||||
AppendMessage, Message, MessageQuery, MessageSort, MessageTimePeriod, PartialMessage,
|
||||
};
|
||||
use crate::r#impl::mongo::DocumentId;
|
||||
use crate::{AbstractMessage, Error, Result};
|
||||
|
||||
@@ -139,145 +141,138 @@ impl AbstractMessage for MongoDb {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn fetch_messages(
|
||||
&self,
|
||||
channel: &str,
|
||||
limit: Option<i64>,
|
||||
before: Option<String>,
|
||||
after: Option<String>,
|
||||
sort: Option<MessageSort>,
|
||||
nearby: Option<String>,
|
||||
) -> Result<Vec<Message>> {
|
||||
let limit = limit.unwrap_or(50);
|
||||
Ok(if let Some(nearby) = nearby {
|
||||
let (a, b) = try_join!(
|
||||
self.find_with_options::<_, Message>(
|
||||
COL,
|
||||
async fn fetch_messages(&self, query: MessageQuery) -> Result<Vec<Message>> {
|
||||
let mut filter = doc! {};
|
||||
|
||||
// 1. Apply message filters
|
||||
if let Some(channel) = query.filter.channel {
|
||||
filter.insert("channel", channel);
|
||||
}
|
||||
|
||||
if let Some(author) = query.filter.author {
|
||||
filter.insert("author", author);
|
||||
}
|
||||
|
||||
let is_search_query = if let Some(query) = query.filter.query {
|
||||
filter.insert(
|
||||
"$text",
|
||||
doc! {
|
||||
"$search": query
|
||||
},
|
||||
);
|
||||
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
// 2. Find query limit
|
||||
let limit = query.limit.unwrap_or(50);
|
||||
|
||||
// 3. Apply message time period
|
||||
match query.time_period {
|
||||
MessageTimePeriod::Relative { nearby } => {
|
||||
// 3.1. Prepare filters
|
||||
let mut older_message_filter = filter.clone();
|
||||
let mut newer_message_filter = filter;
|
||||
|
||||
older_message_filter.insert(
|
||||
"_id",
|
||||
doc! {
|
||||
"channel": channel,
|
||||
"_id": {
|
||||
"$gte": &nearby
|
||||
}
|
||||
"$lt": &nearby
|
||||
},
|
||||
FindOptions::builder()
|
||||
.limit(limit / 2 + 1)
|
||||
.sort(doc! {
|
||||
"_id": 1_i32
|
||||
})
|
||||
.build(),
|
||||
),
|
||||
self.find_with_options::<_, Message>(
|
||||
COL,
|
||||
);
|
||||
|
||||
newer_message_filter.insert(
|
||||
"_id",
|
||||
doc! {
|
||||
"channel": channel,
|
||||
"_id": {
|
||||
"$lt": &nearby
|
||||
}
|
||||
"$gte": &nearby
|
||||
},
|
||||
);
|
||||
|
||||
// 3.2. Execute in both directions
|
||||
let (a, b) = try_join!(
|
||||
self.find_with_options::<_, Message>(
|
||||
COL,
|
||||
newer_message_filter,
|
||||
FindOptions::builder()
|
||||
.limit(limit / 2 + 1)
|
||||
.sort(doc! {
|
||||
"_id": 1_i32
|
||||
})
|
||||
.build(),
|
||||
),
|
||||
self.find_with_options::<_, Message>(
|
||||
COL,
|
||||
older_message_filter,
|
||||
FindOptions::builder()
|
||||
.limit(limit / 2)
|
||||
.sort(doc! {
|
||||
"_id": -1_i32
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
)?;
|
||||
|
||||
Ok([a, b].concat())
|
||||
}
|
||||
MessageTimePeriod::Absolute {
|
||||
before,
|
||||
after,
|
||||
sort,
|
||||
} => {
|
||||
// 3.1. Apply message ID filter
|
||||
if let Some(doc) = match (before, after) {
|
||||
(Some(before), Some(after)) => Some(doc! {
|
||||
"$lt": before,
|
||||
"$gt": after
|
||||
}),
|
||||
(Some(before), _) => Some(doc! {
|
||||
"$lt": before
|
||||
}),
|
||||
(_, Some(after)) => Some(doc! {
|
||||
"$gt": after
|
||||
}),
|
||||
_ => None,
|
||||
} {
|
||||
filter.insert("_id", doc);
|
||||
}
|
||||
|
||||
// 3.2. Execute with given message sort
|
||||
self.find_with_options(
|
||||
COL,
|
||||
filter,
|
||||
FindOptions::builder()
|
||||
.limit(limit / 2)
|
||||
.sort(doc! {
|
||||
"_id": -1_i32
|
||||
.limit(limit)
|
||||
.sort(match sort.unwrap_or(MessageSort::Latest) {
|
||||
// Sort by relevance, fallback to latest
|
||||
MessageSort::Relevance => {
|
||||
if is_search_query {
|
||||
doc! {
|
||||
"score": {
|
||||
"$meta": "textScore"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
doc! {
|
||||
"_id": -1_i32
|
||||
}
|
||||
}
|
||||
}
|
||||
// Sort by latest first
|
||||
MessageSort::Latest => doc! {
|
||||
"_id": -1_i32
|
||||
},
|
||||
// Sort by oldest first
|
||||
MessageSort::Oldest => doc! {
|
||||
"_id": 1_i32
|
||||
},
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
)?;
|
||||
|
||||
[a, b].concat()
|
||||
} else {
|
||||
let mut query = doc! { "channel": channel };
|
||||
if let Some(before) = before {
|
||||
query.insert("_id", doc! { "$lt": before });
|
||||
.await
|
||||
}
|
||||
|
||||
if let Some(after) = after {
|
||||
query.insert("_id", doc! { "$gt": after });
|
||||
}
|
||||
|
||||
let sort: i32 = if let MessageSort::Latest = sort.unwrap_or(MessageSort::Latest) {
|
||||
-1
|
||||
} else {
|
||||
1
|
||||
};
|
||||
|
||||
self.find_with_options::<_, Message>(
|
||||
COL,
|
||||
query,
|
||||
FindOptions::builder()
|
||||
.limit(limit)
|
||||
.sort(doc! {
|
||||
"_id": sort
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.await?
|
||||
})
|
||||
}
|
||||
|
||||
async fn search_messages(
|
||||
&self,
|
||||
channel: &str,
|
||||
query: &str,
|
||||
limit: Option<i64>,
|
||||
before: Option<String>,
|
||||
after: Option<String>,
|
||||
sort: MessageSort,
|
||||
) -> Result<Vec<Message>> {
|
||||
let limit = limit.unwrap_or(50);
|
||||
|
||||
let mut filter = doc! {
|
||||
"channel": channel,
|
||||
"$text": {
|
||||
"$search": query
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(doc) = match (before, after) {
|
||||
(Some(before), Some(after)) => Some(doc! {
|
||||
"lt": before,
|
||||
"gt": after
|
||||
}),
|
||||
(Some(before), _) => Some(doc! {
|
||||
"lt": before
|
||||
}),
|
||||
(_, Some(after)) => Some(doc! {
|
||||
"gt": after
|
||||
}),
|
||||
_ => None,
|
||||
} {
|
||||
filter.insert("_id", doc);
|
||||
}
|
||||
|
||||
self.find_with_options(
|
||||
COL,
|
||||
filter,
|
||||
FindOptions::builder()
|
||||
.projection(if let MessageSort::Relevance = &sort {
|
||||
doc! {
|
||||
"score": {
|
||||
"$meta": "textScore"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
doc! {}
|
||||
})
|
||||
.limit(limit)
|
||||
.sort(match &sort {
|
||||
MessageSort::Relevance => doc! {
|
||||
"score": {
|
||||
"$meta": "textScore"
|
||||
}
|
||||
},
|
||||
MessageSort::Latest => doc! {
|
||||
"_id": -1_i32
|
||||
},
|
||||
MessageSort::Oldest => doc! {
|
||||
"_id": 1_i32
|
||||
},
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Add a new reaction to a message
|
||||
|
||||
@@ -37,7 +37,7 @@ impl AbstractAttachment for MongoDb {
|
||||
parent_type: &str,
|
||||
parent_id: &str,
|
||||
) -> Result<File> {
|
||||
let key = format!("{}_id", parent_type);
|
||||
let key = format!("{parent_type}_id");
|
||||
match self
|
||||
.find_one::<File>(
|
||||
COL,
|
||||
|
||||
@@ -13,6 +13,7 @@ use crate::{util::manipulation::prefix_keys, AbstractDatabase, Error, Result};
|
||||
|
||||
pub mod admin {
|
||||
pub mod migrations;
|
||||
pub mod stats;
|
||||
}
|
||||
|
||||
pub mod media {
|
||||
@@ -89,24 +90,25 @@ impl MongoDb {
|
||||
where
|
||||
O: Into<Option<FindOptions>>,
|
||||
{
|
||||
Ok(self
|
||||
.col::<T>(collection)
|
||||
.find(projection, options)
|
||||
.await
|
||||
.map_err(|_| Error::DatabaseError {
|
||||
let result = self.col::<T>(collection).find(projection, options).await;
|
||||
Ok(if cfg!(debug_assertions) {
|
||||
result.unwrap()
|
||||
} else {
|
||||
result.map_err(|_| Error::DatabaseError {
|
||||
operation: "find",
|
||||
with: collection,
|
||||
})?
|
||||
.filter_map(|s| async {
|
||||
if cfg!(debug_assertions) {
|
||||
// Hard fail on invalid documents
|
||||
Some(s.unwrap())
|
||||
} else {
|
||||
s.ok()
|
||||
}
|
||||
})
|
||||
.collect::<Vec<T>>()
|
||||
.await)
|
||||
}
|
||||
.filter_map(|s| async {
|
||||
if cfg!(debug_assertions) {
|
||||
// Hard fail on invalid documents
|
||||
Some(s.unwrap())
|
||||
} else {
|
||||
s.ok()
|
||||
}
|
||||
})
|
||||
.collect::<Vec<T>>()
|
||||
.await)
|
||||
}
|
||||
|
||||
async fn find<T: DeserializeOwned + Unpin + Send + Sync>(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use bson::Document;
|
||||
use futures::StreamExt;
|
||||
use mongodb::options::{Collation, CollationStrength, FindOneOptions, FindOptions};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use crate::models::user::{FieldsUser, PartialUser, RelationshipStatus, User};
|
||||
use crate::r#impl::mongo::IntoDocumentPath;
|
||||
@@ -8,16 +9,14 @@ use crate::{AbstractUser, Error, Result};
|
||||
|
||||
use super::super::MongoDb;
|
||||
|
||||
lazy_static! {
|
||||
static ref FIND_USERNAME_OPTIONS: FindOneOptions = FindOneOptions::builder()
|
||||
.collation(
|
||||
Collation::builder()
|
||||
.locale("en")
|
||||
.strength(CollationStrength::Secondary)
|
||||
.build()
|
||||
)
|
||||
.build();
|
||||
}
|
||||
static FIND_USERNAME_OPTIONS: Lazy<FindOneOptions> = Lazy::new(|| FindOneOptions::builder()
|
||||
.collation(
|
||||
Collation::builder()
|
||||
.locale("en")
|
||||
.strength(CollationStrength::Secondary)
|
||||
.build()
|
||||
)
|
||||
.build());
|
||||
|
||||
static COL: &str = "users";
|
||||
|
||||
@@ -265,7 +264,7 @@ impl AbstractUser for MongoDb {
|
||||
[
|
||||
{
|
||||
"_id": target_id,
|
||||
"status": format!("{:?}", relationship)
|
||||
"status": format!("{relationship:?}")
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
@@ -11,8 +11,6 @@ extern crate impl_ops;
|
||||
#[macro_use]
|
||||
extern crate optional_struct;
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
#[macro_use]
|
||||
extern crate bitfield;
|
||||
#[macro_use]
|
||||
extern crate bson;
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use iso8601_timestamp::Timestamp;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Index access information
|
||||
#[derive(Serialize, Deserialize, JsonSchema, Debug)]
|
||||
pub struct IndexAccess {
|
||||
/// Operations since timestamp
|
||||
ops: i32,
|
||||
|
||||
/// Timestamp at which data keeping begun
|
||||
since: Timestamp,
|
||||
}
|
||||
|
||||
/// Collection index
|
||||
#[derive(Serialize, Deserialize, JsonSchema, Debug)]
|
||||
pub struct Index {
|
||||
/// Index name
|
||||
name: String,
|
||||
|
||||
/// Access information
|
||||
accesses: IndexAccess,
|
||||
}
|
||||
|
||||
/// Histogram entry
|
||||
#[derive(Serialize, Deserialize, JsonSchema, Debug)]
|
||||
pub struct LatencyHistogramEntry {
|
||||
/// Time
|
||||
micros: i64,
|
||||
|
||||
/// Count
|
||||
count: i64,
|
||||
}
|
||||
|
||||
/// Collection latency stats
|
||||
#[derive(Serialize, Deserialize, JsonSchema, Debug)]
|
||||
pub struct LatencyStats {
|
||||
/// Total operations
|
||||
ops: i64,
|
||||
|
||||
/// Timestamp at which data keeping begun
|
||||
latency: i64,
|
||||
|
||||
/// Histogram representation of latency data
|
||||
histogram: Vec<LatencyHistogramEntry>,
|
||||
}
|
||||
|
||||
/// Collection storage stats
|
||||
#[derive(Serialize, Deserialize, JsonSchema, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct StorageStats {
|
||||
/// Uncompressed data size
|
||||
size: i64,
|
||||
|
||||
/// Data size on disk
|
||||
storage_size: i64,
|
||||
|
||||
/// Total size of all indexes
|
||||
total_index_size: i64,
|
||||
|
||||
/// Sum of storage size and total index size
|
||||
total_size: i64,
|
||||
|
||||
/// Individual index sizes
|
||||
index_sizes: HashMap<String, i64>,
|
||||
|
||||
/// Number of documents in collection
|
||||
count: i64,
|
||||
|
||||
/// Average size of each document
|
||||
avg_obj_size: i64,
|
||||
}
|
||||
|
||||
/// Query collection scan stats
|
||||
#[derive(Serialize, Deserialize, JsonSchema, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CollectionScans {
|
||||
/// Number of total collection scans
|
||||
total: i64,
|
||||
|
||||
/// Number of total collection scans not using a tailable cursor
|
||||
non_tailable: i64,
|
||||
}
|
||||
|
||||
/// Collection query execution stats
|
||||
#[derive(Serialize, Deserialize, JsonSchema, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct QueryExecStats {
|
||||
/// Stats regarding collection scans
|
||||
collection_scans: CollectionScans,
|
||||
}
|
||||
|
||||
/// Collection stats
|
||||
#[derive(Serialize, Deserialize, JsonSchema, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CollectionStats {
|
||||
/// Namespace
|
||||
ns: String,
|
||||
|
||||
/// Local time
|
||||
local_time: Timestamp,
|
||||
|
||||
/// Latency stats
|
||||
latency_stats: HashMap<String, LatencyStats>,
|
||||
|
||||
/// Query exec stats
|
||||
query_exec_stats: QueryExecStats,
|
||||
|
||||
/// Number of documents in collection
|
||||
count: u64,
|
||||
}
|
||||
|
||||
/// Server Stats
|
||||
#[derive(Serialize, JsonSchema, Debug)]
|
||||
pub struct Stats {
|
||||
/// Index usage information
|
||||
pub indices: HashMap<String, Vec<Index>>,
|
||||
|
||||
/// Collection stats
|
||||
pub coll_stats: HashMap<String, CollectionStats>,
|
||||
}
|
||||
@@ -157,10 +157,11 @@ pub struct Message {
|
||||
/// # Message Sort
|
||||
///
|
||||
/// Sort used for retrieving messages
|
||||
#[derive(Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Serialize, Deserialize, JsonSchema, Debug, Default)]
|
||||
#[cfg_attr(feature = "rocket_impl", derive(FromFormField))]
|
||||
pub enum MessageSort {
|
||||
/// Sort by the most relevant messages
|
||||
#[default]
|
||||
Relevance,
|
||||
/// Sort by the newest messages first
|
||||
Latest,
|
||||
@@ -168,10 +169,54 @@ pub enum MessageSort {
|
||||
Oldest,
|
||||
}
|
||||
|
||||
impl Default for MessageSort {
|
||||
fn default() -> MessageSort {
|
||||
MessageSort::Relevance
|
||||
}
|
||||
/// # Message Time Period
|
||||
///
|
||||
/// Filter and sort messages by time
|
||||
#[derive(Serialize, Deserialize, JsonSchema)]
|
||||
#[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
|
||||
#[derive(Serialize, Deserialize, JsonSchema, Default)]
|
||||
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
|
||||
#[derive(Serialize, Deserialize, JsonSchema)]
|
||||
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,
|
||||
}
|
||||
|
||||
/// # Bulk Message Response
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Metadata associated with file
|
||||
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)]
|
||||
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, Default)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum Metadata {
|
||||
/// File is just a generic uncategorised file
|
||||
#[default]
|
||||
File,
|
||||
/// File contains textual data and should be displayed as such
|
||||
Text,
|
||||
@@ -16,12 +17,6 @@ pub enum Metadata {
|
||||
Audio,
|
||||
}
|
||||
|
||||
impl Default for Metadata {
|
||||
fn default() -> Metadata {
|
||||
Metadata::File
|
||||
}
|
||||
}
|
||||
|
||||
/// Representation of a File on Revolt
|
||||
/// Generated by Autumn
|
||||
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, Default)]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
mod admin {
|
||||
pub mod migrations;
|
||||
pub mod simple;
|
||||
pub mod stats;
|
||||
}
|
||||
|
||||
mod media {
|
||||
|
||||
@@ -118,7 +118,7 @@ pub struct Server {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub banner: Option<File>,
|
||||
|
||||
/// Enum of server flags
|
||||
/// Bitfield of server flags
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub flags: Option<i32>,
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use num_enum::TryFromPrimitive;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::ops;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::ops::{self, Add};
|
||||
|
||||
/// Permission value on Revolt
|
||||
///
|
||||
@@ -97,24 +98,18 @@ pub enum Permission {
|
||||
impl_op_ex!(+ |a: &Permission, b: &Permission| -> u64 { *a as u64 | *b as u64 });
|
||||
impl_op_ex_commutative!(+ |a: &u64, b: &Permission| -> u64 { *a | *b as u64 });
|
||||
|
||||
lazy_static! {
|
||||
pub static ref ALLOW_IN_TIMEOUT: u64 = Permission::ViewChannel + Permission::ReadMessageHistory;
|
||||
pub static ref DEFAULT_PERMISSION_VIEW_ONLY: u64 =
|
||||
Permission::ViewChannel + Permission::ReadMessageHistory;
|
||||
pub static ref DEFAULT_PERMISSION: u64 = *DEFAULT_PERMISSION_VIEW_ONLY
|
||||
+ Permission::SendMessage
|
||||
+ Permission::InviteOthers
|
||||
+ Permission::SendEmbeds
|
||||
+ Permission::UploadFiles
|
||||
+ Permission::Connect
|
||||
+ Permission::Speak;
|
||||
pub static ref DEFAULT_PERMISSION_SAVED_MESSAGES: u64 = Permission::GrantAllSafe as u64;
|
||||
pub static ref DEFAULT_PERMISSION_DIRECT_MESSAGE: u64 = *DEFAULT_PERMISSION
|
||||
+ Permission::ManageChannel
|
||||
+ Permission::React;
|
||||
pub static ref DEFAULT_PERMISSION_SERVER: u64 =
|
||||
*DEFAULT_PERMISSION + Permission::React + Permission::ChangeNickname + Permission::ChangeAvatar;
|
||||
}
|
||||
pub static ALLOW_IN_TIMEOUT: Lazy<u64> = Lazy::new(|| Permission::ViewChannel + Permission::ReadMessageHistory);
|
||||
pub static DEFAULT_PERMISSION_VIEW_ONLY: Lazy<u64> = Lazy::new(|| Permission::ViewChannel + Permission::ReadMessageHistory);
|
||||
pub static DEFAULT_PERMISSION: Lazy<u64> = Lazy::new(|| DEFAULT_PERMISSION_VIEW_ONLY.add(
|
||||
Permission::SendMessage
|
||||
+ Permission::InviteOthers
|
||||
+ Permission::SendEmbeds
|
||||
+ Permission::UploadFiles
|
||||
+ Permission::Connect
|
||||
+ Permission::Speak));
|
||||
pub static DEFAULT_PERMISSION_SAVED_MESSAGES: u64 = Permission::GrantAllSafe as u64;
|
||||
pub static DEFAULT_PERMISSION_DIRECT_MESSAGE: Lazy<u64> = Lazy::new(|| DEFAULT_PERMISSION.add(Permission::ManageChannel + Permission::React));
|
||||
pub static DEFAULT_PERMISSION_SERVER: Lazy<u64> = Lazy::new(|| DEFAULT_PERMISSION.add(Permission::React + Permission::ChangeNickname + Permission::ChangeAvatar));
|
||||
|
||||
bitfield! {
|
||||
#[derive(Default)]
|
||||
|
||||
@@ -113,7 +113,7 @@ async fn calculate_channel_permission(
|
||||
let value: PermissionValue = match channel {
|
||||
Channel::SavedMessages { user, .. } => {
|
||||
if user == &data.perspective.id {
|
||||
(*DEFAULT_PERMISSION_SAVED_MESSAGES).into()
|
||||
DEFAULT_PERMISSION_SAVED_MESSAGES.into()
|
||||
} else {
|
||||
0_u64.into()
|
||||
}
|
||||
|
||||
@@ -51,6 +51,10 @@ pub fn get_relationship(a: &User, b: &str) -> RelationshipStatus {
|
||||
async fn calculate_permission(data: &mut PermissionCalculator<'_>, db: &crate::Database) -> u32 {
|
||||
let user = data.user.get().unwrap();
|
||||
|
||||
if data.perspective.privileged {
|
||||
return u32::MAX;
|
||||
}
|
||||
|
||||
if data.perspective.id == user.id {
|
||||
return u32::MAX;
|
||||
}
|
||||
|
||||
@@ -1,64 +1,13 @@
|
||||
use std::env;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref REGION_ID: u16 = env::var("REGION_ID")
|
||||
pub static REGION_ID: Lazy<u16> = Lazy::new(|| {
|
||||
env::var("REGION_ID")
|
||||
.unwrap_or_else(|_| "0".to_string())
|
||||
.parse()
|
||||
.unwrap();
|
||||
pub static ref REGION_KEY: String = format!("region{}", &*REGION_ID);
|
||||
}
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
/// Compact presence information for a user
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct PresenceEntry {
|
||||
/// Region this session exists in
|
||||
///
|
||||
/// We can have up to 65535 regions
|
||||
pub region_id: u16,
|
||||
|
||||
/// Unique session ID
|
||||
pub session_id: u8,
|
||||
|
||||
/// Known flags about session
|
||||
pub flags: u8,
|
||||
}
|
||||
|
||||
impl PresenceEntry {
|
||||
/// Create a new presence entry from a given session ID and known flags
|
||||
pub fn from(session_id: u8, flags: u8) -> Self {
|
||||
Self {
|
||||
region_id: *REGION_ID,
|
||||
session_id,
|
||||
flags,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait PresenceOp {
|
||||
/// Find next available session ID
|
||||
fn find_next_id(&self) -> u8;
|
||||
}
|
||||
|
||||
impl PresenceOp for Vec<PresenceEntry> {
|
||||
fn find_next_id(&self) -> u8 {
|
||||
// O(n^2) scan algorithm
|
||||
// should be relatively fast at low numbers anyways
|
||||
for i in 0..255 {
|
||||
let mut found = false;
|
||||
for entry in self {
|
||||
if entry.session_id == i {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
255
|
||||
}
|
||||
}
|
||||
pub static REGION_KEY: Lazy<String> = Lazy::new(|| format!("region{}", &*REGION_ID));
|
||||
pub static ONLINE_SET: &str = "online";
|
||||
|
||||
@@ -2,87 +2,79 @@ use std::collections::HashSet;
|
||||
|
||||
use redis_kiss::{get_connection, AsyncCommands};
|
||||
|
||||
use rand::Rng;
|
||||
mod entry;
|
||||
mod operations;
|
||||
|
||||
use entry::{PresenceEntry, PresenceOp};
|
||||
use operations::{
|
||||
__add_to_set_sessions, __delete_key_presence_entry, __get_key_presence_entry,
|
||||
__get_set_sessions, __remove_from_set_sessions, __set_key_presence_entry,
|
||||
__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 crate::presence::operations::__delete_set_sessions;
|
||||
|
||||
use self::entry::REGION_KEY;
|
||||
use self::entry::{ONLINE_SET, REGION_KEY};
|
||||
|
||||
/// Create a new presence session, returns the ID of this session
|
||||
pub async fn presence_create_session(user_id: &str, flags: u8) -> (bool, u8) {
|
||||
pub async fn presence_create_session(user_id: &str, flags: u8) -> (bool, u32) {
|
||||
info!("Creating a presence session for {user_id} with flags {flags}");
|
||||
|
||||
// Try to find the presence entry for this user.
|
||||
let mut conn = get_connection().await.unwrap();
|
||||
let mut entry: Vec<PresenceEntry> = __get_key_presence_entry(&mut conn, user_id)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
if let Ok(mut conn) = get_connection().await {
|
||||
// Check whether this is the first session
|
||||
let was_empty = __get_set_size(&mut conn, user_id).await == 0;
|
||||
|
||||
// Return whether this was the first session.
|
||||
let was_empty = entry.is_empty();
|
||||
info!("User ID {} just came online.", &user_id);
|
||||
// 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)
|
||||
};
|
||||
|
||||
// Generate session ID and push new entry.
|
||||
let session_id = entry.find_next_id();
|
||||
entry.push(PresenceEntry::from(session_id, flags));
|
||||
__set_key_presence_entry(&mut conn, user_id, entry).await;
|
||||
// Add session to user's sessions and to the region
|
||||
__add_to_set_u32(&mut conn, user_id, session_id).await;
|
||||
__add_to_set_string(&mut conn, ONLINE_SET, user_id).await;
|
||||
__add_to_set_string(&mut conn, ®ION_KEY, &format!("{user_id}:{session_id}")).await;
|
||||
info!("Created session for {user_id}, assigned them a session ID of {session_id}.");
|
||||
|
||||
// Add to region set in case of failure.
|
||||
__add_to_set_sessions(&mut conn, ®ION_KEY, user_id, session_id).await;
|
||||
(was_empty, session_id)
|
||||
(was_empty, session_id)
|
||||
} else {
|
||||
// Fail through
|
||||
(false, 0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete existing presence session
|
||||
pub async fn presence_delete_session(user_id: &str, session_id: u8) -> bool {
|
||||
pub async fn presence_delete_session(user_id: &str, session_id: u32) -> bool {
|
||||
presence_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: u8,
|
||||
session_id: u32,
|
||||
skip_region: bool,
|
||||
) -> bool {
|
||||
info!("Deleting presence session for {user_id} with id {session_id}");
|
||||
|
||||
// Return whether this was the last session.
|
||||
let mut is_empty = false;
|
||||
if let Ok(mut conn) = get_connection().await {
|
||||
// Remove the session
|
||||
__remove_from_set_u32(&mut conn, user_id, session_id).await;
|
||||
|
||||
// Only continue if we can actually find one.
|
||||
let mut conn = get_connection().await.unwrap();
|
||||
let entry: Option<Vec<PresenceEntry>> = __get_key_presence_entry(&mut conn, user_id).await;
|
||||
if let Some(entry) = entry {
|
||||
let entries = entry
|
||||
.into_iter()
|
||||
.filter(|x| x.session_id != session_id)
|
||||
.collect::<Vec<PresenceEntry>>();
|
||||
|
||||
// If entry is empty, then just delete it.
|
||||
if entries.is_empty() {
|
||||
__delete_key_presence_entry(&mut conn, user_id).await;
|
||||
is_empty = true;
|
||||
} else {
|
||||
__set_key_presence_entry(&mut conn, user_id, entries).await;
|
||||
}
|
||||
|
||||
// Remove from region set.
|
||||
// Remove from the region
|
||||
if !skip_region {
|
||||
__remove_from_set_sessions(&mut conn, ®ION_KEY, user_id, session_id).await;
|
||||
__remove_from_set_string(&mut conn, ®ION_KEY, &format!("{user_id}:{session_id}"))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
if is_empty {
|
||||
info!("User ID {} just went offline.", &user_id);
|
||||
}
|
||||
// Return whether this was the last session
|
||||
let is_empty = __get_set_size(&mut conn, user_id).await == 0;
|
||||
if is_empty {
|
||||
__remove_from_set_string(&mut conn, ONLINE_SET, user_id).await;
|
||||
info!("User ID {} just went offline.", &user_id);
|
||||
}
|
||||
|
||||
is_empty
|
||||
is_empty
|
||||
} else {
|
||||
// Fail through
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Check whether a given user ID is online
|
||||
@@ -102,6 +94,10 @@ pub async fn presence_filter_online(user_ids: &'_ [String]) -> HashSet<String> {
|
||||
return set;
|
||||
}
|
||||
|
||||
// NOTE: at the point that we need mobile indicators
|
||||
// you can interpret the data here and return a new data
|
||||
// structure like HashMap<String /* id */, u8 /* flags */>
|
||||
|
||||
// We need to handle a special case where only one is present
|
||||
// as for some reason or another, Redis does not like us sending
|
||||
// a list of just one ID to the server.
|
||||
@@ -115,14 +111,19 @@ pub async fn presence_filter_online(user_ids: &'_ [String]) -> HashSet<String> {
|
||||
|
||||
// Otherwise, go ahead as normal.
|
||||
if let Ok(mut conn) = get_connection().await {
|
||||
let data: Vec<Option<Vec<u8>>> = conn.get(user_ids).await.unwrap_or_default();
|
||||
// Ok so, if this breaks, that means we've lost the Redis patch which adds SMISMEMBER
|
||||
// Currently it's patched in through a forked repository, investigate what happen to it
|
||||
let data: Vec<bool> = conn
|
||||
.sismember("online", user_ids)
|
||||
.await
|
||||
.expect("this shouldn't happen, please read this code! presence/mod.rs");
|
||||
if data.is_empty() {
|
||||
return set;
|
||||
}
|
||||
|
||||
// We filter known values to figure out who is online.
|
||||
for i in 0..user_ids.len() {
|
||||
if data[i].is_some() {
|
||||
if data[i] {
|
||||
set.insert(user_ids[i].to_string());
|
||||
}
|
||||
}
|
||||
@@ -136,7 +137,7 @@ pub async fn presence_clear_region(region_id: Option<&str>) {
|
||||
let region_id = region_id.unwrap_or(&*REGION_KEY);
|
||||
let mut conn = get_connection().await.expect("Redis connection");
|
||||
|
||||
let sessions = __get_set_sessions(&mut conn, region_id).await;
|
||||
let sessions = __get_set_members_as_string(&mut conn, region_id).await;
|
||||
if !sessions.is_empty() {
|
||||
info!(
|
||||
"Cleaning up {} sessions, this may take a while...",
|
||||
@@ -155,7 +156,7 @@ pub async fn presence_clear_region(region_id: Option<&str>) {
|
||||
}
|
||||
|
||||
// Then clear the set in Redis.
|
||||
__delete_set_sessions(&mut conn, region_id).await;
|
||||
__delete_key(&mut conn, region_id).await;
|
||||
|
||||
info!("Clean up complete.");
|
||||
}
|
||||
|
||||
@@ -1,57 +1,42 @@
|
||||
use redis_kiss::{AsyncCommands, Conn};
|
||||
|
||||
use super::entry::PresenceEntry;
|
||||
|
||||
/// Set presence entry by given ID
|
||||
pub async fn __set_key_presence_entry(conn: &mut Conn, id: &str, data: Vec<PresenceEntry>) {
|
||||
let _: Option<()> = conn.set(id, bincode::serialize(&data).unwrap()).await.ok();
|
||||
/// Add to set (string)
|
||||
pub async fn __add_to_set_string(conn: &mut Conn, key: &str, value: &str) {
|
||||
let _: Option<()> = conn.sadd(key, value).await.ok();
|
||||
}
|
||||
|
||||
/// Delete presence entry by given ID
|
||||
pub async fn __delete_key_presence_entry(conn: &mut Conn, id: &str) {
|
||||
let _: Option<()> = conn.del(id).await.ok();
|
||||
/// Add to set (u32)
|
||||
pub async fn __add_to_set_u32(conn: &mut Conn, key: &str, value: u32) {
|
||||
let _: Option<()> = conn.sadd(key, value).await.ok();
|
||||
}
|
||||
|
||||
/// Get presence entry by given ID
|
||||
pub async fn __get_key_presence_entry(conn: &mut Conn, id: &str) -> Option<Vec<PresenceEntry>> {
|
||||
conn.get::<_, Option<Vec<u8>>>(id)
|
||||
/// Remove from set (string)
|
||||
pub async fn __remove_from_set_string(conn: &mut Conn, key: &str, value: &str) {
|
||||
let _: Option<()> = conn.srem(key, value).await.ok();
|
||||
}
|
||||
|
||||
/// Remove from set (u32)
|
||||
pub async fn __remove_from_set_u32(conn: &mut Conn, key: &str, value: u32) {
|
||||
let _: Option<()> = conn.srem(key, value).await.ok();
|
||||
}
|
||||
|
||||
/// Get set members as string
|
||||
pub async fn __get_set_members_as_string(conn: &mut Conn, key: &str) -> Vec<String> {
|
||||
conn.smembers::<_, Vec<String>>(key)
|
||||
.await
|
||||
.unwrap()
|
||||
.map(|entry| bincode::deserialize(&entry[..]).unwrap())
|
||||
.expect("could not get set members as string")
|
||||
}
|
||||
|
||||
/// Add to region session set
|
||||
pub async fn __add_to_set_sessions(
|
||||
conn: &mut Conn,
|
||||
region_id: &str,
|
||||
user_id: &str,
|
||||
session_id: u8,
|
||||
) {
|
||||
let _: Option<()> = conn
|
||||
.sadd(region_id, format!("{user_id}:{session_id}"))
|
||||
/// Get set size
|
||||
pub async fn __get_set_size(conn: &mut Conn, id: &str) -> u32 {
|
||||
conn.scard::<_, u32>(id)
|
||||
.await
|
||||
.ok();
|
||||
.expect("could not get set size")
|
||||
}
|
||||
|
||||
/// Remove from region session set
|
||||
pub async fn __remove_from_set_sessions(
|
||||
conn: &mut Conn,
|
||||
region_id: &str,
|
||||
user_id: &str,
|
||||
session_id: u8,
|
||||
) {
|
||||
let _: Option<()> = conn
|
||||
.srem(region_id, format!("{user_id}:{session_id}"))
|
||||
/// Delete key by id
|
||||
pub async fn __delete_key(conn: &mut Conn, id: &str) {
|
||||
conn.del::<_, ()>(id)
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
|
||||
/// Get region session set as list
|
||||
pub async fn __get_set_sessions(conn: &mut Conn, region_id: &str) -> Vec<String> {
|
||||
conn.smembers::<_, Vec<String>>(region_id).await.unwrap()
|
||||
}
|
||||
|
||||
/// Delete region session set
|
||||
pub async fn __delete_set_sessions(conn: &mut Conn, region_id: &str) {
|
||||
conn.del::<_, ()>(region_id).await.unwrap();
|
||||
.expect("could not delete key by id");
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
use crate::Database;
|
||||
|
||||
use deadqueue::limited::Queue;
|
||||
use mongodb::bson::doc;
|
||||
use std::{collections::HashMap, time::Duration};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use super::DelayedTask;
|
||||
|
||||
@@ -38,9 +38,7 @@ struct Task {
|
||||
event: AckEvent,
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref Q: Queue<Data> = Queue::new(10_000);
|
||||
}
|
||||
static Q: Lazy<Queue<Data>> = Lazy::new(|| Queue::new(10_000));
|
||||
|
||||
/// Queue a new task for a worker
|
||||
pub async fn queue(channel: String, user: String, event: AckEvent) {
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
use crate::{models::channel::PartialChannel, Database};
|
||||
|
||||
use deadqueue::limited::Queue;
|
||||
use mongodb::bson::doc;
|
||||
use std::{collections::HashMap, time::Duration};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use super::DelayedTask;
|
||||
|
||||
@@ -26,9 +26,7 @@ struct Task {
|
||||
is_dm: bool,
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref Q: Queue<Data> = Queue::new(10_000);
|
||||
}
|
||||
static Q: Lazy<Queue<Data>> = Lazy::new(|| Queue::new(10_000));
|
||||
|
||||
/// Queue a new task for a worker
|
||||
pub async fn queue(channel: String, id: String, is_dm: bool) {
|
||||
|
||||
@@ -9,6 +9,7 @@ use async_lock::Semaphore;
|
||||
use async_std::task::spawn;
|
||||
use deadqueue::limited::Queue;
|
||||
use std::sync::Arc;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
/// Task information
|
||||
#[derive(Debug)]
|
||||
@@ -21,9 +22,8 @@ struct EmbedTask {
|
||||
content: String,
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref Q: Queue<EmbedTask> = Queue::new(10_000);
|
||||
}
|
||||
static Q: Lazy<Queue<EmbedTask>> = Lazy::new(|| Queue::new(10_000));
|
||||
|
||||
|
||||
/// Queue a new task for a worker
|
||||
pub async fn queue(channel: String, id: String, content: String) {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::bson::doc;
|
||||
use crate::util::variables::delta::VAPID_PRIVATE_KEY;
|
||||
|
||||
use authifier::Database;
|
||||
use deadqueue::limited::Queue;
|
||||
use once_cell::sync::Lazy;
|
||||
use web_push::{
|
||||
ContentEncoding, SubscriptionInfo, SubscriptionKeys, VapidSignatureBuilder, WebPushClient,
|
||||
WebPushMessageBuilder,
|
||||
@@ -17,9 +17,8 @@ struct PushTask {
|
||||
payload: String,
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref Q: Queue<PushTask> = Queue::new(10_000);
|
||||
}
|
||||
static Q: Lazy<Queue<PushTask>> = Lazy::new(|| Queue::new(10_000));
|
||||
|
||||
|
||||
/// Queue a new task for a worker
|
||||
pub async fn queue(recipients: Vec<String>, payload: String) {
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
use crate::{models::stats::Stats, Result};
|
||||
|
||||
#[async_trait]
|
||||
pub trait AbstractStats: Sync + Send {
|
||||
async fn generate_stats(&self) -> Result<Stats>;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::models::message::{AppendMessage, Message, MessageSort, PartialMessage};
|
||||
use crate::models::message::{AppendMessage, Message, MessageQuery, PartialMessage};
|
||||
use crate::Result;
|
||||
|
||||
#[async_trait]
|
||||
@@ -21,27 +21,8 @@ pub trait AbstractMessage: Sync + Send {
|
||||
/// Delete messages from a channel by their ids and corresponding channel id
|
||||
async fn delete_messages(&self, channel: &str, ids: Vec<String>) -> Result<()>;
|
||||
|
||||
/// Fetch multiple messages
|
||||
async fn fetch_messages(
|
||||
&self,
|
||||
channel: &str,
|
||||
limit: Option<i64>,
|
||||
before: Option<String>,
|
||||
after: Option<String>,
|
||||
sort: Option<MessageSort>,
|
||||
nearby: Option<String>,
|
||||
) -> Result<Vec<Message>>;
|
||||
|
||||
/// Search for messages
|
||||
async fn search_messages(
|
||||
&self,
|
||||
channel: &str,
|
||||
query: &str,
|
||||
limit: Option<i64>,
|
||||
before: Option<String>,
|
||||
after: Option<String>,
|
||||
sort: MessageSort,
|
||||
) -> Result<Vec<Message>>;
|
||||
/// Fetch multiple messages by given query
|
||||
async fn fetch_messages(&self, query: MessageQuery) -> Result<Vec<Message>>;
|
||||
|
||||
/// Add a new reaction to a message
|
||||
async fn add_reaction(&self, id: &str, emoji: &str, user: &str) -> Result<()>;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
mod admin {
|
||||
pub mod migrations;
|
||||
pub mod stats;
|
||||
}
|
||||
|
||||
mod media {
|
||||
@@ -32,6 +33,7 @@ mod safety {
|
||||
}
|
||||
|
||||
pub use admin::migrations::AbstractMigrations;
|
||||
pub use admin::stats::AbstractStats;
|
||||
|
||||
pub use media::attachment::AbstractAttachment;
|
||||
pub use media::emoji::AbstractEmoji;
|
||||
@@ -56,6 +58,7 @@ pub trait AbstractDatabase:
|
||||
Sync
|
||||
+ Send
|
||||
+ AbstractMigrations
|
||||
+ AbstractStats
|
||||
+ AbstractAttachment
|
||||
+ AbstractEmoji
|
||||
+ AbstractChannel
|
||||
|
||||
@@ -5,6 +5,7 @@ use linkify::{LinkFinder, LinkKind};
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{collections::HashSet, sync::Arc};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use crate::{models::attachment::File, Error, Result};
|
||||
|
||||
@@ -175,6 +176,9 @@ pub enum Embed {
|
||||
None,
|
||||
}
|
||||
|
||||
static RE_CODE: Lazy<Regex> = Lazy::new(|| Regex::new("```(?:.|\n)+?```|`(?:.|\n)+?`").unwrap());
|
||||
static RE_IGNORED: Lazy<Regex> = Lazy::new(|| Regex::new("(<http.+>)").unwrap());
|
||||
|
||||
impl Embed {
|
||||
/// Generate embeds from given content
|
||||
pub async fn generate(
|
||||
@@ -183,10 +187,7 @@ impl Embed {
|
||||
max_embeds: usize,
|
||||
semaphore: Arc<Semaphore>,
|
||||
) -> Result<Vec<Embed>> {
|
||||
lazy_static! {
|
||||
static ref RE_CODE: Regex = Regex::new("```(?:.|\n)+?```|`(?:.|\n)+?`").unwrap();
|
||||
static ref RE_IGNORED: Regex = Regex::new("(<http.+>)").unwrap();
|
||||
}
|
||||
|
||||
|
||||
// Ignore code blocks.
|
||||
let content = RE_CODE.replace_all(&content, "");
|
||||
|
||||
@@ -14,7 +14,7 @@ pub fn setup_logging(release: &'static str) -> sentry::ClientInitGuard {
|
||||
info!("Starting {release}");
|
||||
|
||||
sentry::init((
|
||||
"https://62fd0e02c5354905b4e286757f4beb16@sentry.insert.moe/4",
|
||||
"https://d1d2a6f15c6245a987c532bbbcb30a04@glitchtip.insert.moe/2",
|
||||
sentry::ClientOptions {
|
||||
release: Some(release.into()),
|
||||
..Default::default()
|
||||
|
||||
@@ -3,7 +3,6 @@ use revolt_rocket_okapi::revolt_okapi::openapi3;
|
||||
use rocket::{
|
||||
http::{ContentType, Status},
|
||||
response::{self, Responder},
|
||||
serde::json::serde_json::json,
|
||||
Request, Response,
|
||||
};
|
||||
use schemars::schema::Schema;
|
||||
@@ -39,8 +38,15 @@ pub enum Error {
|
||||
UnknownMessage,
|
||||
CannotEditMessage,
|
||||
CannotJoinCall,
|
||||
TooManyAttachments,
|
||||
TooManyReplies,
|
||||
TooManyAttachments {
|
||||
max: usize
|
||||
},
|
||||
TooManyReplies {
|
||||
max: usize
|
||||
},
|
||||
TooManyChannels {
|
||||
max: usize
|
||||
},
|
||||
EmptyMessage,
|
||||
PayloadTooLarge,
|
||||
CannotRemoveYourself,
|
||||
@@ -57,7 +63,12 @@ pub enum Error {
|
||||
TooManyServers {
|
||||
max: usize,
|
||||
},
|
||||
TooManyEmoji,
|
||||
TooManyEmoji {
|
||||
max: usize
|
||||
},
|
||||
TooManyRoles {
|
||||
max: usize
|
||||
},
|
||||
|
||||
// ? Bot related errors
|
||||
ReachedMaximumBots,
|
||||
@@ -151,8 +162,8 @@ impl<'r> Responder<'r, 'static> for Error {
|
||||
Error::UnknownAttachment => Status::BadRequest,
|
||||
Error::CannotEditMessage => Status::Forbidden,
|
||||
Error::CannotJoinCall => Status::BadRequest,
|
||||
Error::TooManyAttachments => Status::BadRequest,
|
||||
Error::TooManyReplies => Status::BadRequest,
|
||||
Error::TooManyAttachments { .. } => Status::BadRequest,
|
||||
Error::TooManyReplies { .. } => Status::BadRequest,
|
||||
Error::EmptyMessage => Status::UnprocessableEntity,
|
||||
Error::PayloadTooLarge => Status::UnprocessableEntity,
|
||||
Error::CannotRemoveYourself => Status::BadRequest,
|
||||
@@ -163,8 +174,11 @@ impl<'r> Responder<'r, 'static> for Error {
|
||||
Error::UnknownServer => Status::NotFound,
|
||||
Error::InvalidRole => Status::NotFound,
|
||||
Error::Banned => Status::Forbidden,
|
||||
Error::TooManyServers { .. } => Status::Forbidden,
|
||||
Error::TooManyEmoji => Status::BadRequest,
|
||||
|
||||
Error::TooManyServers { .. } => Status::BadRequest,
|
||||
Error::TooManyEmoji { .. } => Status::BadRequest,
|
||||
Error::TooManyChannels { .. } => Status::BadRequest,
|
||||
Error::TooManyRoles { .. } => Status::BadRequest,
|
||||
|
||||
Error::ReachedMaximumBots => Status::BadRequest,
|
||||
Error::IsBot => Status::BadRequest,
|
||||
@@ -193,7 +207,7 @@ impl<'r> Responder<'r, 'static> for Error {
|
||||
};
|
||||
|
||||
// Serialize the error data structure into JSON.
|
||||
let string = json!(self).to_string();
|
||||
let string = serde_json::to_string(&self).unwrap();
|
||||
|
||||
// Build and send the request.
|
||||
Response::build()
|
||||
|
||||
@@ -1,73 +1,56 @@
|
||||
use std::env;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
lazy_static! {
|
||||
// Application Settings
|
||||
pub static ref PUBLIC_URL: String =
|
||||
env::var("REVOLT_PUBLIC_URL").expect("Missing REVOLT_PUBLIC_URL environment variable.");
|
||||
pub static ref APP_URL: String =
|
||||
env::var("REVOLT_APP_URL").expect("Missing REVOLT_APP_URL environment variable.");
|
||||
pub static ref EXTERNAL_WS_URL: String =
|
||||
env::var("REVOLT_EXTERNAL_WS_URL").expect("Missing REVOLT_EXTERNAL_WS_URL environment variable.");
|
||||
// Application Settings
|
||||
pub static PUBLIC_URL: Lazy<String> = Lazy::new(|| env::var("REVOLT_PUBLIC_URL").expect("Missing REVOLT_PUBLIC_URL environment variable."));
|
||||
pub static APP_URL: Lazy<String> = Lazy::new(|| env::var("REVOLT_APP_URL").expect("Missing REVOLT_APP_URL environment variable."));
|
||||
pub static EXTERNAL_WS_URL: Lazy<String> = Lazy::new(|| env::var("REVOLT_EXTERNAL_WS_URL").expect("Missing REVOLT_EXTERNAL_WS_URL environment variable."));
|
||||
|
||||
pub static ref AUTUMN_URL: String =
|
||||
env::var("AUTUMN_PUBLIC_URL").unwrap_or_else(|_| "https://example.com".to_string());
|
||||
pub static ref JANUARY_URL: String =
|
||||
env::var("JANUARY_PUBLIC_URL").unwrap_or_else(|_| "https://example.com".to_string());
|
||||
pub static ref JANUARY_CONCURRENT_CONNECTIONS: usize =
|
||||
env::var("JANUARY_CONCURRENT_CONNECTIONS").map_or(50, |v| v.parse().unwrap());
|
||||
pub static ref VOSO_URL: String =
|
||||
env::var("VOSO_PUBLIC_URL").unwrap_or_else(|_| "https://example.com".to_string());
|
||||
pub static ref VOSO_WS_HOST: String =
|
||||
env::var("VOSO_WS_HOST").unwrap_or_else(|_| "wss://example.com".to_string());
|
||||
pub static ref VOSO_MANAGE_TOKEN: String =
|
||||
env::var("VOSO_MANAGE_TOKEN").unwrap_or_else(|_| "0".to_string());
|
||||
pub static AUTUMN_URL: Lazy<String> = Lazy::new(|| env::var("AUTUMN_PUBLIC_URL").unwrap_or_else(|_| "https://example.com".to_string()));
|
||||
pub static JANUARY_URL: Lazy<String> = Lazy::new(|| env::var("JANUARY_PUBLIC_URL").unwrap_or_else(|_| "https://example.com".to_string()));
|
||||
pub static JANUARY_CONCURRENT_CONNECTIONS: Lazy<usize> = Lazy::new(|| env::var("JANUARY_CONCURRENT_CONNECTIONS").map_or(50, |v| v.parse().unwrap()));
|
||||
pub static VOSO_URL: Lazy<String> = Lazy::new(|| env::var("VOSO_PUBLIC_URL").unwrap_or_else(|_| "https://example.com".to_string()));
|
||||
pub static VOSO_WS_HOST: Lazy<String> = Lazy::new(|| env::var("VOSO_WS_HOST").unwrap_or_else(|_| "wss://example.com".to_string()));
|
||||
pub static VOSO_MANAGE_TOKEN: Lazy<String> = Lazy::new(|| env::var("VOSO_MANAGE_TOKEN").unwrap_or_else(|_| "0".to_string()));
|
||||
|
||||
pub static ref HCAPTCHA_KEY: String =
|
||||
env::var("REVOLT_HCAPTCHA_KEY").unwrap_or_else(|_| "0x0000000000000000000000000000000000000000".to_string());
|
||||
pub static ref HCAPTCHA_SITEKEY: String =
|
||||
env::var("REVOLT_HCAPTCHA_SITEKEY").unwrap_or_else(|_| "10000000-ffff-ffff-ffff-000000000001".to_string());
|
||||
pub static ref VAPID_PRIVATE_KEY: String =
|
||||
env::var("REVOLT_VAPID_PRIVATE_KEY").expect("Missing REVOLT_VAPID_PRIVATE_KEY environment variable.");
|
||||
pub static ref VAPID_PUBLIC_KEY: String =
|
||||
env::var("REVOLT_VAPID_PUBLIC_KEY").expect("Missing REVOLT_VAPID_PUBLIC_KEY environment variable.");
|
||||
pub static ref AUTHIFIER_SHIELD_KEY: Option<String> =
|
||||
env::var("REVOLT_AUTHIFIER_SHIELD_KEY").ok();
|
||||
pub static HCAPTCHA_KEY: Lazy<String> = Lazy::new(|| env::var("REVOLT_HCAPTCHA_KEY").unwrap_or_else(|_| "0x0000000000000000000000000000000000000000".to_string()));
|
||||
pub static HCAPTCHA_SITEKEY: Lazy<String> = Lazy::new(|| env::var("REVOLT_HCAPTCHA_SITEKEY").unwrap_or_else(|_| "10000000-ffff-ffff-ffff-000000000001".to_string()));
|
||||
pub static VAPID_PRIVATE_KEY: Lazy<String> = Lazy::new(|| env::var("REVOLT_VAPID_PRIVATE_KEY").expect("Missing REVOLT_VAPID_PRIVATE_KEY environment variable."));
|
||||
pub static VAPID_PUBLIC_KEY: Lazy<String> = Lazy::new(|| env::var("REVOLT_VAPID_PUBLIC_KEY").expect("Missing REVOLT_VAPID_PUBLIC_KEY environment variable."));
|
||||
pub static AUTHIFIER_SHIELD_KEY: Lazy<Option<String>> = Lazy::new(|| env::var("REVOLT_AUTHIFIER_SHIELD_KEY").ok());
|
||||
|
||||
// Application Flags
|
||||
pub static ref INVITE_ONLY: bool = env::var("REVOLT_INVITE_ONLY").map_or(false, |v| v == "1");
|
||||
pub static ref USE_EMAIL: bool = env::var("REVOLT_USE_EMAIL_VERIFICATION").map_or(
|
||||
env::var("REVOLT_SMTP_HOST").is_ok()
|
||||
&& env::var("REVOLT_SMTP_USERNAME").is_ok()
|
||||
&& env::var("REVOLT_SMTP_PASSWORD").is_ok()
|
||||
&& env::var("REVOLT_SMTP_FROM").is_ok(),
|
||||
|v| v == *"1"
|
||||
);
|
||||
pub static ref USE_HCAPTCHA: bool = env::var("REVOLT_HCAPTCHA_KEY").is_ok();
|
||||
pub static ref USE_AUTUMN: bool = env::var("AUTUMN_PUBLIC_URL").is_ok();
|
||||
pub static ref USE_JANUARY: bool = env::var("JANUARY_PUBLIC_URL").is_ok();
|
||||
pub static ref USE_VOSO: bool = env::var("VOSO_PUBLIC_URL").is_ok() && env::var("VOSO_MANAGE_TOKEN").is_ok();
|
||||
// Application Flags
|
||||
pub static INVITE_ONLY: Lazy<bool> = Lazy::new(|| env::var("REVOLT_INVITE_ONLY").map_or(false, |v| v == "1"));
|
||||
pub static USE_EMAIL: Lazy<bool> = Lazy::new(|| env::var("REVOLT_USE_EMAIL_VERIFICATION").map_or(
|
||||
env::var("REVOLT_SMTP_HOST").is_ok()
|
||||
&& env::var("REVOLT_SMTP_USERNAME").is_ok()
|
||||
&& env::var("REVOLT_SMTP_PASSWORD").is_ok()
|
||||
&& env::var("REVOLT_SMTP_FROM").is_ok(),
|
||||
|v| v == *"1"
|
||||
));
|
||||
pub static USE_HCAPTCHA: Lazy<bool> = Lazy::new(|| env::var("REVOLT_HCAPTCHA_KEY").is_ok());
|
||||
pub static USE_AUTUMN: Lazy<bool> = Lazy::new(|| env::var("AUTUMN_PUBLIC_URL").is_ok());
|
||||
pub static USE_JANUARY: Lazy<bool> = Lazy::new(|| env::var("JANUARY_PUBLIC_URL").is_ok());
|
||||
pub static USE_VOSO: Lazy<bool> = Lazy::new(|| env::var("VOSO_PUBLIC_URL").is_ok() && env::var("VOSO_MANAGE_TOKEN").is_ok());
|
||||
|
||||
// SMTP Settings
|
||||
pub static ref SMTP_HOST: String =
|
||||
env::var("REVOLT_SMTP_HOST").unwrap_or_else(|_| "".to_string());
|
||||
pub static ref SMTP_USERNAME: String =
|
||||
env::var("REVOLT_SMTP_USERNAME").unwrap_or_else(|_| "".to_string());
|
||||
pub static ref SMTP_PASSWORD: String =
|
||||
env::var("REVOLT_SMTP_PASSWORD").unwrap_or_else(|_| "".to_string());
|
||||
pub static ref SMTP_FROM: String = env::var("REVOLT_SMTP_FROM").unwrap_or_else(|_| "".to_string());
|
||||
// SMTP Settings
|
||||
pub static SMTP_HOST: Lazy<String> = Lazy::new(|| env::var("REVOLT_SMTP_HOST").unwrap_or_else(|_| "".to_string()));
|
||||
pub static SMTP_USERNAME: Lazy<String> = Lazy::new(|| env::var("REVOLT_SMTP_USERNAME").unwrap_or_else(|_| "".to_string()));
|
||||
pub static SMTP_PASSWORD: Lazy<String> = Lazy::new(|| env::var("REVOLT_SMTP_PASSWORD").unwrap_or_else(|_| "".to_string()));
|
||||
pub static SMTP_FROM: Lazy<String> = Lazy::new(|| env::var("REVOLT_SMTP_FROM").unwrap_or_else(|_| "".to_string()));
|
||||
|
||||
// Application Logic Settings
|
||||
pub static ref MAX_GROUP_SIZE: usize =
|
||||
env::var("REVOLT_MAX_GROUP_SIZE").unwrap_or_else(|_| "50".to_string()).parse().unwrap();
|
||||
pub static ref MAX_BOT_COUNT: usize =
|
||||
env::var("REVOLT_MAX_BOT_COUNT").unwrap_or_else(|_| "5".to_string()).parse().unwrap();
|
||||
pub static ref MAX_EMBED_COUNT: usize =
|
||||
env::var("REVOLT_MAX_EMBED_COUNT").unwrap_or_else(|_| "5".to_string()).parse().unwrap();
|
||||
pub static ref MAX_SERVER_COUNT: usize =
|
||||
env::var("REVOLT_MAX_SERVER_COUNT").unwrap_or_else(|_| "100".to_string()).parse().unwrap();
|
||||
pub static ref EARLY_ADOPTER_BADGE: i64 =
|
||||
env::var("REVOLT_EARLY_ADOPTER_BADGE").unwrap_or_else(|_| "0".to_string()).parse().unwrap();
|
||||
}
|
||||
// Application Logic Settings
|
||||
pub static MAX_GROUP_SIZE: Lazy<usize> = Lazy::new(|| env::var("REVOLT_MAX_GROUP_SIZE").unwrap_or_else(|_| "50".to_string()).parse().unwrap());
|
||||
pub static MAX_BOT_COUNT: Lazy<usize> = Lazy::new(|| env::var("REVOLT_MAX_BOT_COUNT").unwrap_or_else(|_| "5".to_string()).parse().unwrap());
|
||||
pub static MAX_EMBED_COUNT: Lazy<usize> = Lazy::new(|| env::var("REVOLT_MAX_EMBED_COUNT").unwrap_or_else(|_| "5".to_string()).parse().unwrap());
|
||||
pub static MAX_SERVER_COUNT: Lazy<usize> = Lazy::new(|| env::var("REVOLT_MAX_SERVER_COUNT").unwrap_or_else(|_| "100".to_string()).parse().unwrap());
|
||||
pub static MAX_CHANNEL_COUNT: Lazy<usize> = Lazy::new(|| env::var("REVOLT_MAX_CHANNEL_COUNT").unwrap_or_else(|_| "200".to_string()).parse().unwrap());
|
||||
pub static MAX_ROLE_COUNT: Lazy<usize> = Lazy::new(|| env::var("REVOLT_MAX_ROLE_COUNT").unwrap_or_else(|_| "200".to_string()).parse().unwrap());
|
||||
pub static MAX_EMOJI_COUNT: Lazy<usize> = Lazy::new(|| env::var("REVOLT_MAX_EMOJI_COUNT").unwrap_or_else(|_| "100".to_string()).parse().unwrap());
|
||||
pub static MAX_ATTACHMENT_COUNT: Lazy<usize> = Lazy::new(|| env::var("REVOLT_MAX_ATTACHMENT_COUNT").unwrap_or_else(|_| "5".to_string()).parse().unwrap());
|
||||
pub static MAX_REPLY_COUNT: Lazy<usize> = Lazy::new(|| env::var("REVOLT_MAX_REPLY_COUNT").unwrap_or_else(|_| "5".to_string()).parse().unwrap());
|
||||
|
||||
pub static EARLY_ADOPTER_BADGE: Lazy<i64> = Lazy::new(|| env::var("REVOLT_EARLY_ADOPTER_BADGE").unwrap_or_else(|_| "0".to_string()).parse().unwrap());
|
||||
|
||||
pub fn preflight_checks() {
|
||||
format!("url = {}", *APP_URL);
|
||||
|
||||
@@ -9,6 +9,7 @@ use rocket::request::{FromRequest, Outcome};
|
||||
use schemars::schema::{InstanceType, SchemaObject, SingleOrVec};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use validator::Validate;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
#[derive(Validate, Serialize, Deserialize)]
|
||||
pub struct IdempotencyKey {
|
||||
@@ -16,9 +17,7 @@ pub struct IdempotencyKey {
|
||||
key: String,
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref TOKEN_CACHE: Mutex<lru::LruCache<String, ()>> = Mutex::new(lru::LruCache::new(100));
|
||||
}
|
||||
static TOKEN_CACHE: Lazy<Mutex<lru::LruCache<String, ()>>> = Lazy::new(|| Mutex::new(lru::LruCache::new(100)));
|
||||
|
||||
impl IdempotencyKey {
|
||||
// Backwards compatibility.
|
||||
|
||||
@@ -22,17 +22,16 @@ use revolt_rocket_okapi::request::{OpenApiFromRequest, RequestHeaderInput};
|
||||
use serde::Serialize;
|
||||
|
||||
use dashmap::DashMap;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
/// Ratelimit Bucket
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct Entry {
|
||||
used: u8,
|
||||
reset: u128,
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref MAP: DashMap<u64, Entry> = DashMap::new();
|
||||
}
|
||||
static MAP: Lazy<DashMap<u64, Entry>> = Lazy::new(DashMap::new);
|
||||
|
||||
/// Get the current time from Unix Epoch as a Duration
|
||||
fn now() -> Duration {
|
||||
@@ -51,7 +50,7 @@ impl Entry {
|
||||
}
|
||||
|
||||
/// Deduct one unit from the bucket and save
|
||||
pub fn deduct(mut self) {
|
||||
pub fn deduct(&mut self) {
|
||||
let current_time = now().as_millis();
|
||||
if current_time > self.reset {
|
||||
self.used = 1;
|
||||
@@ -193,7 +192,7 @@ impl Ratelimiter {
|
||||
|
||||
let key = key.finish();
|
||||
let limit = resolve_bucket_limit(bucket);
|
||||
let entry = Entry::from(key);
|
||||
let mut entry = Entry::from(key);
|
||||
|
||||
let remaining = entry.get_remaining(limit);
|
||||
if remaining > 0 {
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
version: "3.3"
|
||||
services:
|
||||
# Redis
|
||||
redis:
|
||||
image: eqalpha/keydb
|
||||
ports:
|
||||
- "6379:6379"
|
||||
|
||||
# MongoDB
|
||||
database:
|
||||
image: mongo
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -eu
|
||||
|
||||
case "${TARGETARCH}" in
|
||||
"amd64")
|
||||
LINKER_NAME="x86_64-linux-gnu-gcc"
|
||||
LINKER_PACKAGE="gcc-x86-64-linux-gnu"
|
||||
BUILD_TARGET="x86_64-unknown-linux-gnu" ;;
|
||||
"arm64")
|
||||
LINKER_NAME="aarch64-linux-gnu-gcc"
|
||||
LINKER_PACKAGE="gcc-aarch64-linux-gnu"
|
||||
BUILD_TARGET="aarch64-unknown-linux-gnu" ;;
|
||||
esac
|
||||
|
||||
tools() {
|
||||
apt-get install -y "${LINKER_PACKAGE}"
|
||||
rustup target add "${BUILD_TARGET}"
|
||||
}
|
||||
|
||||
deps() {
|
||||
mkdir -p \
|
||||
crates/bonfire/src \
|
||||
crates/delta/src \
|
||||
crates/quark/src
|
||||
echo 'fn main() { panic!("stub"); }' |
|
||||
tee crates/bonfire/src/main.rs |
|
||||
tee crates/delta/src/main.rs
|
||||
echo '' |
|
||||
tee crates/quark/src/lib.rs
|
||||
cargo build --locked --release --target "${BUILD_TARGET}"
|
||||
}
|
||||
|
||||
apps() {
|
||||
touch -am \
|
||||
crates/bonfire/src/main.rs \
|
||||
crates/delta/src/main.rs \
|
||||
crates/quark/src/lib.rs
|
||||
cargo build --locked --release --target "${BUILD_TARGET}"
|
||||
mv target _target && mv _target/"${BUILD_TARGET}" target
|
||||
}
|
||||
|
||||
export RUSTFLAGS="-C linker=${LINKER_NAME}"
|
||||
export PKG_CONFIG_ALLOW_CROSS="1"
|
||||
export PKG_CONFIG_PATH="/usr/lib/pkgconfig:/usr/lib/aarch64-linux-gnu/pkgconfig:/usr/lib/x86_64-linux-gnu/pkgconfig"
|
||||
|
||||
"$@"
|
||||
Reference in New Issue
Block a user