forked from jmug/stoatchat
Merge remote-tracking branch 'revoltchat/master' into webhooks
This commit is contained in:
52
.github/workflows/docker.yaml
vendored
52
.github/workflows/docker.yaml
vendored
@@ -20,47 +20,52 @@ on:
|
|||||||
- "Dockerfile"
|
- "Dockerfile"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
base:
|
base:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: Build base image (amd64)
|
name: Build base image
|
||||||
steps:
|
steps:
|
||||||
# Configure build environment
|
# Configure build environment
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v2
|
||||||
|
|
||||||
# Authenticate with GHCR
|
# Authenticate with GHCR
|
||||||
- name: Login to Github Container Registry
|
- name: Login to Github Container Registry
|
||||||
uses: docker/login-action@v1
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
# Build all projects and cache
|
- name: Build base image
|
||||||
- name: Build Base Image
|
uses: docker/build-push-action@v4
|
||||||
uses: docker/build-push-action@v3
|
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: true
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
tags: ghcr.io/revoltchat/base:latest
|
platforms: linux/amd64,linux/arm64
|
||||||
cache-from: type=gha
|
tags: ghcr.io/${{ github.repository_owner }}/base:latest
|
||||||
cache-to: type=gha,mode=max
|
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]
|
needs: [base]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
project: [delta, bonfire]
|
project: [delta, bonfire]
|
||||||
name: Build ${{ matrix.project }} image (amd64)
|
name: Build ${{ matrix.project }} image
|
||||||
steps:
|
steps:
|
||||||
# Configure build environment
|
# Configure build environment
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v2
|
||||||
|
|
||||||
@@ -68,10 +73,11 @@ jobs:
|
|||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
|
registry: docker.io
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
- name: Login to Github Container Registry
|
- name: Login to Github Container Registry
|
||||||
uses: docker/login-action@v1
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
@@ -86,11 +92,11 @@ jobs:
|
|||||||
{
|
{
|
||||||
"delta": {
|
"delta": {
|
||||||
"path": "crates/delta",
|
"path": "crates/delta",
|
||||||
"tag": "revoltchat/server"
|
"tag": "${{ github.repository_owner }}/server"
|
||||||
},
|
},
|
||||||
"bonfire": {
|
"bonfire": {
|
||||||
"path": "crates/bonfire",
|
"path": "crates/bonfire",
|
||||||
"tag": "revoltchat/bonfire"
|
"tag": "${{ github.repository_owner }}/bonfire"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export_to: output
|
export_to: output
|
||||||
@@ -98,19 +104,21 @@ jobs:
|
|||||||
# Configure metadata
|
# Configure metadata
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v3
|
uses: docker/metadata-action@v4
|
||||||
with:
|
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
|
# Build crate image
|
||||||
- name: Publish
|
- name: Publish
|
||||||
uses: docker/build-push-action@v3
|
uses: docker/build-push-action@v4
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: true
|
push: true
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64,linux/arm64
|
||||||
file: ${{ steps.export.outputs.path }}/Dockerfile
|
file: ${{ steps.export.outputs.path }}/Dockerfile
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
build-args: |
|
||||||
|
BASE_IMAGE=ghcr.io/${{ github.repository_owner }}/base:latest
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
cache-from: type=gha
|
|
||||||
cache-to: type=gha,mode=max
|
|
||||||
|
|||||||
29
.github/workflows/rust.yaml
vendored
29
.github/workflows/rust.yaml
vendored
@@ -2,7 +2,6 @@ name: Rust build, test, and generate specification
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [master]
|
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [master]
|
branches: [master]
|
||||||
|
|
||||||
@@ -27,34 +26,40 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
command: build
|
command: build
|
||||||
|
|
||||||
|
- name: Run services in background
|
||||||
|
run: |
|
||||||
|
docker-compose -f docker-compose.db.yml up -d
|
||||||
|
|
||||||
- name: Run cargo test
|
- name: Run cargo test
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
command: test
|
command: test
|
||||||
|
|
||||||
|
- name: Run cargo test (with MongoDB)
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
env:
|
||||||
|
MONGODB: mongodb://localhost
|
||||||
|
with:
|
||||||
|
command: test
|
||||||
|
|
||||||
- name: Copy .env.example
|
- name: Copy .env.example
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request' && github.ref_name == 'master'
|
||||||
run: |
|
run: |
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
|
|
||||||
- name: Run services in background
|
|
||||||
if: github.event_name != 'pull_request'
|
|
||||||
run: |
|
|
||||||
docker-compose -f docker-compose.db.yml up -d
|
|
||||||
|
|
||||||
- name: Start API in background
|
- name: Start API in background
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request' && github.ref_name == 'master'
|
||||||
run: |
|
run: |
|
||||||
cargo run --bin revolt-delta &
|
cargo run --bin revolt-delta &
|
||||||
|
|
||||||
- name: Wait for API to go up
|
- name: Wait for API to go up
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request' && github.ref_name == 'master'
|
||||||
uses: nev7n/wait_for_response@v1
|
uses: nev7n/wait_for_response@v1
|
||||||
with:
|
with:
|
||||||
url: "http://localhost:8000/"
|
url: "http://localhost:8000/"
|
||||||
|
|
||||||
- name: Checkout API repository
|
- name: Checkout API repository
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request' && github.ref_name == 'master'
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
repository: revoltchat/api
|
repository: revoltchat/api
|
||||||
@@ -62,11 +67,11 @@ jobs:
|
|||||||
token: ${{ secrets.PAT }}
|
token: ${{ secrets.PAT }}
|
||||||
|
|
||||||
- name: Download OpenAPI specification
|
- name: Download OpenAPI specification
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request' && github.ref_name == 'master'
|
||||||
run: curl http://localhost:8000/openapi.json -o api/OpenAPI.json
|
run: curl http://localhost:8000/openapi.json -o api/OpenAPI.json
|
||||||
|
|
||||||
- name: Commit changes
|
- name: Commit changes
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request' && github.ref_name == 'master'
|
||||||
uses: EndBug/add-and-commit@v4
|
uses: EndBug/add-and-commit@v4
|
||||||
with:
|
with:
|
||||||
cwd: "api"
|
cwd: "api"
|
||||||
|
|||||||
333
Cargo.lock
generated
333
Cargo.lock
generated
@@ -105,7 +105,7 @@ version = "1.1.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5"
|
checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote 1.0.18",
|
"quote 1.0.26",
|
||||||
"syn 1.0.107",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -199,20 +199,20 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-recursion"
|
name = "async-recursion"
|
||||||
version = "1.0.0"
|
version = "1.0.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2cda8f4bcc10624c4e85bc66b3f452cca98cfa5ca002dc83a16aad2367641bea"
|
checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote 1.0.18",
|
"quote 1.0.26",
|
||||||
"syn 1.0.107",
|
"syn 2.0.15",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-std"
|
name = "async-std"
|
||||||
version = "1.11.0"
|
version = "1.12.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "52580991739c5cdb36cde8b2a516371c0a3b70dda36d916cc08b82372916808c"
|
checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-attributes",
|
"async-attributes",
|
||||||
"async-channel",
|
"async-channel",
|
||||||
@@ -229,7 +229,6 @@ dependencies = [
|
|||||||
"kv-log-macro",
|
"kv-log-macro",
|
||||||
"log",
|
"log",
|
||||||
"memchr",
|
"memchr",
|
||||||
"num_cpus",
|
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"pin-project-lite 0.2.9",
|
"pin-project-lite 0.2.9",
|
||||||
"pin-utils",
|
"pin-utils",
|
||||||
@@ -269,7 +268,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27"
|
checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote 1.0.18",
|
"quote 1.0.26",
|
||||||
"syn 1.0.107",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -286,7 +285,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "96cf8829f67d2eab0b2dfa42c5d0ef737e0724e4a82b01b3e292456202b19716"
|
checksum = "96cf8829f67d2eab0b2dfa42c5d0ef737e0724e4a82b01b3e292456202b19716"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote 1.0.18",
|
"quote 1.0.26",
|
||||||
"syn 1.0.107",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -364,6 +363,12 @@ dependencies = [
|
|||||||
"validator 0.15.0",
|
"validator 0.15.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "auto_ops"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7460f7dd8e100147b82a63afca1a20eb6c231ee36b90ba7272e14951cb58af59"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "0.1.8"
|
version = "0.1.8"
|
||||||
@@ -715,7 +720,7 @@ version = "0.1.22"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f877be4f7c9f246b183111634f75baa039715e3f46ce860677d3b19a69fb229c"
|
checksum = "f877be4f7c9f246b183111634f75baa039715e3f46ce860677d3b19a69fb229c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote 1.0.18",
|
"quote 1.0.26",
|
||||||
"syn 1.0.107",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -747,7 +752,7 @@ dependencies = [
|
|||||||
"fnv",
|
"fnv",
|
||||||
"ident_case",
|
"ident_case",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote 1.0.18",
|
"quote 1.0.26",
|
||||||
"strsim",
|
"strsim",
|
||||||
"syn 1.0.107",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
@@ -759,7 +764,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835"
|
checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core",
|
"darling_core",
|
||||||
"quote 1.0.18",
|
"quote 1.0.26",
|
||||||
"syn 1.0.107",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -808,7 +813,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
|
checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote 1.0.18",
|
"quote 1.0.26",
|
||||||
"syn 1.0.107",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -829,7 +834,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "123c73e7a6e51b05c75fe1a1b2f4e241399ea5740ed810b0e3e6cacd9db5e7b2"
|
checksum = "123c73e7a6e51b05c75fe1a1b2f4e241399ea5740ed810b0e3e6cacd9db5e7b2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"devise_core",
|
"devise_core",
|
||||||
"quote 1.0.18",
|
"quote 1.0.26",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -841,7 +846,7 @@ dependencies = [
|
|||||||
"bitflags",
|
"bitflags",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"proc-macro2-diagnostics",
|
"proc-macro2-diagnostics",
|
||||||
"quote 1.0.18",
|
"quote 1.0.26",
|
||||||
"syn 1.0.107",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -871,12 +876,6 @@ version = "0.15.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
|
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "dtoa"
|
|
||||||
version = "0.4.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dyn-clone"
|
name = "dyn-clone"
|
||||||
version = "1.0.5"
|
version = "1.0.5"
|
||||||
@@ -915,7 +914,7 @@ checksum = "21cdad81446a7f7dc43f6a77409efeb9733d2fa65553efef6018ef257c959b73"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote 1.0.18",
|
"quote 1.0.26",
|
||||||
"syn 1.0.107",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -935,7 +934,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "828de45d0ca18782232dfb8f3ea9cc428e8ced380eb26a520baaacfc70de39ce"
|
checksum = "828de45d0ca18782232dfb8f3ea9cc428e8ced380eb26a520baaacfc70de39ce"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote 1.0.18",
|
"quote 1.0.26",
|
||||||
"syn 1.0.107",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1119,7 +1118,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512"
|
checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote 1.0.18",
|
"quote 1.0.26",
|
||||||
"syn 1.0.107",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1210,7 +1209,7 @@ checksum = "e45727250e75cc04ff2846a66397da8ef2b3db8e40e0cef4df67950a07621eb9"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro-error",
|
"proc-macro-error",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote 1.0.18",
|
"quote 1.0.26",
|
||||||
"syn 1.0.107",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1831,6 +1830,27 @@ version = "2.5.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
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.26",
|
||||||
|
"syn 1.0.107",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mime"
|
name = "mime"
|
||||||
version = "0.3.16"
|
version = "0.3.16"
|
||||||
@@ -1897,9 +1917,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mobc"
|
name = "mobc"
|
||||||
version = "0.7.3"
|
version = "0.8.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7f76d2f2e2dcbb00a8d3b2b09f026a74a82693ea52cd071647aa6cfa7f1ff37e"
|
checksum = "fc79c4a77e312fee9c7bd4b957c12ad1196db73c4a81e5c0b13f02083c4f7f2f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-std",
|
"async-std",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@@ -1908,17 +1928,21 @@ dependencies = [
|
|||||||
"futures-timer",
|
"futures-timer",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"log",
|
"log",
|
||||||
|
"metrics",
|
||||||
|
"thiserror",
|
||||||
"tokio 1.18.2",
|
"tokio 1.18.2",
|
||||||
|
"tracing",
|
||||||
|
"tracing-subscriber",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mobc-redis"
|
name = "mobc-redis"
|
||||||
version = "0.7.0"
|
version = "0.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e7b5db77b37c9224d5b9949b214041ea3e1c15b6b1e5dd24a5acb8e73975d6d6"
|
checksum = "7bd8e2fd6bf7e35263b86662e663a9496a0352ceddd413b6c33313c36d5068fd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"mobc",
|
"mobc",
|
||||||
"redis 0.19.0",
|
"redis 0.22.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2080,7 +2104,16 @@ version = "0.5.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cf5395665662ef45796a4ff5486c5d41d29e0c09640af4c5f17fd94ee2c119c9"
|
checksum = "cf5395665662ef45796a4ff5486c5d41d29e0c09640af4c5f17fd94ee2c119c9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"num_enum_derive",
|
"num_enum_derive 0.5.7",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num_enum"
|
||||||
|
version = "0.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7a015b430d3c108a207fd776d2e2196aaf8b1cf8cf93253e3a097ff3085076a1"
|
||||||
|
dependencies = [
|
||||||
|
"num_enum_derive 0.6.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2091,10 +2124,22 @@ checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro-crate",
|
"proc-macro-crate",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote 1.0.18",
|
"quote 1.0.26",
|
||||||
"syn 1.0.107",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num_enum_derive"
|
||||||
|
version = "0.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro-crate",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote 1.0.26",
|
||||||
|
"syn 2.0.15",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "object"
|
name = "object"
|
||||||
version = "0.28.4"
|
version = "0.28.4"
|
||||||
@@ -2144,7 +2189,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c"
|
checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote 1.0.18",
|
"quote 1.0.26",
|
||||||
"syn 1.0.107",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2167,15 +2212,6 @@ dependencies = [
|
|||||||
"vcpkg",
|
"vcpkg",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "optional_struct"
|
|
||||||
version = "0.2.0"
|
|
||||||
source = "git+https://github.com/insertish/OptionalStruct?rev=ee56427cee1f007839825d93d07fffd5a5e038c7#ee56427cee1f007839825d93d07fffd5a5e038c7"
|
|
||||||
dependencies = [
|
|
||||||
"quote 0.3.15",
|
|
||||||
"syn 0.11.11",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "os_info"
|
name = "os_info"
|
||||||
version = "3.4.0"
|
version = "3.4.0"
|
||||||
@@ -2249,7 +2285,7 @@ checksum = "82a5ca643c2303ecb740d506539deba189e16f2754040a42901cd8105d0282d0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"proc-macro2-diagnostics",
|
"proc-macro2-diagnostics",
|
||||||
"quote 1.0.18",
|
"quote 1.0.26",
|
||||||
"syn 1.0.107",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2287,7 +2323,7 @@ dependencies = [
|
|||||||
"pest",
|
"pest",
|
||||||
"pest_meta",
|
"pest_meta",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote 1.0.18",
|
"quote 1.0.26",
|
||||||
"syn 1.0.107",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2318,7 +2354,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb"
|
checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote 1.0.18",
|
"quote 1.0.26",
|
||||||
"syn 1.0.107",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2405,7 +2441,7 @@ checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro-error-attr",
|
"proc-macro-error-attr",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote 1.0.18",
|
"quote 1.0.26",
|
||||||
"syn 1.0.107",
|
"syn 1.0.107",
|
||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
@@ -2417,15 +2453,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote 1.0.18",
|
"quote 1.0.26",
|
||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.50"
|
version = "1.0.56"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2"
|
checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
@@ -2437,7 +2473,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "4bf29726d67464d49fa6224a1d07936a8c08bb3fba727c7493f6cf1616fdaada"
|
checksum = "4bf29726d67464d49fa6224a1d07936a8c08bb3fba727c7493f6cf1616fdaada"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote 1.0.18",
|
"quote 1.0.26",
|
||||||
"syn 1.0.107",
|
"syn 1.0.107",
|
||||||
"version_check",
|
"version_check",
|
||||||
"yansi",
|
"yansi",
|
||||||
@@ -2463,9 +2499,9 @@ checksum = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.18"
|
version = "1.0.26"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1"
|
checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
@@ -2624,57 +2660,55 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redis"
|
name = "redis"
|
||||||
version = "0.19.0"
|
version = "0.22.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/insertish/redis-rs?rev=1a41faf356fd21aebba71cea7eb7eb2653e5f0ef#1a41faf356fd21aebba71cea7eb7eb2653e5f0ef"
|
||||||
checksum = "1a6ddfecac9391fed21cce10e83c65fa4abafd77df05c98b1c647c65374ce9b3"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-std",
|
"async-std",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"bytes 1.1.0",
|
"bytes 1.1.0",
|
||||||
"combine",
|
"combine",
|
||||||
"dtoa",
|
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"itoa 0.4.8",
|
"itoa 1.0.2",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project-lite 0.2.9",
|
"pin-project-lite 0.2.9",
|
||||||
"sha1",
|
"ryu",
|
||||||
|
"sha1_smol",
|
||||||
"tokio 1.18.2",
|
"tokio 1.18.2",
|
||||||
"tokio-util 0.6.10",
|
"tokio-util 0.7.2",
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redis"
|
name = "redis"
|
||||||
version = "0.21.5"
|
version = "0.23.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1a80b5f38d7f5a020856a0e16e40a9cfabf88ae8f0e4c2dcd8a3114c1e470852"
|
checksum = "3ea8c51b5dc1d8e5fd3350ec8167f464ec0995e79f2e90a075b63371500d557f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-std",
|
"async-std",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"bytes 1.1.0",
|
"bytes 1.1.0",
|
||||||
"combine",
|
"combine",
|
||||||
"dtoa",
|
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"itoa 0.4.8",
|
"itoa 1.0.2",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project-lite 0.2.9",
|
"pin-project-lite 0.2.9",
|
||||||
"sha1",
|
"ryu",
|
||||||
"tokio 1.18.2",
|
"tokio 1.18.2",
|
||||||
"tokio-util 0.6.10",
|
"tokio-util 0.7.2",
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redis-kiss"
|
name = "redis-kiss"
|
||||||
version = "0.1.3"
|
version = "0.1.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0d4f2f48fb776a308331c4a1ecb3a7b7b99f4d46c6beb2fc03db2da0f63fcad6"
|
checksum = "58605cfd83a2146161de13bb253f24db25eef6919b35403fc90f508ef89bec92"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bincode",
|
"bincode",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"mobc",
|
"mobc",
|
||||||
"mobc-redis",
|
"mobc-redis",
|
||||||
"redis 0.21.5",
|
"redis 0.23.0",
|
||||||
"rmp-serde",
|
"rmp-serde",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -2705,7 +2739,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "a043824e29c94169374ac5183ac0ed43f5724dc4556b19568007486bd840fa1f"
|
checksum = "a043824e29c94169374ac5183ac0ed43f5724dc4556b19568007486bd840fa1f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote 1.0.18",
|
"quote 1.0.26",
|
||||||
"syn 1.0.107",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2792,7 +2826,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "revolt-bonfire"
|
name = "revolt-bonfire"
|
||||||
version = "0.5.17"
|
version = "0.5.19"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-std",
|
"async-std",
|
||||||
"async-tungstenite",
|
"async-tungstenite",
|
||||||
@@ -2800,15 +2834,38 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"querystring",
|
"querystring",
|
||||||
|
"revolt-presence",
|
||||||
"revolt-quark",
|
"revolt-quark",
|
||||||
"rmp-serde",
|
"rmp-serde",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "revolt-database"
|
||||||
|
version = "0.0.2"
|
||||||
|
dependencies = [
|
||||||
|
"async-recursion",
|
||||||
|
"async-std",
|
||||||
|
"async-trait",
|
||||||
|
"authifier",
|
||||||
|
"bson",
|
||||||
|
"futures",
|
||||||
|
"log",
|
||||||
|
"mongodb",
|
||||||
|
"nanoid",
|
||||||
|
"revolt-permissions",
|
||||||
|
"revolt-result",
|
||||||
|
"revolt_optional_struct",
|
||||||
|
"rocket",
|
||||||
|
"schemars",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "revolt-delta"
|
name = "revolt-delta"
|
||||||
version = "0.5.17"
|
version = "0.5.19"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-channel",
|
"async-channel",
|
||||||
"async-std",
|
"async-std",
|
||||||
@@ -2823,14 +2880,13 @@ dependencies = [
|
|||||||
"linkify 0.6.0",
|
"linkify 0.6.0",
|
||||||
"log",
|
"log",
|
||||||
"lru",
|
"lru",
|
||||||
"mobc",
|
|
||||||
"mobc-redis",
|
|
||||||
"nanoid",
|
"nanoid",
|
||||||
"num_enum",
|
"num_enum 0.5.7",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"redis 0.21.5",
|
|
||||||
"regex",
|
"regex",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
|
"revolt-database",
|
||||||
|
"revolt-models",
|
||||||
"revolt-quark",
|
"revolt-quark",
|
||||||
"revolt_rocket_okapi",
|
"revolt_rocket_okapi",
|
||||||
"rocket",
|
"rocket",
|
||||||
@@ -2845,9 +2901,43 @@ dependencies = [
|
|||||||
"vergen",
|
"vergen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "revolt-models"
|
||||||
|
version = "0.0.2"
|
||||||
|
dependencies = [
|
||||||
|
"revolt-database",
|
||||||
|
"revolt-presence",
|
||||||
|
"schemars",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "revolt-permissions"
|
||||||
|
version = "0.0.2"
|
||||||
|
dependencies = [
|
||||||
|
"async-std",
|
||||||
|
"async-trait",
|
||||||
|
"auto_ops",
|
||||||
|
"num_enum 0.6.1",
|
||||||
|
"once_cell",
|
||||||
|
"schemars",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "revolt-presence"
|
||||||
|
version = "0.0.2"
|
||||||
|
dependencies = [
|
||||||
|
"async-std",
|
||||||
|
"log",
|
||||||
|
"once_cell",
|
||||||
|
"rand 0.8.5",
|
||||||
|
"redis-kiss",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "revolt-quark"
|
name = "revolt-quark"
|
||||||
version = "0.5.17"
|
version = "0.5.19"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-lock",
|
"async-lock",
|
||||||
"async-recursion",
|
"async-recursion",
|
||||||
@@ -2870,14 +2960,17 @@ dependencies = [
|
|||||||
"lru",
|
"lru",
|
||||||
"mongodb",
|
"mongodb",
|
||||||
"nanoid",
|
"nanoid",
|
||||||
"num_enum",
|
"num_enum 0.5.7",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"optional_struct",
|
|
||||||
"pretty_env_logger",
|
"pretty_env_logger",
|
||||||
|
"rand 0.8.5",
|
||||||
"redis-kiss",
|
"redis-kiss",
|
||||||
"regex",
|
"regex",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
|
"revolt-presence",
|
||||||
|
"revolt-result",
|
||||||
"revolt_okapi",
|
"revolt_okapi",
|
||||||
|
"revolt_optional_struct",
|
||||||
"revolt_rocket_okapi",
|
"revolt_rocket_okapi",
|
||||||
"rocket",
|
"rocket",
|
||||||
"rocket_cors",
|
"rocket_cors",
|
||||||
@@ -2892,6 +2985,14 @@ dependencies = [
|
|||||||
"web-push",
|
"web-push",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "revolt-result"
|
||||||
|
version = "0.0.2"
|
||||||
|
dependencies = [
|
||||||
|
"schemars",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "revolt_okapi"
|
name = "revolt_okapi"
|
||||||
version = "0.9.1"
|
version = "0.9.1"
|
||||||
@@ -2904,6 +3005,16 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "revolt_optional_struct"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0d342739658623fe9d72b42f7a36ba19094d1d9bf9b423c3e337ab203c15d53e"
|
||||||
|
dependencies = [
|
||||||
|
"quote 0.3.15",
|
||||||
|
"syn 0.11.11",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "revolt_rocket_okapi"
|
name = "revolt_rocket_okapi"
|
||||||
version = "0.9.1"
|
version = "0.9.1"
|
||||||
@@ -2928,7 +3039,7 @@ checksum = "cc6620569d8ac8f0a1690fcca13f488503807a60e96ebf729749b59aca1dbef9"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"darling",
|
"darling",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote 1.0.18",
|
"quote 1.0.26",
|
||||||
"rocket_http",
|
"rocket_http",
|
||||||
"syn 1.0.107",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
@@ -3035,7 +3146,7 @@ dependencies = [
|
|||||||
"glob",
|
"glob",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote 1.0.18",
|
"quote 1.0.26",
|
||||||
"rocket_http",
|
"rocket_http",
|
||||||
"syn 1.0.107",
|
"syn 1.0.107",
|
||||||
"unicode-xid 0.2.3",
|
"unicode-xid 0.2.3",
|
||||||
@@ -3210,7 +3321,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "af4d7e1b012cb3d9129567661a63755ea4b8a7386d339dc945ae187e403c6743"
|
checksum = "af4d7e1b012cb3d9129567661a63755ea4b8a7386d339dc945ae187e403c6743"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote 1.0.18",
|
"quote 1.0.26",
|
||||||
"serde_derive_internals",
|
"serde_derive_internals",
|
||||||
"syn 1.0.107",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
@@ -3363,9 +3474,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.152"
|
version = "1.0.160"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
|
checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
@@ -3381,13 +3492,13 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.152"
|
version = "1.0.160"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
|
checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote 1.0.18",
|
"quote 1.0.26",
|
||||||
"syn 1.0.107",
|
"syn 2.0.15",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3397,15 +3508,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c"
|
checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote 1.0.18",
|
"quote 1.0.26",
|
||||||
"syn 1.0.107",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.91"
|
version = "1.0.96"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883"
|
checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"itoa 1.0.2",
|
"itoa 1.0.2",
|
||||||
@@ -3443,7 +3554,7 @@ checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"darling",
|
"darling",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote 1.0.18",
|
"quote 1.0.26",
|
||||||
"syn 1.0.107",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -3470,15 +3581,6 @@ dependencies = [
|
|||||||
"digest 0.10.3",
|
"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]]
|
[[package]]
|
||||||
name = "sha1_smol"
|
name = "sha1_smol"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
@@ -3627,7 +3729,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
|
checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote 1.0.18",
|
"quote 1.0.26",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote 1.0.26",
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -3699,7 +3812,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
|
checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote 1.0.18",
|
"quote 1.0.26",
|
||||||
"syn 1.0.107",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -3808,7 +3921,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7"
|
checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote 1.0.18",
|
"quote 1.0.26",
|
||||||
"syn 1.0.107",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -3944,7 +4057,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "cc6b8ad3567499f98a1db7a752b07a7c8c7c7c34c332ec00effb2b0027974b7c"
|
checksum = "cc6b8ad3567499f98a1db7a752b07a7c8c7c7c34c332ec00effb2b0027974b7c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote 1.0.18",
|
"quote 1.0.26",
|
||||||
"syn 1.0.107",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -4074,7 +4187,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "89851716b67b937e393b3daa8423e67ddfc4bbbf1654bcf05488e95e0828db0c"
|
checksum = "89851716b67b937e393b3daa8423e67ddfc4bbbf1654bcf05488e95e0828db0c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote 1.0.18",
|
"quote 1.0.26",
|
||||||
"syn 1.0.107",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -4284,7 +4397,7 @@ dependencies = [
|
|||||||
"lazy_static",
|
"lazy_static",
|
||||||
"proc-macro-error",
|
"proc-macro-error",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote 1.0.18",
|
"quote 1.0.26",
|
||||||
"regex",
|
"regex",
|
||||||
"syn 1.0.107",
|
"syn 1.0.107",
|
||||||
"validator_types",
|
"validator_types",
|
||||||
@@ -4394,7 +4507,7 @@ dependencies = [
|
|||||||
"lazy_static",
|
"lazy_static",
|
||||||
"log",
|
"log",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote 1.0.18",
|
"quote 1.0.26",
|
||||||
"syn 1.0.107",
|
"syn 1.0.107",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
@@ -4417,7 +4530,7 @@ version = "0.2.80"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5"
|
checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote 1.0.18",
|
"quote 1.0.26",
|
||||||
"wasm-bindgen-macro-support",
|
"wasm-bindgen-macro-support",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -4428,7 +4541,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b"
|
checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote 1.0.18",
|
"quote 1.0.26",
|
||||||
"syn 1.0.107",
|
"syn 1.0.107",
|
||||||
"wasm-bindgen-backend",
|
"wasm-bindgen-backend",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
|
|||||||
@@ -1,2 +1,6 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = ["crates/*"]
|
members = ["crates/delta", "crates/bonfire", "crates/quark", "crates/core/*"]
|
||||||
|
|
||||||
|
[patch.crates-io]
|
||||||
|
# mobc-redis = { git = "https://github.com/insertish/mobc", rev = "8b880bb59f2ba80b4c7bc40c649c113d8857a186" }
|
||||||
|
redis = { git = "https://github.com/insertish/redis-rs", rev = "1a41faf356fd21aebba71cea7eb7eb2653e5f0ef" }
|
||||||
|
|||||||
30
Dockerfile
30
Dockerfile
@@ -1,12 +1,32 @@
|
|||||||
# Build Stage
|
# Build Stage
|
||||||
FROM rustlang/rust:nightly-slim AS builder
|
FROM --platform="${BUILDPLATFORM}" rustlang/rust:nightly-slim
|
||||||
USER 0:0
|
USER 0:0
|
||||||
WORKDIR /home/rust/src
|
WORKDIR /home/rust/src
|
||||||
|
|
||||||
# Install build requirements
|
ARG TARGETARCH
|
||||||
RUN apt-get update && apt-get install -y libssl-dev pkg-config
|
|
||||||
|
|
||||||
# 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 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/
|
||||||
|
COPY crates/core/database/Cargo.toml ./crates/core/database/
|
||||||
|
COPY crates/core/models/Cargo.toml ./crates/core/models/
|
||||||
|
COPY crates/core/permissions/Cargo.toml ./crates/core/permissions/
|
||||||
|
COPY crates/core/presence/Cargo.toml ./crates/core/presence/
|
||||||
|
COPY crates/core/result/Cargo.toml ./crates/core/result/
|
||||||
|
RUN sh /tmp/build-image-layer.sh deps
|
||||||
|
|
||||||
|
# Build all apps
|
||||||
COPY crates ./crates
|
COPY crates ./crates
|
||||||
RUN cargo build --locked --release
|
RUN sh /tmp/build-image-layer.sh apps
|
||||||
|
|||||||
7
clippy.toml
Normal file
7
clippy.toml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
disallowed-methods = [
|
||||||
|
# Shouldn't need to access these directly
|
||||||
|
"revolt_database::models::bots::model::Bot::remove_field",
|
||||||
|
|
||||||
|
# Prefer to use Object::delete()
|
||||||
|
"revolt_database::models::bots::ops::AbstractBots::update_bot",
|
||||||
|
]
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "revolt-bonfire"
|
name = "revolt-bonfire"
|
||||||
version = "0.5.17"
|
version = "0.5.19"
|
||||||
license = "AGPL-3.0-or-later"
|
license = "AGPL-3.0-or-later"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
@@ -26,3 +26,6 @@ serde = "1.0.136"
|
|||||||
futures = "0.3.21"
|
futures = "0.3.21"
|
||||||
async-tungstenite = { version = "0.17.0", features = ["async-std-runtime"] }
|
async-tungstenite = { version = "0.17.0", features = ["async-std-runtime"] }
|
||||||
async-std = { version = "1.8.0", features = ["tokio1", "tokio02", "attributes"] }
|
async-std = { version = "1.8.0", features = ["tokio1", "tokio02", "attributes"] }
|
||||||
|
|
||||||
|
# core
|
||||||
|
revolt-presence = { path = "../core/presence", features = [ "redis-is-patched" ] }
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
# Build Stage
|
# Build Stage
|
||||||
FROM ghcr.io/revoltchat/base:latest AS builder
|
FROM ghcr.io/revoltchat/base:latest AS builder
|
||||||
RUN cargo install --locked --path crates/bonfire
|
|
||||||
|
|
||||||
# Bundle Stage
|
# Bundle Stage
|
||||||
FROM debian:buster-slim
|
FROM debian:bullseye-slim
|
||||||
RUN apt-get update && apt-get install -y ca-certificates
|
RUN apt-get update && \
|
||||||
COPY --from=builder /usr/local/cargo/bin/revolt-bonfire ./
|
apt-get install -y ca-certificates && \
|
||||||
|
apt-get clean
|
||||||
|
COPY --from=builder /home/rust/src/target/release/revolt-bonfire ./
|
||||||
EXPOSE 9000
|
EXPOSE 9000
|
||||||
CMD ["./revolt-bonfire"]
|
CMD ["./revolt-bonfire"]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
use async_std::net::TcpListener;
|
use async_std::net::TcpListener;
|
||||||
use revolt_quark::presence::presence_clear_region;
|
use revolt_presence::clear_region;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
@@ -18,7 +18,7 @@ async fn main() {
|
|||||||
database::connect().await;
|
database::connect().await;
|
||||||
|
|
||||||
// Clean up the current region information.
|
// Clean up the current region information.
|
||||||
presence_clear_region(None).await;
|
clear_region(None).await;
|
||||||
|
|
||||||
// Setup a TCP listener to accept WebSocket connections on.
|
// Setup a TCP listener to accept WebSocket connections on.
|
||||||
// By default, we bind to port 9000 on all interfaces.
|
// By default, we bind to port 9000 on all interfaces.
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
use futures::{channel::oneshot, pin_mut, select, FutureExt, SinkExt, StreamExt, TryStreamExt};
|
use futures::{channel::oneshot, pin_mut, select, FutureExt, SinkExt, StreamExt, TryStreamExt};
|
||||||
|
use revolt_presence::{create_session, delete_session};
|
||||||
use revolt_quark::{
|
use revolt_quark::{
|
||||||
events::{
|
events::{
|
||||||
client::EventV1,
|
client::EventV1,
|
||||||
@@ -8,7 +9,6 @@ use revolt_quark::{
|
|||||||
state::{State, SubscriptionStateChange},
|
state::{State, SubscriptionStateChange},
|
||||||
},
|
},
|
||||||
models::{user::UserHint, User},
|
models::{user::UserHint, User},
|
||||||
presence::{presence_create_session, presence_delete_session},
|
|
||||||
redis_kiss, Database,
|
redis_kiss, Database,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -69,8 +69,7 @@ pub fn spawn_client(db: &'static Database, stream: TcpStream, addr: SocketAddr)
|
|||||||
let user_id = state.cache.user_id.clone();
|
let user_id = state.cache.user_id.clone();
|
||||||
|
|
||||||
// Create presence session.
|
// Create presence session.
|
||||||
let (first_session, session_id) =
|
let (first_session, session_id) = create_session(&user_id, 0).await;
|
||||||
presence_create_session(&user_id, 0).await;
|
|
||||||
|
|
||||||
// Notify socket we have authenticated.
|
// Notify socket we have authenticated.
|
||||||
write
|
write
|
||||||
@@ -225,7 +224,7 @@ pub fn spawn_client(db: &'static Database, stream: TcpStream, addr: SocketAddr)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Clean up presence session.
|
// Clean up presence session.
|
||||||
let last_session = presence_delete_session(&user_id, session_id).await;
|
let last_session = delete_session(&user_id, session_id).await;
|
||||||
|
|
||||||
// If this was the last session, notify other users that we just went offline.
|
// If this was the last session, notify other users that we just went offline.
|
||||||
if last_session {
|
if last_session {
|
||||||
|
|||||||
53
crates/core/database/Cargo.toml
Normal file
53
crates/core/database/Cargo.toml
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
[package]
|
||||||
|
name = "revolt-database"
|
||||||
|
version = "0.0.2"
|
||||||
|
edition = "2021"
|
||||||
|
license = "AGPL-3.0-or-later"
|
||||||
|
authors = [ "Paul Makles <me@insrt.uk>" ]
|
||||||
|
description = "Revolt Backend: Database Implementation"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[features]
|
||||||
|
# Databases
|
||||||
|
mongodb = [ "dep:mongodb", "bson" ]
|
||||||
|
|
||||||
|
# ... Other
|
||||||
|
async-std-runtime = [ "async-std" ]
|
||||||
|
rocket-impl = [ "rocket", "schemars" ]
|
||||||
|
|
||||||
|
# Default Features
|
||||||
|
default = [ "mongodb", "async-std-runtime" ]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
# Core
|
||||||
|
revolt-result = { version = "0.0.2", path = "../result" }
|
||||||
|
revolt-permissions = { version = "0.0.2", path = "../permissions" }
|
||||||
|
|
||||||
|
# Utility
|
||||||
|
log = "0.4"
|
||||||
|
nanoid = "0.4.0"
|
||||||
|
|
||||||
|
# Serialisation
|
||||||
|
serde_json = "1"
|
||||||
|
revolt_optional_struct = "0.2.0"
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|
||||||
|
# Database
|
||||||
|
bson = { optional = true, version = "2.1.0" }
|
||||||
|
mongodb = { optional = true, version = "2.1.0", default-features = false }
|
||||||
|
|
||||||
|
# Async Language Features
|
||||||
|
futures = "0.3.19"
|
||||||
|
async-trait = "0.1.51"
|
||||||
|
async-recursion = "1.0.4"
|
||||||
|
|
||||||
|
# Async
|
||||||
|
async-std = { version = "1.8.0", features = ["attributes"], optional = true }
|
||||||
|
|
||||||
|
# Rocket Impl
|
||||||
|
schemars = { version = "0.8.8", optional = true }
|
||||||
|
rocket = { version = "0.5.0-rc.2", default-features = false, features = ["json"], optional = true }
|
||||||
|
|
||||||
|
# Authifier
|
||||||
|
authifier = { version = "1.0" }
|
||||||
67
crates/core/database/src/drivers/mod.rs
Normal file
67
crates/core/database/src/drivers/mod.rs
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
mod mongodb;
|
||||||
|
mod reference;
|
||||||
|
|
||||||
|
pub use self::mongodb::*;
|
||||||
|
pub use self::reference::*;
|
||||||
|
|
||||||
|
/// Database information to use to create a client
|
||||||
|
pub enum DatabaseInfo {
|
||||||
|
/// Auto-detect the database in use
|
||||||
|
Auto,
|
||||||
|
/// Auto-detect the database in use and create an empty testing database
|
||||||
|
Test(String),
|
||||||
|
/// Use the mock database
|
||||||
|
Reference,
|
||||||
|
/// Connect to MongoDB
|
||||||
|
MongoDb { uri: String, database_name: String },
|
||||||
|
/// Use existing MongoDB connection
|
||||||
|
MongoDbFromClient(::mongodb::Client, String),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Database
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum Database {
|
||||||
|
/// Mock database
|
||||||
|
Reference(ReferenceDb),
|
||||||
|
/// MongoDB database
|
||||||
|
MongoDb(MongoDb),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DatabaseInfo {
|
||||||
|
/// Create a database client from the given database information
|
||||||
|
#[async_recursion]
|
||||||
|
pub async fn connect(self) -> Result<Database, String> {
|
||||||
|
Ok(match self {
|
||||||
|
DatabaseInfo::Auto => {
|
||||||
|
if let Ok(uri) = std::env::var("MONGODB") {
|
||||||
|
return DatabaseInfo::MongoDb {
|
||||||
|
uri,
|
||||||
|
database_name: "revolt".to_string(),
|
||||||
|
}
|
||||||
|
.connect()
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
DatabaseInfo::Reference.connect().await?
|
||||||
|
}
|
||||||
|
DatabaseInfo::Test(database_name) => {
|
||||||
|
if let Ok(uri) = std::env::var("MONGODB") {
|
||||||
|
return DatabaseInfo::MongoDb { uri, database_name }.connect().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
DatabaseInfo::Reference.connect().await?
|
||||||
|
}
|
||||||
|
DatabaseInfo::Reference => Database::Reference(Default::default()),
|
||||||
|
DatabaseInfo::MongoDb { uri, database_name } => {
|
||||||
|
let client = ::mongodb::Client::with_uri_str(uri)
|
||||||
|
.await
|
||||||
|
.map_err(|_| "Failed to init db connection.".to_string())?;
|
||||||
|
|
||||||
|
Database::MongoDb(MongoDb(client, database_name))
|
||||||
|
}
|
||||||
|
DatabaseInfo::MongoDbFromClient(client, database_name) => {
|
||||||
|
Database::MongoDb(MongoDb(client, database_name))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
243
crates/core/database/src/drivers/mongodb.rs
Normal file
243
crates/core/database/src/drivers/mongodb.rs
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
use futures::StreamExt;
|
||||||
|
use mongodb::bson::{doc, to_document, Document};
|
||||||
|
use mongodb::error::Result;
|
||||||
|
use mongodb::options::{FindOneOptions, FindOptions};
|
||||||
|
use mongodb::results::{DeleteResult, InsertOneResult, UpdateResult};
|
||||||
|
use serde::de::DeserializeOwned;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
database_derived!(
|
||||||
|
#[cfg(feature = "mongodb")]
|
||||||
|
/// MongoDB implementation
|
||||||
|
pub struct MongoDb(pub ::mongodb::Client, pub String);
|
||||||
|
);
|
||||||
|
|
||||||
|
impl Deref for MongoDb {
|
||||||
|
type Target = mongodb::Client;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
impl MongoDb {
|
||||||
|
/// Get the Revolt database
|
||||||
|
pub fn db(&self) -> mongodb::Database {
|
||||||
|
self.database(&self.1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a collection by its name
|
||||||
|
pub fn col<T>(&self, collection: &str) -> mongodb::Collection<T> {
|
||||||
|
self.db().collection(collection)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert one document into a collection
|
||||||
|
pub async fn insert_one<T: Serialize>(
|
||||||
|
&self,
|
||||||
|
collection: &'static str,
|
||||||
|
document: T,
|
||||||
|
) -> Result<InsertOneResult> {
|
||||||
|
self.col::<T>(collection).insert_one(document, None).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Count documents by projection
|
||||||
|
pub async fn count_documents(
|
||||||
|
&self,
|
||||||
|
collection: &'static str,
|
||||||
|
projection: Document,
|
||||||
|
) -> Result<u64> {
|
||||||
|
self.col::<Document>(collection)
|
||||||
|
.count_documents(projection, None)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find multiple documents in a collection with options
|
||||||
|
pub async fn find_with_options<O, T: DeserializeOwned + Unpin + Send + Sync>(
|
||||||
|
&self,
|
||||||
|
collection: &'static str,
|
||||||
|
projection: Document,
|
||||||
|
options: O,
|
||||||
|
) -> Result<Vec<T>>
|
||||||
|
where
|
||||||
|
O: Into<Option<FindOptions>>,
|
||||||
|
{
|
||||||
|
Ok(self
|
||||||
|
.col::<T>(collection)
|
||||||
|
.find(projection, options)
|
||||||
|
.await?
|
||||||
|
.filter_map(|s| async {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
// Hard fail on invalid documents
|
||||||
|
Some(s.unwrap())
|
||||||
|
} else {
|
||||||
|
s.ok()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<T>>()
|
||||||
|
.await)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find multiple documents in a collection
|
||||||
|
pub async fn find<T: DeserializeOwned + Unpin + Send + Sync>(
|
||||||
|
&self,
|
||||||
|
collection: &'static str,
|
||||||
|
projection: Document,
|
||||||
|
) -> Result<Vec<T>> {
|
||||||
|
self.find_with_options(collection, projection, None).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find one document with options
|
||||||
|
pub async fn find_one_with_options<O, T: DeserializeOwned + Unpin + Send + Sync>(
|
||||||
|
&self,
|
||||||
|
collection: &'static str,
|
||||||
|
projection: Document,
|
||||||
|
options: O,
|
||||||
|
) -> Result<Option<T>>
|
||||||
|
where
|
||||||
|
O: Into<Option<FindOneOptions>>,
|
||||||
|
{
|
||||||
|
self.col::<T>(collection)
|
||||||
|
.find_one(projection, options)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find one document
|
||||||
|
pub async fn find_one<T: DeserializeOwned + Unpin + Send + Sync>(
|
||||||
|
&self,
|
||||||
|
collection: &'static str,
|
||||||
|
projection: Document,
|
||||||
|
) -> Result<Option<T>> {
|
||||||
|
self.find_one_with_options(collection, projection, None)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find one document by its ID
|
||||||
|
pub async fn find_one_by_id<T: DeserializeOwned + Unpin + Send + Sync>(
|
||||||
|
&self,
|
||||||
|
collection: &'static str,
|
||||||
|
id: &str,
|
||||||
|
) -> Result<Option<T>> {
|
||||||
|
self.find_one(
|
||||||
|
collection,
|
||||||
|
doc! {
|
||||||
|
"_id": id
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update one document given a projection, partial document, and list of paths to unset
|
||||||
|
pub async fn update_one<P, T: Serialize>(
|
||||||
|
&self,
|
||||||
|
collection: &'static str,
|
||||||
|
projection: Document,
|
||||||
|
partial: T,
|
||||||
|
remove: Vec<&dyn IntoDocumentPath>,
|
||||||
|
prefix: P,
|
||||||
|
) -> Result<UpdateResult>
|
||||||
|
where
|
||||||
|
P: Into<Option<String>>,
|
||||||
|
{
|
||||||
|
let prefix = prefix.into();
|
||||||
|
|
||||||
|
let mut unset = doc! {};
|
||||||
|
for field in remove {
|
||||||
|
if let Some(path) = field.as_path() {
|
||||||
|
if let Some(prefix) = &prefix {
|
||||||
|
unset.insert(prefix.to_owned() + path, 1_i32);
|
||||||
|
} else {
|
||||||
|
unset.insert(path, 1_i32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let query = doc! {
|
||||||
|
"$unset": unset,
|
||||||
|
"$set": if let Some(prefix) = &prefix {
|
||||||
|
to_document(&prefix_keys(&partial, prefix))
|
||||||
|
} else {
|
||||||
|
to_document(&partial)
|
||||||
|
}?
|
||||||
|
};
|
||||||
|
|
||||||
|
self.col::<Document>(collection)
|
||||||
|
.update_one(projection, query, None)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update one document given an ID, partial document, and list of paths to unset
|
||||||
|
pub async fn update_one_by_id<P, T: Serialize>(
|
||||||
|
&self,
|
||||||
|
collection: &'static str,
|
||||||
|
id: &str,
|
||||||
|
partial: T,
|
||||||
|
remove: Vec<&dyn IntoDocumentPath>,
|
||||||
|
prefix: P,
|
||||||
|
) -> Result<UpdateResult>
|
||||||
|
where
|
||||||
|
P: Into<Option<String>>,
|
||||||
|
{
|
||||||
|
self.update_one(
|
||||||
|
collection,
|
||||||
|
doc! {
|
||||||
|
"_id": id
|
||||||
|
},
|
||||||
|
partial,
|
||||||
|
remove,
|
||||||
|
prefix,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete one document by the given projection
|
||||||
|
pub async fn delete_one(
|
||||||
|
&self,
|
||||||
|
collection: &'static str,
|
||||||
|
projection: Document,
|
||||||
|
) -> Result<DeleteResult> {
|
||||||
|
self.col::<Document>(collection)
|
||||||
|
.delete_one(projection, None)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete one document by the given ID
|
||||||
|
pub async fn delete_one_by_id(
|
||||||
|
&self,
|
||||||
|
collection: &'static str,
|
||||||
|
id: &str,
|
||||||
|
) -> Result<DeleteResult> {
|
||||||
|
self.delete_one(
|
||||||
|
collection,
|
||||||
|
doc! {
|
||||||
|
"_id": id
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Just a string ID struct
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct DocumentId {
|
||||||
|
#[serde(rename = "_id")]
|
||||||
|
pub id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait IntoDocumentPath: Send + Sync {
|
||||||
|
/// Create JSON key path
|
||||||
|
fn as_path(&self) -> Option<&'static str>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prefix keys on an arbitrary object
|
||||||
|
pub fn prefix_keys<T: Serialize>(t: &T, prefix: &str) -> HashMap<String, serde_json::Value> {
|
||||||
|
let v: String = serde_json::to_string(t).unwrap();
|
||||||
|
let v: HashMap<String, serde_json::Value> = serde_json::from_str(&v).unwrap();
|
||||||
|
v.into_iter()
|
||||||
|
.filter(|(_k, v)| !v.is_null())
|
||||||
|
.map(|(k, v)| (prefix.to_owned() + &k, v))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
14
crates/core/database/src/drivers/reference.rs
Normal file
14
crates/core/database/src/drivers/reference.rs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
use std::{collections::HashMap, sync::Arc};
|
||||||
|
|
||||||
|
use futures::lock::Mutex;
|
||||||
|
|
||||||
|
use crate::{Bot, User};
|
||||||
|
|
||||||
|
database_derived!(
|
||||||
|
/// Reference implementation
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct ReferenceDb {
|
||||||
|
pub bots: Arc<Mutex<HashMap<String, Bot>>>,
|
||||||
|
pub users: Arc<Mutex<HashMap<String, User>>>,
|
||||||
|
}
|
||||||
|
);
|
||||||
86
crates/core/database/src/lib.rs
Normal file
86
crates/core/database/src/lib.rs
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
#[macro_use]
|
||||||
|
extern crate serde;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate async_recursion;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate async_trait;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate log;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate revolt_optional_struct;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate revolt_result;
|
||||||
|
|
||||||
|
#[cfg(feature = "mongodb")]
|
||||||
|
pub use mongodb;
|
||||||
|
|
||||||
|
#[cfg(feature = "mongodb")]
|
||||||
|
#[macro_use]
|
||||||
|
extern crate bson;
|
||||||
|
|
||||||
|
macro_rules! database_derived {
|
||||||
|
( $( $item:item )+ ) => {
|
||||||
|
$(
|
||||||
|
#[derive(Clone)]
|
||||||
|
$item
|
||||||
|
)+
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! auto_derived {
|
||||||
|
( $( $item:item )+ ) => {
|
||||||
|
$(
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
|
||||||
|
$item
|
||||||
|
)+
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! auto_derived_partial {
|
||||||
|
( $item:item, $name:expr ) => {
|
||||||
|
#[derive(OptionalStruct, Serialize, Deserialize, Debug, Clone, Default, Eq, PartialEq)]
|
||||||
|
#[optional_derive(Serialize, Deserialize, Debug, Clone, Default, Eq, PartialEq)]
|
||||||
|
#[optional_name = $name]
|
||||||
|
#[opt_skip_serializing_none]
|
||||||
|
#[opt_some_priority]
|
||||||
|
$item
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
mod drivers;
|
||||||
|
pub use drivers::*;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
macro_rules! database_test {
|
||||||
|
( | $db: ident | $test:expr ) => {
|
||||||
|
let db = $crate::DatabaseInfo::Test(format!(
|
||||||
|
"{}:{}",
|
||||||
|
file!().replace('/', "_").replace(".rs", ""),
|
||||||
|
line!()
|
||||||
|
))
|
||||||
|
.connect()
|
||||||
|
.await
|
||||||
|
.expect("Database connection failed.");
|
||||||
|
|
||||||
|
db.drop_database().await;
|
||||||
|
|
||||||
|
#[allow(clippy::redundant_closure_call)]
|
||||||
|
(|$db: $crate::Database| $test)(db.clone()).await;
|
||||||
|
|
||||||
|
db.drop_database().await
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
mod models;
|
||||||
|
pub mod util;
|
||||||
|
pub use models::*;
|
||||||
|
|
||||||
|
/// Utility function to check if a boolean value is false
|
||||||
|
pub fn if_false(t: &bool) -> bool {
|
||||||
|
!t
|
||||||
|
}
|
||||||
5
crates/core/database/src/models/admin_migrations/mod.rs
Normal file
5
crates/core/database/src/models/admin_migrations/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
mod model;
|
||||||
|
mod ops;
|
||||||
|
|
||||||
|
pub use model::*;
|
||||||
|
pub use ops::*;
|
||||||
24
crates/core/database/src/models/admin_migrations/model.rs
Normal file
24
crates/core/database/src/models/admin_migrations/model.rs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
auto_derived!(
|
||||||
|
/// Document representing migration information
|
||||||
|
pub struct MigrationInfo {
|
||||||
|
/// Unique Id
|
||||||
|
#[serde(rename = "_id")]
|
||||||
|
pub id: i32,
|
||||||
|
/// Current database revision
|
||||||
|
pub revision: i32,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#[async_std::test]
|
||||||
|
async fn migrate() {
|
||||||
|
database_test!(|db| async move {
|
||||||
|
// Initialise the database
|
||||||
|
db.migrate_database().await.unwrap();
|
||||||
|
|
||||||
|
// Migrate the existing database
|
||||||
|
db.migrate_database().await.unwrap()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
12
crates/core/database/src/models/admin_migrations/ops.rs
Normal file
12
crates/core/database/src/models/admin_migrations/ops.rs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
mod mongodb;
|
||||||
|
mod reference;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait AbstractMigrations: Sync + Send {
|
||||||
|
#[cfg(test)]
|
||||||
|
/// Drop the database
|
||||||
|
async fn drop_database(&self);
|
||||||
|
|
||||||
|
/// Migrate the database
|
||||||
|
async fn migrate_database(&self) -> Result<(), ()>;
|
||||||
|
}
|
||||||
@@ -1,13 +1,20 @@
|
|||||||
use crate::{AbstractMigrations, Result};
|
use crate::MongoDb;
|
||||||
|
|
||||||
use super::super::MongoDb;
|
use super::AbstractMigrations;
|
||||||
|
|
||||||
mod init;
|
mod init;
|
||||||
mod scripts;
|
mod scripts;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl AbstractMigrations for MongoDb {
|
impl AbstractMigrations for MongoDb {
|
||||||
async fn migrate_database(&self) -> Result<()> {
|
#[cfg(test)]
|
||||||
|
/// Drop the database
|
||||||
|
async fn drop_database(&self) {
|
||||||
|
self.db().drop(None).await.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Migrate the database
|
||||||
|
async fn migrate_database(&self) -> Result<(), ()> {
|
||||||
info!("Migrating the database.");
|
info!("Migrating the database.");
|
||||||
|
|
||||||
let list = self
|
let list = self
|
||||||
@@ -15,7 +22,7 @@ impl AbstractMigrations for MongoDb {
|
|||||||
.await
|
.await
|
||||||
.expect("Failed to fetch database names.");
|
.expect("Failed to fetch database names.");
|
||||||
|
|
||||||
if list.iter().any(|x| x == "revolt") {
|
if list.iter().any(|x| x == &self.1) {
|
||||||
scripts::migrate_database(self).await;
|
scripts::migrate_database(self).await;
|
||||||
} else {
|
} else {
|
||||||
init::create_database(self).await;
|
init::create_database(self).await;
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
use crate::r#impl::mongo::MongoDb;
|
|
||||||
|
|
||||||
use super::scripts::LATEST_REVISION;
|
use super::scripts::LATEST_REVISION;
|
||||||
|
|
||||||
use mongodb::bson::doc;
|
use crate::mongodb::bson::doc;
|
||||||
use mongodb::options::CreateCollectionOptions;
|
use crate::mongodb::options::CreateCollectionOptions;
|
||||||
|
use crate::MongoDb;
|
||||||
|
|
||||||
pub async fn create_database(db: &MongoDb) {
|
pub async fn create_database(db: &MongoDb) {
|
||||||
info!("Creating database.");
|
info!("Creating database.");
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
use std::{time::Duration, ops::BitXor};
|
use std::{ops::BitXor, time::Duration};
|
||||||
|
|
||||||
use bson::{Bson, DateTime};
|
use crate::{
|
||||||
use futures::StreamExt;
|
mongodb::{
|
||||||
use mongodb::{
|
bson::{doc, from_bson, from_document, to_document, Bson, DateTime, Document},
|
||||||
bson::{doc, from_bson, from_document, to_document, Document},
|
options::FindOptions,
|
||||||
options::FindOptions,
|
},
|
||||||
|
MongoDb,
|
||||||
};
|
};
|
||||||
|
use futures::StreamExt;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{r#impl::mongo::MongoDb, Permission, DEFAULT_PERMISSION_SERVER};
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
struct MigrationInfo {
|
struct MigrationInfo {
|
||||||
_id: i32,
|
_id: i32,
|
||||||
@@ -504,7 +504,7 @@ pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 {
|
|||||||
update.insert(
|
update.insert(
|
||||||
"default_permissions",
|
"default_permissions",
|
||||||
// Remove Send Message permission if it wasn't originally granted
|
// Remove Send Message permission if it wasn't originally granted
|
||||||
DEFAULT_PERMISSION_SERVER.bitxor(if has_send { 0 } else { Permission::SendMessage as u64}) as i64,
|
(4000323584).bitxor(if has_send { 0 } else { (1 << 22) as u64 }) as i64,
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(Bson::Document(mut roles)) = document.remove("roles") {
|
if let Some(Bson::Document(mut roles)) = document.remove("roles") {
|
||||||
@@ -563,7 +563,7 @@ pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 {
|
|||||||
doc! {
|
doc! {
|
||||||
"default_permissions": {
|
"default_permissions": {
|
||||||
"a": 0_i64,
|
"a": 0_i64,
|
||||||
"d": Permission::SendMessage as i64
|
"d": (1 << 22) as i64
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
use crate::ReferenceDb;
|
||||||
|
|
||||||
|
use super::AbstractMigrations;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl AbstractMigrations for ReferenceDb {
|
||||||
|
#[cfg(test)]
|
||||||
|
/// Drop the database
|
||||||
|
async fn drop_database(&self) {}
|
||||||
|
|
||||||
|
/// Migrate the database
|
||||||
|
async fn migrate_database(&self) -> Result<(), ()> {
|
||||||
|
// Here you would do your typical migrations if this was a real database.
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
5
crates/core/database/src/models/bots/mod.rs
Normal file
5
crates/core/database/src/models/bots/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
mod model;
|
||||||
|
mod ops;
|
||||||
|
|
||||||
|
pub use model::*;
|
||||||
|
pub use ops::*;
|
||||||
145
crates/core/database/src/models/bots/model.rs
Normal file
145
crates/core/database/src/models/bots/model.rs
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
use revolt_result::Result;
|
||||||
|
|
||||||
|
use crate::Database;
|
||||||
|
|
||||||
|
auto_derived_partial!(
|
||||||
|
/// Bot
|
||||||
|
pub struct Bot {
|
||||||
|
/// Bot Id
|
||||||
|
///
|
||||||
|
/// This equals the associated bot user's id.
|
||||||
|
#[serde(rename = "_id")]
|
||||||
|
pub id: String,
|
||||||
|
/// User Id of the bot owner
|
||||||
|
pub owner: String,
|
||||||
|
/// Token used to authenticate requests for this bot
|
||||||
|
pub token: String,
|
||||||
|
/// Whether the bot is public
|
||||||
|
/// (may be invited by anyone)
|
||||||
|
pub public: bool,
|
||||||
|
|
||||||
|
/// Whether to enable analytics
|
||||||
|
#[serde(skip_serializing_if = "crate::if_false", default)]
|
||||||
|
pub analytics: bool,
|
||||||
|
/// Whether this bot should be publicly discoverable
|
||||||
|
#[serde(skip_serializing_if = "crate::if_false", default)]
|
||||||
|
pub discoverable: bool,
|
||||||
|
/// Reserved; URL for handling interactions
|
||||||
|
#[serde(skip_serializing_if = "String::is_empty", default)]
|
||||||
|
pub interactions_url: String,
|
||||||
|
/// URL for terms of service
|
||||||
|
#[serde(skip_serializing_if = "String::is_empty", default)]
|
||||||
|
pub terms_of_service_url: String,
|
||||||
|
/// URL for privacy policy
|
||||||
|
#[serde(skip_serializing_if = "String::is_empty", default)]
|
||||||
|
pub privacy_policy_url: String,
|
||||||
|
|
||||||
|
/// Enum of bot flags
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub flags: Option<i32>,
|
||||||
|
},
|
||||||
|
"PartialBot"
|
||||||
|
);
|
||||||
|
|
||||||
|
auto_derived!(
|
||||||
|
/// Optional fields on bot object
|
||||||
|
pub enum FieldsBot {
|
||||||
|
Token,
|
||||||
|
InteractionsURL,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
#[allow(clippy::disallowed_methods)]
|
||||||
|
impl Bot {
|
||||||
|
/// Remove a field from this object
|
||||||
|
pub fn remove_field(&mut self, field: &FieldsBot) {
|
||||||
|
match field {
|
||||||
|
FieldsBot::Token => self.token = nanoid::nanoid!(64),
|
||||||
|
FieldsBot::InteractionsURL => {
|
||||||
|
self.interactions_url = String::new();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update this bot
|
||||||
|
pub async fn update(
|
||||||
|
&mut self,
|
||||||
|
db: &Database,
|
||||||
|
mut partial: PartialBot,
|
||||||
|
remove: Vec<FieldsBot>,
|
||||||
|
) -> Result<()> {
|
||||||
|
if remove.contains(&FieldsBot::Token) {
|
||||||
|
partial.token = Some(nanoid::nanoid!(64));
|
||||||
|
}
|
||||||
|
|
||||||
|
for field in &remove {
|
||||||
|
self.remove_field(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
db.update_bot(&self.id, &partial, remove).await?;
|
||||||
|
|
||||||
|
self.apply_options(partial);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete this bot
|
||||||
|
pub async fn delete(&self, db: &Database) -> Result<()> {
|
||||||
|
// db.fetch_user(&self.id).await?.mark_deleted(db).await?;
|
||||||
|
db.delete_bot(&self.id).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::{Bot, FieldsBot, PartialBot};
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn crud() {
|
||||||
|
database_test!(|db| async move {
|
||||||
|
let bot_id = "bot";
|
||||||
|
let user_id = "user";
|
||||||
|
let token = "my_token";
|
||||||
|
|
||||||
|
let bot = Bot {
|
||||||
|
id: bot_id.to_string(),
|
||||||
|
owner: user_id.to_string(),
|
||||||
|
token: token.to_string(),
|
||||||
|
interactions_url: "some url".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
db.insert_bot(&bot).await.unwrap();
|
||||||
|
|
||||||
|
let mut updated_bot = bot.clone();
|
||||||
|
updated_bot
|
||||||
|
.update(
|
||||||
|
&db,
|
||||||
|
PartialBot {
|
||||||
|
public: Some(true),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
vec![FieldsBot::Token, FieldsBot::InteractionsURL],
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let fetched_bot1 = db.fetch_bot(bot_id).await.unwrap();
|
||||||
|
let fetched_bot2 = db.fetch_bot_by_token(&fetched_bot1.token).await.unwrap();
|
||||||
|
let fetched_bots = db.fetch_bots_by_user(user_id).await.unwrap();
|
||||||
|
|
||||||
|
assert!(!bot.public);
|
||||||
|
assert!(fetched_bot1.public);
|
||||||
|
assert!(!bot.interactions_url.is_empty());
|
||||||
|
assert!(fetched_bot1.interactions_url.is_empty());
|
||||||
|
assert_ne!(bot.token, fetched_bot1.token);
|
||||||
|
assert_eq!(updated_bot, fetched_bot1);
|
||||||
|
assert_eq!(fetched_bot1, fetched_bot2);
|
||||||
|
assert_eq!(fetched_bot1, fetched_bots[0]);
|
||||||
|
assert_eq!(1, db.get_number_of_bots_by_user(user_id).await.unwrap());
|
||||||
|
|
||||||
|
bot.delete(&db).await.unwrap();
|
||||||
|
assert!(db.fetch_bot(bot_id).await.is_err());
|
||||||
|
assert_eq!(0, db.get_number_of_bots_by_user(user_id).await.unwrap())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
35
crates/core/database/src/models/bots/ops.rs
Normal file
35
crates/core/database/src/models/bots/ops.rs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
use revolt_result::Result;
|
||||||
|
|
||||||
|
use crate::{Bot, FieldsBot, PartialBot};
|
||||||
|
|
||||||
|
mod mongodb;
|
||||||
|
mod reference;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait AbstractBots: Sync + Send {
|
||||||
|
/// Fetch a bot by its id
|
||||||
|
async fn fetch_bot(&self, id: &str) -> Result<Bot>;
|
||||||
|
|
||||||
|
/// Fetch a bot by its token
|
||||||
|
async fn fetch_bot_by_token(&self, token: &str) -> Result<Bot>;
|
||||||
|
|
||||||
|
/// Insert new bot into the database
|
||||||
|
async fn insert_bot(&self, bot: &Bot) -> Result<()>;
|
||||||
|
|
||||||
|
/// Update bot with new information
|
||||||
|
async fn update_bot(
|
||||||
|
&self,
|
||||||
|
id: &str,
|
||||||
|
partial: &PartialBot,
|
||||||
|
remove: Vec<FieldsBot>,
|
||||||
|
) -> Result<()>;
|
||||||
|
|
||||||
|
/// Delete a bot from the database
|
||||||
|
async fn delete_bot(&self, id: &str) -> Result<()>;
|
||||||
|
|
||||||
|
/// Fetch bots owned by a user
|
||||||
|
async fn fetch_bots_by_user(&self, user_id: &str) -> Result<Vec<Bot>>;
|
||||||
|
|
||||||
|
/// Get the number of bots owned by a user
|
||||||
|
async fn get_number_of_bots_by_user(&self, user_id: &str) -> Result<usize>;
|
||||||
|
}
|
||||||
92
crates/core/database/src/models/bots/ops/mongodb.rs
Normal file
92
crates/core/database/src/models/bots/ops/mongodb.rs
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
use revolt_result::Result;
|
||||||
|
|
||||||
|
use crate::{Bot, FieldsBot, PartialBot};
|
||||||
|
use crate::{IntoDocumentPath, MongoDb};
|
||||||
|
|
||||||
|
use super::AbstractBots;
|
||||||
|
|
||||||
|
static COL: &str = "bots";
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl AbstractBots for MongoDb {
|
||||||
|
/// Fetch a bot by its id
|
||||||
|
async fn fetch_bot(&self, id: &str) -> Result<Bot> {
|
||||||
|
query!(self, find_one_by_id, COL, id)?.ok_or_else(|| create_error!(NotFound))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch a bot by its token
|
||||||
|
async fn fetch_bot_by_token(&self, token: &str) -> Result<Bot> {
|
||||||
|
query!(
|
||||||
|
self,
|
||||||
|
find_one,
|
||||||
|
COL,
|
||||||
|
doc! {
|
||||||
|
"token": token
|
||||||
|
}
|
||||||
|
)?
|
||||||
|
.ok_or_else(|| create_error!(NotFound))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert new bot into the database
|
||||||
|
async fn insert_bot(&self, bot: &Bot) -> Result<()> {
|
||||||
|
query!(self, insert_one, COL, &bot).map(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update bot with new information
|
||||||
|
async fn update_bot(
|
||||||
|
&self,
|
||||||
|
id: &str,
|
||||||
|
partial: &PartialBot,
|
||||||
|
remove: Vec<FieldsBot>,
|
||||||
|
) -> Result<()> {
|
||||||
|
query!(
|
||||||
|
self,
|
||||||
|
update_one_by_id,
|
||||||
|
COL,
|
||||||
|
id,
|
||||||
|
partial,
|
||||||
|
remove.iter().map(|x| x as &dyn IntoDocumentPath).collect(),
|
||||||
|
None
|
||||||
|
)
|
||||||
|
.map(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete a bot from the database
|
||||||
|
async fn delete_bot(&self, id: &str) -> Result<()> {
|
||||||
|
query!(self, delete_one_by_id, COL, id).map(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch bots owned by a user
|
||||||
|
async fn fetch_bots_by_user(&self, user_id: &str) -> Result<Vec<Bot>> {
|
||||||
|
query!(
|
||||||
|
self,
|
||||||
|
find,
|
||||||
|
COL,
|
||||||
|
doc! {
|
||||||
|
"owner": user_id
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the number of bots owned by a user
|
||||||
|
async fn get_number_of_bots_by_user(&self, user_id: &str) -> Result<usize> {
|
||||||
|
query!(
|
||||||
|
self,
|
||||||
|
count_documents,
|
||||||
|
COL,
|
||||||
|
doc! {
|
||||||
|
"owner": user_id
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.map(|v| v as usize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoDocumentPath for FieldsBot {
|
||||||
|
fn as_path(&self) -> Option<&'static str> {
|
||||||
|
match self {
|
||||||
|
FieldsBot::InteractionsURL => Some("interactions_url"),
|
||||||
|
FieldsBot::Token => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
82
crates/core/database/src/models/bots/ops/reference.rs
Normal file
82
crates/core/database/src/models/bots/ops/reference.rs
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
use revolt_result::Result;
|
||||||
|
|
||||||
|
use crate::ReferenceDb;
|
||||||
|
use crate::{Bot, FieldsBot, PartialBot};
|
||||||
|
|
||||||
|
use super::AbstractBots;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl AbstractBots for ReferenceDb {
|
||||||
|
/// Fetch a bot by its id
|
||||||
|
async fn fetch_bot(&self, id: &str) -> Result<Bot> {
|
||||||
|
let bots = self.bots.lock().await;
|
||||||
|
bots.get(id).cloned().ok_or_else(|| create_error!(NotFound))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch a bot by its token
|
||||||
|
async fn fetch_bot_by_token(&self, token: &str) -> Result<Bot> {
|
||||||
|
let bots = self.bots.lock().await;
|
||||||
|
bots.values()
|
||||||
|
.find(|bot| bot.token == token)
|
||||||
|
.cloned()
|
||||||
|
.ok_or_else(|| create_error!(NotFound))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert new bot into the database
|
||||||
|
async fn insert_bot(&self, bot: &Bot) -> Result<()> {
|
||||||
|
let mut bots = self.bots.lock().await;
|
||||||
|
if bots.contains_key(&bot.id) {
|
||||||
|
Err(create_database_error!("insert", "bot"))
|
||||||
|
} else {
|
||||||
|
bots.insert(bot.id.to_string(), bot.clone());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update bot with new information
|
||||||
|
async fn update_bot(
|
||||||
|
&self,
|
||||||
|
id: &str,
|
||||||
|
partial: &PartialBot,
|
||||||
|
remove: Vec<FieldsBot>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let mut bots = self.bots.lock().await;
|
||||||
|
if let Some(bot) = bots.get_mut(id) {
|
||||||
|
for field in remove {
|
||||||
|
#[allow(clippy::disallowed_methods)]
|
||||||
|
bot.remove_field(&field);
|
||||||
|
}
|
||||||
|
|
||||||
|
bot.apply_options(partial.clone());
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(create_error!(NotFound))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete a bot from the database
|
||||||
|
async fn delete_bot(&self, id: &str) -> Result<()> {
|
||||||
|
let mut bots = self.bots.lock().await;
|
||||||
|
if bots.remove(id).is_some() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(create_error!(NotFound))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch bots owned by a user
|
||||||
|
async fn fetch_bots_by_user(&self, user_id: &str) -> Result<Vec<Bot>> {
|
||||||
|
let bots = self.bots.lock().await;
|
||||||
|
Ok(bots
|
||||||
|
.values()
|
||||||
|
.filter(|bot| bot.owner == user_id)
|
||||||
|
.cloned()
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the number of bots owned by a user
|
||||||
|
async fn get_number_of_bots_by_user(&self, user_id: &str) -> Result<usize> {
|
||||||
|
let bots = self.bots.lock().await;
|
||||||
|
Ok(bots.values().filter(|bot| bot.owner == user_id).count())
|
||||||
|
}
|
||||||
|
}
|
||||||
3
crates/core/database/src/models/files/mod.rs
Normal file
3
crates/core/database/src/models/files/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
mod model;
|
||||||
|
|
||||||
|
pub use model::*;
|
||||||
59
crates/core/database/src/models/files/model.rs
Normal file
59
crates/core/database/src/models/files/model.rs
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
auto_derived_partial!(
|
||||||
|
/// File
|
||||||
|
pub struct File {
|
||||||
|
/// Unique Id
|
||||||
|
#[serde(rename = "_id")]
|
||||||
|
pub id: String,
|
||||||
|
/// Tag / bucket this file was uploaded to
|
||||||
|
pub tag: String,
|
||||||
|
/// Original filename
|
||||||
|
pub filename: String,
|
||||||
|
/// Parsed metadata of this file
|
||||||
|
pub metadata: Metadata,
|
||||||
|
/// Raw content type of this file
|
||||||
|
pub content_type: String,
|
||||||
|
/// Size of this file (in bytes)
|
||||||
|
pub size: isize,
|
||||||
|
|
||||||
|
/// Whether this file was deleted
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub deleted: Option<bool>,
|
||||||
|
/// Whether this file was reported
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub reported: Option<bool>,
|
||||||
|
|
||||||
|
// TODO: migrate this mess to having:
|
||||||
|
// - author_id
|
||||||
|
// - parent: Parent { Message(id), User(id), etc }
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub message_id: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub user_id: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub server_id: Option<String>,
|
||||||
|
|
||||||
|
/// Id of the object this file is associated with
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub object_id: Option<String>,
|
||||||
|
},
|
||||||
|
"PartialFile"
|
||||||
|
);
|
||||||
|
|
||||||
|
auto_derived!(
|
||||||
|
/// Metadata associated with a file
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
#[derive(Default)]
|
||||||
|
pub enum Metadata {
|
||||||
|
/// File is just a generic uncategorised file
|
||||||
|
#[default]
|
||||||
|
File,
|
||||||
|
/// File contains textual data and should be displayed as such
|
||||||
|
Text,
|
||||||
|
/// File is an image with specific dimensions
|
||||||
|
Image { width: isize, height: isize },
|
||||||
|
/// File is a video with specific dimensions
|
||||||
|
Video { width: isize, height: isize },
|
||||||
|
/// File is audio
|
||||||
|
Audio,
|
||||||
|
}
|
||||||
|
);
|
||||||
30
crates/core/database/src/models/mod.rs
Normal file
30
crates/core/database/src/models/mod.rs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
mod admin_migrations;
|
||||||
|
mod bots;
|
||||||
|
mod files;
|
||||||
|
mod users;
|
||||||
|
|
||||||
|
pub use admin_migrations::*;
|
||||||
|
pub use bots::*;
|
||||||
|
pub use files::*;
|
||||||
|
pub use users::*;
|
||||||
|
|
||||||
|
use crate::{Database, MongoDb, ReferenceDb};
|
||||||
|
|
||||||
|
pub trait AbstractDatabase:
|
||||||
|
Sync + Send + admin_migrations::AbstractMigrations + bots::AbstractBots + users::AbstractUsers
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AbstractDatabase for ReferenceDb {}
|
||||||
|
impl AbstractDatabase for MongoDb {}
|
||||||
|
|
||||||
|
impl std::ops::Deref for Database {
|
||||||
|
type Target = dyn AbstractDatabase;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
match &self {
|
||||||
|
Database::Reference(dummy) => dummy,
|
||||||
|
Database::MongoDb(mongo) => mongo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
crates/core/database/src/models/users/mod.rs
Normal file
9
crates/core/database/src/models/users/mod.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
mod model;
|
||||||
|
mod ops;
|
||||||
|
#[cfg(feature = "rocket-impl")]
|
||||||
|
mod rocket;
|
||||||
|
|
||||||
|
#[cfg(feature = "rocket-impl")]
|
||||||
|
pub use self::rocket::*;
|
||||||
|
pub use model::*;
|
||||||
|
pub use ops::*;
|
||||||
99
crates/core/database/src/models/users/model.rs
Normal file
99
crates/core/database/src/models/users/model.rs
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
use crate::File;
|
||||||
|
|
||||||
|
auto_derived_partial!(
|
||||||
|
/// # User
|
||||||
|
pub struct User {
|
||||||
|
/// Unique Id
|
||||||
|
#[serde(rename = "_id")]
|
||||||
|
pub id: String,
|
||||||
|
/// Username
|
||||||
|
pub username: String,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
/// Avatar attachment
|
||||||
|
pub avatar: Option<File>,
|
||||||
|
/// Relationships with other users
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub relations: Option<Vec<Relationship>>,
|
||||||
|
|
||||||
|
/// Bitfield of user badges
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub badges: Option<i32>,
|
||||||
|
/// User's current status
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub status: Option<UserStatus>,
|
||||||
|
/// User's profile page
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub profile: Option<UserProfile>,
|
||||||
|
|
||||||
|
/// Enum of user flags
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub flags: Option<i32>,
|
||||||
|
/// Whether this user is privileged
|
||||||
|
#[serde(skip_serializing_if = "crate::if_false", default)]
|
||||||
|
pub privileged: bool,
|
||||||
|
/// Bot information
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub bot: Option<BotInformation>,
|
||||||
|
},
|
||||||
|
"PartialUser"
|
||||||
|
);
|
||||||
|
|
||||||
|
auto_derived!(
|
||||||
|
/// User's relationship with another user (or themselves)
|
||||||
|
pub enum RelationshipStatus {
|
||||||
|
None,
|
||||||
|
User,
|
||||||
|
Friend,
|
||||||
|
Outgoing,
|
||||||
|
Incoming,
|
||||||
|
Blocked,
|
||||||
|
BlockedOther,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Relationship entry indicating current status with other user
|
||||||
|
pub struct Relationship {
|
||||||
|
#[serde(rename = "_id")]
|
||||||
|
pub id: String,
|
||||||
|
pub status: RelationshipStatus,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Presence status
|
||||||
|
pub enum Presence {
|
||||||
|
/// User is online
|
||||||
|
Online,
|
||||||
|
/// User is not currently available
|
||||||
|
Idle,
|
||||||
|
/// User is focusing / will only receive mentions
|
||||||
|
Focus,
|
||||||
|
/// User is busy / will not receive any notifications
|
||||||
|
Busy,
|
||||||
|
/// User appears to be offline
|
||||||
|
Invisible,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// User's active status
|
||||||
|
pub struct UserStatus {
|
||||||
|
/// Custom status text
|
||||||
|
#[serde(skip_serializing_if = "String::is_empty")]
|
||||||
|
pub text: String,
|
||||||
|
/// Current presence option
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub presence: Option<Presence>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// User's profile
|
||||||
|
pub struct UserProfile {
|
||||||
|
/// Text content on user's profile
|
||||||
|
#[serde(skip_serializing_if = "String::is_empty")]
|
||||||
|
pub content: String,
|
||||||
|
/// Background visible on user's profile
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub background: Option<File>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bot information for if the user is a bot
|
||||||
|
pub struct BotInformation {
|
||||||
|
/// Id of the owner of this bot
|
||||||
|
pub owner: String,
|
||||||
|
}
|
||||||
|
);
|
||||||
12
crates/core/database/src/models/users/ops.rs
Normal file
12
crates/core/database/src/models/users/ops.rs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
use revolt_result::Result;
|
||||||
|
|
||||||
|
use crate::User;
|
||||||
|
|
||||||
|
mod mongodb;
|
||||||
|
mod reference;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait AbstractUsers: Sync + Send {
|
||||||
|
/// Fetch a user from the database
|
||||||
|
async fn fetch_user(&self, id: &str) -> Result<User>;
|
||||||
|
}
|
||||||
16
crates/core/database/src/models/users/ops/mongodb.rs
Normal file
16
crates/core/database/src/models/users/ops/mongodb.rs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
use revolt_result::Result;
|
||||||
|
|
||||||
|
use crate::MongoDb;
|
||||||
|
use crate::User;
|
||||||
|
|
||||||
|
use super::AbstractUsers;
|
||||||
|
|
||||||
|
static COL: &str = "bots";
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl AbstractUsers for MongoDb {
|
||||||
|
/// Fetch a user from the database
|
||||||
|
async fn fetch_user(&self, id: &str) -> Result<User> {
|
||||||
|
query!(self, find_one_by_id, COL, id)?.ok_or_else(|| create_error!(NotFound))
|
||||||
|
}
|
||||||
|
}
|
||||||
18
crates/core/database/src/models/users/ops/reference.rs
Normal file
18
crates/core/database/src/models/users/ops/reference.rs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
use revolt_result::Result;
|
||||||
|
|
||||||
|
use crate::ReferenceDb;
|
||||||
|
use crate::User;
|
||||||
|
|
||||||
|
use super::AbstractUsers;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl AbstractUsers for ReferenceDb {
|
||||||
|
/// Fetch a user from the database
|
||||||
|
async fn fetch_user(&self, id: &str) -> Result<User> {
|
||||||
|
let users = self.users.lock().await;
|
||||||
|
users
|
||||||
|
.get(id)
|
||||||
|
.cloned()
|
||||||
|
.ok_or_else(|| create_error!(NotFound))
|
||||||
|
}
|
||||||
|
}
|
||||||
44
crates/core/database/src/models/users/rocket.rs
Normal file
44
crates/core/database/src/models/users/rocket.rs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
use authifier::models::Session;
|
||||||
|
use rocket::http::Status;
|
||||||
|
use rocket::request::{self, FromRequest, Outcome, Request};
|
||||||
|
|
||||||
|
use crate::{Database, User};
|
||||||
|
|
||||||
|
#[rocket::async_trait]
|
||||||
|
impl<'r> FromRequest<'r> for User {
|
||||||
|
type Error = authifier::Error;
|
||||||
|
|
||||||
|
async fn from_request(request: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
|
||||||
|
let user: &Option<User> = request
|
||||||
|
.local_cache_async(async {
|
||||||
|
let db = request.rocket().state::<Database>().expect("`Database`");
|
||||||
|
|
||||||
|
let _header_bot_token = request
|
||||||
|
.headers()
|
||||||
|
.get("x-bot-token")
|
||||||
|
.next()
|
||||||
|
.map(|x| x.to_string());
|
||||||
|
|
||||||
|
/* if let Some(bot_token) = header_bot_token {
|
||||||
|
if let Ok(user) = User::from_token(db, &bot_token, UserHint::Bot).await {
|
||||||
|
return Some(user);
|
||||||
|
}
|
||||||
|
} else */
|
||||||
|
if let Outcome::Success(session) = request.guard::<Session>().await {
|
||||||
|
// This uses a guard so can't really easily be refactored into from_token at this stage.
|
||||||
|
if let Ok(user) = db.fetch_user(&session.user_id).await {
|
||||||
|
return Some(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if let Some(user) = user {
|
||||||
|
Outcome::Success(user.clone())
|
||||||
|
} else {
|
||||||
|
Outcome::Failure((Status::Unauthorized, authifier::Error::InvalidSession))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
crates/core/database/src/util/mod.rs
Normal file
2
crates/core/database/src/util/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
pub mod permissions;
|
||||||
|
pub mod reference;
|
||||||
205
crates/core/database/src/util/permissions.rs
Normal file
205
crates/core/database/src/util/permissions.rs
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use revolt_permissions::{
|
||||||
|
calculate_user_permissions, ChannelType, Override, PermissionQuery, RelationshipStatus,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{Database, User};
|
||||||
|
|
||||||
|
/// Permissions calculator
|
||||||
|
pub struct PermissionCalculator<'a> {
|
||||||
|
database: &'a Database,
|
||||||
|
|
||||||
|
perspective: &'a User,
|
||||||
|
user: Option<Cow<'a, User>>,
|
||||||
|
// pub channel: Cow<'a, Channel>,
|
||||||
|
// pub server: Cow<'a, Server>,
|
||||||
|
// pub member: Cow<'a, Member>,
|
||||||
|
|
||||||
|
// flag_known_relationship: Option<&'a RelationshipStatus>,
|
||||||
|
cached_user_permission: Option<u32>,
|
||||||
|
cached_permission: Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl PermissionQuery for PermissionCalculator<'_> {
|
||||||
|
// * For calculating user permission
|
||||||
|
|
||||||
|
/// Is our perspective user privileged?
|
||||||
|
async fn are_we_privileged(&mut self) -> bool {
|
||||||
|
self.perspective.privileged
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Is our perspective user a bot?
|
||||||
|
async fn are_we_a_bot(&mut self) -> bool {
|
||||||
|
self.perspective.bot.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Is our perspective user and the currently selected user the same?
|
||||||
|
async fn are_the_users_same(&mut self) -> bool {
|
||||||
|
if let Some(other_user) = &self.user {
|
||||||
|
self.perspective.id == other_user.id
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the relationship with have with the currently selected user
|
||||||
|
async fn user_relationship(&mut self) -> RelationshipStatus {
|
||||||
|
if let Some(other_user) = &self.user {
|
||||||
|
if let Some(relations) = &self.perspective.relations {
|
||||||
|
for entry in relations {
|
||||||
|
if entry.id == other_user.id {
|
||||||
|
return match entry.status {
|
||||||
|
crate::RelationshipStatus::None => RelationshipStatus::None,
|
||||||
|
crate::RelationshipStatus::User => RelationshipStatus::User,
|
||||||
|
crate::RelationshipStatus::Friend => RelationshipStatus::Friend,
|
||||||
|
crate::RelationshipStatus::Outgoing => RelationshipStatus::Outgoing,
|
||||||
|
crate::RelationshipStatus::Incoming => RelationshipStatus::Incoming,
|
||||||
|
crate::RelationshipStatus::Blocked => RelationshipStatus::Blocked,
|
||||||
|
crate::RelationshipStatus::BlockedOther => {
|
||||||
|
RelationshipStatus::BlockedOther
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RelationshipStatus::None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the currently selected user is a bot
|
||||||
|
async fn user_is_bot(&mut self) -> bool {
|
||||||
|
if let Some(other_user) = &self.user {
|
||||||
|
other_user.bot.is_some()
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Do we have a mutual connection with the currently selected user?
|
||||||
|
async fn have_mutual_connection(&mut self) -> bool {
|
||||||
|
// TODO: User::has_mutual_connection
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
// * For calculating server permission
|
||||||
|
|
||||||
|
/// Is our perspective user the server's owner?
|
||||||
|
async fn are_we_server_owner(&mut self) -> bool {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Is our perspective user a member of the server?
|
||||||
|
async fn are_we_a_member(&mut self) -> bool {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get default server permission
|
||||||
|
async fn get_default_server_permissions(&mut self) -> u64 {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the ordered role overrides (from lowest to highest) for this member in this server
|
||||||
|
async fn get_our_server_role_overrides(&mut self) -> Vec<Override> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Is our perspective user timed out on this server?
|
||||||
|
async fn are_we_timed_out(&mut self) -> bool {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
// * For calculating channel permission
|
||||||
|
|
||||||
|
/// Get the type of the channel
|
||||||
|
async fn get_channel_type(&mut self) -> ChannelType {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the default channel permissions
|
||||||
|
/// Group channel defaults should be mapped to an allow-only override
|
||||||
|
async fn get_default_channel_permissions(&mut self) -> Override {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the ordered role overrides (from lowest to highest) for this member in this channel
|
||||||
|
async fn get_our_channel_role_overrides(&mut self) -> Vec<Override> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Do we own this group or saved messages channel if it is one of those?
|
||||||
|
async fn do_we_own_the_channel(&mut self) -> bool {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Are we a recipient of this channel?
|
||||||
|
async fn are_we_part_of_the_channel(&mut self) -> bool {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the current user as the recipient of this channel
|
||||||
|
/// (this will only ever be called for DirectMessage channels, use unimplemented!() for other code paths)
|
||||||
|
async fn set_recipient_as_user(&mut self) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the current server as the server owning this channel
|
||||||
|
/// (this will only ever be called for server channels, use unimplemented!() for other code paths)
|
||||||
|
async fn set_server_from_channel(&mut self) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> PermissionCalculator<'a> {
|
||||||
|
/// Create a new permission calculator
|
||||||
|
pub fn new(database: &'a Database, perspective: &'a User) -> PermissionCalculator<'a> {
|
||||||
|
PermissionCalculator {
|
||||||
|
database,
|
||||||
|
perspective,
|
||||||
|
user: None,
|
||||||
|
|
||||||
|
cached_user_permission: None,
|
||||||
|
cached_permission: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculate the user permission value
|
||||||
|
pub async fn calc_user(mut self) -> PermissionCalculator<'a> {
|
||||||
|
if self.cached_user_permission.is_some() {
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.user.is_none() {
|
||||||
|
panic!("Expected `PermissionCalculator.user to exist.");
|
||||||
|
}
|
||||||
|
|
||||||
|
PermissionCalculator {
|
||||||
|
cached_user_permission: Some(calculate_user_permissions(&mut self).await),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculate the permission value
|
||||||
|
pub async fn calc(self) -> PermissionCalculator<'a> {
|
||||||
|
if self.cached_permission.is_some() {
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Use user
|
||||||
|
pub fn user(self, user: Cow<'a, User>) -> PermissionCalculator {
|
||||||
|
PermissionCalculator {
|
||||||
|
user: Some(user),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Short-hand for creating a permission calculator
|
||||||
|
pub fn perms<'a>(database: &'a Database, perspective: &'a User) -> PermissionCalculator<'a> {
|
||||||
|
PermissionCalculator::new(database, perspective)
|
||||||
|
}
|
||||||
52
crates/core/database/src/util/reference.rs
Normal file
52
crates/core/database/src/util/reference.rs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
use revolt_result::Result;
|
||||||
|
#[cfg(feature = "rocket-impl")]
|
||||||
|
use rocket::request::FromParam;
|
||||||
|
#[cfg(feature = "rocket-impl")]
|
||||||
|
use schemars::{
|
||||||
|
schema::{InstanceType, Schema, SchemaObject, SingleOrVec},
|
||||||
|
JsonSchema,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{Bot, Database};
|
||||||
|
|
||||||
|
/// Reference to some object in the database
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct Reference {
|
||||||
|
/// Id of object
|
||||||
|
pub id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Reference {
|
||||||
|
/// Create a Ref from an unchecked string
|
||||||
|
pub fn from_unchecked(id: String) -> Reference {
|
||||||
|
Reference { id }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch bot from Ref
|
||||||
|
pub async fn as_bot(&self, db: &Database) -> Result<Bot> {
|
||||||
|
db.fetch_bot(&self.id).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "rocket-impl")]
|
||||||
|
impl<'r> FromParam<'r> for Reference {
|
||||||
|
type Error = &'r str;
|
||||||
|
|
||||||
|
fn from_param(param: &'r str) -> Result<Self, Self::Error> {
|
||||||
|
Ok(Reference::from_unchecked(param.into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "rocket-impl")]
|
||||||
|
impl JsonSchema for Reference {
|
||||||
|
fn schema_name() -> String {
|
||||||
|
"Id".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> Schema {
|
||||||
|
Schema::Object(SchemaObject {
|
||||||
|
instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
29
crates/core/models/Cargo.toml
Normal file
29
crates/core/models/Cargo.toml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
[package]
|
||||||
|
name = "revolt-models"
|
||||||
|
version = "0.0.2"
|
||||||
|
edition = "2021"
|
||||||
|
license = "AGPL-3.0-or-later"
|
||||||
|
authors = [ "Paul Makles <me@insrt.uk>" ]
|
||||||
|
description = "Revolt Backend: API Models"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[features]
|
||||||
|
serde = [ "dep:serde" ]
|
||||||
|
schemas = [ "dep:schemars" ]
|
||||||
|
from_database = [ "revolt-database", "revolt-presence" ]
|
||||||
|
|
||||||
|
redis-is-patched = [ "revolt-presence/redis-is-patched" ]
|
||||||
|
|
||||||
|
default = [ "serde", "from_database" ]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
# Repo
|
||||||
|
revolt-database = { version = "0.0.2", path = "../database", optional = true }
|
||||||
|
revolt-presence = { version = "0.0.2", path = "../presence", optional = true }
|
||||||
|
|
||||||
|
# Serialisation
|
||||||
|
serde = { version = "1", features = ["derive"], optional = true }
|
||||||
|
|
||||||
|
# Spec Generation
|
||||||
|
schemars = { version = "0.8.8", optional = true }
|
||||||
30
crates/core/models/src/lib.rs
Normal file
30
crates/core/models/src/lib.rs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#[cfg(feature = "serde")]
|
||||||
|
#[macro_use]
|
||||||
|
extern crate serde;
|
||||||
|
|
||||||
|
#[cfg(feature = "schemas")]
|
||||||
|
#[macro_use]
|
||||||
|
extern crate schemars;
|
||||||
|
|
||||||
|
macro_rules! auto_derived {
|
||||||
|
( $( $item:item )+ ) => {
|
||||||
|
$(
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
#[cfg_attr(feature = "schemas", derive(JsonSchema))]
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
|
$item
|
||||||
|
)+
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod v0;
|
||||||
|
|
||||||
|
/// Utility function to check if a boolean value is false
|
||||||
|
pub fn if_false(t: &bool) -> bool {
|
||||||
|
!t
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Utility function to check if an u32 is zero
|
||||||
|
pub fn if_zero_u32(t: &u32) -> bool {
|
||||||
|
t == &0
|
||||||
|
}
|
||||||
124
crates/core/models/src/v0/bots.rs
Normal file
124
crates/core/models/src/v0/bots.rs
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
use super::User;
|
||||||
|
|
||||||
|
auto_derived!(
|
||||||
|
/// Bot
|
||||||
|
pub struct Bot {
|
||||||
|
/// Bot Id
|
||||||
|
#[serde(rename = "_id")]
|
||||||
|
pub id: String,
|
||||||
|
|
||||||
|
/// User Id of the bot owner
|
||||||
|
#[serde(rename = "owner")]
|
||||||
|
pub owner_id: String,
|
||||||
|
/// Token used to authenticate requests for this bot
|
||||||
|
pub token: String,
|
||||||
|
/// Whether the bot is public
|
||||||
|
/// (may be invited by anyone)
|
||||||
|
pub public: bool,
|
||||||
|
|
||||||
|
/// Whether to enable analytics
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "serde",
|
||||||
|
serde(skip_serializing_if = "crate::if_false", default)
|
||||||
|
)]
|
||||||
|
pub analytics: bool,
|
||||||
|
/// Whether this bot should be publicly discoverable
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "serde",
|
||||||
|
serde(skip_serializing_if = "crate::if_false", default)
|
||||||
|
)]
|
||||||
|
pub discoverable: bool,
|
||||||
|
/// Reserved; URL for handling interactions
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "serde",
|
||||||
|
serde(skip_serializing_if = "String::is_empty", default)
|
||||||
|
)]
|
||||||
|
pub interactions_url: String,
|
||||||
|
/// URL for terms of service
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "serde",
|
||||||
|
serde(skip_serializing_if = "String::is_empty", default)
|
||||||
|
)]
|
||||||
|
pub terms_of_service_url: String,
|
||||||
|
/// URL for privacy policy
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "serde",
|
||||||
|
serde(skip_serializing_if = "String::is_empty", default)
|
||||||
|
)]
|
||||||
|
pub privacy_policy_url: String,
|
||||||
|
|
||||||
|
/// Enum of bot flags
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "serde",
|
||||||
|
serde(skip_serializing_if = "crate::if_zero_u32", default)
|
||||||
|
)]
|
||||||
|
pub flags: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Flags that may be attributed to a bot
|
||||||
|
#[repr(u32)]
|
||||||
|
pub enum BotFlags {
|
||||||
|
Verified = 1,
|
||||||
|
Official = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Public Bot
|
||||||
|
pub struct PublicBot {
|
||||||
|
/// Bot Id
|
||||||
|
#[serde(rename = "_id")]
|
||||||
|
id: String,
|
||||||
|
|
||||||
|
/// Bot Username
|
||||||
|
username: String,
|
||||||
|
/// Profile Avatar
|
||||||
|
#[serde(skip_serializing_if = "String::is_empty")]
|
||||||
|
avatar: String,
|
||||||
|
/// Profile Description
|
||||||
|
#[serde(skip_serializing_if = "String::is_empty")]
|
||||||
|
description: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bot Response
|
||||||
|
pub struct FetchBotResponse {
|
||||||
|
/// Bot object
|
||||||
|
pub bot: Bot,
|
||||||
|
/// User object
|
||||||
|
pub user: User,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(feature = "from_database")]
|
||||||
|
impl PublicBot {
|
||||||
|
pub fn from(bot: revolt_database::Bot, user: revolt_database::User) -> Self {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
assert_eq!(bot.id, user.id);
|
||||||
|
|
||||||
|
PublicBot {
|
||||||
|
id: bot.id,
|
||||||
|
username: user.username,
|
||||||
|
avatar: user.avatar.map(|x| x.id).unwrap_or_default(),
|
||||||
|
description: user
|
||||||
|
.profile
|
||||||
|
.map(|profile| profile.content)
|
||||||
|
.unwrap_or_default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "from_database")]
|
||||||
|
impl From<revolt_database::Bot> for Bot {
|
||||||
|
fn from(value: revolt_database::Bot) -> Self {
|
||||||
|
Bot {
|
||||||
|
id: value.id,
|
||||||
|
owner_id: value.owner,
|
||||||
|
token: value.token,
|
||||||
|
public: value.public,
|
||||||
|
analytics: value.analytics,
|
||||||
|
discoverable: value.discoverable,
|
||||||
|
interactions_url: value.interactions_url,
|
||||||
|
terms_of_service_url: value.terms_of_service_url,
|
||||||
|
privacy_policy_url: value.privacy_policy_url,
|
||||||
|
flags: value.flags.unwrap_or_default() as u32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
95
crates/core/models/src/v0/files.rs
Normal file
95
crates/core/models/src/v0/files.rs
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
auto_derived!(
|
||||||
|
/// File
|
||||||
|
pub struct File {
|
||||||
|
/// Unique Id
|
||||||
|
#[serde(rename = "_id")]
|
||||||
|
pub id: String,
|
||||||
|
/// Tag / bucket this file was uploaded to
|
||||||
|
pub tag: String,
|
||||||
|
/// Original filename
|
||||||
|
pub filename: String,
|
||||||
|
/// Parsed metadata of this file
|
||||||
|
pub metadata: Metadata,
|
||||||
|
/// Raw content type of this file
|
||||||
|
pub content_type: String,
|
||||||
|
/// Size of this file (in bytes)
|
||||||
|
pub size: isize,
|
||||||
|
|
||||||
|
/// Whether this file was deleted
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub deleted: Option<bool>,
|
||||||
|
/// Whether this file was reported
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub reported: Option<bool>,
|
||||||
|
|
||||||
|
// TODO: migrate this mess to having:
|
||||||
|
// - author_id
|
||||||
|
// - parent: Parent { Message(id), User(id), etc }
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub message_id: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub user_id: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub server_id: Option<String>,
|
||||||
|
|
||||||
|
/// Id of the object this file is associated with
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub object_id: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Metadata associated with a file
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
#[derive(Default)]
|
||||||
|
pub enum Metadata {
|
||||||
|
/// File is just a generic uncategorised file
|
||||||
|
#[default]
|
||||||
|
File,
|
||||||
|
/// File contains textual data and should be displayed as such
|
||||||
|
Text,
|
||||||
|
/// File is an image with specific dimensions
|
||||||
|
Image { width: usize, height: usize },
|
||||||
|
/// File is a video with specific dimensions
|
||||||
|
Video { width: usize, height: usize },
|
||||||
|
/// File is audio
|
||||||
|
Audio,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(feature = "from_database")]
|
||||||
|
impl From<revolt_database::File> for File {
|
||||||
|
fn from(value: revolt_database::File) -> Self {
|
||||||
|
File {
|
||||||
|
id: value.id,
|
||||||
|
tag: value.tag,
|
||||||
|
filename: value.filename,
|
||||||
|
metadata: value.metadata.into(),
|
||||||
|
content_type: value.content_type,
|
||||||
|
size: value.size,
|
||||||
|
deleted: value.deleted,
|
||||||
|
reported: value.reported,
|
||||||
|
message_id: value.message_id,
|
||||||
|
user_id: value.user_id,
|
||||||
|
server_id: value.server_id,
|
||||||
|
object_id: value.object_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "from_database")]
|
||||||
|
impl From<revolt_database::Metadata> for Metadata {
|
||||||
|
fn from(value: revolt_database::Metadata) -> Self {
|
||||||
|
match value {
|
||||||
|
revolt_database::Metadata::File => Metadata::File,
|
||||||
|
revolt_database::Metadata::Text => Metadata::Text,
|
||||||
|
revolt_database::Metadata::Image { width, height } => Metadata::Image {
|
||||||
|
width: width as usize,
|
||||||
|
height: height as usize,
|
||||||
|
},
|
||||||
|
revolt_database::Metadata::Video { width, height } => Metadata::Video {
|
||||||
|
width: width as usize,
|
||||||
|
height: height as usize,
|
||||||
|
},
|
||||||
|
revolt_database::Metadata::Audio => Metadata::Audio,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
crates/core/models/src/v0/mod.rs
Normal file
7
crates/core/models/src/v0/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
mod bots;
|
||||||
|
mod files;
|
||||||
|
mod users;
|
||||||
|
|
||||||
|
pub use bots::*;
|
||||||
|
pub use files::*;
|
||||||
|
pub use users::*;
|
||||||
266
crates/core/models/src/v0/users.rs
Normal file
266
crates/core/models/src/v0/users.rs
Normal file
@@ -0,0 +1,266 @@
|
|||||||
|
use super::File;
|
||||||
|
|
||||||
|
auto_derived!(
|
||||||
|
/// User
|
||||||
|
pub struct User {
|
||||||
|
/// Unique Id
|
||||||
|
#[serde(rename = "_id")]
|
||||||
|
pub id: String,
|
||||||
|
/// Username
|
||||||
|
pub username: String,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
/// Avatar attachment
|
||||||
|
pub avatar: Option<File>,
|
||||||
|
/// Relationships with other users
|
||||||
|
#[serde(skip_serializing_if = "Vec::is_empty", default)]
|
||||||
|
pub relations: Vec<Relationship>,
|
||||||
|
|
||||||
|
/// Bitfield of user badges
|
||||||
|
#[serde(skip_serializing_if = "crate::if_zero_u32", default)]
|
||||||
|
pub badges: u32,
|
||||||
|
/// User's current status
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub status: Option<UserStatus>,
|
||||||
|
/// User's profile page
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub profile: Option<UserProfile>,
|
||||||
|
|
||||||
|
/// Enum of user flags
|
||||||
|
#[serde(skip_serializing_if = "crate::if_zero_u32", default)]
|
||||||
|
pub flags: u32,
|
||||||
|
/// Whether this user is privileged
|
||||||
|
#[serde(skip_serializing_if = "crate::if_false", default)]
|
||||||
|
pub privileged: bool,
|
||||||
|
/// Bot information
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub bot: Option<BotInformation>,
|
||||||
|
|
||||||
|
/// Current session user's relationship with this user
|
||||||
|
pub relationship: RelationshipStatus,
|
||||||
|
/// Whether this user is currently online
|
||||||
|
pub online: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// User's relationship with another user (or themselves)
|
||||||
|
#[derive(Default)]
|
||||||
|
pub enum RelationshipStatus {
|
||||||
|
#[default]
|
||||||
|
None,
|
||||||
|
User,
|
||||||
|
Friend,
|
||||||
|
Outgoing,
|
||||||
|
Incoming,
|
||||||
|
Blocked,
|
||||||
|
BlockedOther,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Relationship entry indicating current status with other user
|
||||||
|
pub struct Relationship {
|
||||||
|
#[serde(rename = "_id")]
|
||||||
|
pub user_id: String,
|
||||||
|
pub status: RelationshipStatus,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Presence status
|
||||||
|
pub enum Presence {
|
||||||
|
/// User is online
|
||||||
|
Online,
|
||||||
|
/// User is not currently available
|
||||||
|
Idle,
|
||||||
|
/// User is focusing / will only receive mentions
|
||||||
|
Focus,
|
||||||
|
/// User is busy / will not receive any notifications
|
||||||
|
Busy,
|
||||||
|
/// User appears to be offline
|
||||||
|
Invisible,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// User's active status
|
||||||
|
pub struct UserStatus {
|
||||||
|
/// Custom status text
|
||||||
|
#[serde(skip_serializing_if = "String::is_empty")]
|
||||||
|
pub text: String,
|
||||||
|
/// Current presence option
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub presence: Option<Presence>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// User's profile
|
||||||
|
pub struct UserProfile {
|
||||||
|
/// Text content on user's profile
|
||||||
|
#[serde(skip_serializing_if = "String::is_empty")]
|
||||||
|
pub content: String,
|
||||||
|
/// Background visible on user's profile
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub background: Option<File>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// User badge bitfield
|
||||||
|
#[repr(u32)]
|
||||||
|
pub enum UserBadges {
|
||||||
|
/// Revolt Developer
|
||||||
|
Developer = 1,
|
||||||
|
/// Helped translate Revolt
|
||||||
|
Translator = 2,
|
||||||
|
/// Monetarily supported Revolt
|
||||||
|
Supporter = 4,
|
||||||
|
/// Responsibly disclosed a security issue
|
||||||
|
ResponsibleDisclosure = 8,
|
||||||
|
/// Revolt Founder
|
||||||
|
Founder = 16,
|
||||||
|
/// Platform moderator
|
||||||
|
PlatformModeration = 32,
|
||||||
|
/// Active monetary supporter
|
||||||
|
ActiveSupporter = 64,
|
||||||
|
/// 🦊🦝
|
||||||
|
Paw = 128,
|
||||||
|
/// Joined as one of the first 1000 users in 2021
|
||||||
|
EarlyAdopter = 256,
|
||||||
|
/// Amogus
|
||||||
|
ReservedRelevantJokeBadge1 = 512,
|
||||||
|
/// Low resolution troll face
|
||||||
|
ReservedRelevantJokeBadge2 = 1024,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// User flag enum
|
||||||
|
#[repr(u32)]
|
||||||
|
pub enum UserFlags {
|
||||||
|
/// User has been suspended from the platform
|
||||||
|
Suspended = 1,
|
||||||
|
/// User has deleted their account
|
||||||
|
Deleted = 2,
|
||||||
|
/// User was banned off the platform
|
||||||
|
Banned = 4,
|
||||||
|
/// User was marked as spam and removed from platform
|
||||||
|
Spam = 8,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bot information for if the user is a bot
|
||||||
|
pub struct BotInformation {
|
||||||
|
/// Id of the owner of this bot
|
||||||
|
#[serde(rename = "owner")]
|
||||||
|
pub owner_id: String,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
pub trait CheckRelationship {
|
||||||
|
fn with(&self, user: &str) -> RelationshipStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CheckRelationship for Vec<Relationship> {
|
||||||
|
fn with(&self, user: &str) -> RelationshipStatus {
|
||||||
|
for entry in self {
|
||||||
|
if entry.user_id == user {
|
||||||
|
return entry.status.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RelationshipStatus::None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "from_database")]
|
||||||
|
impl User {
|
||||||
|
pub async fn from<P>(user: revolt_database::User, perspective: P) -> Self
|
||||||
|
where
|
||||||
|
P: Into<Option<revolt_database::User>>,
|
||||||
|
{
|
||||||
|
let relationship = if let Some(perspective) = perspective.into() {
|
||||||
|
perspective
|
||||||
|
.relations
|
||||||
|
.unwrap_or_default()
|
||||||
|
.into_iter()
|
||||||
|
.find(|relationship| relationship.id == user.id)
|
||||||
|
.map(|relationship| relationship.status.into())
|
||||||
|
.unwrap_or_default()
|
||||||
|
} else {
|
||||||
|
RelationshipStatus::None
|
||||||
|
};
|
||||||
|
|
||||||
|
// do permission stuff here
|
||||||
|
// TODO: implement permissions =)
|
||||||
|
let can_see_profile = false;
|
||||||
|
|
||||||
|
Self {
|
||||||
|
username: user.username,
|
||||||
|
avatar: user.avatar.map(|file| file.into()),
|
||||||
|
relations: vec![],
|
||||||
|
badges: user.badges.unwrap_or_default() as u32,
|
||||||
|
status: None,
|
||||||
|
profile: None,
|
||||||
|
flags: user.flags.unwrap_or_default() as u32,
|
||||||
|
privileged: user.privileged,
|
||||||
|
bot: user.bot.map(|bot| bot.into()),
|
||||||
|
relationship,
|
||||||
|
online: can_see_profile && revolt_presence::is_online(&user.id).await,
|
||||||
|
id: user.id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "from_database")]
|
||||||
|
impl From<revolt_database::RelationshipStatus> for RelationshipStatus {
|
||||||
|
fn from(value: revolt_database::RelationshipStatus) -> Self {
|
||||||
|
match value {
|
||||||
|
revolt_database::RelationshipStatus::None => RelationshipStatus::None,
|
||||||
|
revolt_database::RelationshipStatus::User => RelationshipStatus::User,
|
||||||
|
revolt_database::RelationshipStatus::Friend => RelationshipStatus::Friend,
|
||||||
|
revolt_database::RelationshipStatus::Outgoing => RelationshipStatus::Outgoing,
|
||||||
|
revolt_database::RelationshipStatus::Incoming => RelationshipStatus::Incoming,
|
||||||
|
revolt_database::RelationshipStatus::Blocked => RelationshipStatus::Blocked,
|
||||||
|
revolt_database::RelationshipStatus::BlockedOther => RelationshipStatus::BlockedOther,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "from_database")]
|
||||||
|
impl From<revolt_database::Relationship> for Relationship {
|
||||||
|
fn from(value: revolt_database::Relationship) -> Self {
|
||||||
|
Self {
|
||||||
|
user_id: value.id,
|
||||||
|
status: value.status.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "from_database")]
|
||||||
|
impl From<revolt_database::Presence> for Presence {
|
||||||
|
fn from(value: revolt_database::Presence) -> Self {
|
||||||
|
match value {
|
||||||
|
revolt_database::Presence::Online => Presence::Online,
|
||||||
|
revolt_database::Presence::Idle => Presence::Idle,
|
||||||
|
revolt_database::Presence::Focus => Presence::Focus,
|
||||||
|
revolt_database::Presence::Busy => Presence::Busy,
|
||||||
|
revolt_database::Presence::Invisible => Presence::Invisible,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "from_database")]
|
||||||
|
impl From<revolt_database::UserStatus> for UserStatus {
|
||||||
|
fn from(value: revolt_database::UserStatus) -> Self {
|
||||||
|
UserStatus {
|
||||||
|
text: value.text,
|
||||||
|
presence: value.presence.map(|presence| presence.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "from_database")]
|
||||||
|
impl From<revolt_database::UserProfile> for UserProfile {
|
||||||
|
fn from(value: revolt_database::UserProfile) -> Self {
|
||||||
|
UserProfile {
|
||||||
|
content: value.content,
|
||||||
|
background: value.background.map(|file| file.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "from_database")]
|
||||||
|
impl From<revolt_database::BotInformation> for BotInformation {
|
||||||
|
fn from(value: revolt_database::BotInformation) -> Self {
|
||||||
|
BotInformation {
|
||||||
|
owner_id: value.owner,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
33
crates/core/permissions/Cargo.toml
Normal file
33
crates/core/permissions/Cargo.toml
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
[package]
|
||||||
|
name = "revolt-permissions"
|
||||||
|
version = "0.0.2"
|
||||||
|
edition = "2021"
|
||||||
|
license = "AGPL-3.0-or-later"
|
||||||
|
authors = [ "Paul Makles <me@insrt.uk>" ]
|
||||||
|
description = "Revolt Backend: Permission Logic"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[features]
|
||||||
|
serde = [ "dep:serde" ]
|
||||||
|
schemas = [ "dep:schemars" ]
|
||||||
|
try-from-primitive = [ "dep:num_enum" ]
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
# Async
|
||||||
|
async-std = { version = "1.8.0", features = ["attributes"] }
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
# Utility
|
||||||
|
auto_ops = "0.3.0"
|
||||||
|
once_cell = "1.17"
|
||||||
|
num_enum = { version = "0.6.1", optional = true }
|
||||||
|
|
||||||
|
# Async
|
||||||
|
async-trait = "0.1.51"
|
||||||
|
|
||||||
|
# Serialisation
|
||||||
|
serde = { version = "1", features = ["derive"], optional = true }
|
||||||
|
|
||||||
|
# Spec Generation
|
||||||
|
schemars = { version = "0.8.8", optional = true }
|
||||||
128
crates/core/permissions/src/impl.rs
Normal file
128
crates/core/permissions/src/impl.rs
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
use crate::{
|
||||||
|
ChannelPermission, ChannelType, PermissionQuery, PermissionValue, RelationshipStatus,
|
||||||
|
UserPermission, ALLOW_IN_TIMEOUT, DEFAULT_PERMISSION_DIRECT_MESSAGE,
|
||||||
|
DEFAULT_PERMISSION_SAVED_MESSAGES, DEFAULT_PERMISSION_VIEW_ONLY,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Calculate permissions against a user
|
||||||
|
pub async fn calculate_user_permissions<P: PermissionQuery>(query: &mut P) -> u32 {
|
||||||
|
if query.are_we_privileged().await {
|
||||||
|
return u32::MAX;
|
||||||
|
}
|
||||||
|
|
||||||
|
if query.are_the_users_same().await {
|
||||||
|
return u32::MAX;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut permissions = 0_u32;
|
||||||
|
match query.user_relationship().await {
|
||||||
|
RelationshipStatus::Friend => return u32::MAX,
|
||||||
|
RelationshipStatus::Blocked | RelationshipStatus::BlockedOther => {
|
||||||
|
return UserPermission::Access as u32
|
||||||
|
}
|
||||||
|
RelationshipStatus::Incoming | RelationshipStatus::Outgoing => {
|
||||||
|
permissions = UserPermission::Access as u32;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if query.have_mutual_connection().await {
|
||||||
|
permissions = UserPermission::Access + UserPermission::ViewProfile;
|
||||||
|
|
||||||
|
if query.user_is_bot().await || query.are_we_a_bot().await {
|
||||||
|
permissions += UserPermission::SendMessage as u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
permissions
|
||||||
|
} else {
|
||||||
|
permissions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculate permissions against a server
|
||||||
|
pub async fn calculate_server_permissions<P: PermissionQuery>(query: &mut P) -> PermissionValue {
|
||||||
|
if query.are_we_privileged().await || query.are_we_server_owner().await {
|
||||||
|
return ChannelPermission::GrantAllSafe.into();
|
||||||
|
}
|
||||||
|
|
||||||
|
if !query.are_we_a_member().await {
|
||||||
|
return 0_u64.into();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut permissions: PermissionValue = query.get_default_server_permissions().await.into();
|
||||||
|
|
||||||
|
for role_override in query.get_our_server_role_overrides().await {
|
||||||
|
permissions.apply(role_override);
|
||||||
|
}
|
||||||
|
|
||||||
|
if query.are_we_timed_out().await {
|
||||||
|
permissions.restrict(*ALLOW_IN_TIMEOUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
permissions
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculate permissions against a channel
|
||||||
|
pub async fn calculate_channel_permissions<P: PermissionQuery>(query: &mut P) -> PermissionValue {
|
||||||
|
if query.are_we_privileged().await {
|
||||||
|
return ChannelPermission::GrantAllSafe.into();
|
||||||
|
}
|
||||||
|
|
||||||
|
match query.get_channel_type().await {
|
||||||
|
ChannelType::SavedMessages => {
|
||||||
|
if query.do_we_own_the_channel().await {
|
||||||
|
DEFAULT_PERMISSION_SAVED_MESSAGES.into()
|
||||||
|
} else {
|
||||||
|
0_u64.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ChannelType::DirectMessage => {
|
||||||
|
if query.are_we_part_of_the_channel().await {
|
||||||
|
query.set_recipient_as_user().await;
|
||||||
|
|
||||||
|
let permissions = calculate_user_permissions(query).await;
|
||||||
|
if (permissions & UserPermission::SendMessage as u32)
|
||||||
|
== UserPermission::SendMessage as u32
|
||||||
|
{
|
||||||
|
(*DEFAULT_PERMISSION_DIRECT_MESSAGE).into()
|
||||||
|
} else {
|
||||||
|
(*DEFAULT_PERMISSION_VIEW_ONLY).into()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
0_u64.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ChannelType::Group => {
|
||||||
|
if query.do_we_own_the_channel().await {
|
||||||
|
ChannelPermission::GrantAllSafe.into()
|
||||||
|
} else if query.are_we_part_of_the_channel().await {
|
||||||
|
(*DEFAULT_PERMISSION_VIEW_ONLY
|
||||||
|
| query.get_default_channel_permissions().await.allow)
|
||||||
|
.into()
|
||||||
|
} else {
|
||||||
|
0_u64.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ChannelType::ServerChannel => {
|
||||||
|
query.set_server_from_channel().await;
|
||||||
|
|
||||||
|
if query.are_we_a_member().await {
|
||||||
|
let mut permissions = calculate_server_permissions(query).await;
|
||||||
|
permissions.apply(query.get_default_channel_permissions().await);
|
||||||
|
|
||||||
|
for role_override in query.get_our_channel_role_overrides().await {
|
||||||
|
permissions.apply(role_override);
|
||||||
|
}
|
||||||
|
|
||||||
|
if query.are_we_timed_out().await {
|
||||||
|
permissions.restrict(*ALLOW_IN_TIMEOUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
permissions
|
||||||
|
} else {
|
||||||
|
0_u64.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ChannelType::Unknown => 0_u64.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
16
crates/core/permissions/src/lib.rs
Normal file
16
crates/core/permissions/src/lib.rs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#[macro_use]
|
||||||
|
extern crate auto_ops;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate async_trait;
|
||||||
|
|
||||||
|
mod r#impl;
|
||||||
|
mod models;
|
||||||
|
mod r#trait;
|
||||||
|
|
||||||
|
pub use models::*;
|
||||||
|
pub use r#impl::*;
|
||||||
|
pub use r#trait::*;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test;
|
||||||
137
crates/core/permissions/src/models/channel.rs
Normal file
137
crates/core/permissions/src/models/channel.rs
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use std::ops::Add;
|
||||||
|
|
||||||
|
/// Abstract channel type
|
||||||
|
pub enum ChannelType {
|
||||||
|
SavedMessages,
|
||||||
|
DirectMessage,
|
||||||
|
Group,
|
||||||
|
ServerChannel,
|
||||||
|
Unknown,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Permission value on Revolt
|
||||||
|
///
|
||||||
|
/// This should be restricted to the lower 52 bits to prevent any
|
||||||
|
/// potential issues with Javascript. Also leave empty spaces for
|
||||||
|
/// future permission flags to be added.
|
||||||
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
#[cfg_attr(feature = "try-from-primitive", derive(num_enum::TryFromPrimitive))]
|
||||||
|
#[repr(u64)]
|
||||||
|
pub enum ChannelPermission {
|
||||||
|
// * Generic permissions
|
||||||
|
/// Manage the channel or channels on the server
|
||||||
|
ManageChannel = 1 << 0,
|
||||||
|
/// Manage the server
|
||||||
|
ManageServer = 1 << 1,
|
||||||
|
/// Manage permissions on servers or channels
|
||||||
|
ManagePermissions = 1 << 2,
|
||||||
|
/// Manage roles on server
|
||||||
|
ManageRole = 1 << 3,
|
||||||
|
/// Manage server customisation (includes emoji)
|
||||||
|
ManageCustomisation = 1 << 4,
|
||||||
|
|
||||||
|
// % 1 bit reserved
|
||||||
|
|
||||||
|
// * Member permissions
|
||||||
|
/// Kick other members below their ranking
|
||||||
|
KickMembers = 1 << 6,
|
||||||
|
/// Ban other members below their ranking
|
||||||
|
BanMembers = 1 << 7,
|
||||||
|
/// Timeout other members below their ranking
|
||||||
|
TimeoutMembers = 1 << 8,
|
||||||
|
/// Assign roles to members below their ranking
|
||||||
|
AssignRoles = 1 << 9,
|
||||||
|
/// Change own nickname
|
||||||
|
ChangeNickname = 1 << 10,
|
||||||
|
/// Change or remove other's nicknames below their ranking
|
||||||
|
ManageNicknames = 1 << 11,
|
||||||
|
/// Change own avatar
|
||||||
|
ChangeAvatar = 1 << 12,
|
||||||
|
/// Remove other's avatars below their ranking
|
||||||
|
RemoveAvatars = 1 << 13,
|
||||||
|
|
||||||
|
// % 7 bits reserved
|
||||||
|
|
||||||
|
// * Channel permissions
|
||||||
|
/// View a channel
|
||||||
|
ViewChannel = 1 << 20,
|
||||||
|
/// Read a channel's past message history
|
||||||
|
ReadMessageHistory = 1 << 21,
|
||||||
|
/// Send a message in a channel
|
||||||
|
SendMessage = 1 << 22,
|
||||||
|
/// Delete messages in a channel
|
||||||
|
ManageMessages = 1 << 23,
|
||||||
|
/// Manage webhook entries on a channel
|
||||||
|
ManageWebhooks = 1 << 24,
|
||||||
|
/// Create invites to this channel
|
||||||
|
InviteOthers = 1 << 25,
|
||||||
|
/// Send embedded content in this channel
|
||||||
|
SendEmbeds = 1 << 26,
|
||||||
|
/// Send attachments and media in this channel
|
||||||
|
UploadFiles = 1 << 27,
|
||||||
|
/// Masquerade messages using custom nickname and avatar
|
||||||
|
Masquerade = 1 << 28,
|
||||||
|
/// React to messages with emojis
|
||||||
|
React = 1 << 29,
|
||||||
|
|
||||||
|
// * Voice permissions
|
||||||
|
/// Connect to a voice channel
|
||||||
|
Connect = 1 << 30,
|
||||||
|
/// Speak in a voice call
|
||||||
|
Speak = 1 << 31,
|
||||||
|
/// Share video in a voice call
|
||||||
|
Video = 1 << 32,
|
||||||
|
/// Mute other members with lower ranking in a voice call
|
||||||
|
MuteMembers = 1 << 33,
|
||||||
|
/// Deafen other members with lower ranking in a voice call
|
||||||
|
DeafenMembers = 1 << 34,
|
||||||
|
/// Move members between voice channels
|
||||||
|
MoveMembers = 1 << 35,
|
||||||
|
|
||||||
|
// * Misc. permissions
|
||||||
|
// % Bits 36 to 52: free area
|
||||||
|
// % Bits 53 to 64: do not use
|
||||||
|
|
||||||
|
// * Grant all permissions
|
||||||
|
/// Safely grant all permissions
|
||||||
|
GrantAllSafe = 0x000F_FFFF_FFFF_FFFF,
|
||||||
|
|
||||||
|
/// Grant all permissions
|
||||||
|
GrantAll = u64::MAX,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_op_ex!(+ |a: &ChannelPermission, b: &ChannelPermission| -> u64 { *a as u64 | *b as u64 });
|
||||||
|
impl_op_ex_commutative!(+ |a: &u64, b: &ChannelPermission| -> u64 { *a | *b as u64 });
|
||||||
|
|
||||||
|
pub static ALLOW_IN_TIMEOUT: Lazy<u64> =
|
||||||
|
Lazy::new(|| ChannelPermission::ViewChannel + ChannelPermission::ReadMessageHistory);
|
||||||
|
|
||||||
|
pub static DEFAULT_PERMISSION_VIEW_ONLY: Lazy<u64> =
|
||||||
|
Lazy::new(|| ChannelPermission::ViewChannel + ChannelPermission::ReadMessageHistory);
|
||||||
|
|
||||||
|
pub static DEFAULT_PERMISSION: Lazy<u64> = Lazy::new(|| {
|
||||||
|
DEFAULT_PERMISSION_VIEW_ONLY.add(
|
||||||
|
ChannelPermission::SendMessage
|
||||||
|
+ ChannelPermission::InviteOthers
|
||||||
|
+ ChannelPermission::SendEmbeds
|
||||||
|
+ ChannelPermission::UploadFiles
|
||||||
|
+ ChannelPermission::Connect
|
||||||
|
+ ChannelPermission::Speak,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
pub static DEFAULT_PERMISSION_SAVED_MESSAGES: u64 = ChannelPermission::GrantAllSafe as u64;
|
||||||
|
|
||||||
|
pub static DEFAULT_PERMISSION_DIRECT_MESSAGE: Lazy<u64> = Lazy::new(|| {
|
||||||
|
DEFAULT_PERMISSION.add(ChannelPermission::ManageChannel + ChannelPermission::React)
|
||||||
|
});
|
||||||
|
|
||||||
|
pub static DEFAULT_PERMISSION_SERVER: Lazy<u64> = Lazy::new(|| {
|
||||||
|
DEFAULT_PERMISSION.add(
|
||||||
|
ChannelPermission::React
|
||||||
|
+ ChannelPermission::ChangeNickname
|
||||||
|
+ ChannelPermission::ChangeAvatar,
|
||||||
|
)
|
||||||
|
});
|
||||||
58
crates/core/permissions/src/models/mod.rs
Normal file
58
crates/core/permissions/src/models/mod.rs
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
mod channel;
|
||||||
|
mod server;
|
||||||
|
mod user;
|
||||||
|
|
||||||
|
pub use channel::*;
|
||||||
|
pub use server::*;
|
||||||
|
pub use user::*;
|
||||||
|
|
||||||
|
/// Holds a permission value to manipulate.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct PermissionValue(u64);
|
||||||
|
|
||||||
|
impl PermissionValue {
|
||||||
|
/// Apply a given override to this value
|
||||||
|
pub fn apply(&mut self, v: Override) {
|
||||||
|
self.allow(v.allow);
|
||||||
|
self.revoke(v.deny);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allow given permissions
|
||||||
|
pub fn allow(&mut self, v: u64) {
|
||||||
|
self.0 |= v;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Revoke given permissions
|
||||||
|
pub fn revoke(&mut self, v: u64) {
|
||||||
|
self.0 &= !v;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Restrict to given permissions
|
||||||
|
pub fn restrict(&mut self, v: u64) {
|
||||||
|
self.0 &= v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<i64> for PermissionValue {
|
||||||
|
fn from(v: i64) -> Self {
|
||||||
|
Self(v as u64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u64> for PermissionValue {
|
||||||
|
fn from(v: u64) -> Self {
|
||||||
|
Self(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PermissionValue> for u64 {
|
||||||
|
fn from(v: PermissionValue) -> Self {
|
||||||
|
v.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ChannelPermission> for PermissionValue {
|
||||||
|
fn from(v: ChannelPermission) -> Self {
|
||||||
|
(v as u64).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
56
crates/core/permissions/src/models/server.rs
Normal file
56
crates/core/permissions/src/models/server.rs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
/// Representation of a single permission override
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
pub struct Override {
|
||||||
|
/// Allow bit flags
|
||||||
|
pub allow: u64,
|
||||||
|
/// Disallow bit flags
|
||||||
|
pub deny: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Representation of a single permission override
|
||||||
|
/// as it appears on models and in the database
|
||||||
|
#[derive(/*JsonSchema, */ Debug, Clone, Copy, Default)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
pub struct OverrideField {
|
||||||
|
/// Allow bit flags
|
||||||
|
a: i64,
|
||||||
|
/// Disallow bit flags
|
||||||
|
d: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Override {
|
||||||
|
/// Into allows
|
||||||
|
pub fn allows(&self) -> u64 {
|
||||||
|
self.allow
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Into denies
|
||||||
|
pub fn denies(&self) -> u64 {
|
||||||
|
self.deny
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Override> for OverrideField {
|
||||||
|
fn from(v: Override) -> Self {
|
||||||
|
Self {
|
||||||
|
a: v.allow as i64,
|
||||||
|
d: v.deny as i64,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<OverrideField> for Override {
|
||||||
|
fn from(v: OverrideField) -> Self {
|
||||||
|
Self {
|
||||||
|
allow: v.a as u64,
|
||||||
|
deny: v.d as u64,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*impl From<OverrideField> for Bson {
|
||||||
|
fn from(v: OverrideField) -> Self {
|
||||||
|
Self::Document(bson::to_document(&v).unwrap())
|
||||||
|
}
|
||||||
|
}*/
|
||||||
25
crates/core/permissions/src/models/user.rs
Normal file
25
crates/core/permissions/src/models/user.rs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/// User's relationship with another user (or themselves)
|
||||||
|
pub enum RelationshipStatus {
|
||||||
|
None,
|
||||||
|
User,
|
||||||
|
Friend,
|
||||||
|
Outgoing,
|
||||||
|
Incoming,
|
||||||
|
Blocked,
|
||||||
|
BlockedOther,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// User permission definitions
|
||||||
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
#[cfg_attr(feature = "try-from-primitive", derive(num_enum::TryFromPrimitive))]
|
||||||
|
#[repr(u32)]
|
||||||
|
pub enum UserPermission {
|
||||||
|
Access = 1 << 0,
|
||||||
|
ViewProfile = 1 << 1,
|
||||||
|
SendMessage = 1 << 2,
|
||||||
|
Invite = 1 << 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_op_ex!(+ |a: &UserPermission, b: &UserPermission| -> u32 { *a as u32 | *b as u32 });
|
||||||
|
impl_op_ex_commutative!(+ |a: &u32, b: &UserPermission| -> u32 { *a | *b as u32 });
|
||||||
377
crates/core/permissions/src/test.rs
Normal file
377
crates/core/permissions/src/test.rs
Normal file
@@ -0,0 +1,377 @@
|
|||||||
|
use crate::{
|
||||||
|
calculate_channel_permissions, calculate_user_permissions, ChannelPermission, ChannelType,
|
||||||
|
Override, PermissionQuery, RelationshipStatus, DEFAULT_PERMISSION_DIRECT_MESSAGE,
|
||||||
|
DEFAULT_PERMISSION_SERVER, DEFAULT_PERMISSION_VIEW_ONLY,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn validate_user_permissions() {
|
||||||
|
/// Scenario in which we are friends with a user
|
||||||
|
/// and we have a DM channel open with them
|
||||||
|
struct Scenario {}
|
||||||
|
let mut query = Scenario {};
|
||||||
|
|
||||||
|
let perms = calculate_user_permissions(&mut query).await;
|
||||||
|
assert_eq!(perms, u32::MAX);
|
||||||
|
|
||||||
|
let perms = calculate_channel_permissions(&mut query).await;
|
||||||
|
let value: u64 = perms.into();
|
||||||
|
assert_eq!(value, *DEFAULT_PERMISSION_DIRECT_MESSAGE);
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl PermissionQuery for Scenario {
|
||||||
|
async fn are_we_privileged(&mut self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn are_we_a_bot(&mut self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn are_the_users_same(&mut self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn user_relationship(&mut self) -> RelationshipStatus {
|
||||||
|
RelationshipStatus::Friend
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn user_is_bot(&mut self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn have_mutual_connection(&mut self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn are_we_server_owner(&mut self) -> bool {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn are_we_a_member(&mut self) -> bool {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_default_server_permissions(&mut self) -> u64 {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_our_server_role_overrides(&mut self) -> Vec<Override> {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn are_we_timed_out(&mut self) -> bool {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_channel_type(&mut self) -> ChannelType {
|
||||||
|
ChannelType::DirectMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_default_channel_permissions(&mut self) -> Override {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_our_channel_role_overrides(&mut self) -> Vec<Override> {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn do_we_own_the_channel(&mut self) -> bool {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn are_we_part_of_the_channel(&mut self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn set_recipient_as_user(&mut self) {
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn set_server_from_channel(&mut self) {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn validate_group_permissions() {
|
||||||
|
/// Scenario in which we are in a group channel with only talking permission
|
||||||
|
struct Scenario {}
|
||||||
|
let mut query = Scenario {};
|
||||||
|
|
||||||
|
let perms = calculate_channel_permissions(&mut query).await;
|
||||||
|
let value: u64 = perms.into();
|
||||||
|
assert_eq!(
|
||||||
|
value,
|
||||||
|
*DEFAULT_PERMISSION_VIEW_ONLY | ChannelPermission::SendMessage as u64
|
||||||
|
);
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl PermissionQuery for Scenario {
|
||||||
|
async fn are_we_privileged(&mut self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn are_we_a_bot(&mut self) -> bool {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn are_the_users_same(&mut self) -> bool {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn user_relationship(&mut self) -> RelationshipStatus {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn user_is_bot(&mut self) -> bool {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn have_mutual_connection(&mut self) -> bool {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn are_we_server_owner(&mut self) -> bool {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn are_we_a_member(&mut self) -> bool {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_default_server_permissions(&mut self) -> u64 {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_our_server_role_overrides(&mut self) -> Vec<Override> {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn are_we_timed_out(&mut self) -> bool {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_channel_type(&mut self) -> ChannelType {
|
||||||
|
ChannelType::Group
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_default_channel_permissions(&mut self) -> Override {
|
||||||
|
Override {
|
||||||
|
allow: ChannelPermission::SendMessage as u64,
|
||||||
|
deny: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_our_channel_role_overrides(&mut self) -> Vec<Override> {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn do_we_own_the_channel(&mut self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn are_we_part_of_the_channel(&mut self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn set_recipient_as_user(&mut self) {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn set_server_from_channel(&mut self) {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn validate_server_permissions() {
|
||||||
|
/// Scenario in which we are in a server channel where:
|
||||||
|
/// - the server grants reading history and sending messages by default
|
||||||
|
/// - we have a role that allows us to upload files and react but denies reading history
|
||||||
|
/// - however the channel disallows sending messages
|
||||||
|
/// - and removes our role specific react permission
|
||||||
|
struct Scenario {}
|
||||||
|
let mut query = Scenario {};
|
||||||
|
|
||||||
|
let perms = calculate_channel_permissions(&mut query).await;
|
||||||
|
let value: u64 = perms.into();
|
||||||
|
assert_eq!(
|
||||||
|
value,
|
||||||
|
ChannelPermission::ViewChannel as u64 | ChannelPermission::UploadFiles as u64
|
||||||
|
);
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl PermissionQuery for Scenario {
|
||||||
|
async fn are_we_privileged(&mut self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn are_we_a_bot(&mut self) -> bool {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn are_the_users_same(&mut self) -> bool {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn user_relationship(&mut self) -> RelationshipStatus {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn user_is_bot(&mut self) -> bool {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn have_mutual_connection(&mut self) -> bool {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn are_we_server_owner(&mut self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn are_we_a_member(&mut self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_default_server_permissions(&mut self) -> u64 {
|
||||||
|
ChannelPermission::ViewChannel as u64
|
||||||
|
| ChannelPermission::SendMessage as u64
|
||||||
|
| ChannelPermission::ReadMessageHistory as u64
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_our_server_role_overrides(&mut self) -> Vec<Override> {
|
||||||
|
vec![Override {
|
||||||
|
allow: ChannelPermission::UploadFiles as u64 | ChannelPermission::React as u64,
|
||||||
|
deny: ChannelPermission::ReadMessageHistory as u64,
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn are_we_timed_out(&mut self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_channel_type(&mut self) -> ChannelType {
|
||||||
|
ChannelType::ServerChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_default_channel_permissions(&mut self) -> Override {
|
||||||
|
Override {
|
||||||
|
allow: 0,
|
||||||
|
deny: ChannelPermission::SendMessage as u64,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_our_channel_role_overrides(&mut self) -> Vec<Override> {
|
||||||
|
vec![Override {
|
||||||
|
allow: 0,
|
||||||
|
deny: ChannelPermission::React as u64,
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn do_we_own_the_channel(&mut self) -> bool {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn are_we_part_of_the_channel(&mut self) -> bool {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn set_recipient_as_user(&mut self) {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn set_server_from_channel(&mut self) {
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn validate_timed_out_member() {
|
||||||
|
/// Scenario in which we are in a server that we have been timed out from
|
||||||
|
struct Scenario {}
|
||||||
|
let mut query = Scenario {};
|
||||||
|
|
||||||
|
let perms = calculate_channel_permissions(&mut query).await;
|
||||||
|
let value: u64 = perms.into();
|
||||||
|
assert_eq!(value, *DEFAULT_PERMISSION_VIEW_ONLY);
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl PermissionQuery for Scenario {
|
||||||
|
async fn are_we_privileged(&mut self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn are_we_a_bot(&mut self) -> bool {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn are_the_users_same(&mut self) -> bool {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn user_relationship(&mut self) -> RelationshipStatus {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn user_is_bot(&mut self) -> bool {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn have_mutual_connection(&mut self) -> bool {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn are_we_server_owner(&mut self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn are_we_a_member(&mut self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_default_server_permissions(&mut self) -> u64 {
|
||||||
|
*DEFAULT_PERMISSION_SERVER
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_our_server_role_overrides(&mut self) -> Vec<Override> {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn are_we_timed_out(&mut self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_channel_type(&mut self) -> ChannelType {
|
||||||
|
ChannelType::ServerChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_default_channel_permissions(&mut self) -> Override {
|
||||||
|
Override { allow: 0, deny: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_our_channel_role_overrides(&mut self) -> Vec<Override> {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn do_we_own_the_channel(&mut self) -> bool {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn are_we_part_of_the_channel(&mut self) -> bool {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn set_recipient_as_user(&mut self) {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn set_server_from_channel(&mut self) {
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
67
crates/core/permissions/src/trait.rs
Normal file
67
crates/core/permissions/src/trait.rs
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
use crate::{ChannelType, Override, RelationshipStatus};
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait PermissionQuery {
|
||||||
|
// * For calculating user permission
|
||||||
|
|
||||||
|
/// Is our perspective user privileged?
|
||||||
|
async fn are_we_privileged(&mut self) -> bool;
|
||||||
|
|
||||||
|
/// Is our perspective user a bot?
|
||||||
|
async fn are_we_a_bot(&mut self) -> bool;
|
||||||
|
|
||||||
|
/// Is our perspective user and the currently selected user the same?
|
||||||
|
async fn are_the_users_same(&mut self) -> bool;
|
||||||
|
|
||||||
|
/// Get the relationship with have with the currently selected user
|
||||||
|
async fn user_relationship(&mut self) -> RelationshipStatus;
|
||||||
|
|
||||||
|
/// Whether the currently selected user is a bot
|
||||||
|
async fn user_is_bot(&mut self) -> bool;
|
||||||
|
|
||||||
|
/// Do we have a mutual connection with the currently selected user?
|
||||||
|
async fn have_mutual_connection(&mut self) -> bool;
|
||||||
|
|
||||||
|
// * For calculating server permission
|
||||||
|
|
||||||
|
/// Is our perspective user the server's owner?
|
||||||
|
async fn are_we_server_owner(&mut self) -> bool;
|
||||||
|
|
||||||
|
/// Is our perspective user a member of the server?
|
||||||
|
async fn are_we_a_member(&mut self) -> bool;
|
||||||
|
|
||||||
|
/// Get default server permission
|
||||||
|
async fn get_default_server_permissions(&mut self) -> u64;
|
||||||
|
|
||||||
|
/// Get the ordered role overrides (from lowest to highest) for this member in this server
|
||||||
|
async fn get_our_server_role_overrides(&mut self) -> Vec<Override>;
|
||||||
|
|
||||||
|
/// Is our perspective user timed out on this server?
|
||||||
|
async fn are_we_timed_out(&mut self) -> bool;
|
||||||
|
|
||||||
|
// * For calculating channel permission
|
||||||
|
|
||||||
|
/// Get the type of the channel
|
||||||
|
async fn get_channel_type(&mut self) -> ChannelType;
|
||||||
|
|
||||||
|
/// Get the default channel permissions
|
||||||
|
/// Group channel defaults should be mapped to an allow-only override
|
||||||
|
async fn get_default_channel_permissions(&mut self) -> Override;
|
||||||
|
|
||||||
|
/// Get the ordered role overrides (from lowest to highest) for this member in this channel
|
||||||
|
async fn get_our_channel_role_overrides(&mut self) -> Vec<Override>;
|
||||||
|
|
||||||
|
/// Do we own this group or saved messages channel if it is one of those?
|
||||||
|
async fn do_we_own_the_channel(&mut self) -> bool;
|
||||||
|
|
||||||
|
/// Are we a recipient of this channel?
|
||||||
|
async fn are_we_part_of_the_channel(&mut self) -> bool;
|
||||||
|
|
||||||
|
/// Set the current user as the recipient of this channel
|
||||||
|
/// (this will only ever be called for DirectMessage channels, use unimplemented!() for other code paths)
|
||||||
|
async fn set_recipient_as_user(&mut self);
|
||||||
|
|
||||||
|
/// Set the current server as the server owning this channel
|
||||||
|
/// (this will only ever be called for server channels, use unimplemented!() for other code paths)
|
||||||
|
async fn set_server_from_channel(&mut self);
|
||||||
|
}
|
||||||
25
crates/core/presence/Cargo.toml
Normal file
25
crates/core/presence/Cargo.toml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
[package]
|
||||||
|
name = "revolt-presence"
|
||||||
|
version = "0.0.2"
|
||||||
|
edition = "2021"
|
||||||
|
license = "AGPL-3.0-or-later"
|
||||||
|
authors = [ "Paul Makles <me@insrt.uk>" ]
|
||||||
|
description = "Revolt Backend: User Presence"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[features]
|
||||||
|
redis-is-patched = []
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
# Async
|
||||||
|
async-std = { version = "1.8.0", features = ["attributes"] }
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
# Utility
|
||||||
|
log = "0.4.17"
|
||||||
|
rand = "0.8.5"
|
||||||
|
once_cell = "1.17.1"
|
||||||
|
|
||||||
|
# Redis
|
||||||
|
redis-kiss = "0.1.4"
|
||||||
247
crates/core/presence/src/lib.rs
Normal file
247
crates/core/presence/src/lib.rs
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
#[macro_use]
|
||||||
|
extern crate log;
|
||||||
|
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use rand::Rng;
|
||||||
|
use redis_kiss::{get_connection, AsyncCommands};
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
mod operations;
|
||||||
|
use operations::{
|
||||||
|
__add_to_set_string, __add_to_set_u32, __delete_key, __get_set_members_as_string,
|
||||||
|
__get_set_size, __remove_from_set_string, __remove_from_set_u32,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub static REGION_ID: Lazy<u16> = Lazy::new(|| {
|
||||||
|
std::env::var("REGION_ID")
|
||||||
|
.unwrap_or_else(|_| "0".to_string())
|
||||||
|
.parse()
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
|
|
||||||
|
pub static REGION_KEY: Lazy<String> = Lazy::new(|| format!("region{}", &*REGION_ID));
|
||||||
|
pub static ONLINE_SET: &str = "online";
|
||||||
|
|
||||||
|
pub static FLAG_BITS: u32 = 0b1;
|
||||||
|
|
||||||
|
/// Create a new presence session, returns the ID of this session
|
||||||
|
pub async fn create_session(user_id: &str, flags: u8) -> (bool, u32) {
|
||||||
|
info!("Creating a presence session for {user_id} with flags {flags}");
|
||||||
|
|
||||||
|
if let Ok(mut conn) = get_connection().await {
|
||||||
|
// Check whether this is the first session
|
||||||
|
let was_empty = __get_set_size(&mut conn, user_id).await == 0;
|
||||||
|
|
||||||
|
// 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>() & !FLAG_BITS) | (flags as u32 & FLAG_BITS)
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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}.");
|
||||||
|
|
||||||
|
(was_empty, session_id)
|
||||||
|
} else {
|
||||||
|
// Fail through
|
||||||
|
(false, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete existing presence session
|
||||||
|
pub async fn delete_session(user_id: &str, session_id: u32) -> bool {
|
||||||
|
delete_session_internal(user_id, session_id, false).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete existing presence session (but also choose whether to skip region)
|
||||||
|
async fn delete_session_internal(user_id: &str, session_id: u32, skip_region: bool) -> bool {
|
||||||
|
info!("Deleting presence session for {user_id} with id {session_id}");
|
||||||
|
|
||||||
|
if let Ok(mut conn) = get_connection().await {
|
||||||
|
// Remove the session
|
||||||
|
__remove_from_set_u32(&mut conn, user_id, session_id).await;
|
||||||
|
|
||||||
|
// Remove from the region
|
||||||
|
if !skip_region {
|
||||||
|
__remove_from_set_string(&mut conn, ®ION_KEY, &format!("{user_id}:{session_id}"))
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
} else {
|
||||||
|
// Fail through
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check whether a given user ID is online
|
||||||
|
pub async fn is_online(user_id: &str) -> bool {
|
||||||
|
if let Ok(mut conn) = get_connection().await {
|
||||||
|
conn.exists(user_id).await.unwrap_or(false)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check whether a set of users is online, returns a set of the online user IDs
|
||||||
|
#[cfg(feature = "redis-is-patched")]
|
||||||
|
pub async fn filter_online(user_ids: &'_ [String]) -> HashSet<String> {
|
||||||
|
// Ignore empty list immediately, to save time.
|
||||||
|
let mut set = HashSet::new();
|
||||||
|
if user_ids.is_empty() {
|
||||||
|
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.
|
||||||
|
if user_ids.len() == 1 {
|
||||||
|
if is_online(&user_ids[0]).await {
|
||||||
|
set.insert(user_ids[0].to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, go ahead as normal.
|
||||||
|
if let Ok(mut conn) = get_connection().await {
|
||||||
|
// 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
|
||||||
|
.smismember(ONLINE_SET, 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] {
|
||||||
|
set.insert(user_ids[i].to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check whether a set of users is online, returns a set of the online user IDs
|
||||||
|
#[cfg(not(feature = "redis-is-patched"))]
|
||||||
|
pub async fn filter_online(user_ids: &'_ [String]) -> HashSet<String> {
|
||||||
|
if user_ids.is_empty() {
|
||||||
|
HashSet::new()
|
||||||
|
} else if let Ok(mut conn) = get_connection().await {
|
||||||
|
let members: Vec<String> = conn.smembers(ONLINE_SET).await.unwrap_or_default();
|
||||||
|
let members: HashSet<&String> = members.iter().collect();
|
||||||
|
let user_ids: HashSet<&String> = user_ids.iter().collect();
|
||||||
|
|
||||||
|
members
|
||||||
|
.intersection(&user_ids)
|
||||||
|
.map(|x| x.to_string())
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
HashSet::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reset any stale presence data
|
||||||
|
pub async fn clear_region(region_id: Option<&str>) {
|
||||||
|
let region_id = region_id.unwrap_or(&*REGION_KEY);
|
||||||
|
let mut conn = get_connection().await.expect("Redis connection");
|
||||||
|
|
||||||
|
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...",
|
||||||
|
sessions.len()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Iterate and delete each session, this will
|
||||||
|
// also send out any relevant events.
|
||||||
|
for session in sessions {
|
||||||
|
let parts = session.split(':').collect::<Vec<&str>>();
|
||||||
|
if let (Some(user_id), Some(session_id)) = (parts.first(), parts.get(1)) {
|
||||||
|
if let Ok(session_id) = session_id.parse() {
|
||||||
|
delete_session_internal(user_id, session_id, true).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then clear the set in Redis.
|
||||||
|
__delete_key(&mut conn, region_id).await;
|
||||||
|
|
||||||
|
info!("Clean up complete.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::{clear_region, create_session, delete_session, filter_online, is_online};
|
||||||
|
use rand::Rng;
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn it_works() {
|
||||||
|
// Clear the region before we start the tests:
|
||||||
|
clear_region(None).await;
|
||||||
|
|
||||||
|
// Generate some data we'll use:
|
||||||
|
let user_id = rand::thread_rng().gen::<u32>().to_string();
|
||||||
|
let other_id = rand::thread_rng().gen::<u32>().to_string();
|
||||||
|
let flags = 1;
|
||||||
|
|
||||||
|
// Create a session
|
||||||
|
let (first_session, session_id) = create_session(&user_id, flags).await;
|
||||||
|
assert!(first_session);
|
||||||
|
assert_ne!(session_id, 0);
|
||||||
|
assert_eq!(session_id as u8 & flags, flags);
|
||||||
|
|
||||||
|
// Check if the user is online
|
||||||
|
assert!(is_online(&user_id).await);
|
||||||
|
|
||||||
|
let user_ids = filter_online(&[user_id.to_string()]).await;
|
||||||
|
assert_eq!(user_ids.len(), 1);
|
||||||
|
assert!(user_ids.contains(&user_id));
|
||||||
|
|
||||||
|
// Create a few more sessions
|
||||||
|
let (first_session, second_session_id) = create_session(&user_id, 0).await;
|
||||||
|
assert!(!first_session);
|
||||||
|
dbg!(second_session_id);
|
||||||
|
assert_eq!(second_session_id as u8 & 1, 0);
|
||||||
|
|
||||||
|
let (first_session, other_session_id) = create_session(&other_id, 0).await;
|
||||||
|
assert!(first_session);
|
||||||
|
|
||||||
|
let user_ids = filter_online(&[user_id.to_string(), other_id.to_string()]).await;
|
||||||
|
assert_eq!(user_ids.len(), 2);
|
||||||
|
assert!(user_ids.contains(&user_id));
|
||||||
|
assert!(user_ids.contains(&other_id));
|
||||||
|
|
||||||
|
// Remove sessions
|
||||||
|
delete_session(&user_id, session_id).await;
|
||||||
|
delete_session(&other_id, other_session_id).await;
|
||||||
|
assert!(!is_online(&other_id).await);
|
||||||
|
|
||||||
|
// Check if we can wipe everything too
|
||||||
|
clear_region(None).await;
|
||||||
|
assert!(!is_online(&user_id).await);
|
||||||
|
|
||||||
|
let user_ids = filter_online(&[user_id.to_string(), other_id.to_string()]).await;
|
||||||
|
assert!(user_ids.is_empty())
|
||||||
|
}
|
||||||
|
}
|
||||||
42
crates/core/presence/src/operations.rs
Normal file
42
crates/core/presence/src/operations.rs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
use redis_kiss::{AsyncCommands, Conn};
|
||||||
|
|
||||||
|
/// 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
.expect("could not get set members as string")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get set size
|
||||||
|
pub async fn __get_set_size(conn: &mut Conn, id: &str) -> u32 {
|
||||||
|
conn.scard::<_, u32>(id)
|
||||||
|
.await
|
||||||
|
.expect("could not get set size")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete key by id
|
||||||
|
pub async fn __delete_key(conn: &mut Conn, id: &str) {
|
||||||
|
conn.del::<_, ()>(id)
|
||||||
|
.await
|
||||||
|
.expect("could not delete key by id");
|
||||||
|
}
|
||||||
22
crates/core/result/Cargo.toml
Normal file
22
crates/core/result/Cargo.toml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
[package]
|
||||||
|
name = "revolt-result"
|
||||||
|
version = "0.0.2"
|
||||||
|
edition = "2021"
|
||||||
|
license = "AGPL-3.0-or-later"
|
||||||
|
authors = [ "Paul Makles <me@insrt.uk>" ]
|
||||||
|
description = "Revolt Backend: Result and Error types"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[features]
|
||||||
|
serde = [ "dep:serde" ]
|
||||||
|
schemas = [ "dep:schemars" ]
|
||||||
|
|
||||||
|
default = [ "serde" ]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
# Serialisation
|
||||||
|
serde = { version = "1", features = ["derive"], optional = true }
|
||||||
|
|
||||||
|
# Spec Generation
|
||||||
|
schemars = { version = "0.8.8", optional = true }
|
||||||
178
crates/core/result/src/lib.rs
Normal file
178
crates/core/result/src/lib.rs
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
#[cfg(feature = "serde")]
|
||||||
|
#[macro_use]
|
||||||
|
extern crate serde;
|
||||||
|
|
||||||
|
#[cfg(feature = "schemas")]
|
||||||
|
#[macro_use]
|
||||||
|
extern crate schemars;
|
||||||
|
|
||||||
|
/// Result type with custom Error
|
||||||
|
pub type Result<T, E = Error> = std::result::Result<T, E>;
|
||||||
|
|
||||||
|
/// Error information
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
#[cfg_attr(feature = "schemas", derive(JsonSchema))]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Error {
|
||||||
|
/// Type of error and additional information
|
||||||
|
#[cfg_attr(feature = "serde", serde(flatten))]
|
||||||
|
pub error_type: ErrorType,
|
||||||
|
|
||||||
|
/// Where this error occurred
|
||||||
|
pub location: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Possible error types
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
#[cfg_attr(feature = "serde", serde(tag = "type"))]
|
||||||
|
#[cfg_attr(feature = "schemas", derive(JsonSchema))]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum ErrorType {
|
||||||
|
/// This error was not labeled :(
|
||||||
|
LabelMe,
|
||||||
|
|
||||||
|
// ? Onboarding related errors
|
||||||
|
AlreadyOnboarded,
|
||||||
|
|
||||||
|
// ? User related errors
|
||||||
|
UsernameTaken,
|
||||||
|
InvalidUsername,
|
||||||
|
UnknownUser,
|
||||||
|
AlreadyFriends,
|
||||||
|
AlreadySentRequest,
|
||||||
|
Blocked,
|
||||||
|
BlockedByOther,
|
||||||
|
NotFriends,
|
||||||
|
|
||||||
|
// ? Channel related errors
|
||||||
|
UnknownChannel,
|
||||||
|
UnknownAttachment,
|
||||||
|
UnknownMessage,
|
||||||
|
CannotEditMessage,
|
||||||
|
CannotJoinCall,
|
||||||
|
TooManyAttachments {
|
||||||
|
max: usize,
|
||||||
|
},
|
||||||
|
TooManyReplies {
|
||||||
|
max: usize,
|
||||||
|
},
|
||||||
|
TooManyChannels {
|
||||||
|
max: usize,
|
||||||
|
},
|
||||||
|
EmptyMessage,
|
||||||
|
PayloadTooLarge,
|
||||||
|
CannotRemoveYourself,
|
||||||
|
GroupTooLarge {
|
||||||
|
max: usize,
|
||||||
|
},
|
||||||
|
AlreadyInGroup,
|
||||||
|
NotInGroup,
|
||||||
|
|
||||||
|
// ? Server related errors
|
||||||
|
UnknownServer,
|
||||||
|
InvalidRole,
|
||||||
|
Banned,
|
||||||
|
TooManyServers {
|
||||||
|
max: usize,
|
||||||
|
},
|
||||||
|
TooManyEmoji {
|
||||||
|
max: usize,
|
||||||
|
},
|
||||||
|
TooManyRoles {
|
||||||
|
max: usize,
|
||||||
|
},
|
||||||
|
|
||||||
|
// ? Bot related errors
|
||||||
|
ReachedMaximumBots,
|
||||||
|
IsBot,
|
||||||
|
BotIsPrivate,
|
||||||
|
|
||||||
|
// ? User safety related errors
|
||||||
|
CannotReportYourself,
|
||||||
|
|
||||||
|
// ? Permission errors
|
||||||
|
MissingPermission {
|
||||||
|
permission: String,
|
||||||
|
},
|
||||||
|
MissingUserPermission {
|
||||||
|
permission: String,
|
||||||
|
},
|
||||||
|
NotElevated,
|
||||||
|
NotPrivileged,
|
||||||
|
CannotGiveMissingPermissions,
|
||||||
|
NotOwner,
|
||||||
|
|
||||||
|
// ? General errors
|
||||||
|
DatabaseError {
|
||||||
|
operation: String,
|
||||||
|
collection: String,
|
||||||
|
},
|
||||||
|
InternalError,
|
||||||
|
InvalidOperation,
|
||||||
|
InvalidCredentials,
|
||||||
|
InvalidProperty,
|
||||||
|
InvalidSession,
|
||||||
|
DuplicateNonce,
|
||||||
|
NotFound,
|
||||||
|
NoEffect,
|
||||||
|
FailedValidation {
|
||||||
|
error: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
// ? Legacy errors
|
||||||
|
VosoUnavailable,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! create_error {
|
||||||
|
( $error: ident $( $tt:tt )? ) => {
|
||||||
|
$crate::Error {
|
||||||
|
error_type: $crate::ErrorType::$error $( $tt )?,
|
||||||
|
location: format!("{}:{}:{}", file!(), line!(), column!()),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! create_database_error {
|
||||||
|
( $operation: expr, $collection: expr ) => {
|
||||||
|
create_error!(DatabaseError {
|
||||||
|
operation: $operation.to_string(),
|
||||||
|
collection: $collection.to_string()
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
macro_rules! query {
|
||||||
|
( $self: ident, $type: ident, $collection: expr, $($rest:expr),+ ) => {
|
||||||
|
Ok($self.$type($collection, $($rest),+).await.unwrap())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
macro_rules! query {
|
||||||
|
( $self: ident, $type: ident, $collection: expr, $($rest:expr),+ ) => {
|
||||||
|
$self.$type($collection, $($rest),+).await
|
||||||
|
.map_err(|_| create_database_error!(stringify!($type), $collection))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::ErrorType;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn use_macro_to_construct_error() {
|
||||||
|
let error = create_error!(LabelMe);
|
||||||
|
assert!(matches!(error.error_type, ErrorType::LabelMe));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn use_macro_to_construct_complex_error() {
|
||||||
|
let error = create_error!(LabelMe);
|
||||||
|
assert!(matches!(error.error_type, ErrorType::LabelMe));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "revolt-delta"
|
name = "revolt-delta"
|
||||||
version = "0.5.17"
|
version = "0.5.19"
|
||||||
license = "AGPL-3.0-or-later"
|
license = "AGPL-3.0-or-later"
|
||||||
authors = ["Paul Makles <paulmakles@gmail.com>"]
|
authors = ["Paul Makles <paulmakles@gmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
@@ -43,11 +43,6 @@ async-std = { version = "1.8.0", features = ["tokio1", "tokio02", "attributes"]
|
|||||||
# internal util
|
# internal util
|
||||||
lettre = "0.10.0-alpha.4"
|
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
|
# web
|
||||||
rocket = { version = "0.5.0-rc.2", default-features = false, features = ["json"] }
|
rocket = { version = "0.5.0-rc.2", default-features = false, features = ["json"] }
|
||||||
rocket_empty = { version = "0.1.1", features = ["schema"] }
|
rocket_empty = { version = "0.1.1", features = ["schema"] }
|
||||||
@@ -60,5 +55,9 @@ revolt_rocket_okapi = { version = "0.9.1", features = [ "swagger" ] }
|
|||||||
# quark
|
# quark
|
||||||
revolt-quark = { path = "../quark" }
|
revolt-quark = { path = "../quark" }
|
||||||
|
|
||||||
|
# core
|
||||||
|
revolt-database = { path = "../core/database", features = [ "rocket-impl" ] }
|
||||||
|
revolt-models = { path = "../core/models", features = [ "schemas", "redis-is-patched" ] }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
vergen = "7.5.0"
|
vergen = "7.5.0"
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
# Build Stage
|
# Build Stage
|
||||||
FROM ghcr.io/revoltchat/base:latest AS builder
|
FROM ghcr.io/revoltchat/base:latest AS builder
|
||||||
RUN cargo install --locked --path crates/delta
|
|
||||||
|
|
||||||
# Bundle Stage
|
# Bundle Stage
|
||||||
FROM debian:buster-slim
|
FROM debian:bullseye-slim
|
||||||
RUN apt-get update && apt-get install -y ca-certificates
|
RUN apt-get update && \
|
||||||
COPY --from=builder /usr/local/cargo/bin/revolt-delta ./
|
apt-get install -y ca-certificates && \
|
||||||
|
apt-get clean
|
||||||
|
COPY --from=builder /home/rust/src/target/release/revolt-delta ./
|
||||||
|
|
||||||
EXPOSE 8000
|
EXPOSE 8000
|
||||||
ENV ROCKET_ADDRESS 0.0.0.0
|
ENV ROCKET_ADDRESS 0.0.0.0
|
||||||
|
|||||||
@@ -23,15 +23,18 @@ async fn rocket() -> _ {
|
|||||||
revolt_quark::variables::delta::preflight_checks();
|
revolt_quark::variables::delta::preflight_checks();
|
||||||
|
|
||||||
// Setup database
|
// Setup database
|
||||||
let db = DatabaseInfo::Auto.connect().await.unwrap();
|
let db = revolt_database::DatabaseInfo::Auto.connect().await.unwrap();
|
||||||
db.migrate_database().await.unwrap();
|
db.migrate_database().await.unwrap();
|
||||||
|
|
||||||
|
// Legacy database setup from quark
|
||||||
|
let legacy_db = DatabaseInfo::Auto.connect().await.unwrap();
|
||||||
|
|
||||||
// Setup Authifier event channel
|
// Setup Authifier event channel
|
||||||
let (sender, receiver) = unbounded();
|
let (sender, receiver) = unbounded();
|
||||||
|
|
||||||
// Setup Authifier
|
// Setup Authifier
|
||||||
let authifier = Authifier {
|
let authifier = Authifier {
|
||||||
database: db.clone().into(),
|
database: legacy_db.clone().into(),
|
||||||
config: revolt_quark::util::authifier::config(),
|
config: revolt_quark::util::authifier::config(),
|
||||||
event_channel: Some(sender),
|
event_channel: Some(sender),
|
||||||
};
|
};
|
||||||
@@ -53,7 +56,7 @@ async fn rocket() -> _ {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Launch background task workers
|
// Launch background task workers
|
||||||
async_std::task::spawn(revolt_quark::tasks::start_workers(db.clone()));
|
async_std::task::spawn(revolt_quark::tasks::start_workers(legacy_db.clone()));
|
||||||
|
|
||||||
// Configure CORS
|
// Configure CORS
|
||||||
let cors = revolt_quark::web::cors::new();
|
let cors = revolt_quark::web::cors::new();
|
||||||
@@ -66,6 +69,7 @@ async fn rocket() -> _ {
|
|||||||
.mount("/swagger/", revolt_quark::web::swagger::routes())
|
.mount("/swagger/", revolt_quark::web::swagger::routes())
|
||||||
.manage(authifier)
|
.manage(authifier)
|
||||||
.manage(db)
|
.manage(db)
|
||||||
|
.manage(legacy_db)
|
||||||
.manage(cors.clone())
|
.manage(cors.clone())
|
||||||
.attach(revolt_quark::web::ratelimiter::RatelimitFairing)
|
.attach(revolt_quark::web::ratelimiter::RatelimitFairing)
|
||||||
.attach(cors)
|
.attach(cors)
|
||||||
|
|||||||
@@ -1,36 +1,33 @@
|
|||||||
use revolt_quark::{
|
use revolt_database::{util::reference::Reference, Database};
|
||||||
models::{Bot, User},
|
use revolt_models::v0::FetchBotResponse;
|
||||||
Db, Error, Ref, Result,
|
use revolt_quark::{models::User, Error, Result};
|
||||||
};
|
use rocket::{serde::json::Json, State};
|
||||||
use rocket::serde::json::Json;
|
|
||||||
use serde::Serialize;
|
|
||||||
|
|
||||||
/// # Bot Response
|
|
||||||
#[derive(Serialize, JsonSchema)]
|
|
||||||
pub struct BotResponse {
|
|
||||||
/// Bot object
|
|
||||||
bot: Bot,
|
|
||||||
/// User object
|
|
||||||
user: User,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// # Fetch Bot
|
/// # Fetch Bot
|
||||||
///
|
///
|
||||||
/// Fetch details of a bot you own by its id.
|
/// Fetch details of a bot you own by its id.
|
||||||
#[openapi(tag = "Bots")]
|
#[openapi(tag = "Bots")]
|
||||||
#[get("/<target>")]
|
#[get("/<bot>")]
|
||||||
pub async fn fetch_bot(db: &Db, user: User, target: Ref) -> Result<Json<BotResponse>> {
|
pub async fn fetch_bot(
|
||||||
|
db: &State<Database>,
|
||||||
|
user: User,
|
||||||
|
bot: Reference,
|
||||||
|
) -> Result<Json<FetchBotResponse>> {
|
||||||
if user.bot.is_some() {
|
if user.bot.is_some() {
|
||||||
return Err(Error::IsBot);
|
return Err(Error::IsBot);
|
||||||
}
|
}
|
||||||
|
|
||||||
let bot = target.as_bot(db).await?;
|
let bot = bot.as_bot(db).await.map_err(Error::from_core)?;
|
||||||
if bot.owner != user.id {
|
if bot.owner != user.id {
|
||||||
return Err(Error::NotFound);
|
return Err(Error::NotFound);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Json(BotResponse {
|
Ok(Json(FetchBotResponse {
|
||||||
user: db.fetch_user(&bot.id).await?.foreign(),
|
user: revolt_models::v0::User::from(
|
||||||
bot,
|
db.fetch_user(&bot.id).await.map_err(Error::from_core)?,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await,
|
||||||
|
bot: bot.into(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,44 +1,25 @@
|
|||||||
use revolt_quark::{
|
use revolt_database::Database;
|
||||||
models::{File, User},
|
use revolt_models::v0::PublicBot;
|
||||||
Db, Error, Ref, Result,
|
use revolt_quark::{models::User, Error, Ref, Result};
|
||||||
};
|
|
||||||
|
|
||||||
use rocket::serde::json::Json;
|
use rocket::serde::json::Json;
|
||||||
use serde::{Deserialize, Serialize};
|
use rocket::State;
|
||||||
|
|
||||||
/// # Public Bot
|
|
||||||
#[derive(Serialize, Deserialize, JsonSchema)]
|
|
||||||
pub struct PublicBot {
|
|
||||||
/// Bot Id
|
|
||||||
#[serde(rename = "_id")]
|
|
||||||
id: String,
|
|
||||||
/// Bot Username
|
|
||||||
username: String,
|
|
||||||
/// Profile Avatar
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
avatar: Option<File>,
|
|
||||||
/// Profile Description
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
description: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// # Fetch Public Bot
|
/// # Fetch Public Bot
|
||||||
///
|
///
|
||||||
/// Fetch details of a public (or owned) bot by its id.
|
/// Fetch details of a public (or owned) bot by its id.
|
||||||
#[openapi(tag = "Bots")]
|
#[openapi(tag = "Bots")]
|
||||||
#[get("/<target>/invite")]
|
#[get("/<target>/invite")]
|
||||||
pub async fn fetch_public_bot(db: &Db, user: Option<User>, target: Ref) -> Result<Json<PublicBot>> {
|
pub async fn fetch_public_bot(
|
||||||
let bot = target.as_bot(db).await?;
|
db: &State<Database>,
|
||||||
|
user: Option<User>,
|
||||||
|
target: Ref,
|
||||||
|
) -> Result<Json<PublicBot>> {
|
||||||
|
let bot = db.fetch_bot(&target.id).await.map_err(Error::from_core)?;
|
||||||
if !bot.public && user.map_or(true, |x| x.id != bot.owner) {
|
if !bot.public && user.map_or(true, |x| x.id != bot.owner) {
|
||||||
return Err(Error::NotFound);
|
return Err(Error::NotFound);
|
||||||
}
|
}
|
||||||
|
|
||||||
let user = db.fetch_user(&bot.id).await?;
|
let user = db.fetch_user(&bot.id).await.map_err(Error::from_core)?;
|
||||||
|
Ok(Json(PublicBot::from(bot, user)))
|
||||||
Ok(Json(PublicBot {
|
|
||||||
id: bot.id,
|
|
||||||
username: user.username,
|
|
||||||
avatar: user.avatar,
|
|
||||||
description: user.profile.and_then(|p| p.content),
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use revolt_quark::{
|
use revolt_quark::{
|
||||||
models::{Invite, User},
|
models::{Invite, User},
|
||||||
perms, Db, Error, Permission, Ref, Result,
|
perms, Db, Permission, Ref, Result,
|
||||||
};
|
};
|
||||||
|
|
||||||
use rocket::serde::json::Json;
|
use rocket::serde::json::Json;
|
||||||
@@ -11,10 +11,6 @@ use rocket::serde::json::Json;
|
|||||||
#[openapi(tag = "Server Members")]
|
#[openapi(tag = "Server Members")]
|
||||||
#[get("/<target>/invites")]
|
#[get("/<target>/invites")]
|
||||||
pub async fn req(db: &Db, user: User, target: Ref) -> Result<Json<Vec<Invite>>> {
|
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?;
|
let server = target.as_server(db).await?;
|
||||||
perms(&user)
|
perms(&user)
|
||||||
.server(&server)
|
.server(&server)
|
||||||
|
|||||||
@@ -44,6 +44,10 @@ pub async fn req(
|
|||||||
user: User,
|
user: User,
|
||||||
info: Json<DataCreateServer>,
|
info: Json<DataCreateServer>,
|
||||||
) -> Result<Json<CreateServerResponse>> {
|
) -> Result<Json<CreateServerResponse>> {
|
||||||
|
if user.bot.is_some() {
|
||||||
|
return Err(Error::IsBot);
|
||||||
|
}
|
||||||
|
|
||||||
let info = info.into_inner();
|
let info = info.into_inner();
|
||||||
info.validate()
|
info.validate()
|
||||||
.map_err(|error| Error::FailedValidation { error })?;
|
.map_err(|error| Error::FailedValidation { error })?;
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "revolt-quark"
|
name = "revolt-quark"
|
||||||
version = "0.5.17"
|
version = "0.5.19"
|
||||||
license = "AGPL-3.0-or-later"
|
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
license = "AGPL-3.0-or-later"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
@@ -26,10 +26,10 @@ default = [ "test" ]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# Serialisation
|
# Serialisation
|
||||||
|
revolt_optional_struct = "0.2.0"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
validator = { version = "0.14", features = ["derive"] }
|
validator = { version = "0.14", features = ["derive"] }
|
||||||
iso8601-timestamp = { version = "0.1.8", features = ["schema", "bson"] }
|
iso8601-timestamp = { version = "0.1.8", features = ["schema", "bson"] }
|
||||||
optional_struct = { git = "https://github.com/insertish/OptionalStruct", rev = "ee56427cee1f007839825d93d07fffd5a5e038c7" }
|
|
||||||
|
|
||||||
# Formats
|
# Formats
|
||||||
bincode = "1.3.3"
|
bincode = "1.3.3"
|
||||||
@@ -42,7 +42,7 @@ revolt_okapi = "0.9.1"
|
|||||||
revolt_rocket_okapi = { version = "0.9.1", features = [ "swagger" ] }
|
revolt_rocket_okapi = { version = "0.9.1", features = [ "swagger" ] }
|
||||||
|
|
||||||
# Databases
|
# Databases
|
||||||
redis-kiss = { version = "0.1.3" }
|
redis-kiss = { version = "0.1.4" }
|
||||||
mongodb = { optional = true, version = "2.1.0", default-features = false }
|
mongodb = { optional = true, version = "2.1.0", default-features = false }
|
||||||
|
|
||||||
# Async
|
# Async
|
||||||
@@ -57,6 +57,7 @@ log = "0.4.14"
|
|||||||
pretty_env_logger = "0.4.0"
|
pretty_env_logger = "0.4.0"
|
||||||
|
|
||||||
# Util
|
# Util
|
||||||
|
rand = "0.8.5"
|
||||||
ulid = "0.5.0"
|
ulid = "0.5.0"
|
||||||
regex = "1.5.5"
|
regex = "1.5.5"
|
||||||
nanoid = "0.4.0"
|
nanoid = "0.4.0"
|
||||||
@@ -88,3 +89,7 @@ authifier = { version = "1.0.7", features = [ "async-std-runtime" ] }
|
|||||||
|
|
||||||
# Sentry
|
# Sentry
|
||||||
sentry = "0.25.0"
|
sentry = "0.25.0"
|
||||||
|
|
||||||
|
# Core
|
||||||
|
revolt-result = { path = "../core/result", features = [ "serde", "schemas" ] }
|
||||||
|
revolt-presence = { path = "../core/presence", features = [ "redis-is-patched" ] }
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
use revolt_quark::models::user::PartialUser;
|
|
||||||
use revolt_quark::presence::{
|
|
||||||
presence_create_session, presence_delete_session, presence_filter_online, presence_is_online,
|
|
||||||
};
|
|
||||||
use revolt_quark::*;
|
|
||||||
|
|
||||||
#[async_std::main]
|
|
||||||
async fn main() {
|
|
||||||
let db = DatabaseInfo::Dummy.connect().await.unwrap();
|
|
||||||
|
|
||||||
let sus = PartialUser {
|
|
||||||
username: Some("neat".into()),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
db.update_user("user id", &sus, vec![]).await.unwrap();
|
|
||||||
|
|
||||||
dbg!(presence_create_session("entry", 0).await);
|
|
||||||
dbg!(presence_is_online("entry").await);
|
|
||||||
dbg!(presence_filter_online(&["a".into(), "b".into(), "entry".into()]).await);
|
|
||||||
|
|
||||||
dbg!(presence_delete_session("entry", 0).await);
|
|
||||||
dbg!(presence_is_online("entry").await);
|
|
||||||
|
|
||||||
dbg!(presence_create_session("entry", 0).await);
|
|
||||||
dbg!(presence_create_session("entry", 0).await);
|
|
||||||
dbg!(presence_delete_session("entry", 0).await);
|
|
||||||
dbg!(presence_is_online("entry").await);
|
|
||||||
dbg!(presence_delete_session("entry", 1).await);
|
|
||||||
dbg!(presence_is_online("entry").await);
|
|
||||||
|
|
||||||
// __set_key("dietz", vec![0xFF]).await;
|
|
||||||
// dbg!(presence_filter_online(&["dietz".into(), "nuts".into()]).await);
|
|
||||||
}
|
|
||||||
@@ -7,11 +7,11 @@ use crate::{
|
|||||||
user::{PartialUser, Presence, RelationshipStatus},
|
user::{PartialUser, Presence, RelationshipStatus},
|
||||||
Channel, Member, User,
|
Channel, Member, User,
|
||||||
},
|
},
|
||||||
perms,
|
perms, Database, Permission, Result,
|
||||||
presence::presence_filter_online,
|
|
||||||
Database, Permission, Result,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use revolt_presence::filter_online;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
client::EventV1,
|
client::EventV1,
|
||||||
state::{Cache, State},
|
state::{Cache, State},
|
||||||
@@ -99,7 +99,7 @@ impl State {
|
|||||||
let mut user = self.clone_user();
|
let mut user = self.clone_user();
|
||||||
|
|
||||||
// Find all relationships to the user.
|
// Find all relationships to the user.
|
||||||
let mut user_ids: Vec<String> = user
|
let mut user_ids: HashSet<String> = user
|
||||||
.relations
|
.relations
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|arr| arr.iter().map(|x| x.id.to_string()).collect())
|
.map(|arr| arr.iter().map(|x| x.id.to_string()).collect())
|
||||||
@@ -128,14 +128,14 @@ impl State {
|
|||||||
for channel in &channels {
|
for channel in &channels {
|
||||||
match channel {
|
match channel {
|
||||||
Channel::DirectMessage { recipients, .. } | Channel::Group { recipients, .. } => {
|
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.
|
// Fetch presence data for known users.
|
||||||
let online_ids = presence_filter_online(&user_ids).await;
|
let online_ids = filter_online(&user_ids.iter().cloned().collect::<Vec<String>>()).await;
|
||||||
user.online = Some(true);
|
user.online = Some(true);
|
||||||
|
|
||||||
// Fetch user data.
|
// Fetch user data.
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
use crate::{AbstractMigrations, Result};
|
|
||||||
|
|
||||||
use super::super::DummyDb;
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl AbstractMigrations for DummyDb {
|
|
||||||
async fn migrate_database(&self) -> Result<()> {
|
|
||||||
info!("Migrating the database.");
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
use crate::AbstractDatabase;
|
use crate::AbstractDatabase;
|
||||||
|
|
||||||
pub mod admin {
|
pub mod admin {
|
||||||
pub mod migrations;
|
|
||||||
pub mod stats;
|
pub mod stats;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use revolt_presence::filter_online;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use ulid::Ulid;
|
use ulid::Ulid;
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
@@ -14,7 +15,6 @@ use crate::{
|
|||||||
Channel, Emoji, Message, User,
|
Channel, Emoji, Message, User,
|
||||||
},
|
},
|
||||||
permissions::PermissionCalculator,
|
permissions::PermissionCalculator,
|
||||||
presence::presence_filter_online,
|
|
||||||
tasks::ack::AckEvent,
|
tasks::ack::AckEvent,
|
||||||
types::{
|
types::{
|
||||||
january::{Embed, Text},
|
january::{Embed, Text},
|
||||||
@@ -79,7 +79,7 @@ impl Message {
|
|||||||
Channel::DirectMessage { recipients, .. }
|
Channel::DirectMessage { recipients, .. }
|
||||||
| Channel::Group { recipients, .. } => {
|
| Channel::Group { recipients, .. } => {
|
||||||
target_ids = (&recipients.iter().cloned().collect::<HashSet<String>>()
|
target_ids = (&recipients.iter().cloned().collect::<HashSet<String>>()
|
||||||
- &presence_filter_online(recipients).await)
|
- &filter_online(recipients).await)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect::<Vec<String>>();
|
.collect::<Vec<String>>();
|
||||||
}
|
}
|
||||||
@@ -313,12 +313,12 @@ impl IntoUsers for Message {
|
|||||||
|
|
||||||
impl IntoUsers for Vec<Message> {
|
impl IntoUsers for Vec<Message> {
|
||||||
fn get_user_ids(&self) -> Vec<String> {
|
fn get_user_ids(&self) -> Vec<String> {
|
||||||
let mut ids = vec![];
|
let mut ids = HashSet::new();
|
||||||
for message in self {
|
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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
//! Database agnostic implementations.
|
//! Database agnostic implementations.
|
||||||
|
|
||||||
pub mod admin {
|
|
||||||
pub mod migrations;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod media {
|
pub mod media {
|
||||||
pub mod attachment;
|
pub mod attachment;
|
||||||
pub mod emoji;
|
pub mod emoji;
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ use crate::models::user::{
|
|||||||
};
|
};
|
||||||
use crate::permissions::defn::UserPerms;
|
use crate::permissions::defn::UserPerms;
|
||||||
use crate::permissions::r#impl::user::get_relationship;
|
use crate::permissions::r#impl::user::get_relationship;
|
||||||
use crate::presence::presence_filter_online;
|
|
||||||
use crate::{perms, Database, Error, Result};
|
use crate::{perms, Database, Error, Result};
|
||||||
|
|
||||||
use futures::try_join;
|
use futures::try_join;
|
||||||
use impl_ops::impl_op_ex_commutative;
|
use impl_ops::impl_op_ex_commutative;
|
||||||
|
use revolt_presence::filter_online;
|
||||||
use std::ops;
|
use std::ops;
|
||||||
|
|
||||||
impl_op_ex_commutative!(+ |a: &i32, b: &Badges| -> i32 { *a | *b as i32 });
|
impl_op_ex_commutative!(+ |a: &i32, b: &Badges| -> i32 { *a | *b as i32 });
|
||||||
@@ -98,7 +98,7 @@ impl User {
|
|||||||
|
|
||||||
/// Fetch foreign users by a list of IDs
|
/// Fetch foreign users by a list of IDs
|
||||||
pub async fn fetch_foreign_users(db: &Database, user_ids: &[String]) -> Result<Vec<User>> {
|
pub async fn fetch_foreign_users(db: &Database, user_ids: &[String]) -> Result<Vec<User>> {
|
||||||
let online_ids = presence_filter_online(user_ids).await;
|
let online_ids = filter_online(user_ids).await;
|
||||||
|
|
||||||
Ok(db
|
Ok(db
|
||||||
.fetch_users(user_ids)
|
.fetch_users(user_ids)
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ use serde::{Deserialize, Serialize};
|
|||||||
use crate::{util::manipulation::prefix_keys, AbstractDatabase, Error, Result};
|
use crate::{util::manipulation::prefix_keys, AbstractDatabase, Error, Result};
|
||||||
|
|
||||||
pub mod admin {
|
pub mod admin {
|
||||||
pub mod migrations;
|
|
||||||
pub mod stats;
|
pub mod stats;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ extern crate log;
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate impl_ops;
|
extern crate impl_ops;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate optional_struct;
|
extern crate revolt_optional_struct;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate bitfield;
|
extern crate bitfield;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
@@ -22,7 +22,6 @@ pub use redis_kiss;
|
|||||||
pub mod events;
|
pub mod events;
|
||||||
pub mod r#impl;
|
pub mod r#impl;
|
||||||
pub mod models;
|
pub mod models;
|
||||||
pub mod presence;
|
|
||||||
pub mod tasks;
|
pub mod tasks;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
/// Document representing migration information
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct MigrationInfo {
|
|
||||||
/// Unique Id
|
|
||||||
#[serde(rename = "_id")]
|
|
||||||
id: i32,
|
|
||||||
/// Current database revision
|
|
||||||
revision: i32,
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
mod admin {
|
mod admin {
|
||||||
pub mod migrations;
|
|
||||||
pub mod simple;
|
pub mod simple;
|
||||||
pub mod stats;
|
pub mod stats;
|
||||||
}
|
}
|
||||||
@@ -48,7 +47,6 @@ pub use channel_invite::Invite;
|
|||||||
pub use channel_unread::ChannelUnread;
|
pub use channel_unread::ChannelUnread;
|
||||||
pub use emoji::Emoji;
|
pub use emoji::Emoji;
|
||||||
pub use message::Message;
|
pub use message::Message;
|
||||||
pub use migrations::MigrationInfo;
|
|
||||||
pub use report::Report;
|
pub use report::Report;
|
||||||
pub use server::Server;
|
pub use server::Server;
|
||||||
pub use server_ban::ServerBan;
|
pub use server_ban::ServerBan;
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
use std::env;
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
|
|
||||||
pub static REGION_ID: Lazy<u16> = Lazy::new(|| env::var("REGION_ID")
|
|
||||||
.unwrap_or_else(|_| "0".to_string())
|
|
||||||
.parse()
|
|
||||||
.unwrap());
|
|
||||||
|
|
||||||
pub static REGION_KEY: Lazy<String> = Lazy::new(|| format!("region{}", &*REGION_ID));
|
|
||||||
|
|
||||||
/// 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,162 +0,0 @@
|
|||||||
use std::collections::HashSet;
|
|
||||||
|
|
||||||
use redis_kiss::{get_connection, AsyncCommands};
|
|
||||||
|
|
||||||
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,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::presence::operations::__delete_set_sessions;
|
|
||||||
|
|
||||||
use self::entry::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) {
|
|
||||||
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();
|
|
||||||
|
|
||||||
// Return whether this was the first session.
|
|
||||||
let was_empty = entry.is_empty();
|
|
||||||
info!("User ID {} just came online.", &user_id);
|
|
||||||
|
|
||||||
// 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 to region set in case of failure.
|
|
||||||
__add_to_set_sessions(&mut conn, ®ION_KEY, user_id, session_id).await;
|
|
||||||
(was_empty, session_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Delete existing presence session
|
|
||||||
pub async fn presence_delete_session(user_id: &str, session_id: u8) -> 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,
|
|
||||||
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;
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
if !skip_region {
|
|
||||||
__remove_from_set_sessions(&mut conn, ®ION_KEY, user_id, session_id).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if is_empty {
|
|
||||||
info!("User ID {} just went offline.", &user_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
is_empty
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check whether a given user ID is online
|
|
||||||
pub async fn presence_is_online(user_id: &str) -> bool {
|
|
||||||
if let Ok(mut conn) = get_connection().await {
|
|
||||||
conn.exists(user_id).await.unwrap_or(false)
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check whether a set of users is online, returns a set of the online user IDs
|
|
||||||
pub async fn presence_filter_online(user_ids: &'_ [String]) -> HashSet<String> {
|
|
||||||
// Ignore empty list immediately, to save time.
|
|
||||||
let mut set = HashSet::new();
|
|
||||||
if user_ids.is_empty() {
|
|
||||||
return set;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
if user_ids.len() == 1 {
|
|
||||||
if presence_is_online(&user_ids[0]).await {
|
|
||||||
set.insert(user_ids[0].to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
return set;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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();
|
|
||||||
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() {
|
|
||||||
set.insert(user_ids[i].to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
set
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reset any stale presence data
|
|
||||||
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;
|
|
||||||
if !sessions.is_empty() {
|
|
||||||
info!(
|
|
||||||
"Cleaning up {} sessions, this may take a while...",
|
|
||||||
sessions.len()
|
|
||||||
);
|
|
||||||
|
|
||||||
// Iterate and delete each session, this will
|
|
||||||
// also send out any relevant events.
|
|
||||||
for session in sessions {
|
|
||||||
let parts = session.split(':').collect::<Vec<&str>>();
|
|
||||||
if let (Some(user_id), Some(session_id)) = (parts.first(), parts.get(1)) {
|
|
||||||
if let Ok(session_id) = session_id.parse() {
|
|
||||||
presence_delete_session_internal(user_id, session_id, true).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then clear the set in Redis.
|
|
||||||
__delete_set_sessions(&mut conn, region_id).await;
|
|
||||||
|
|
||||||
info!("Clean up complete.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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)
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.map(|entry| bincode::deserialize(&entry[..]).unwrap())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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}"))
|
|
||||||
.await
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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}"))
|
|
||||||
.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();
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
use crate::Result;
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
pub trait AbstractMigrations: Sync + Send {
|
|
||||||
async fn migrate_database(&self) -> Result<()>;
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
mod admin {
|
mod admin {
|
||||||
pub mod migrations;
|
|
||||||
pub mod stats;
|
pub mod stats;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,7 +32,6 @@ mod safety {
|
|||||||
pub mod snapshot;
|
pub mod snapshot;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub use admin::migrations::AbstractMigrations;
|
|
||||||
pub use admin::stats::AbstractStats;
|
pub use admin::stats::AbstractStats;
|
||||||
|
|
||||||
pub use media::attachment::AbstractAttachment;
|
pub use media::attachment::AbstractAttachment;
|
||||||
@@ -59,7 +57,6 @@ pub use safety::snapshot::AbstractSnapshot;
|
|||||||
pub trait AbstractDatabase:
|
pub trait AbstractDatabase:
|
||||||
Sync
|
Sync
|
||||||
+ Send
|
+ Send
|
||||||
+ AbstractMigrations
|
|
||||||
+ AbstractStats
|
+ AbstractStats
|
||||||
+ AbstractAttachment
|
+ AbstractAttachment
|
||||||
+ AbstractEmoji
|
+ AbstractEmoji
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ pub fn setup_logging(release: &'static str) -> sentry::ClientInitGuard {
|
|||||||
info!("Starting {release}");
|
info!("Starting {release}");
|
||||||
|
|
||||||
sentry::init((
|
sentry::init((
|
||||||
"https://62fd0e02c5354905b4e286757f4beb16@sentry.insert.moe/4",
|
"https://d1d2a6f15c6245a987c532bbbcb30a04@glitchtip.insert.moe/2",
|
||||||
sentry::ClientOptions {
|
sentry::ClientOptions {
|
||||||
release: Some(release.into()),
|
release: Some(release.into()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
use futures::future::join;
|
use futures::future::join;
|
||||||
|
use revolt_presence::is_online;
|
||||||
use rocket::request::FromParam;
|
use rocket::request::FromParam;
|
||||||
use schemars::schema::{InstanceType, Schema, SchemaObject, SingleOrVec};
|
use schemars::schema::{InstanceType, Schema, SchemaObject, SingleOrVec};
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::models::{Bot, Channel, Emoji, Invite, Member, Message, Server, ServerBan, User, Webhook, Report};
|
use crate::models::{
|
||||||
use crate::presence::presence_is_online;
|
Bot, Channel, Emoji, Invite, Member, Message, Report, Server, ServerBan, User, Webhook
|
||||||
|
};
|
||||||
use crate::{Database, Error, Result};
|
use crate::{Database, Error, Result};
|
||||||
|
|
||||||
/// Reference to some object in the database
|
/// Reference to some object in the database
|
||||||
@@ -23,7 +25,7 @@ impl Ref {
|
|||||||
|
|
||||||
/// Fetch user from Ref
|
/// Fetch user from Ref
|
||||||
pub async fn as_user(&self, db: &Database) -> Result<User> {
|
pub async fn as_user(&self, db: &Database) -> Result<User> {
|
||||||
let (user, online) = join(db.fetch_user(&self.id), presence_is_online(&self.id)).await;
|
let (user, online) = join(db.fetch_user(&self.id), is_online(&self.id)).await;
|
||||||
let mut user = user?;
|
let mut user = user?;
|
||||||
user.online = Some(online);
|
user.online = Some(online);
|
||||||
Ok(user)
|
Ok(user)
|
||||||
|
|||||||
@@ -19,6 +19,12 @@ pub enum Error {
|
|||||||
/// This error was not labeled :(
|
/// This error was not labeled :(
|
||||||
LabelMe,
|
LabelMe,
|
||||||
|
|
||||||
|
/// Core crate error
|
||||||
|
Core {
|
||||||
|
#[serde(flatten)]
|
||||||
|
error: revolt_result::Error,
|
||||||
|
},
|
||||||
|
|
||||||
// ? Onboarding related errors
|
// ? Onboarding related errors
|
||||||
AlreadyOnboarded,
|
AlreadyOnboarded,
|
||||||
|
|
||||||
@@ -39,13 +45,13 @@ pub enum Error {
|
|||||||
CannotEditMessage,
|
CannotEditMessage,
|
||||||
CannotJoinCall,
|
CannotJoinCall,
|
||||||
TooManyAttachments {
|
TooManyAttachments {
|
||||||
max: usize
|
max: usize,
|
||||||
},
|
},
|
||||||
TooManyReplies {
|
TooManyReplies {
|
||||||
max: usize
|
max: usize,
|
||||||
},
|
},
|
||||||
TooManyChannels {
|
TooManyChannels {
|
||||||
max: usize
|
max: usize,
|
||||||
},
|
},
|
||||||
EmptyMessage,
|
EmptyMessage,
|
||||||
PayloadTooLarge,
|
PayloadTooLarge,
|
||||||
@@ -64,10 +70,10 @@ pub enum Error {
|
|||||||
max: usize,
|
max: usize,
|
||||||
},
|
},
|
||||||
TooManyEmoji {
|
TooManyEmoji {
|
||||||
max: usize
|
max: usize,
|
||||||
},
|
},
|
||||||
TooManyRoles {
|
TooManyRoles {
|
||||||
max: usize
|
max: usize,
|
||||||
},
|
},
|
||||||
|
|
||||||
// ? Bot related errors
|
// ? Bot related errors
|
||||||
@@ -135,6 +141,11 @@ impl Error {
|
|||||||
error: validation_error,
|
error: validation_error,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a error from core error
|
||||||
|
pub fn from_core(error: revolt_result::Error) -> Error {
|
||||||
|
Error::Core { error }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Result type with custom Error
|
/// Result type with custom Error
|
||||||
@@ -145,6 +156,7 @@ impl<'r> Responder<'r, 'static> for Error {
|
|||||||
fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> {
|
fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> {
|
||||||
let status = match self {
|
let status = match self {
|
||||||
Error::LabelMe => Status::InternalServerError,
|
Error::LabelMe => Status::InternalServerError,
|
||||||
|
Error::Core { .. } => Status::InternalServerError,
|
||||||
|
|
||||||
Error::AlreadyOnboarded => Status::Forbidden,
|
Error::AlreadyOnboarded => Status::Forbidden,
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
version: "3.3"
|
version: "3.3"
|
||||||
services:
|
services:
|
||||||
|
# Redis
|
||||||
|
redis:
|
||||||
|
image: eqalpha/keydb
|
||||||
|
ports:
|
||||||
|
- "6379:6379"
|
||||||
|
|
||||||
# MongoDB
|
# MongoDB
|
||||||
database:
|
database:
|
||||||
image: mongo
|
image: mongo
|
||||||
|
|||||||
6
justfile
Normal file
6
justfile
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
publish:
|
||||||
|
cargo publish --package revolt-result
|
||||||
|
cargo publish --package revolt-permissions
|
||||||
|
cargo publish --package revolt-database
|
||||||
|
cargo publish --package revolt-presence
|
||||||
|
cargo publish --package revolt-models
|
||||||
62
scripts/build-image-layer.sh
Normal file
62
scripts/build-image-layer.sh
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
#!/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 \
|
||||||
|
crates/core/database/src \
|
||||||
|
crates/core/models/src \
|
||||||
|
crates/core/permissions/src \
|
||||||
|
crates/core/presence/src \
|
||||||
|
crates/core/result/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 |
|
||||||
|
tee crates/core/database/src/lib.rs |
|
||||||
|
tee crates/core/models/src/lib.rs |
|
||||||
|
tee crates/core/permissions/src/lib.rs |
|
||||||
|
tee crates/core/presence/src/lib.rs |
|
||||||
|
tee crates/core/result/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 \
|
||||||
|
crates/core/database/src/lib.rs \
|
||||||
|
crates/core/models/src/lib.rs \
|
||||||
|
crates/core/permissions/src/lib.rs \
|
||||||
|
crates/core/presence/src/lib.rs \
|
||||||
|
crates/core/result/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