forked from jmug/stoatchat
Compare commits
157
Commits
0.5.0
...
0.5.3-alpha.6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a4e290489 | ||
|
|
193846e289 | ||
|
|
7421ffc8a2 | ||
|
|
2ef6b28029 | ||
|
|
39a759396f | ||
|
|
e5e0031cad | ||
|
|
211d74bb47 | ||
|
|
0dcf8cb281 | ||
|
|
24a20a8719 | ||
|
|
37d45889ec | ||
|
|
8e9991a2de | ||
|
|
a6d49d388f | ||
|
|
6eee1bc669 | ||
|
|
f8e9924f18 | ||
|
|
6e1f3a79cf | ||
|
|
b1f5ba32ff | ||
|
|
6e70a39fce | ||
|
|
53aa2818b4 | ||
|
|
00a7bcb958 | ||
|
|
edcb7c50ce | ||
|
|
2e40927457 | ||
|
|
e636879065 | ||
|
|
46c4de8c68 | ||
|
|
6e46362d13 | ||
|
|
a2bfaa5791 | ||
|
|
e2da00e6b0 | ||
|
|
839b66109c | ||
|
|
7f3141eec4 | ||
|
|
536e335b25 | ||
|
|
4b8205b75b | ||
|
|
3cbc548dd8 | ||
|
|
6a44a00eeb | ||
|
|
2660b96250 | ||
|
|
b4db321d0e | ||
|
|
0eb52d32ef | ||
|
|
9b6893ca48 | ||
|
|
8aa92564fc | ||
|
|
ddfc0a5f86 | ||
|
|
ad364320c8 | ||
|
|
4f1d841312 | ||
|
|
405f45d0f7 | ||
|
|
0b0249f544 | ||
|
|
d0cb73106d | ||
|
|
5d3fef3bfa | ||
|
|
8bb5fb25dd | ||
|
|
fa8eb874d0 | ||
|
|
06f968022d | ||
|
|
65165b1f8e | ||
|
|
6a29c7cf03 | ||
|
|
8443cfab00 | ||
|
|
99a6e19cfc | ||
|
|
77422f023e | ||
|
|
b99807cf2a | ||
|
|
fc5d14d4aa | ||
|
|
fcb59b2493 | ||
|
|
2f452b15b1 | ||
|
|
602a65f809 | ||
|
|
88a1d46bcc | ||
|
|
d8d0d93a21 | ||
|
|
fe085ef100 | ||
|
|
8813515786 | ||
|
|
6813fdd513 | ||
|
|
175d257e34 | ||
|
|
5328c66cbb | ||
|
|
862f57235c | ||
|
|
835a32932f | ||
|
|
03cb48ed00 | ||
|
|
dbfed52f17 | ||
|
|
43b75a2310 | ||
|
|
5a17038012 | ||
|
|
f6b55574b1 | ||
|
|
b5aa0ee971 | ||
|
|
697132082a | ||
|
|
1a847d0084 | ||
|
|
17a378494c | ||
|
|
6c6a1508dc | ||
|
|
b15e07c707 | ||
|
|
ffa721117e | ||
|
|
c29f6d830c | ||
|
|
effe3763da | ||
|
|
b0d2e99588 | ||
|
|
1ecd74eb6c | ||
|
|
ceb35e58ce | ||
|
|
31ebf19cf9 | ||
|
|
40a4cf244d | ||
|
|
6d2d3003bd | ||
|
|
302c3e0d4b | ||
|
|
867962cc3c | ||
|
|
bcfde79745 | ||
|
|
326e539ad3 | ||
|
|
c632341eb7 | ||
|
|
6de1d1f98b | ||
|
|
721ca3a4f5 | ||
|
|
48d2fbe5ae | ||
|
|
c1d03960ed | ||
|
|
7c7573d3cf | ||
|
|
8ca3e50d91 | ||
|
|
906dd3fb04 | ||
|
|
08fe01a7f2 | ||
|
|
d972395e43 | ||
|
|
5179504ad9 | ||
|
|
d4f7c4a77d | ||
|
|
28c4312880 | ||
|
|
7e84d2671a | ||
|
|
bffac8c4c0 | ||
|
|
3b5bebab78 | ||
|
|
e35a6b12e1 | ||
|
|
f04137d50f | ||
|
|
b3bdf403cf | ||
|
|
bd3606176a | ||
|
|
a71bfa908c | ||
|
|
9f749cfdf8 | ||
|
|
93610ebbc3 | ||
|
|
084d71f050 | ||
|
|
e70f848f21 | ||
|
|
e7065cbf1d | ||
|
|
fdba2d9e15 | ||
|
|
8890e3bab0 | ||
|
|
f09fde616a | ||
|
|
6fe0725799 | ||
|
|
382972ea22 | ||
|
|
9fec4e48c0 | ||
|
|
33262cc703 | ||
|
|
e655f53641 | ||
|
|
74b4c6bf2a | ||
|
|
3c0d736f1e | ||
|
|
8f6e5be1d4 | ||
|
|
7d3ce0c96a | ||
|
|
c6de871ad3 | ||
|
|
f32eaa98d4 | ||
|
|
d4743b1453 | ||
|
|
dd65b9f358 | ||
|
|
ec59d0e9a9 | ||
|
|
2407d4da3a | ||
|
|
a5f7314f97 | ||
|
|
deb2344428 | ||
|
|
37bb0f397f | ||
|
|
ca04f4d510 | ||
|
|
682b6cbd9f | ||
|
|
1a8006b3e4 | ||
|
|
27a9fc3e21 | ||
|
|
1872432725 | ||
|
|
6f8802ab5f | ||
|
|
fe821b8283 | ||
|
|
a4c227ce41 | ||
|
|
edd8ef8a1d | ||
|
|
96c9190fa8 | ||
|
|
e452d6a362 | ||
|
|
57f81e018c | ||
|
|
31c93b9954 | ||
|
|
51dc25c94c | ||
|
|
a017872c99 | ||
|
|
f35a1049da | ||
|
|
07e74e1074 | ||
|
|
35b0c69cdb | ||
|
|
b45ea266d8 | ||
|
|
9a417aa6ad |
No files matched your search
@@ -0,0 +1,2 @@
|
|||||||
|
ko_fi: insertish
|
||||||
|
custom: https://insrt.uk/donate
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
name: Docker
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- "master"
|
||||||
|
tags:
|
||||||
|
- "*"
|
||||||
|
paths-ignore:
|
||||||
|
- ".github/**"
|
||||||
|
- "!.github/workflows/docker.yml"
|
||||||
|
- ".vscode/**"
|
||||||
|
- ".gitignore"
|
||||||
|
- ".gitlab-ci.yml"
|
||||||
|
- "LICENSE"
|
||||||
|
- "README"
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- "master"
|
||||||
|
paths:
|
||||||
|
- "Dockerfile"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
architecture: [linux/amd64]
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: "recursive"
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v1
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v1
|
||||||
|
- name: Cache Docker layers
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: /tmp/.buildx-cache/${{ matrix.architecture }}
|
||||||
|
key: ${{ runner.os }}-buildx-${{ matrix.architecture }}-${{ github.sha }}
|
||||||
|
- name: Build
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
platforms: ${{ matrix.architecture }}
|
||||||
|
cache-from: type=local,src=/tmp/.buildx-cache/${{ matrix.architecture }}
|
||||||
|
cache-to: type=local,dest=/tmp/.buildx-cache-new/${{ matrix.architecture }},mode=max
|
||||||
|
- name: Move cache
|
||||||
|
run: |
|
||||||
|
rm -rf /tmp/.buildx-cache/${{ matrix.architecture }}
|
||||||
|
mv /tmp/.buildx-cache-new/${{ matrix.architecture }} /tmp/.buildx-cache/${{ matrix.architecture }}
|
||||||
|
|
||||||
|
publish:
|
||||||
|
needs: [test]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: "recursive"
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v1
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v1
|
||||||
|
- name: Cache amd64 Docker layers
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: /tmp/.buildx-cache/linux/amd64
|
||||||
|
key: ${{ runner.os }}-buildx-linux/amd64-${{ github.sha }}
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v3
|
||||||
|
with:
|
||||||
|
images: revoltchat/server, ghcr.io/revoltchat/server
|
||||||
|
- name: Login to DockerHub
|
||||||
|
uses: docker/login-action@v1
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Login to Github Container Registry
|
||||||
|
uses: docker/login-action@v1
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Build and publish
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: true
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
cache-from: type=local,src=/tmp/.buildx-cache/linux/amd64
|
||||||
|
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
|
||||||
|
- name: Move cache
|
||||||
|
run: |
|
||||||
|
rm -rf /tmp/.buildx-cache
|
||||||
|
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
name: Rust build and test
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ master ]
|
||||||
|
|
||||||
|
env:
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check:
|
||||||
|
name: Rust project
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Install latest nightly
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: nightly
|
||||||
|
override: true
|
||||||
|
components: rustfmt, clippy
|
||||||
|
|
||||||
|
- name: Run cargo build
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: build
|
||||||
|
|
||||||
|
- name: Run cargo test
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: test
|
||||||
Generated
+1215
-758
File diff suppressed because it is too large.
Load diff
+42
-27
@@ -10,39 +10,54 @@ edition = "2018"
|
|||||||
# 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
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
regex = "1"
|
# Utility
|
||||||
md5 = "0.7.0"
|
url = "2.2.2"
|
||||||
log = "0.4.11"
|
log = "0.4.11"
|
||||||
ulid = "0.4.1"
|
|
||||||
rand = "0.7.3"
|
|
||||||
time = "0.2.16"
|
|
||||||
nanoid = "0.4.0"
|
|
||||||
base64 = "0.13.0"
|
|
||||||
linkify = "0.6.0"
|
|
||||||
dotenv = "0.15.0"
|
dotenv = "0.15.0"
|
||||||
futures = "0.3.8"
|
linkify = "0.6.0"
|
||||||
chrono = "0.4.15"
|
once_cell = "1.4.1"
|
||||||
|
env_logger = "0.7.1"
|
||||||
|
lazy_static = "1.4.0"
|
||||||
|
ctrlc = { version = "3.0", features = ["termination"] }
|
||||||
|
|
||||||
|
# Lang. Utilities
|
||||||
|
regex = "1"
|
||||||
num_enum = "0.5.1"
|
num_enum = "0.5.1"
|
||||||
impl_ops = "0.1.1"
|
impl_ops = "0.1.1"
|
||||||
web-push = "0.7.2"
|
|
||||||
once_cell = "1.4.1"
|
|
||||||
bitfield = "0.13.2"
|
bitfield = "0.13.2"
|
||||||
env_logger = "0.7.1"
|
phf = { version = "0.9.0", features = ["macros"] }
|
||||||
|
|
||||||
|
# ID / key generation
|
||||||
|
ulid = "0.4.1"
|
||||||
|
nanoid = "0.4.0"
|
||||||
|
base64 = "0.13.0"
|
||||||
|
|
||||||
|
# serde
|
||||||
serde_json = "1.0.57"
|
serde_json = "1.0.57"
|
||||||
lazy_static = "1.4.0"
|
|
||||||
urlencoding = "1.1.1"
|
|
||||||
many-to-many = "0.1.2"
|
|
||||||
lettre = "0.10.0-alpha.1"
|
|
||||||
reqwest = { version = "0.10.8", features = ["json"] }
|
|
||||||
serde = { version = "1.0.115", features = ["derive"] }
|
serde = { version = "1.0.115", features = ["derive"] }
|
||||||
validator = { version = "0.11", features = ["derive"] }
|
validator = { version = "0.11", features = ["derive"] }
|
||||||
ctrlc = { version = "3.0", features = ["termination"] }
|
rmp-serde = { git = "https://github.com/3Hren/msgpack-rust", rev = "5bf2c24203ad422233cf35b7b7bfad9f7e811814" }
|
||||||
hive_pubsub = { version = "0.4.4", features = ["mongo"] }
|
|
||||||
async-std = { version = "1.8.0", features = ["tokio02", "attributes"] }
|
# async
|
||||||
|
futures = "0.3.8"
|
||||||
|
chrono = "0.4.15"
|
||||||
|
reqwest = { version = "0.11.4", features = ["json"] }
|
||||||
|
async-std = { version = "1.8.0", features = ["tokio1", "tokio02", "attributes"] }
|
||||||
|
|
||||||
|
# internal util
|
||||||
|
web-push = "0.7.2"
|
||||||
|
many-to-many = "0.1.2"
|
||||||
|
lettre = "0.10.0-alpha.4"
|
||||||
|
rauth = { git = "https://github.com/insertish/rauth", rev = "775a6f3ffb9ffc0e382af32fac2924e9a707e890" }
|
||||||
|
hive_pubsub = { git = "https://gitlab.insrt.uk/insert/hive", rev = "b0f3db9d33990530d7640d4bbb309c8d9eb5c0cf", features = ["redis-backend"] }
|
||||||
|
|
||||||
|
# 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
|
||||||
async-tungstenite = { version = "0.10.0", features = ["async-std-runtime"] }
|
async-tungstenite = { version = "0.10.0", features = ["async-std-runtime"] }
|
||||||
rocket_cors = { git = "https://github.com/insertish/rocket_cors", branch = "master" }
|
rocket = { version = "0.5.0-rc.1", default-features = false, features = ["json"] }
|
||||||
mongodb = { version = "1.1.1", features = ["tokio-runtime"], default-features = false }
|
mongodb = { version = "1.2.2", features = ["tokio-runtime"], default-features = false }
|
||||||
rauth = { git = "https://gitlab.insrt.uk/insert/rauth", rev = "1db825a0059639144ea8802f6d51b5d27c5b986e" }
|
rocket_cors = { git = "https://github.com/lawliet89/rocket_cors", rev = "5843861a88958c16bfaa0b40f0d8910772bcd2f6" }
|
||||||
rocket_contrib = { git = "https://github.com/SergioBenitez/Rocket", rev = "031948c1daaa146128d8a435be116476f2adde00" }
|
|
||||||
rocket_prometheus = { git = "https://github.com/insertish/rocket_prometheus", rev = "3d825aedb42793246c306a81fe67c5b187948983" }
|
|
||||||
rocket = { git = "https://github.com/SergioBenitez/Rocket", rev = "031948c1daaa146128d8a435be116476f2adde00", default-features = false }
|
|
||||||
+6
-6
@@ -1,5 +1,5 @@
|
|||||||
# Build Stage
|
# Build Stage
|
||||||
FROM ekidd/rust-musl-builder:nightly-2021-01-01 AS builder
|
FROM rustlang/rust:nightly-slim AS builder
|
||||||
USER 0:0
|
USER 0:0
|
||||||
WORKDIR /home/rust/src
|
WORKDIR /home/rust/src
|
||||||
|
|
||||||
@@ -7,16 +7,16 @@ RUN USER=root cargo new --bin revolt
|
|||||||
WORKDIR /home/rust/src/revolt
|
WORKDIR /home/rust/src/revolt
|
||||||
COPY Cargo.toml Cargo.lock ./
|
COPY Cargo.toml Cargo.lock ./
|
||||||
COPY src/bin/dummy.rs ./src/bin/dummy.rs
|
COPY src/bin/dummy.rs ./src/bin/dummy.rs
|
||||||
RUN cargo build --release --bin dummy
|
RUN apt-get update && apt-get install -y libssl-dev pkg-config && cargo build --release --bin dummy
|
||||||
|
|
||||||
COPY assets/templates ./assets/templates
|
COPY assets/templates ./assets/templates
|
||||||
COPY src ./src
|
COPY src ./src
|
||||||
RUN cargo build --release
|
RUN cargo install --locked --path .
|
||||||
|
|
||||||
# Bundle Stage
|
# Bundle Stage
|
||||||
FROM alpine:latest
|
FROM debian:buster-slim
|
||||||
RUN apk update && apk add ca-certificates && rm -rf /var/cache/apk/*
|
RUN apt-get update && apt-get install -y ca-certificates
|
||||||
COPY --from=builder /home/rust/src/revolt/target/x86_64-unknown-linux-musl/release/revolt ./
|
COPY --from=builder /usr/local/cargo/bin/revolt ./
|
||||||
COPY assets ./assets
|
COPY assets ./assets
|
||||||
EXPOSE 8000
|
EXPOSE 8000
|
||||||
EXPOSE 9000
|
EXPOSE 9000
|
||||||
|
|||||||
@@ -1,20 +1,45 @@
|
|||||||

|
# Delta
|
||||||
|
|
||||||
Delta is a **blazing fast API server** built with Rust for the REVOLT platform.
|
## Description
|
||||||
|
|
||||||
|
Delta is a blazing fast API server built with Rust for Revolt.
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
|
||||||
Features:
|
|
||||||
- Robust and efficient API routes for running a chat platform.
|
- Robust and efficient API routes for running a chat platform.
|
||||||
- Distributed notification system, allowing any node to be seamlessly connected.
|
- Distributed notification system, allowing any node to be seamlessly connected.
|
||||||
- Simple deployment, based mostly on pure Rust code and libraries.
|
- Simple deployment, based mostly on pure Rust code and libraries.
|
||||||
- Hooks up to a MongoDB deployment, provide URI and no extra work needed.
|
- Hooks up to a MongoDB deployment, provide URI and no extra work needed.
|
||||||
|
|
||||||
## Docker Helper Scripts
|
## Stack
|
||||||
|
|
||||||
If you have Docker installed, you can use the helper scripts to deploy Revolt in your development environment.
|
- [Rocket](https://rocket.rs/) (REST)
|
||||||
|
- [Async Tungstenite](https://github.com/sdroege/async-tungstenite) (WebSockets)
|
||||||
|
- [MongoDB](https://mongodb.com/)
|
||||||
|
|
||||||
```bash
|
## Resources
|
||||||
./build.sh # build Docker image
|
|
||||||
./run.sh # run Docker container
|
### Revolt
|
||||||
./monitor.sh # view container logs
|
|
||||||
./remove.sh # kill and remove container
|
- [Revolt Project Board](https://github.com/revoltchat/revolt/discussions) (Submit feature requests here)
|
||||||
```
|
- [Revolt Testers Server](https://app.revolt.chat/invite/Testers)
|
||||||
|
- [Contribution Guide](https://developers.revolt.chat/contributing)
|
||||||
|
|
||||||
|
## CLI Commands
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
| ------------------ | ----------------------------------------------------------------------------------------- |
|
||||||
|
| `./publish.sh` | Publish a Docker Image. |
|
||||||
|
| `./set_version.sh` | Update the version. **Not intended for PR use.** |
|
||||||
|
| `cargo build` | Build/compile Delta. |
|
||||||
|
| `cargo run` | Run Delta. |
|
||||||
|
| `cargo fmt` | Format Delta. Not intended for PR use to avoid accidentally formatting unformatted files. |
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
The contribution guide is located at [developers.revolt.chat/contributing](https://developers.revolt.chat/contributing).
|
||||||
|
Please note that a pull request should only take care of one issue so that we can review it quickly.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Delta is licensed under the [GNU Affero General Public License v3.0](https://github.com/revoltchat/delta/blob/master/LICENSE).
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.9 KiB |
@@ -11,14 +11,14 @@
|
|||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body style="margin: 0; padding: 0;">
|
<body style="margin: 0; padding: 0;">
|
||||||
<table role="presentation" border="0" cellpadding="0" cellspacing="0" width="100%">
|
<table border="0" cellpadding="0" cellspacing="0" width="100%">
|
||||||
<tr>
|
<tr>
|
||||||
<td style="padding: 20px 0 30px 0;">
|
<td style="padding: 20px 0 30px 0;">
|
||||||
|
|
||||||
<table align="center" border="0" cellpadding="0" cellspacing="0" width="600" style="border-collapse: collapse; border: 1px solid #cccccc;">
|
<table align="center" border="0" cellpadding="0" cellspacing="0" width="600" style="border-collapse: collapse; border: 1px solid #cccccc;">
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center" bgcolor="#ff4654">
|
<td align="center" bgcolor="#ff4654">
|
||||||
<img src="https://revolt.chat/header.png" alt="REVOLT logo" width="600" height="168" style="display: block;" />
|
<img src="https://revolt.chat/header.png" alt="Revolt logo" width="600" height="168" style="display: block;" />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -31,11 +31,11 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td style="color: #153643; font-family: Arial, sans-serif; font-size: 16px; line-height: 24px; padding: 20px 0 0 0;">
|
<td style="color: #153643; font-family: Arial, sans-serif; font-size: 16px; line-height: 24px; padding: 20px 0 0 0;">
|
||||||
<p style="margin: 0;">
|
|
||||||
Reset your password by <a href="{{url}}">clicking here</a>.
|
|
||||||
</p>
|
|
||||||
<p>
|
<p>
|
||||||
Or by manually navigating to the URL: {{url}}
|
You requested a password reset, if you didn't perform this action you can safely ignore this email.
|
||||||
|
</p>
|
||||||
|
<p style="margin: 0;">
|
||||||
|
Reset your password by navigating to <a href="{{url}}">{{url}}</a>.
|
||||||
</p>
|
</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -47,7 +47,8 @@
|
|||||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;">
|
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;">
|
||||||
<tr>
|
<tr>
|
||||||
<td style="color: #ffffff; font-family: Arial, sans-serif; font-size: 14px;">
|
<td style="color: #ffffff; font-family: Arial, sans-serif; font-size: 14px;">
|
||||||
<p style="margin: 0;">Sent by REVOLT. · <a style="color: white;" href="https://revolt.chat">Website</a> · <a style="color: white;" href="https://gitlab.insrt.uk/revolt">Source Code</a></p>
|
<p style="margin: 0;">Sent by Revolt. · Website: <a style="color: white;" href="https://revolt.chat">https://revolt.chat</a></p>
|
||||||
|
<p>Revolt is a user-first chat platform built with modern web technologies.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
You requested a password reset, if you did not perform this action you can safely ignore this email.
|
||||||
|
|
||||||
|
Reset your password here: {{url}}
|
||||||
|
|
||||||
|
Sent by Revolt.
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<h2>Reset your password.</h2>
|
||||||
|
<p>
|
||||||
|
You requested a password reset, if you did not perform this action you can safely ignore this email.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Reset your password here: <a href="{{url}}">{{url}}</a>
|
||||||
|
</p>
|
||||||
|
<br/>
|
||||||
|
<p>
|
||||||
|
Sent by Revolt. · Website: <a href="https://revolt.chat">https://revolt.chat</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Revolt is a user-first chat platform built with modern web technologies.
|
||||||
|
</p>
|
||||||
@@ -11,14 +11,14 @@
|
|||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body style="margin: 0; padding: 0;">
|
<body style="margin: 0; padding: 0;">
|
||||||
<table role="presentation" border="0" cellpadding="0" cellspacing="0" width="100%">
|
<table border="0" cellpadding="0" cellspacing="0" width="100%">
|
||||||
<tr>
|
<tr>
|
||||||
<td style="padding: 20px 0 30px 0;">
|
<td style="padding: 20px 0 30px 0;">
|
||||||
|
|
||||||
<table align="center" border="0" cellpadding="0" cellspacing="0" width="600" style="border-collapse: collapse; border: 1px solid #cccccc;">
|
<table align="center" border="0" cellpadding="0" cellspacing="0" width="600" style="border-collapse: collapse; border: 1px solid #cccccc;">
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center" bgcolor="#ff4654">
|
<td align="center" bgcolor="#ff4654">
|
||||||
<img src="https://revolt.chat/header.png" alt="REVOLT logo" width="600" height="168" style="display: block;" />
|
<img src="https://revolt.chat/header.png" alt="Revolt logo" width="600" height="168" style="display: block;" />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -26,16 +26,16 @@
|
|||||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;">
|
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;">
|
||||||
<tr>
|
<tr>
|
||||||
<td style="color: #153643; font-family: Arial, sans-serif;">
|
<td style="color: #153643; font-family: Arial, sans-serif;">
|
||||||
<h1 style="font-size: 24px; margin: 0;">Verify your account!</h1>
|
<h1 style="font-size: 24px; margin: 0;">You're almost there!</h1>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td style="color: #153643; font-family: Arial, sans-serif; font-size: 16px; line-height: 24px; padding: 20px 0 0 0;">
|
<td style="color: #153643; font-family: Arial, sans-serif; font-size: 16px; line-height: 24px; padding: 20px 0 0 0;">
|
||||||
<p style="margin: 0;">
|
|
||||||
Please verify your account by <a href="{{url}}">clicking here</a>.
|
|
||||||
</p>
|
|
||||||
<p>
|
<p>
|
||||||
Or by manually navigating to the URL: {{url}}
|
Verify your account to be able to log into the platform. If you didn't perform this action you can safely ignore this email.
|
||||||
|
</p>
|
||||||
|
<p style="margin: 0;">
|
||||||
|
Please verify your account by navigating to <a href="{{url}}">{{url}}</a>.
|
||||||
</p>
|
</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -47,7 +47,8 @@
|
|||||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;">
|
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;">
|
||||||
<tr>
|
<tr>
|
||||||
<td style="color: #ffffff; font-family: Arial, sans-serif; font-size: 14px;">
|
<td style="color: #ffffff; font-family: Arial, sans-serif; font-size: 14px;">
|
||||||
<p style="margin: 0;">Sent by REVOLT. · <a style="color: white;" href="https://revolt.chat">Website</a> · <a style="color: white;" href="https://gitlab.insrt.uk/revolt">Source Code</a></p>
|
<p style="margin: 0;">Sent by Revolt. · Website: <a style="color: white;" href="https://revolt.chat">https://revolt.chat</a></p>
|
||||||
|
<p>Revolt is a user-first chat platform built with modern web technologies.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
You're almost there!
|
||||||
|
If you did not perform this action you can safely ignore this email.
|
||||||
|
|
||||||
|
Please verify your account here: {{url}}
|
||||||
|
|
||||||
|
Sent by Revolt.
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<h2>You're almost there!</h2>
|
||||||
|
<p>
|
||||||
|
Verify your account to be able to log into the platform.<br/>
|
||||||
|
If you did not perform this action you can safely ignore this email.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Please verify your account here: <a href="{{url}}">{{url}}</a>
|
||||||
|
</p>
|
||||||
|
<br/>
|
||||||
|
<p>
|
||||||
|
Sent by Revolt. · Website: <a href="https://revolt.chat">https://revolt.chat</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Revolt is a user-first chat platform built with modern web technologies.
|
||||||
|
</p>
|
||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
export version=0.5.0-alpha.5
|
export version=0.5.3-alpha.6
|
||||||
echo "pub const VERSION: &str = \"${version}\";" > src/version.rs
|
echo "pub const VERSION: &str = \"${version}\";" > src/version.rs
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct Bot {
|
||||||
|
#[serde(rename = "_id")]
|
||||||
|
pub id: String,
|
||||||
|
pub owner: String,
|
||||||
|
pub token: String,
|
||||||
|
pub public: bool,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub interactions_url: Option<String>,
|
||||||
|
}
|
||||||
@@ -1,22 +1,18 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::database::*;
|
use crate::database::*;
|
||||||
use crate::notifications::events::ClientboundNotification;
|
use crate::notifications::events::ClientboundNotification;
|
||||||
use crate::util::result::{Error, Result};
|
use crate::util::result::{Error, Result};
|
||||||
|
use crate::util::variables::MAX_GROUP_SIZE;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
|
use mongodb::bson::Bson;
|
||||||
use mongodb::{
|
use mongodb::{
|
||||||
bson::{doc, to_document, Document},
|
bson::{doc, to_document, Document},
|
||||||
options::FindOptions,
|
options::FindOptions,
|
||||||
};
|
};
|
||||||
use rocket_contrib::json::JsonValue;
|
use rocket::serde::json::Value;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
||||||
pub struct LastMessage {
|
|
||||||
#[serde(rename = "_id")]
|
|
||||||
id: String,
|
|
||||||
author: String,
|
|
||||||
short: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
#[serde(tag = "channel_type")]
|
#[serde(tag = "channel_type")]
|
||||||
pub enum Channel {
|
pub enum Channel {
|
||||||
@@ -32,7 +28,7 @@ pub enum Channel {
|
|||||||
active: bool,
|
active: bool,
|
||||||
recipients: Vec<String>,
|
recipients: Vec<String>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
last_message: Option<LastMessage>,
|
last_message_id: Option<String>,
|
||||||
},
|
},
|
||||||
Group {
|
Group {
|
||||||
#[serde(rename = "_id")]
|
#[serde(rename = "_id")]
|
||||||
@@ -49,7 +45,13 @@ pub enum Channel {
|
|||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
icon: Option<File>,
|
icon: Option<File>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
last_message: Option<LastMessage>,
|
last_message_id: Option<String>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
permissions: Option<i32>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "entities::server::if_false", default)]
|
||||||
|
nsfw: bool
|
||||||
},
|
},
|
||||||
TextChannel {
|
TextChannel {
|
||||||
#[serde(rename = "_id")]
|
#[serde(rename = "_id")]
|
||||||
@@ -61,10 +63,40 @@ pub enum Channel {
|
|||||||
name: String,
|
name: String,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
description: Option<String>,
|
description: Option<String>,
|
||||||
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
icon: Option<File>,
|
icon: Option<File>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
last_message: Option<String>,
|
last_message_id: Option<String>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
default_permissions: Option<i32>,
|
||||||
|
#[serde(default = "HashMap::new", skip_serializing_if = "HashMap::is_empty")]
|
||||||
|
role_permissions: HashMap<String, i32>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "entities::server::if_false", default)]
|
||||||
|
nsfw: bool
|
||||||
|
},
|
||||||
|
VoiceChannel {
|
||||||
|
#[serde(rename = "_id")]
|
||||||
|
id: String,
|
||||||
|
server: String,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
nonce: Option<String>,
|
||||||
|
|
||||||
|
name: String,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
description: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
icon: Option<File>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
default_permissions: Option<i32>,
|
||||||
|
#[serde(default = "HashMap::new", skip_serializing_if = "HashMap::is_empty")]
|
||||||
|
role_permissions: HashMap<String, i32>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "entities::server::if_false", default)]
|
||||||
|
nsfw: bool
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +106,17 @@ impl Channel {
|
|||||||
Channel::SavedMessages { id, .. }
|
Channel::SavedMessages { id, .. }
|
||||||
| Channel::DirectMessage { id, .. }
|
| Channel::DirectMessage { id, .. }
|
||||||
| Channel::Group { id, .. }
|
| Channel::Group { id, .. }
|
||||||
| Channel::TextChannel { id, .. } => id,
|
| Channel::TextChannel { id, .. }
|
||||||
|
| Channel::VoiceChannel { id, .. } => id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn has_messaging(&self) -> Result<()> {
|
||||||
|
match self {
|
||||||
|
Channel::SavedMessages { .. }
|
||||||
|
| Channel::DirectMessage { .. }
|
||||||
|
| Channel::Group { .. }
|
||||||
|
| Channel::TextChannel { .. } => Ok(()),
|
||||||
|
Channel::VoiceChannel { .. } => Err(Error::InvalidOperation)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,7 +141,7 @@ impl Channel {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn publish_update(&self, data: JsonValue) -> Result<()> {
|
pub async fn publish_update(&self, data: Value) -> Result<()> {
|
||||||
let id = self.id().to_string();
|
let id = self.id().to_string();
|
||||||
ClientboundNotification::ChannelUpdate {
|
ClientboundNotification::ChannelUpdate {
|
||||||
id: id.clone(),
|
id: id.clone(),
|
||||||
@@ -111,11 +153,7 @@ impl Channel {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete(&self) -> Result<()> {
|
pub async fn delete_associated_objects(id: Bson) -> Result<()> {
|
||||||
let id = self.id();
|
|
||||||
let messages = get_collection("messages");
|
|
||||||
|
|
||||||
// Delete any invites.
|
|
||||||
get_collection("channel_invites")
|
get_collection("channel_invites")
|
||||||
.delete_many(
|
.delete_many(
|
||||||
doc! {
|
doc! {
|
||||||
@@ -124,16 +162,21 @@ impl Channel {
|
|||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
.map(|_| ())
|
||||||
.map_err(|_| Error::DatabaseError {
|
.map_err(|_| Error::DatabaseError {
|
||||||
operation: "delete_many",
|
operation: "delete_many",
|
||||||
with: "channel_invites",
|
with: "channel_invites",
|
||||||
})?;
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete_messages(id: Bson) -> Result<()> {
|
||||||
|
let messages = get_collection("messages");
|
||||||
|
|
||||||
// Delete any unreads.
|
// Delete any unreads.
|
||||||
get_collection("channel_unreads")
|
get_collection("channel_unreads")
|
||||||
.delete_many(
|
.delete_many(
|
||||||
doc! {
|
doc! {
|
||||||
"_id.channel": id
|
"_id.channel": &id
|
||||||
},
|
},
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
@@ -147,7 +190,7 @@ impl Channel {
|
|||||||
let message_ids = messages
|
let message_ids = messages
|
||||||
.find(
|
.find(
|
||||||
doc! {
|
doc! {
|
||||||
"channel": id,
|
"channel": &id,
|
||||||
"attachment": {
|
"attachment": {
|
||||||
"$exists": 1
|
"$exists": 1
|
||||||
}
|
}
|
||||||
@@ -198,13 +241,31 @@ impl Channel {
|
|||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
.map(|_| ())
|
||||||
.map_err(|_| Error::DatabaseError {
|
.map_err(|_| Error::DatabaseError {
|
||||||
operation: "delete_many",
|
operation: "delete_many",
|
||||||
with: "messages",
|
with: "messages",
|
||||||
})?;
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete(&self) -> Result<()> {
|
||||||
|
let id = self.id();
|
||||||
|
|
||||||
|
// Delete any invites.
|
||||||
|
Channel::delete_associated_objects(Bson::String(id.to_string())).await?;
|
||||||
|
|
||||||
|
// Delete messages.
|
||||||
|
match &self {
|
||||||
|
Channel::VoiceChannel { .. } => {},
|
||||||
|
_ => {
|
||||||
|
Channel::delete_messages(Bson::String(id.to_string())).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Remove from server object.
|
// Remove from server object.
|
||||||
if let Channel::TextChannel { server, .. } = &self {
|
match &self {
|
||||||
|
Channel::TextChannel { server, .. }
|
||||||
|
| Channel::VoiceChannel { server, .. } => {
|
||||||
let server = Ref::from_unchecked(server.clone()).fetch_server().await?;
|
let server = Ref::from_unchecked(server.clone()).fetch_server().await?;
|
||||||
let mut update = doc! {
|
let mut update = doc! {
|
||||||
"$pull": {
|
"$pull": {
|
||||||
@@ -257,6 +318,8 @@ impl Channel {
|
|||||||
operation: "update_one",
|
operation: "update_one",
|
||||||
with: "servers",
|
with: "servers",
|
||||||
})?;
|
})?;
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally, delete the channel object.
|
// Finally, delete the channel object.
|
||||||
@@ -283,4 +346,53 @@ impl Channel {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn add_to_group(&self, member: String, by_user: String) -> Result<()> {
|
||||||
|
if let Channel::Group { id, recipients, .. } = &self {
|
||||||
|
if recipients.len() >= *MAX_GROUP_SIZE {
|
||||||
|
Err(Error::GroupTooLarge {
|
||||||
|
max: *MAX_GROUP_SIZE,
|
||||||
|
})?
|
||||||
|
}
|
||||||
|
|
||||||
|
if recipients.iter().find(|x| *x == &member).is_some() {
|
||||||
|
Err(Error::AlreadyInGroup)?
|
||||||
|
}
|
||||||
|
|
||||||
|
get_collection("channels")
|
||||||
|
.update_one(
|
||||||
|
doc! {
|
||||||
|
"_id": &id
|
||||||
|
},
|
||||||
|
doc! {
|
||||||
|
"$push": {
|
||||||
|
"recipients": &member
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "update_one",
|
||||||
|
with: "channel",
|
||||||
|
})?;
|
||||||
|
|
||||||
|
ClientboundNotification::ChannelGroupJoin {
|
||||||
|
id: id.clone(),
|
||||||
|
user: member.clone(),
|
||||||
|
}
|
||||||
|
.publish(id.clone());
|
||||||
|
|
||||||
|
Content::SystemMessage(SystemMessage::UserAdded {
|
||||||
|
id: member,
|
||||||
|
by: by_user,
|
||||||
|
})
|
||||||
|
.send_as_system(&self)
|
||||||
|
.await
|
||||||
|
.ok();
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(Error::InvalidOperation)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+134
-108
@@ -1,4 +1,4 @@
|
|||||||
use crate::util::variables::{USE_JANUARY, VAPID_PRIVATE_KEY};
|
use crate::util::variables::{USE_JANUARY, VAPID_PRIVATE_KEY, PUBLIC_URL};
|
||||||
use crate::{
|
use crate::{
|
||||||
database::*,
|
database::*,
|
||||||
notifications::{events::ClientboundNotification, websocket::is_online},
|
notifications::{events::ClientboundNotification, websocket::is_online},
|
||||||
@@ -8,16 +8,71 @@ use crate::{
|
|||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use mongodb::options::UpdateOptions;
|
use mongodb::options::UpdateOptions;
|
||||||
use mongodb::{
|
use mongodb::{
|
||||||
bson::{doc, to_bson, DateTime},
|
bson::{doc, to_bson, DateTime, Document},
|
||||||
options::FindOptions,
|
|
||||||
};
|
};
|
||||||
use rocket_contrib::json::JsonValue;
|
use rauth::entities::{Model, Session};
|
||||||
|
use rocket::serde::json::Value;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use ulid::Ulid;
|
use ulid::Ulid;
|
||||||
use web_push::{
|
use web_push::{ContentEncoding, SubscriptionInfo, SubscriptionKeys, VapidSignatureBuilder, WebPushClient, WebPushMessageBuilder};
|
||||||
ContentEncoding, SubscriptionInfo, VapidSignatureBuilder, WebPushClient, WebPushMessageBuilder,
|
use std::time::SystemTime;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct PushNotification {
|
||||||
|
pub author: String,
|
||||||
|
pub icon: String,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub image: Option<String>,
|
||||||
|
pub body: String,
|
||||||
|
pub tag: String,
|
||||||
|
pub timestamp: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PushNotification {
|
||||||
|
pub async fn new(msg: Message, channel: &Channel) -> Self {
|
||||||
|
let author = Ref::from_unchecked(msg.author.clone())
|
||||||
|
.fetch_user()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let (author, avatar) = if let Ok(author) = author {
|
||||||
|
(Some(author.username), author.avatar)
|
||||||
|
} else {
|
||||||
|
(None, None)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let icon = if let Some(avatar) = avatar {
|
||||||
|
avatar.get_autumn_url()
|
||||||
|
} else {
|
||||||
|
format!("{}/users/{}/default_avatar", PUBLIC_URL.as_str(), msg.author)
|
||||||
|
};
|
||||||
|
|
||||||
|
let image = msg.attachments.map_or(None, |attachments| {
|
||||||
|
attachments
|
||||||
|
.first()
|
||||||
|
.map_or(None, |v| Some(v.get_autumn_url()))
|
||||||
|
});
|
||||||
|
|
||||||
|
let body = match msg.content {
|
||||||
|
Content::Text(body) => body,
|
||||||
|
Content::SystemMessage(sys_msg) => sys_msg.into()
|
||||||
|
};
|
||||||
|
|
||||||
|
let timestamp = SystemTime::now()
|
||||||
|
.duration_since(SystemTime::UNIX_EPOCH)
|
||||||
|
.expect("system time should be valid")
|
||||||
|
.as_secs();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
author: author.unwrap_or_else(|| "Unknown".into()),
|
||||||
|
icon,
|
||||||
|
image,
|
||||||
|
body,
|
||||||
|
tag: channel.id().to_string(),
|
||||||
|
timestamp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
pub enum SystemMessage {
|
pub enum SystemMessage {
|
||||||
@@ -43,6 +98,23 @@ pub enum SystemMessage {
|
|||||||
ChannelIconChanged { by: String },
|
ChannelIconChanged { by: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Into<String> for SystemMessage {
|
||||||
|
fn into(self) -> String {
|
||||||
|
match self {
|
||||||
|
SystemMessage::Text { content } => content,
|
||||||
|
SystemMessage::UserAdded { .. } => "User added to the channel.".to_string(),
|
||||||
|
SystemMessage::UserRemove { .. } => "User removed from the channel.".to_string(),
|
||||||
|
SystemMessage::UserJoined { .. } => "User joined the channel.".to_string(),
|
||||||
|
SystemMessage::UserLeft { .. } => "User left the channel.".to_string(),
|
||||||
|
SystemMessage::UserKicked { .. } => "User kicked from the channel.".to_string(),
|
||||||
|
SystemMessage::UserBanned { .. } => "User banned from the channel.".to_string(),
|
||||||
|
SystemMessage::ChannelRenamed { .. } => "Channel renamed.".to_string(),
|
||||||
|
SystemMessage::ChannelDescriptionChanged { .. } => "Channel description changed.".to_string(),
|
||||||
|
SystemMessage::ChannelIconChanged { .. } => "Channel icon changed.".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
pub enum Content {
|
pub enum Content {
|
||||||
@@ -57,8 +129,9 @@ impl Content {
|
|||||||
target.id().to_string(),
|
target.id().to_string(),
|
||||||
self,
|
self,
|
||||||
None,
|
None,
|
||||||
|
None
|
||||||
)
|
)
|
||||||
.publish(&target)
|
.publish(&target, false)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -81,6 +154,8 @@ pub struct Message {
|
|||||||
pub embeds: Option<Vec<Embed>>,
|
pub embeds: Option<Vec<Embed>>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub mentions: Option<Vec<String>>,
|
pub mentions: Option<Vec<String>>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub replies: Option<Vec<String>>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Message {
|
impl Message {
|
||||||
@@ -89,22 +164,23 @@ impl Message {
|
|||||||
channel: String,
|
channel: String,
|
||||||
content: Content,
|
content: Content,
|
||||||
mentions: Option<Vec<String>>,
|
mentions: Option<Vec<String>>,
|
||||||
|
replies: Option<Vec<String>>,
|
||||||
) -> Message {
|
) -> Message {
|
||||||
Message {
|
Message {
|
||||||
id: Ulid::new().to_string(),
|
id: Ulid::new().to_string(),
|
||||||
nonce: None,
|
nonce: None,
|
||||||
channel,
|
channel,
|
||||||
author,
|
author,
|
||||||
|
|
||||||
content,
|
content,
|
||||||
attachments: None,
|
attachments: None,
|
||||||
edited: None,
|
edited: None,
|
||||||
embeds: None,
|
embeds: None,
|
||||||
mentions,
|
mentions,
|
||||||
|
replies
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn publish(self, channel: &Channel) -> Result<()> {
|
pub async fn publish(self, channel: &Channel, process_embeds: bool) -> Result<()> {
|
||||||
get_collection("messages")
|
get_collection("messages")
|
||||||
.insert_one(to_bson(&self).unwrap().as_document().unwrap().clone(), None)
|
.insert_one(to_bson(&self).unwrap().as_document().unwrap().clone(), None)
|
||||||
.await
|
.await
|
||||||
@@ -119,76 +195,19 @@ impl Message {
|
|||||||
let ss = self.clone();
|
let ss = self.clone();
|
||||||
let c_clone = channel.clone();
|
let c_clone = channel.clone();
|
||||||
async_std::task::spawn(async move {
|
async_std::task::spawn(async move {
|
||||||
let mut set = if let Content::Text(text) = &ss.content {
|
|
||||||
doc! {
|
|
||||||
"last_message": {
|
|
||||||
"_id": ss.id.clone(),
|
|
||||||
"author": ss.author.clone(),
|
|
||||||
"short": text.chars().take(128).collect::<String>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
doc! {}
|
|
||||||
};
|
|
||||||
|
|
||||||
// ! MARK AS ACTIVE
|
let last_message_id = ss.id.clone();
|
||||||
// ! FIXME: temp code
|
let mut set = doc! { "last_message_id": last_message_id };
|
||||||
|
|
||||||
let channels = get_collection("channels");
|
let channels = get_collection("channels");
|
||||||
match &c_clone {
|
match &c_clone {
|
||||||
Channel::DirectMessage { id, .. } => {
|
Channel::DirectMessage { id, .. } => {
|
||||||
|
// ! MARK AS ACTIVE
|
||||||
set.insert("active", true);
|
set.insert("active", true);
|
||||||
channels
|
update_channels_last_message(&channels, id, &set).await;
|
||||||
.update_one(
|
|
||||||
doc! { "_id": id },
|
|
||||||
doc! {
|
|
||||||
"$set": set
|
|
||||||
},
|
},
|
||||||
None,
|
Channel::Group { id, .. } | Channel::TextChannel { id, .. } => {
|
||||||
)
|
update_channels_last_message(&channels, id, &set).await;
|
||||||
.await
|
|
||||||
/*.map_err(|_| Error::DatabaseError {
|
|
||||||
operation: "update_one",
|
|
||||||
with: "channel",
|
|
||||||
})?;*/
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
Channel::Group { id, .. } => {
|
|
||||||
if let Content::Text(_) = &ss.content {
|
|
||||||
channels
|
|
||||||
.update_one(
|
|
||||||
doc! { "_id": id },
|
|
||||||
doc! {
|
|
||||||
"$set": set
|
|
||||||
},
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
/*.map_err(|_| Error::DatabaseError {
|
|
||||||
operation: "update_one",
|
|
||||||
with: "channel",
|
|
||||||
})?;*/
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Channel::TextChannel { id, .. } => {
|
|
||||||
if let Content::Text(_) = &ss.content {
|
|
||||||
channels
|
|
||||||
.update_one(
|
|
||||||
doc! { "_id": id },
|
|
||||||
doc! {
|
|
||||||
"$set": {
|
|
||||||
"last_message": &ss.id
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
/*.map_err(|_| Error::DatabaseError {
|
|
||||||
operation: "update_one",
|
|
||||||
with: "channel",
|
|
||||||
})?;*/
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
@@ -225,10 +244,12 @@ impl Message {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if process_embeds {
|
||||||
self.process_embed();
|
self.process_embed();
|
||||||
|
}
|
||||||
|
|
||||||
let enc = serde_json::to_string(&self).unwrap();
|
let mentions = self.mentions.clone();
|
||||||
ClientboundNotification::Message(self).publish(channel.id().to_string());
|
ClientboundNotification::Message(self.clone()).publish(channel.id().to_string());
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Web Push Test Code
|
Web Push Test Code
|
||||||
@@ -245,52 +266,44 @@ impl Message {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Channel::TextChannel { .. } => {
|
||||||
|
if let Some(mut mentions) = mentions {
|
||||||
|
target_ids.append(&mut mentions);
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch their corresponding sessions.
|
// Fetch their corresponding sessions.
|
||||||
if let Ok(mut cursor) = get_collection("accounts")
|
if target_ids.len() > 0 {
|
||||||
.find(
|
if let Ok(mut cursor) = Session::find(
|
||||||
|
&get_db(),
|
||||||
doc! {
|
doc! {
|
||||||
"_id": {
|
"_id": {
|
||||||
"$in": target_ids
|
"$in": target_ids
|
||||||
},
|
},
|
||||||
"sessions.subscription": {
|
"subscription": {
|
||||||
"$exists": true
|
"$exists": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
FindOptions::builder()
|
None
|
||||||
.projection(doc! { "sessions": 1 })
|
|
||||||
.build(),
|
|
||||||
)
|
)
|
||||||
.await
|
.await {
|
||||||
{
|
let enc = serde_json::to_string(&PushNotification::new(self, &c_clone).await).unwrap();
|
||||||
let mut subscriptions = vec![];
|
|
||||||
while let Some(result) = cursor.next().await {
|
|
||||||
if let Ok(doc) = result {
|
|
||||||
if let Ok(sessions) = doc.get_array("sessions") {
|
|
||||||
for session in sessions {
|
|
||||||
if let Some(doc) = session.as_document() {
|
|
||||||
if let Ok(sub) = doc.get_document("subscription") {
|
|
||||||
let endpoint = sub.get_str("endpoint").unwrap().to_string();
|
|
||||||
let p256dh = sub.get_str("p256dh").unwrap().to_string();
|
|
||||||
let auth = sub.get_str("auth").unwrap().to_string();
|
|
||||||
|
|
||||||
subscriptions
|
|
||||||
.push(SubscriptionInfo::new(endpoint, p256dh, auth));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if subscriptions.len() > 0 {
|
|
||||||
let client = WebPushClient::new();
|
let client = WebPushClient::new();
|
||||||
let key =
|
let key =
|
||||||
base64::decode_config(VAPID_PRIVATE_KEY.clone(), base64::URL_SAFE).unwrap();
|
base64::decode_config(VAPID_PRIVATE_KEY.clone(), base64::URL_SAFE).unwrap();
|
||||||
|
|
||||||
for subscription in subscriptions {
|
while let Some(Ok(session)) = cursor.next().await {
|
||||||
|
if let Some(sub) = session.subscription {
|
||||||
|
let subscription = SubscriptionInfo {
|
||||||
|
endpoint: sub.endpoint,
|
||||||
|
keys: SubscriptionKeys {
|
||||||
|
auth: sub.auth,
|
||||||
|
p256dh: sub.p256dh
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let mut builder = WebPushMessageBuilder::new(&subscription).unwrap();
|
let mut builder = WebPushMessageBuilder::new(&subscription).unwrap();
|
||||||
let sig_builder = VapidSignatureBuilder::from_pem(
|
let sig_builder = VapidSignatureBuilder::from_pem(
|
||||||
std::io::Cursor::new(&key),
|
std::io::Cursor::new(&key),
|
||||||
@@ -305,12 +318,13 @@ impl Message {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn publish_update(self, data: JsonValue) -> Result<()> {
|
pub async fn publish_update(self, data: Value) -> Result<()> {
|
||||||
let channel = self.channel.clone();
|
let channel = self.channel.clone();
|
||||||
ClientboundNotification::MessageUpdate {
|
ClientboundNotification::MessageUpdate {
|
||||||
id: self.id.clone(),
|
id: self.id.clone(),
|
||||||
@@ -395,7 +409,7 @@ impl Message {
|
|||||||
let attachment_ids: Vec<String> =
|
let attachment_ids: Vec<String> =
|
||||||
attachments.iter().map(|f| f.id.to_string()).collect();
|
attachments.iter().map(|f| f.id.to_string()).collect();
|
||||||
get_collection("attachments")
|
get_collection("attachments")
|
||||||
.update_one(
|
.update_many(
|
||||||
doc! {
|
doc! {
|
||||||
"_id": {
|
"_id": {
|
||||||
"$in": attachment_ids
|
"$in": attachment_ids
|
||||||
@@ -410,7 +424,7 @@ impl Message {
|
|||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| Error::DatabaseError {
|
.map_err(|_| Error::DatabaseError {
|
||||||
operation: "update_one",
|
operation: "update_many",
|
||||||
with: "attachment",
|
with: "attachment",
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
@@ -418,3 +432,15 @@ impl Message {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn update_channels_last_message(channels: &Collection, channel_id: &String, set: &Document) {
|
||||||
|
channels
|
||||||
|
.update_one(
|
||||||
|
doc! { "_id": channel_id },
|
||||||
|
doc! { "$set": set },
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("Server should not run with no, or a corrupted db");
|
||||||
|
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
use crate::database::*;
|
use crate::database::*;
|
||||||
use crate::util::result::{Error, Result};
|
use crate::util::result::{Error, Result};
|
||||||
|
use crate::util::variables::AUTUMN_URL;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
@@ -26,6 +27,8 @@ pub struct File {
|
|||||||
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
deleted: Option<bool>,
|
deleted: Option<bool>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
reported: Option<bool>,
|
||||||
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
message_id: Option<String>,
|
message_id: Option<String>,
|
||||||
@@ -112,4 +115,8 @@ impl File {
|
|||||||
with: "attachment",
|
with: "attachment",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_autumn_url(&self) -> String {
|
||||||
|
format!("{}/{}/{}", AUTUMN_URL.as_str(), self.tag, self.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
use crate::util::{
|
use crate::util::{
|
||||||
result::{Error, Result},
|
result::{Error, Result},
|
||||||
variables::JANUARY_URL,
|
variables::JANUARY_URL,
|
||||||
|
variables::MAX_EMBED_COUNT,
|
||||||
};
|
};
|
||||||
use linkify::{LinkFinder, LinkKind};
|
use linkify::{LinkFinder, LinkKind};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
@@ -46,6 +47,9 @@ pub enum Special {
|
|||||||
None,
|
None,
|
||||||
YouTube {
|
YouTube {
|
||||||
id: String,
|
id: String,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
timestamp: Option<String>,
|
||||||
},
|
},
|
||||||
Twitch {
|
Twitch {
|
||||||
content_type: TwitchType,
|
content_type: TwitchType,
|
||||||
@@ -85,7 +89,7 @@ pub struct Metadata {
|
|||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
icon_url: Option<String>,
|
icon_url: Option<String>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
color: Option<String>,
|
colour: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
@@ -100,11 +104,15 @@ impl Embed {
|
|||||||
pub async fn generate(content: String) -> Result<Vec<Embed>> {
|
pub async fn generate(content: String) -> Result<Vec<Embed>> {
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref RE_CODE: Regex = Regex::new("```(?:.|\n)+?```|`(?:.|\n)+?`").unwrap();
|
static ref RE_CODE: Regex = Regex::new("```(?:.|\n)+?```|`(?:.|\n)+?`").unwrap();
|
||||||
|
static ref RE_IGNORED: Regex = Regex::new("(<http.+>)").unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ignore code blocks.
|
// Ignore code blocks.
|
||||||
let content = RE_CODE.replace_all(&content, "");
|
let content = RE_CODE.replace_all(&content, "");
|
||||||
|
|
||||||
|
// Ignore all content between angle brackets starting with http.
|
||||||
|
let content = RE_IGNORED.replace_all(&content, "");
|
||||||
|
|
||||||
let content = content
|
let content = content
|
||||||
// Ignore quoted lines.
|
// Ignore quoted lines.
|
||||||
.split("\n")
|
.split("\n")
|
||||||
@@ -120,17 +128,27 @@ impl Embed {
|
|||||||
.collect::<Vec<&str>>()
|
.collect::<Vec<&str>>()
|
||||||
.join("\n");
|
.join("\n");
|
||||||
|
|
||||||
// ! FIXME: allow multiple links
|
|
||||||
// ! FIXME: prevent generation if link is surrounded with < >
|
|
||||||
let mut finder = LinkFinder::new();
|
let mut finder = LinkFinder::new();
|
||||||
finder.kinds(&[LinkKind::Url]);
|
finder.kinds(&[LinkKind::Url]);
|
||||||
let links: Vec<_> = finder.links(&content).collect();
|
let links: Vec<_> = finder.links(&content).take(*MAX_EMBED_COUNT).collect();
|
||||||
|
|
||||||
if links.len() == 0 {
|
if links.len() == 0 {
|
||||||
return Err(Error::LabelMe);
|
return Err(Error::LabelMe);
|
||||||
}
|
}
|
||||||
|
|
||||||
let link = &links[0];
|
let mut embeds: Vec<Embed> = Vec::new();
|
||||||
|
|
||||||
|
let mut link_index = 0;
|
||||||
|
|
||||||
|
// ! FIXME: batch request to january?
|
||||||
|
while link_index < links.len() {
|
||||||
|
let link = &links[link_index];
|
||||||
|
|
||||||
|
// Check if we already processed this link.
|
||||||
|
if link_index != 0 && links.iter().take(link_index).any(|x| x.as_str() == link.as_str()) {
|
||||||
|
link_index = link_index + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::Client::new();
|
||||||
let result = client
|
let result = client
|
||||||
@@ -139,16 +157,26 @@ impl Embed {
|
|||||||
.send()
|
.send()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
match result {
|
if result.is_err() {
|
||||||
Err(_) => return Err(Error::LabelMe),
|
link_index = link_index + 1;
|
||||||
Ok(result) => match result.status() {
|
continue;
|
||||||
reqwest::StatusCode::OK => {
|
}
|
||||||
let res: Embed = result.json().await.map_err(|_| Error::InvalidOperation)?;
|
|
||||||
|
|
||||||
Ok(vec![res])
|
let response = result.unwrap();
|
||||||
|
if response.status().is_success() {
|
||||||
|
let res: Embed = response.json().await.map_err(|_| Error::InvalidOperation)?;
|
||||||
|
|
||||||
|
embeds.push(res);
|
||||||
}
|
}
|
||||||
_ => return Err(Error::LabelMe),
|
|
||||||
},
|
link_index = link_index + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent database update when no embeds are found.
|
||||||
|
if embeds.len() > 0 {
|
||||||
|
Ok(embeds)
|
||||||
|
} else {
|
||||||
|
Err(Error::LabelMe)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,7 @@ mod microservice;
|
|||||||
mod server;
|
mod server;
|
||||||
mod sync;
|
mod sync;
|
||||||
mod user;
|
mod user;
|
||||||
|
mod bots;
|
||||||
|
|
||||||
use microservice::*;
|
use microservice::*;
|
||||||
|
|
||||||
@@ -16,3 +17,4 @@ pub use message::*;
|
|||||||
pub use server::*;
|
pub use server::*;
|
||||||
pub use sync::*;
|
pub use sync::*;
|
||||||
pub use user::*;
|
pub use user::*;
|
||||||
|
pub use bots::*;
|
||||||
+129
-80
@@ -1,14 +1,16 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::database::*;
|
use crate::database::*;
|
||||||
use crate::notifications::events::ClientboundNotification;
|
use crate::notifications::events::ClientboundNotification;
|
||||||
use crate::util::result::{Error, Result};
|
use crate::util::result::{Error, Result};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use mongodb::bson::doc;
|
use mongodb::bson::{Bson, doc};
|
||||||
use mongodb::bson::from_document;
|
use mongodb::bson::from_document;
|
||||||
use mongodb::bson::to_document;
|
use mongodb::bson::to_document;
|
||||||
use mongodb::bson::Document;
|
use mongodb::bson::Document;
|
||||||
use mongodb::options::FindOptions;
|
use rocket::serde::json::Value;
|
||||||
use rocket_contrib::json::JsonValue;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use ulid::Ulid;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub struct MemberCompositeKey {
|
pub struct MemberCompositeKey {
|
||||||
@@ -25,6 +27,37 @@ pub struct Member {
|
|||||||
pub nickname: Option<String>,
|
pub nickname: Option<String>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub avatar: Option<File>,
|
pub avatar: Option<File>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub roles: Option<Vec<String>>
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type PermissionTuple = (
|
||||||
|
i32, // server permission
|
||||||
|
i32 // channel permission
|
||||||
|
);
|
||||||
|
|
||||||
|
pub fn if_false(t: &bool) -> bool {
|
||||||
|
!t
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct Role {
|
||||||
|
pub name: String,
|
||||||
|
pub permissions: PermissionTuple,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub colour: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "if_false", default)]
|
||||||
|
pub hoist: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub rank: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct Category {
|
||||||
|
pub id: String,
|
||||||
|
pub title: String,
|
||||||
|
pub channels: Vec<String>
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
@@ -59,14 +92,27 @@ pub struct Server {
|
|||||||
pub name: String,
|
pub name: String,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
|
|
||||||
pub channels: Vec<String>,
|
pub channels: Vec<String>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub categories: Option<Vec<Category>>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub system_messages: Option<SystemMessageChannels>,
|
pub system_messages: Option<SystemMessageChannels>,
|
||||||
|
|
||||||
|
#[serde(default = "HashMap::new", skip_serializing_if = "HashMap::is_empty")]
|
||||||
|
pub roles: HashMap<String, Role>,
|
||||||
|
pub default_permissions: PermissionTuple,
|
||||||
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub icon: Option<File>,
|
pub icon: Option<File>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub banner: Option<File>,
|
pub banner: Option<File>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub flags: Option<i32>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "if_false", default)]
|
||||||
|
pub nsfw: bool
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Server {
|
impl Server {
|
||||||
@@ -88,7 +134,7 @@ impl Server {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn publish_update(&self, data: JsonValue) -> Result<()> {
|
pub async fn publish_update(&self, data: Value) -> Result<()> {
|
||||||
ClientboundNotification::ServerUpdate {
|
ClientboundNotification::ServerUpdate {
|
||||||
id: self.id.clone(),
|
id: self.id.clone(),
|
||||||
data,
|
data,
|
||||||
@@ -100,58 +146,11 @@ impl Server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete(&self) -> Result<()> {
|
pub async fn delete(&self) -> Result<()> {
|
||||||
let messages = get_collection("messages");
|
|
||||||
|
|
||||||
// Check if there are any attachments we need to delete.
|
// Check if there are any attachments we need to delete.
|
||||||
// ! FIXME: make this generic and merge with channel delete
|
Channel::delete_messages(Bson::Document(doc! { "$in": &self.channels })).await?;
|
||||||
// ! e.g. delete_channel(filter: doc!)
|
|
||||||
let message_ids = messages
|
|
||||||
.find(
|
|
||||||
doc! {
|
|
||||||
"server": &self.id,
|
|
||||||
"attachment": {
|
|
||||||
"$exists": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
FindOptions::builder().projection(doc! { "_id": 1 }).build(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.map_err(|_| Error::DatabaseError {
|
|
||||||
operation: "fetch_many",
|
|
||||||
with: "messages",
|
|
||||||
})?
|
|
||||||
.filter_map(async move |s| s.ok())
|
|
||||||
.collect::<Vec<Document>>()
|
|
||||||
.await
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|x| x.get_str("_id").ok().map(|x| x.to_string()))
|
|
||||||
.collect::<Vec<String>>();
|
|
||||||
|
|
||||||
// If we found any, mark them as deleted.
|
// Delete all channels.
|
||||||
if message_ids.len() > 0 {
|
get_collection("channels")
|
||||||
get_collection("attachments")
|
|
||||||
.update_many(
|
|
||||||
doc! {
|
|
||||||
"message_id": {
|
|
||||||
"$in": message_ids
|
|
||||||
}
|
|
||||||
},
|
|
||||||
doc! {
|
|
||||||
"$set": {
|
|
||||||
"deleted": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.map_err(|_| Error::DatabaseError {
|
|
||||||
operation: "update_many",
|
|
||||||
with: "attachments",
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// And then delete said messages.
|
|
||||||
messages
|
|
||||||
.delete_many(
|
.delete_many(
|
||||||
doc! {
|
doc! {
|
||||||
"server": &self.id
|
"server": &self.id
|
||||||
@@ -161,27 +160,13 @@ impl Server {
|
|||||||
.await
|
.await
|
||||||
.map_err(|_| Error::DatabaseError {
|
.map_err(|_| Error::DatabaseError {
|
||||||
operation: "delete_many",
|
operation: "delete_many",
|
||||||
with: "messages",
|
with: "channels",
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// Delete all channels, members, bans and invites.
|
// Delete any associated objects, e.g. unreads and invites.
|
||||||
for with in &["channels", "channel_invites"] {
|
Channel::delete_associated_objects(Bson::Document(doc! { "$in": &self.channels })).await?;
|
||||||
get_collection(with)
|
|
||||||
.delete_many(
|
|
||||||
doc! {
|
|
||||||
"server": &self.id
|
|
||||||
},
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.map_err(|_| Error::DatabaseError {
|
|
||||||
operation: "delete_many",
|
|
||||||
with,
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ! FIXME: delete any unreads
|
|
||||||
|
|
||||||
|
// Delete members and bans.
|
||||||
for with in &["server_members", "server_bans"] {
|
for with in &["server_members", "server_bans"] {
|
||||||
get_collection(with)
|
get_collection(with)
|
||||||
.delete_many(
|
.delete_many(
|
||||||
@@ -197,6 +182,16 @@ impl Server {
|
|||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Delete server icon / banner.
|
||||||
|
if let Some(attachment) = &self.icon {
|
||||||
|
attachment.delete().await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(attachment) = &self.banner {
|
||||||
|
attachment.delete().await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the server
|
||||||
get_collection("servers")
|
get_collection("servers")
|
||||||
.delete_one(
|
.delete_one(
|
||||||
doc! {
|
doc! {
|
||||||
@@ -215,14 +210,6 @@ impl Server {
|
|||||||
}
|
}
|
||||||
.publish(self.id.clone());
|
.publish(self.id.clone());
|
||||||
|
|
||||||
if let Some(attachment) = &self.icon {
|
|
||||||
attachment.delete().await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(attachment) = &self.banner {
|
|
||||||
attachment.delete().await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,7 +260,48 @@ impl Server {
|
|||||||
.collect::<Vec<String>>())
|
.collect::<Vec<String>>())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn mark_as_read(&self, id: &str) -> Result<()> {
|
||||||
|
let current_time = Ulid::new().to_string();
|
||||||
|
let unreads = get_collection("channel_unreads");
|
||||||
|
|
||||||
|
unreads.delete_many(
|
||||||
|
doc! {
|
||||||
|
"_id.channel": {
|
||||||
|
"$in": &self.channels
|
||||||
|
},
|
||||||
|
"_id.user": &id
|
||||||
|
},
|
||||||
|
None
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "delete_many",
|
||||||
|
with: "channel_unreads",
|
||||||
|
})?;
|
||||||
|
|
||||||
|
unreads.insert_many(
|
||||||
|
self.channels
|
||||||
|
.iter()
|
||||||
|
.map(|channel| doc! {
|
||||||
|
"_id": {
|
||||||
|
"channel": channel,
|
||||||
|
"user": &id
|
||||||
|
},
|
||||||
|
"last_id": ¤t_time
|
||||||
|
})
|
||||||
|
.collect::<Vec<Document>>(),
|
||||||
|
None
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "update_many",
|
||||||
|
with: "channel_unreads",
|
||||||
|
})
|
||||||
|
.map(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn join_member(&self, id: &str) -> Result<()> {
|
pub async fn join_member(&self, id: &str) -> Result<()> {
|
||||||
|
// Check if user is banned.
|
||||||
if get_collection("server_bans")
|
if get_collection("server_bans")
|
||||||
.find_one(
|
.find_one(
|
||||||
doc! {
|
doc! {
|
||||||
@@ -292,6 +320,7 @@ impl Server {
|
|||||||
return Err(Error::Banned);
|
return Err(Error::Banned);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add user to server.
|
||||||
get_collection("server_members")
|
get_collection("server_members")
|
||||||
.insert_one(
|
.insert_one(
|
||||||
doc! {
|
doc! {
|
||||||
@@ -308,12 +337,14 @@ impl Server {
|
|||||||
with: "server_members",
|
with: "server_members",
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
// Announce that user joined server.
|
||||||
ClientboundNotification::ServerMemberJoin {
|
ClientboundNotification::ServerMemberJoin {
|
||||||
id: self.id.clone(),
|
id: self.id.clone(),
|
||||||
user: id.to_string(),
|
user: id.to_string(),
|
||||||
}
|
}
|
||||||
.publish(self.id.clone());
|
.publish(self.id.clone());
|
||||||
|
|
||||||
|
// Broadcast join message.
|
||||||
if let Some(channels) = &self.system_messages {
|
if let Some(channels) = &self.system_messages {
|
||||||
if let Some(cid) = &channels.user_joined {
|
if let Some(cid) = &channels.user_joined {
|
||||||
let channel = Ref::from_unchecked(cid.clone()).fetch_channel().await?;
|
let channel = Ref::from_unchecked(cid.clone()).fetch_channel().await?;
|
||||||
@@ -323,6 +354,9 @@ impl Server {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mark entire server as read.
|
||||||
|
self.mark_as_read(&id).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -392,4 +426,19 @@ impl Server {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_member_count(id: &str) -> Result<i64> {
|
||||||
|
Ok(get_collection("server_members")
|
||||||
|
.count_documents(
|
||||||
|
doc! {
|
||||||
|
"_id.server": id
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "count_documents",
|
||||||
|
with: "server_members",
|
||||||
|
})?)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -16,6 +16,7 @@ use crate::database::*;
|
|||||||
use crate::notifications::websocket::is_online;
|
use crate::notifications::websocket::is_online;
|
||||||
use crate::util::result::{Error, Result};
|
use crate::util::result::{Error, Result};
|
||||||
use crate::util::variables::EARLY_ADOPTER_BADGE;
|
use crate::util::variables::EARLY_ADOPTER_BADGE;
|
||||||
|
use crate::util::variables::MAX_SERVER_COUNT;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||||
pub enum RelationshipStatus {
|
pub enum RelationshipStatus {
|
||||||
@@ -35,7 +36,7 @@ pub struct Relationship {
|
|||||||
pub status: RelationshipStatus,
|
pub status: RelationshipStatus,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||||
pub enum Presence {
|
pub enum Presence {
|
||||||
Online,
|
Online,
|
||||||
Idle,
|
Idle,
|
||||||
@@ -67,12 +68,18 @@ pub enum Badges {
|
|||||||
Translator = 2,
|
Translator = 2,
|
||||||
Supporter = 4,
|
Supporter = 4,
|
||||||
ResponsibleDisclosure = 8,
|
ResponsibleDisclosure = 8,
|
||||||
|
RevoltTeam = 16,
|
||||||
EarlyAdopter = 256,
|
EarlyAdopter = 256,
|
||||||
}
|
}
|
||||||
|
|
||||||
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 });
|
||||||
|
|
||||||
// When changing this struct, update notifications/payload.rs#80
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct BotInformation {
|
||||||
|
owner: String
|
||||||
|
}
|
||||||
|
|
||||||
|
// When changing this struct, update notifications/payload.rs#113
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub struct User {
|
pub struct User {
|
||||||
#[serde(rename = "_id")]
|
#[serde(rename = "_id")]
|
||||||
@@ -90,6 +97,11 @@ pub struct User {
|
|||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub profile: Option<UserProfile>,
|
pub profile: Option<UserProfile>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub flags: Option<i32>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub bot: Option<BotInformation>,
|
||||||
|
|
||||||
// ? This should never be pushed to the collection.
|
// ? This should never be pushed to the collection.
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub relationship: Option<RelationshipStatus>,
|
pub relationship: Option<RelationshipStatus>,
|
||||||
@@ -118,8 +130,8 @@ impl User {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mutate the user object to appear as seen by user.
|
/// Apply any relevant badges.
|
||||||
pub fn with(mut self, permissions: UserPermissions<[u32; 1]>) -> User {
|
pub fn apply_badges(mut self) -> User {
|
||||||
let mut badges = self.badges.unwrap_or_else(|| 0);
|
let mut badges = self.badges.unwrap_or_else(|| 0);
|
||||||
if let Ok(id) = Ulid::from_string(&self.id) {
|
if let Ok(id) = Ulid::from_string(&self.id) {
|
||||||
if id.datetime().timestamp_millis() < *EARLY_ADOPTER_BADGE {
|
if id.datetime().timestamp_millis() < *EARLY_ADOPTER_BADGE {
|
||||||
@@ -128,15 +140,31 @@ impl User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.badges = Some(badges);
|
self.badges = Some(badges);
|
||||||
|
self
|
||||||
if permissions.get_view_profile() {
|
|
||||||
self.online = Some(is_online(&self.id));
|
|
||||||
} else {
|
|
||||||
self.status = None;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.profile = None;
|
/// Mutate the user object to appear as seen by user.
|
||||||
self
|
pub fn with(self, permissions: UserPermissions<[u32; 1]>) -> User {
|
||||||
|
let mut user = self.apply_badges();
|
||||||
|
|
||||||
|
if permissions.get_view_profile() {
|
||||||
|
user.online = Some(is_online(&user.id));
|
||||||
|
} else {
|
||||||
|
user.status = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the user's status is `Presence::Invisible`, return it as `Presence::Offline`
|
||||||
|
if let Some(status) = &user.status {
|
||||||
|
if let Some(presence) = &status.presence {
|
||||||
|
if presence == &Presence::Invisible {
|
||||||
|
user.status = None;
|
||||||
|
user.online = Some(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
user.profile = None;
|
||||||
|
user
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mutate the user object to appear as seen by user.
|
/// Mutate the user object to appear as seen by user.
|
||||||
@@ -158,7 +186,7 @@ impl User {
|
|||||||
|
|
||||||
/// Utility function for checking claimed usernames.
|
/// Utility function for checking claimed usernames.
|
||||||
pub async fn is_username_taken(username: &str) -> Result<bool> {
|
pub async fn is_username_taken(username: &str) -> Result<bool> {
|
||||||
if username.to_lowercase() == "revolt" && username.to_lowercase() == "admin" {
|
if username.to_lowercase() == "revolt" || username.to_lowercase() == "admin" || username.to_lowercase() == "system" {
|
||||||
return Ok(true);
|
return Ok(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,7 +225,7 @@ impl User {
|
|||||||
},
|
},
|
||||||
FindOptions::builder()
|
FindOptions::builder()
|
||||||
.projection(
|
.projection(
|
||||||
doc! { "_id": 1, "username": 1, "avatar": 1, "badges": 1, "status": 1 },
|
doc! { "_id": 1, "username": 1, "avatar": 1, "badges": 1, "status": 1, "flags": 1, "bot": 1 },
|
||||||
)
|
)
|
||||||
.build(),
|
.build(),
|
||||||
)
|
)
|
||||||
@@ -227,12 +255,36 @@ impl User {
|
|||||||
Ok(users)
|
Ok(users)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Utility function to get all the server IDs the user is in.
|
/// Utility function to get all of a user's memberships.
|
||||||
pub async fn fetch_server_ids(&self) -> Result<Vec<String>> {
|
pub async fn fetch_memberships(id: &str) -> Result<Vec<Member>> {
|
||||||
Ok(get_collection("server_members")
|
Ok(get_collection("server_members")
|
||||||
.find(
|
.find(
|
||||||
doc! {
|
doc! {
|
||||||
"_id.user": &self.id
|
"_id.user": id
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "find",
|
||||||
|
with: "server_members",
|
||||||
|
})?
|
||||||
|
.filter_map(async move |s| s.ok())
|
||||||
|
.collect::<Vec<Document>>()
|
||||||
|
.await
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|x| {
|
||||||
|
from_document(x).ok()
|
||||||
|
})
|
||||||
|
.collect::<Vec<Member>>())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Utility function to get all the server IDs the user is in.
|
||||||
|
pub async fn fetch_server_ids(id: &str) -> Result<Vec<String>> {
|
||||||
|
Ok(get_collection("server_members")
|
||||||
|
.find(
|
||||||
|
doc! {
|
||||||
|
"_id.user": id
|
||||||
},
|
},
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
@@ -255,11 +307,11 @@ impl User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Utility function to fetch unread objects for user.
|
/// Utility function to fetch unread objects for user.
|
||||||
pub async fn fetch_unreads(&self) -> Result<Vec<Document>> {
|
pub async fn fetch_unreads(id: &str) -> Result<Vec<Document>> {
|
||||||
Ok(get_collection("channel_unreads")
|
Ok(get_collection("channel_unreads")
|
||||||
.find(
|
.find(
|
||||||
doc! {
|
doc! {
|
||||||
"_id.user": &self.id
|
"_id.user": id
|
||||||
},
|
},
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
@@ -272,4 +324,10 @@ impl User {
|
|||||||
.collect::<Vec<Document>>()
|
.collect::<Vec<Document>>()
|
||||||
.await)
|
.await)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if this user can acquire another server.
|
||||||
|
pub async fn can_acquire_server(id: &str) -> Result<bool> {
|
||||||
|
let server_ids = User::fetch_server_ids(&id).await?;
|
||||||
|
Ok(server_ids.len() < *MAX_SERVER_COUNT)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,6 @@ use crate::database::*;
|
|||||||
use crate::util::result::{Error, Result};
|
use crate::util::result::{Error, Result};
|
||||||
|
|
||||||
use mongodb::bson::{doc, from_document};
|
use mongodb::bson::{doc, from_document};
|
||||||
use rocket::http::RawStr;
|
|
||||||
use rocket::request::FromParam;
|
use rocket::request::FromParam;
|
||||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
@@ -62,6 +61,10 @@ impl Ref {
|
|||||||
self.fetch("channel_invites").await
|
self.fetch("channel_invites").await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn fetch_bot(&self) -> Result<Bot> {
|
||||||
|
self.fetch("bots").await
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn fetch_member(&self, server: &str) -> Result<Member> {
|
pub async fn fetch_member(&self, server: &str) -> Result<Member> {
|
||||||
let doc = get_collection("server_members")
|
let doc = get_collection("server_members")
|
||||||
.find_one(
|
.find_one(
|
||||||
@@ -127,9 +130,9 @@ impl User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'r> FromParam<'r> for Ref {
|
impl<'r> FromParam<'r> for Ref {
|
||||||
type Error = &'r RawStr;
|
type Error = &'r str;
|
||||||
|
|
||||||
fn from_param(param: &'r RawStr) -> Result<Self, Self::Error> {
|
fn from_param(param: &'r str) -> Result<Self, Self::Error> {
|
||||||
if let Ok(result) = Ref::from(param.to_string()) {
|
if let Ok(result) = Ref::from(param.to_string()) {
|
||||||
if result.validate().is_ok() {
|
if result.validate().is_ok() {
|
||||||
return Ok(result);
|
return Ok(result);
|
||||||
|
|||||||
+54
-12
@@ -1,17 +1,53 @@
|
|||||||
use crate::database::*;
|
use crate::database::*;
|
||||||
|
|
||||||
use mongodb::bson::{doc, from_document};
|
use mongodb::bson::{doc, from_document};
|
||||||
use rauth::auth::Session;
|
use rauth::entities::Session;
|
||||||
use rocket::http::Status;
|
use rocket::http::Status;
|
||||||
use rocket::request::{self, FromRequest, Outcome, Request};
|
use rocket::request::{self, FromRequest, Outcome, Request};
|
||||||
|
|
||||||
#[rocket::async_trait]
|
#[rocket::async_trait]
|
||||||
impl<'a, 'r> FromRequest<'a, 'r> for User {
|
impl<'r> FromRequest<'r> for User {
|
||||||
type Error = rauth::util::Error;
|
type Error = rauth::util::Error;
|
||||||
|
|
||||||
async fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
|
async fn from_request(request: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
|
||||||
let session: Session = try_outcome!(request.guard::<Session>().await);
|
let user: &Option<User> = request.local_cache_async(async {
|
||||||
|
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(result) = get_collection("bots")
|
||||||
|
.find_one(
|
||||||
|
doc! {
|
||||||
|
"token": bot_token
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
if let Some(doc) = result {
|
||||||
|
let id = doc.get_str("_id").unwrap();
|
||||||
|
if let Ok(result) = get_collection("users")
|
||||||
|
.find_one(
|
||||||
|
doc! {
|
||||||
|
"_id": &id
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
if let Some(doc) = result {
|
||||||
|
if let Ok(user) = from_document(doc) {
|
||||||
|
return Some(user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let Outcome::Success(session) = request.guard::<Session>().await {
|
||||||
if let Ok(result) = get_collection("users")
|
if let Ok(result) = get_collection("users")
|
||||||
.find_one(
|
.find_one(
|
||||||
doc! {
|
doc! {
|
||||||
@@ -22,17 +58,23 @@ impl<'a, 'r> FromRequest<'a, 'r> for User {
|
|||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
if let Some(doc) = result {
|
if let Some(doc) = result {
|
||||||
Outcome::Success(from_document(doc).unwrap())
|
if let Ok(user) = from_document(doc) {
|
||||||
} else {
|
return Some(user)
|
||||||
Outcome::Failure((Status::Forbidden, rauth::util::Error::InvalidSession))
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}).await;
|
||||||
|
|
||||||
|
if let Some(user) = user {
|
||||||
|
Outcome::Success(user.clone())
|
||||||
} else {
|
} else {
|
||||||
Outcome::Failure((
|
Outcome::Failure((
|
||||||
Status::InternalServerError,
|
Status::Forbidden,
|
||||||
rauth::util::Error::DatabaseError {
|
rauth::util::Error::InvalidSession,
|
||||||
operation: "find_one",
|
|
||||||
with: "user",
|
|
||||||
},
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,10 @@ pub async fn create_database() {
|
|||||||
.await
|
.await
|
||||||
.expect("Failed to create user_settings collection.");
|
.expect("Failed to create user_settings collection.");
|
||||||
|
|
||||||
|
db.create_collection("bots", None)
|
||||||
|
.await
|
||||||
|
.expect("Failed to create bots collection.");
|
||||||
|
|
||||||
db.create_collection(
|
db.create_collection(
|
||||||
"pubsub",
|
"pubsub",
|
||||||
CreateCollectionOptions::builder()
|
CreateCollectionOptions::builder()
|
||||||
@@ -67,39 +71,6 @@ pub async fn create_database() {
|
|||||||
.await
|
.await
|
||||||
.expect("Failed to create pubsub collection.");
|
.expect("Failed to create pubsub collection.");
|
||||||
|
|
||||||
db.run_command(
|
|
||||||
doc! {
|
|
||||||
"createIndexes": "accounts",
|
|
||||||
"indexes": [
|
|
||||||
{
|
|
||||||
"key": {
|
|
||||||
"email": 1
|
|
||||||
},
|
|
||||||
"name": "email",
|
|
||||||
"unique": true,
|
|
||||||
"collation": {
|
|
||||||
"locale": "en",
|
|
||||||
"strength": 2
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": {
|
|
||||||
"email_normalised": 1
|
|
||||||
},
|
|
||||||
"name": "email_normalised",
|
|
||||||
"unique": true,
|
|
||||||
"collation": {
|
|
||||||
"locale": "en",
|
|
||||||
"strength": 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.expect("Failed to create account index.");
|
|
||||||
|
|
||||||
db.run_command(
|
db.run_command(
|
||||||
doc! {
|
doc! {
|
||||||
"createIndexes": "users",
|
"createIndexes": "users",
|
||||||
@@ -122,6 +93,23 @@ pub async fn create_database() {
|
|||||||
.await
|
.await
|
||||||
.expect("Failed to create username index.");
|
.expect("Failed to create username index.");
|
||||||
|
|
||||||
|
db.run_command(
|
||||||
|
doc! {
|
||||||
|
"createIndexes": "messages",
|
||||||
|
"indexes": [
|
||||||
|
{
|
||||||
|
"key": {
|
||||||
|
"content": "text"
|
||||||
|
},
|
||||||
|
"name": "content"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("Failed to create message index.");
|
||||||
|
|
||||||
db.collection("migrations")
|
db.collection("migrations")
|
||||||
.insert_one(
|
.insert_one(
|
||||||
doc! {
|
doc! {
|
||||||
|
|||||||
@@ -16,4 +16,7 @@ pub async fn run_migrations() {
|
|||||||
} else {
|
} else {
|
||||||
scripts::migrate_database().await;
|
scripts::migrate_database().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// panic!("https://pbs.twimg.com/media/EDTpB5JWwAUvyxd.jpg");
|
||||||
|
rauth::entities::sync_models(&super::get_db()).await;
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,8 @@
|
|||||||
use crate::database::{get_collection, get_db};
|
use crate::database::{permissions, get_collection, get_db, PermissionTuple};
|
||||||
|
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use log::info;
|
use log::info;
|
||||||
use mongodb::{
|
use mongodb::{bson::{Document, doc, from_bson, from_document, to_document}, options::FindOptions};
|
||||||
bson::{doc, from_document},
|
|
||||||
options::FindOptions,
|
|
||||||
};
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
@@ -14,7 +11,7 @@ struct MigrationInfo {
|
|||||||
revision: i32,
|
revision: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const LATEST_REVISION: i32 = 5;
|
pub const LATEST_REVISION: i32 = 10;
|
||||||
|
|
||||||
pub async fn migrate_database() {
|
pub async fn migrate_database() {
|
||||||
let migrations = get_collection("migrations");
|
let migrations = get_collection("migrations");
|
||||||
@@ -157,6 +154,196 @@ pub async fn run_migrations(revision: i32) -> i32 {
|
|||||||
.expect("Failed to create channel_invites collection.");
|
.expect("Failed to create channel_invites collection.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if revision <= 5 {
|
||||||
|
info!("Running migration [revision 5 / 2021-06-26]: Add permissions.");
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct Server {
|
||||||
|
pub default_permissions: PermissionTuple,
|
||||||
|
}
|
||||||
|
|
||||||
|
let server = Server {
|
||||||
|
default_permissions: (
|
||||||
|
*permissions::server::DEFAULT_PERMISSION as i32,
|
||||||
|
*permissions::channel::DEFAULT_PERMISSION_SERVER as i32
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
get_collection("servers")
|
||||||
|
.update_many(
|
||||||
|
doc! { },
|
||||||
|
doc! {
|
||||||
|
"$set": to_document(&server).unwrap()
|
||||||
|
},
|
||||||
|
None
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("Failed to migrate servers.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if revision <= 6 {
|
||||||
|
info!("Running migration [revision 6 / 2021-07-09]: Add message text index.");
|
||||||
|
|
||||||
|
get_db()
|
||||||
|
.run_command(
|
||||||
|
doc! {
|
||||||
|
"createIndexes": "messages",
|
||||||
|
"indexes": [
|
||||||
|
{
|
||||||
|
"key": {
|
||||||
|
"content": "text"
|
||||||
|
},
|
||||||
|
"name": "content"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("Failed to create message index.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if revision <= 7 {
|
||||||
|
info!("Running migration [revision 7 / 2021-08-11]: Add message text index.");
|
||||||
|
|
||||||
|
get_db()
|
||||||
|
.create_collection("bots", None)
|
||||||
|
.await
|
||||||
|
.expect("Failed to create bots collection.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if revision <= 8 {
|
||||||
|
info!("Running migration [revision 8 / 2021-09-10]: Update to rAuth version 1.");
|
||||||
|
|
||||||
|
get_db()
|
||||||
|
.run_command(
|
||||||
|
doc! {
|
||||||
|
"dropIndexes": "accounts",
|
||||||
|
"index": ["email", "email_normalised"]
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("Failed to delete legacy account indexes.");
|
||||||
|
|
||||||
|
let col = get_collection("sessions");
|
||||||
|
let mut cursor = get_collection("accounts")
|
||||||
|
.find(doc! { }, None)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
while let Some(doc) = cursor.next().await {
|
||||||
|
if let Ok(account) = doc {
|
||||||
|
let id = account.get_str("_id").unwrap();
|
||||||
|
if let Some(sessions) = account.get("sessions") {
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct Session {
|
||||||
|
id: String,
|
||||||
|
token: String,
|
||||||
|
friendly_name: String,
|
||||||
|
subscription: Option<Document>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let sessions = from_bson::<Vec<Session>>(sessions.clone()).unwrap();
|
||||||
|
for session in sessions {
|
||||||
|
info!("Converting session {} to new format.", &session.id);
|
||||||
|
|
||||||
|
let mut doc = doc! {
|
||||||
|
"_id": session.id,
|
||||||
|
"token": session.token,
|
||||||
|
"user_id": id.clone(),
|
||||||
|
"name": session.friendly_name,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(sub) = session.subscription {
|
||||||
|
doc.insert("subscription", sub);
|
||||||
|
}
|
||||||
|
|
||||||
|
col.insert_one(doc, None).await.ok();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
info!("Account doesn't have any sessions!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get_collection("accounts")
|
||||||
|
.update_many(
|
||||||
|
doc! { },
|
||||||
|
doc! {
|
||||||
|
"$unset": {
|
||||||
|
"sessions": 1,
|
||||||
|
},
|
||||||
|
"$set": {
|
||||||
|
"mfa": {
|
||||||
|
"recovery_codes": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
if revision <= 9 {
|
||||||
|
info!("Running migration [revision 9 / 2021-09-14]: Switch from last_message to last_message_id.");
|
||||||
|
|
||||||
|
let mut cursor = get_collection("channels")
|
||||||
|
.find(doc! { }, None)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
while let Some(doc) = cursor.next().await {
|
||||||
|
if let Ok(channel) = doc {
|
||||||
|
let channel_id = channel.get_str("_id").unwrap();
|
||||||
|
if let Some(last_message) = channel.get("last_message") {
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct Obj {
|
||||||
|
#[serde(rename = "_id")]
|
||||||
|
id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum LastMessage {
|
||||||
|
Obj(Obj),
|
||||||
|
Id(String)
|
||||||
|
}
|
||||||
|
|
||||||
|
let lm = from_bson::<LastMessage>(last_message.clone()).unwrap();
|
||||||
|
let id = match lm {
|
||||||
|
LastMessage::Obj(Obj { id }) => id,
|
||||||
|
LastMessage::Id(id) => id
|
||||||
|
};
|
||||||
|
|
||||||
|
info!("Converting session {} to new format.", &channel_id);
|
||||||
|
get_collection("channels")
|
||||||
|
.update_one(
|
||||||
|
doc! {
|
||||||
|
"_id": channel_id
|
||||||
|
},
|
||||||
|
doc! {
|
||||||
|
"$set": {
|
||||||
|
"last_message_id": id
|
||||||
|
},
|
||||||
|
"$unset": {
|
||||||
|
"last_message": 1,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
} else {
|
||||||
|
info!("{} has no last_message.", &channel_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Need to migrate fields on attachments, change `user_id`, `object_id`, etc to `parent`.
|
||||||
|
|
||||||
// Reminder to update LATEST_REVISION when adding new migrations.
|
// Reminder to update LATEST_REVISION when adding new migrations.
|
||||||
LATEST_REVISION
|
LATEST_REVISION
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::database::*;
|
use crate::database::*;
|
||||||
use crate::util::result::Result;
|
use crate::util::result::{Error, Result};
|
||||||
|
|
||||||
use super::PermissionCalculator;
|
use super::PermissionCalculator;
|
||||||
|
|
||||||
@@ -15,6 +15,27 @@ pub enum ChannelPermission {
|
|||||||
ManageChannel = 0b00000000000000000000000000001000, // 8
|
ManageChannel = 0b00000000000000000000000000001000, // 8
|
||||||
VoiceCall = 0b00000000000000000000000000010000, // 16
|
VoiceCall = 0b00000000000000000000000000010000, // 16
|
||||||
InviteOthers = 0b00000000000000000000000000100000, // 32
|
InviteOthers = 0b00000000000000000000000000100000, // 32
|
||||||
|
EmbedLinks = 0b00000000000000000000000001000000, // 64
|
||||||
|
UploadFiles = 0b00000000000000000000000010000000, // 128
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
pub static ref DEFAULT_PERMISSION_DM: u32 =
|
||||||
|
ChannelPermission::View
|
||||||
|
+ ChannelPermission::SendMessage
|
||||||
|
+ ChannelPermission::ManageChannel
|
||||||
|
+ ChannelPermission::VoiceCall
|
||||||
|
+ ChannelPermission::InviteOthers
|
||||||
|
+ ChannelPermission::EmbedLinks
|
||||||
|
+ ChannelPermission::UploadFiles;
|
||||||
|
|
||||||
|
pub static ref DEFAULT_PERMISSION_SERVER: u32 =
|
||||||
|
ChannelPermission::View
|
||||||
|
+ ChannelPermission::SendMessage
|
||||||
|
+ ChannelPermission::VoiceCall
|
||||||
|
+ ChannelPermission::InviteOthers
|
||||||
|
+ ChannelPermission::EmbedLinks
|
||||||
|
+ ChannelPermission::UploadFiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_op_ex!(+ |a: &ChannelPermission, b: &ChannelPermission| -> u32 { *a as u32 | *b as u32 });
|
impl_op_ex!(+ |a: &ChannelPermission, b: &ChannelPermission| -> u32 { *a as u32 | *b as u32 });
|
||||||
@@ -29,6 +50,8 @@ bitfield! {
|
|||||||
pub get_manage_channel, _: 28;
|
pub get_manage_channel, _: 28;
|
||||||
pub get_voice_call, _: 27;
|
pub get_voice_call, _: 27;
|
||||||
pub get_invite_others, _: 26;
|
pub get_invite_others, _: 26;
|
||||||
|
pub get_embed_links, _: 25;
|
||||||
|
pub get_upload_files, _: 24;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> PermissionCalculator<'a> {
|
impl<'a> PermissionCalculator<'a> {
|
||||||
@@ -58,9 +81,7 @@ impl<'a> PermissionCalculator<'a> {
|
|||||||
let perms = self.for_user(recipient).await?;
|
let perms = self.for_user(recipient).await?;
|
||||||
|
|
||||||
if perms.get_send_message() {
|
if perms.get_send_message() {
|
||||||
return Ok(ChannelPermission::View
|
return Ok(*DEFAULT_PERMISSION_DM);
|
||||||
+ ChannelPermission::SendMessage
|
|
||||||
+ ChannelPermission::VoiceCall);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(ChannelPermission::View as u32);
|
return Ok(ChannelPermission::View as u32);
|
||||||
@@ -69,30 +90,61 @@ impl<'a> PermissionCalculator<'a> {
|
|||||||
|
|
||||||
Ok(0)
|
Ok(0)
|
||||||
}
|
}
|
||||||
Channel::Group { recipients, .. } => {
|
Channel::Group { recipients, permissions, owner, .. } => {
|
||||||
|
if &self.perspective.id == owner {
|
||||||
|
return Ok(*DEFAULT_PERMISSION_DM)
|
||||||
|
}
|
||||||
|
|
||||||
if recipients
|
if recipients
|
||||||
.iter()
|
.iter()
|
||||||
.find(|x| *x == &self.perspective.id)
|
.find(|x| *x == &self.perspective.id)
|
||||||
.is_some()
|
.is_some()
|
||||||
{
|
{
|
||||||
Ok(ChannelPermission::View
|
if let Some(permissions) = permissions {
|
||||||
+ ChannelPermission::SendMessage
|
Ok(permissions.clone() as u32)
|
||||||
+ ChannelPermission::ManageChannel
|
} else {
|
||||||
+ ChannelPermission::VoiceCall
|
Ok(*DEFAULT_PERMISSION_DM)
|
||||||
+ ChannelPermission::InviteOthers)
|
}
|
||||||
} else {
|
} else {
|
||||||
Ok(0)
|
Ok(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Channel::TextChannel { server, .. } => {
|
Channel::TextChannel { server, default_permissions, role_permissions, .. }
|
||||||
|
| Channel::VoiceChannel { server, default_permissions, role_permissions, .. } => {
|
||||||
let server = Ref::from_unchecked(server.clone()).fetch_server().await?;
|
let server = Ref::from_unchecked(server.clone()).fetch_server().await?;
|
||||||
|
|
||||||
if self.perspective.id == server.owner {
|
if self.perspective.id == server.owner {
|
||||||
Ok(u32::MAX)
|
Ok(u32::MAX)
|
||||||
} else {
|
} else {
|
||||||
Ok(ChannelPermission::View
|
match Ref::from_unchecked(self.perspective.id.clone()).fetch_member(&server.id).await {
|
||||||
+ ChannelPermission::SendMessage
|
Ok(member) => {
|
||||||
+ ChannelPermission::InviteOthers)
|
let mut perm = if let Some(permission) = default_permissions {
|
||||||
|
*permission as u32
|
||||||
|
} else {
|
||||||
|
server.default_permissions.1 as u32
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(roles) = member.roles {
|
||||||
|
for role in roles {
|
||||||
|
if let Some(permission) = role_permissions.get(&role) {
|
||||||
|
perm |= *permission as u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(server_role) = server.roles.get(&role) {
|
||||||
|
perm |= server_role.permissions.1 as u32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(perm)
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
match &error {
|
||||||
|
Error::NotFound => Ok(0),
|
||||||
|
_ => Err(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ pub struct PermissionCalculator<'a> {
|
|||||||
relationship: Option<&'a RelationshipStatus>,
|
relationship: Option<&'a RelationshipStatus>,
|
||||||
channel: Option<&'a Channel>,
|
channel: Option<&'a Channel>,
|
||||||
server: Option<&'a Server>,
|
server: Option<&'a Server>,
|
||||||
|
// member: Option<&'a Member>,
|
||||||
|
|
||||||
has_mutual_connection: bool,
|
has_mutual_connection: bool,
|
||||||
}
|
}
|
||||||
@@ -26,6 +27,7 @@ impl<'a> PermissionCalculator<'a> {
|
|||||||
relationship: None,
|
relationship: None,
|
||||||
channel: None,
|
channel: None,
|
||||||
server: None,
|
server: None,
|
||||||
|
// member: None,
|
||||||
|
|
||||||
has_mutual_connection: false,
|
has_mutual_connection: false,
|
||||||
}
|
}
|
||||||
@@ -59,6 +61,13 @@ impl<'a> PermissionCalculator<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* pub fn with_member(self, member: &'a Member) -> PermissionCalculator {
|
||||||
|
PermissionCalculator {
|
||||||
|
member: Some(&member),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
} */
|
||||||
|
|
||||||
pub fn with_mutual_connection(self) -> PermissionCalculator<'a> {
|
pub fn with_mutual_connection(self) -> PermissionCalculator<'a> {
|
||||||
PermissionCalculator {
|
PermissionCalculator {
|
||||||
has_mutual_connection: true,
|
has_mutual_connection: true,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use crate::util::result::Result;
|
use crate::util::result::{Error, Result};
|
||||||
|
|
||||||
use super::PermissionCalculator;
|
use super::PermissionCalculator;
|
||||||
|
use super::Ref;
|
||||||
|
|
||||||
use num_enum::TryFromPrimitive;
|
use num_enum::TryFromPrimitive;
|
||||||
use std::ops;
|
use std::ops;
|
||||||
@@ -17,11 +18,18 @@ pub enum ServerPermission {
|
|||||||
// 6 bits of space
|
// 6 bits of space
|
||||||
ChangeNickname = 0b00000000000000000001000000000000, // 4096
|
ChangeNickname = 0b00000000000000000001000000000000, // 4096
|
||||||
ManageNicknames = 0b00000000000000000010000000000000, // 8192
|
ManageNicknames = 0b00000000000000000010000000000000, // 8192
|
||||||
ChangeAvatar = 0b00000000000000000100000000000000, // 16392
|
ChangeAvatar = 0b00000000000000000100000000000000, // 16382
|
||||||
RemoveAvatars = 0b00000000000000001000000000000000, // 32784
|
RemoveAvatars = 0b00000000000000001000000000000000, // 32768
|
||||||
// 16 bits of space
|
// 16 bits of space
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
pub static ref DEFAULT_PERMISSION: u32 =
|
||||||
|
ServerPermission::View
|
||||||
|
+ ServerPermission::ChangeNickname
|
||||||
|
+ ServerPermission::ChangeAvatar;
|
||||||
|
}
|
||||||
|
|
||||||
impl_op_ex!(+ |a: &ServerPermission, b: &ServerPermission| -> u32 { *a as u32 | *b as u32 });
|
impl_op_ex!(+ |a: &ServerPermission, b: &ServerPermission| -> u32 { *a as u32 | *b as u32 });
|
||||||
impl_op_ex_commutative!(+ |a: &u32, b: &ServerPermission| -> u32 { *a | *b as u32 });
|
impl_op_ex_commutative!(+ |a: &u32, b: &ServerPermission| -> u32 { *a | *b as u32 });
|
||||||
|
|
||||||
@@ -29,7 +37,7 @@ bitfield! {
|
|||||||
pub struct ServerPermissions(MSB0 [u32]);
|
pub struct ServerPermissions(MSB0 [u32]);
|
||||||
u32;
|
u32;
|
||||||
pub get_view, _: 31;
|
pub get_view, _: 31;
|
||||||
pub get_manage_members, _: 30;
|
pub get_manage_roles, _: 30;
|
||||||
pub get_manage_channels, _: 29;
|
pub get_manage_channels, _: 29;
|
||||||
pub get_manage_server, _: 28;
|
pub get_manage_server, _: 28;
|
||||||
pub get_kick_members, _: 27;
|
pub get_kick_members, _: 27;
|
||||||
@@ -52,9 +60,26 @@ impl<'a> PermissionCalculator<'a> {
|
|||||||
if self.perspective.id == server.owner {
|
if self.perspective.id == server.owner {
|
||||||
Ok(u32::MAX)
|
Ok(u32::MAX)
|
||||||
} else {
|
} else {
|
||||||
Ok(ServerPermission::View
|
match Ref::from_unchecked(self.perspective.id.clone()).fetch_member(&server.id).await {
|
||||||
+ ServerPermission::ChangeNickname
|
Ok(member) => {
|
||||||
+ ServerPermission::ChangeAvatar)
|
let mut perm = server.default_permissions.0 as u32;
|
||||||
|
if let Some(roles) = member.roles {
|
||||||
|
for role in roles {
|
||||||
|
if let Some(server_role) = server.roles.get(&role) {
|
||||||
|
perm |= server_role.permissions.0 as u32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(perm)
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
match &error {
|
||||||
|
Error::NotFound => Ok(0),
|
||||||
|
_ => Err(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -69,7 +69,31 @@ impl<'a> PermissionCalculator<'a> {
|
|||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let check_server_overlap = async || {
|
||||||
|
let server_ids = User::fetch_server_ids(&self.perspective.id).await?;
|
||||||
|
|
||||||
|
Ok(
|
||||||
|
get_collection("server_members")
|
||||||
|
.find_one(
|
||||||
|
doc! {
|
||||||
|
"_id.user": &target,
|
||||||
|
"_id.server": {
|
||||||
|
"$in": server_ids
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "find_one",
|
||||||
|
with: "server_members",
|
||||||
|
})?
|
||||||
|
.is_some()
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
if self.has_mutual_connection
|
if self.has_mutual_connection
|
||||||
|
|| check_server_overlap().await?
|
||||||
|| get_collection("channels")
|
|| get_collection("channels")
|
||||||
.find_one(
|
.find_one(
|
||||||
doc! {
|
doc! {
|
||||||
@@ -84,7 +108,7 @@ impl<'a> PermissionCalculator<'a> {
|
|||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| Error::DatabaseError {
|
.map_err(|_| Error::DatabaseError {
|
||||||
operation: "find",
|
operation: "find_one",
|
||||||
with: "channels",
|
with: "channels",
|
||||||
})?
|
})?
|
||||||
.is_some()
|
.is_some()
|
||||||
|
|||||||
+51
-50
@@ -1,10 +1,11 @@
|
|||||||
#![feature(proc_macro_hygiene, decl_macro)]
|
#![feature(proc_macro_hygiene, decl_macro)]
|
||||||
#![feature(async_closure)]
|
#![feature(async_closure)]
|
||||||
|
#![feature(const_option)]
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate rocket;
|
extern crate rocket;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate rocket_contrib;
|
extern crate serde_json;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate lazy_static;
|
extern crate lazy_static;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
@@ -16,24 +17,24 @@ extern crate ctrlc;
|
|||||||
pub mod database;
|
pub mod database;
|
||||||
pub mod notifications;
|
pub mod notifications;
|
||||||
pub mod routes;
|
pub mod routes;
|
||||||
|
pub mod redis;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
pub mod version;
|
pub mod version;
|
||||||
|
|
||||||
use async_std::task;
|
use async_std::task;
|
||||||
use chrono::Duration;
|
|
||||||
use futures::join;
|
use futures::join;
|
||||||
use log::info;
|
use log::info;
|
||||||
use rauth::options::{EmailVerification, Options, SMTP};
|
|
||||||
use rauth::{
|
use rauth::{
|
||||||
auth::Auth,
|
config::{Captcha, Config, EmailVerification, SMTPSettings, Template, Templates},
|
||||||
options::{Template, Templates},
|
logic::Auth,
|
||||||
};
|
};
|
||||||
|
use std::str::FromStr;
|
||||||
use rocket_cors::AllowedOrigins;
|
use rocket_cors::AllowedOrigins;
|
||||||
use rocket_prometheus::PrometheusMetrics;
|
|
||||||
use util::variables::{
|
use util::variables::{
|
||||||
APP_URL, HCAPTCHA_KEY, INVITE_ONLY, PUBLIC_URL, SMTP_FROM, SMTP_HOST, SMTP_PASSWORD,
|
APP_URL, HCAPTCHA_KEY, INVITE_ONLY, SMTP_FROM, SMTP_HOST, SMTP_PASSWORD, SMTP_USERNAME,
|
||||||
SMTP_USERNAME, USE_EMAIL, USE_HCAPTCHA, USE_PROMETHEUS,
|
USE_EMAIL, USE_HCAPTCHA,
|
||||||
};
|
};
|
||||||
|
use crate::util::ratelimit::RatelimitState;
|
||||||
|
|
||||||
#[async_std::main]
|
#[async_std::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
@@ -47,6 +48,7 @@ async fn main() {
|
|||||||
|
|
||||||
util::variables::preflight_checks();
|
util::variables::preflight_checks();
|
||||||
database::connect().await;
|
database::connect().await;
|
||||||
|
redis::connect().await;
|
||||||
notifications::hive::init_hive().await;
|
notifications::hive::init_hive().await;
|
||||||
|
|
||||||
ctrlc::set_handler(move || {
|
ctrlc::set_handler(move || {
|
||||||
@@ -56,7 +58,7 @@ async fn main() {
|
|||||||
.expect("Error setting Ctrl-C handler");
|
.expect("Error setting Ctrl-C handler");
|
||||||
|
|
||||||
let web_task = task::spawn(launch_web());
|
let web_task = task::spawn(launch_web());
|
||||||
let hive_task = task::spawn(notifications::hive::listen());
|
let hive_task = task::spawn_local(notifications::hive::listen());
|
||||||
|
|
||||||
join!(
|
join!(
|
||||||
web_task,
|
web_task,
|
||||||
@@ -68,72 +70,71 @@ async fn main() {
|
|||||||
async fn launch_web() {
|
async fn launch_web() {
|
||||||
let cors = rocket_cors::CorsOptions {
|
let cors = rocket_cors::CorsOptions {
|
||||||
allowed_origins: AllowedOrigins::All,
|
allowed_origins: AllowedOrigins::All,
|
||||||
|
allowed_methods: [
|
||||||
|
"Get", "Put", "Post", "Delete", "Options", "Head", "Trace", "Connect", "Patch",
|
||||||
|
]
|
||||||
|
.iter()
|
||||||
|
.map(|s| FromStr::from_str(s).unwrap())
|
||||||
|
.collect(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
.to_cors()
|
.to_cors()
|
||||||
.expect("Failed to create CORS.");
|
.expect("Failed to create CORS.");
|
||||||
|
|
||||||
let mut options = Options::new()
|
let mut config = Config {
|
||||||
.base_url(format!("{}/auth", *PUBLIC_URL))
|
email_verification: if *USE_EMAIL {
|
||||||
.email_verification(if *USE_EMAIL {
|
|
||||||
EmailVerification::Enabled {
|
EmailVerification::Enabled {
|
||||||
success_redirect_uri: format!("{}/login", *APP_URL),
|
smtp: SMTPSettings {
|
||||||
welcome_redirect_uri: format!("{}/welcome", *APP_URL),
|
|
||||||
password_reset_url: Some(format!("{}/login/reset", *APP_URL)),
|
|
||||||
|
|
||||||
verification_expiry: Duration::days(1),
|
|
||||||
password_reset_expiry: Duration::hours(1),
|
|
||||||
|
|
||||||
templates: Templates {
|
|
||||||
verify_email: Template {
|
|
||||||
title: "Verify your REVOLT account.",
|
|
||||||
text: "Verify your email here: {{url}}",
|
|
||||||
html: include_str!("../assets/templates/verify.html"),
|
|
||||||
},
|
|
||||||
reset_password: Template {
|
|
||||||
title: "Reset your REVOLT password.",
|
|
||||||
text: "Reset your password here: {{url}}",
|
|
||||||
html: include_str!("../assets/templates/reset.html"),
|
|
||||||
},
|
|
||||||
welcome: None,
|
|
||||||
},
|
|
||||||
|
|
||||||
smtp: SMTP {
|
|
||||||
from: (*SMTP_FROM).to_string(),
|
from: (*SMTP_FROM).to_string(),
|
||||||
host: (*SMTP_HOST).to_string(),
|
host: (*SMTP_HOST).to_string(),
|
||||||
username: (*SMTP_USERNAME).to_string(),
|
username: (*SMTP_USERNAME).to_string(),
|
||||||
password: (*SMTP_PASSWORD).to_string(),
|
password: (*SMTP_PASSWORD).to_string(),
|
||||||
|
reply_to: Some("support@revolt.chat".into()),
|
||||||
|
port: None,
|
||||||
|
use_tls: None,
|
||||||
|
},
|
||||||
|
expiry: Default::default(),
|
||||||
|
templates: Templates {
|
||||||
|
verify: Template {
|
||||||
|
title: "Verify your Revolt account.".into(),
|
||||||
|
text: include_str!("../assets/templates/verify.txt").into(),
|
||||||
|
url: format!("{}/login/verify/", *APP_URL),
|
||||||
|
html: None,
|
||||||
|
},
|
||||||
|
reset: Template {
|
||||||
|
title: "Reset your Revolt password.".into(),
|
||||||
|
text: include_str!("../assets/templates/reset.txt").into(),
|
||||||
|
url: format!("{}/login/reset/", *APP_URL),
|
||||||
|
html: None,
|
||||||
|
},
|
||||||
|
welcome: None,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
EmailVerification::Disabled
|
EmailVerification::Disabled
|
||||||
});
|
},
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
if *INVITE_ONLY {
|
if *INVITE_ONLY {
|
||||||
options = options.invite_only_collection(database::get_collection("invites"))
|
config.invite_only = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if *USE_HCAPTCHA {
|
if *USE_HCAPTCHA {
|
||||||
options = options.hcaptcha_secret(HCAPTCHA_KEY.clone());
|
config.captcha = Captcha::HCaptcha {
|
||||||
}
|
secret: HCAPTCHA_KEY.clone(),
|
||||||
|
};
|
||||||
let auth = Auth::new(database::get_collection("accounts"), options);
|
|
||||||
|
|
||||||
let mut rocket = rocket::ignite();
|
|
||||||
|
|
||||||
if *USE_PROMETHEUS {
|
|
||||||
info!("Enabled Prometheus metrics!");
|
|
||||||
let prometheus = PrometheusMetrics::new();
|
|
||||||
rocket = rocket
|
|
||||||
.attach(prometheus.clone())
|
|
||||||
.mount("/metrics", prometheus);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let auth = Auth::new(database::get_db(), config);
|
||||||
|
let rocket = rocket::build();
|
||||||
routes::mount(rocket)
|
routes::mount(rocket)
|
||||||
.mount("/", rocket_cors::catch_all_options_routes())
|
.mount("/", rocket_cors::catch_all_options_routes())
|
||||||
.mount("/auth", rauth::routes::routes())
|
.mount("/auth/account", rauth::web::account::routes())
|
||||||
|
.mount("/auth/session", rauth::web::session::routes())
|
||||||
.manage(auth)
|
.manage(auth)
|
||||||
.manage(cors.clone())
|
.manage(cors.clone())
|
||||||
|
.manage(RatelimitState::new())
|
||||||
.attach(cors)
|
.attach(cors)
|
||||||
.launch()
|
.launch()
|
||||||
.await
|
.await
|
||||||
|
|||||||
+59
-15
@@ -1,7 +1,6 @@
|
|||||||
use hive_pubsub::PubSub;
|
use hive_pubsub::PubSub;
|
||||||
use mongodb::bson::doc;
|
use mongodb::bson::doc;
|
||||||
use rauth::auth::Session;
|
use rocket::serde::json::Value;
|
||||||
use rocket_contrib::json::JsonValue;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::hive::{get_hive, subscribe_if_exists};
|
use super::hive::{get_hive, subscribe_if_exists};
|
||||||
@@ -15,14 +14,28 @@ pub enum WebSocketError {
|
|||||||
InvalidSession,
|
InvalidSession,
|
||||||
OnboardingNotFinished,
|
OnboardingNotFinished,
|
||||||
AlreadyAuthenticated,
|
AlreadyAuthenticated,
|
||||||
|
MalformedData { msg: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct Auth {
|
||||||
|
pub token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum Ping {
|
||||||
|
Binary(Vec<u8>),
|
||||||
|
Number(usize)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
pub enum ServerboundNotification {
|
pub enum ServerboundNotification {
|
||||||
Authenticate(Session),
|
Authenticate(Auth),
|
||||||
BeginTyping { channel: String },
|
BeginTyping { channel: String },
|
||||||
EndTyping { channel: String },
|
EndTyping { channel: String },
|
||||||
|
Ping { data: Ping, responded: Option<()> },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
@@ -46,12 +59,22 @@ pub enum RemoveServerField {
|
|||||||
Description,
|
Description,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub enum RemoveRoleField {
|
||||||
|
Colour,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub enum RemoveMemberField {
|
pub enum RemoveMemberField {
|
||||||
Nickname,
|
Nickname,
|
||||||
Avatar,
|
Avatar,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub enum RemoveBotField {
|
||||||
|
InteractionsURL,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
pub enum ClientboundNotification {
|
pub enum ClientboundNotification {
|
||||||
@@ -61,13 +84,15 @@ pub enum ClientboundNotification {
|
|||||||
users: Vec<User>,
|
users: Vec<User>,
|
||||||
servers: Vec<Server>,
|
servers: Vec<Server>,
|
||||||
channels: Vec<Channel>,
|
channels: Vec<Channel>,
|
||||||
|
members: Vec<Member>,
|
||||||
},
|
},
|
||||||
|
Pong { data: Ping },
|
||||||
|
|
||||||
Message(Message),
|
Message(Message),
|
||||||
MessageUpdate {
|
MessageUpdate {
|
||||||
id: String,
|
id: String,
|
||||||
channel: String,
|
channel: String,
|
||||||
data: JsonValue,
|
data: Value,
|
||||||
},
|
},
|
||||||
MessageDelete {
|
MessageDelete {
|
||||||
id: String,
|
id: String,
|
||||||
@@ -77,7 +102,7 @@ pub enum ClientboundNotification {
|
|||||||
ChannelCreate(Channel),
|
ChannelCreate(Channel),
|
||||||
ChannelUpdate {
|
ChannelUpdate {
|
||||||
id: String,
|
id: String,
|
||||||
data: JsonValue,
|
data: Value,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
clear: Option<RemoveChannelField>,
|
clear: Option<RemoveChannelField>,
|
||||||
},
|
},
|
||||||
@@ -108,7 +133,7 @@ pub enum ClientboundNotification {
|
|||||||
|
|
||||||
ServerUpdate {
|
ServerUpdate {
|
||||||
id: String,
|
id: String,
|
||||||
data: JsonValue,
|
data: Value,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
clear: Option<RemoveServerField>,
|
clear: Option<RemoveServerField>,
|
||||||
},
|
},
|
||||||
@@ -117,7 +142,7 @@ pub enum ClientboundNotification {
|
|||||||
},
|
},
|
||||||
ServerMemberUpdate {
|
ServerMemberUpdate {
|
||||||
id: MemberCompositeKey,
|
id: MemberCompositeKey,
|
||||||
data: JsonValue,
|
data: Value,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
clear: Option<RemoveMemberField>,
|
clear: Option<RemoveMemberField>,
|
||||||
},
|
},
|
||||||
@@ -129,10 +154,21 @@ pub enum ClientboundNotification {
|
|||||||
id: String,
|
id: String,
|
||||||
user: String,
|
user: String,
|
||||||
},
|
},
|
||||||
|
ServerRoleUpdate {
|
||||||
|
id: String,
|
||||||
|
role_id: String,
|
||||||
|
data: Value,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
clear: Option<RemoveRoleField>,
|
||||||
|
},
|
||||||
|
ServerRoleDelete {
|
||||||
|
id: String,
|
||||||
|
role_id: String,
|
||||||
|
},
|
||||||
|
|
||||||
UserUpdate {
|
UserUpdate {
|
||||||
id: String,
|
id: String,
|
||||||
data: JsonValue,
|
data: Value,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
clear: Option<RemoveUserField>,
|
clear: Option<RemoveUserField>,
|
||||||
},
|
},
|
||||||
@@ -141,13 +177,9 @@ pub enum ClientboundNotification {
|
|||||||
user: User,
|
user: User,
|
||||||
status: RelationshipStatus,
|
status: RelationshipStatus,
|
||||||
},
|
},
|
||||||
UserPresence {
|
|
||||||
id: String,
|
|
||||||
online: bool,
|
|
||||||
},
|
|
||||||
UserSettingsUpdate {
|
UserSettingsUpdate {
|
||||||
id: String,
|
id: String,
|
||||||
update: JsonValue,
|
update: Value,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,11 +187,23 @@ impl ClientboundNotification {
|
|||||||
pub fn publish(self, topic: String) {
|
pub fn publish(self, topic: String) {
|
||||||
async_std::task::spawn(async move {
|
async_std::task::spawn(async move {
|
||||||
prehandle_hook(&self).await.ok(); // ! FIXME: this should be moved to pubsub
|
prehandle_hook(&self).await.ok(); // ! FIXME: this should be moved to pubsub
|
||||||
hive_pubsub::backend::mongo::publish(get_hive(), &topic, self)
|
hive_pubsub::backend::redis::publish(get_hive(), topic, self)
|
||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn publish_as_user(self, user: String) {
|
||||||
|
self.clone().publish(user.clone());
|
||||||
|
|
||||||
|
async_std::task::spawn(async move {
|
||||||
|
if let Ok(server_ids) = User::fetch_server_ids(&user).await {
|
||||||
|
for server in server_ids {
|
||||||
|
self.clone().publish(server.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn prehandle_hook(notification: &ClientboundNotification) -> Result<()> {
|
pub async fn prehandle_hook(notification: &ClientboundNotification) -> Result<()> {
|
||||||
@@ -178,7 +222,7 @@ pub async fn prehandle_hook(notification: &ClientboundNotification) -> Result<()
|
|||||||
subscribe_if_exists(recipient.clone(), channel_id.to_string()).ok();
|
subscribe_if_exists(recipient.clone(), channel_id.to_string()).ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Channel::TextChannel { server, .. } => {
|
Channel::TextChannel { server, .. } | Channel::VoiceChannel { server, .. } => {
|
||||||
// ! FIXME: write a better algorithm?
|
// ! FIXME: write a better algorithm?
|
||||||
let members = Server::fetch_member_ids(server).await?;
|
let members = Server::fetch_member_ids(server).await?;
|
||||||
for member in members {
|
for member in members {
|
||||||
|
|||||||
@@ -1,18 +1,23 @@
|
|||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use super::{events::ClientboundNotification, websocket};
|
use super::{events::ClientboundNotification, websocket};
|
||||||
use crate::database::*;
|
use crate::redis::get_pool;
|
||||||
|
use crate::util::variables::REDIS_URI;
|
||||||
|
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use hive_pubsub::backend::mongo::MongodbPubSub;
|
use hive_pubsub::backend::redis::RedisPubSub;
|
||||||
use hive_pubsub::PubSub;
|
use hive_pubsub::PubSub;
|
||||||
use log::{debug, error};
|
use log::{debug, error};
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use serde_json::to_string;
|
use serde_json::to_string;
|
||||||
|
|
||||||
type Hive = MongodbPubSub<String, String, ClientboundNotification>;
|
type Hive<'a> = RedisPubSub<'a, String, String, ClientboundNotification>;
|
||||||
static HIVE: OnceCell<Hive> = OnceCell::new();
|
static HIVE: OnceCell<Hive<'static>> = OnceCell::new();
|
||||||
|
|
||||||
pub async fn init_hive() {
|
pub async fn init_hive() {
|
||||||
let hive = MongodbPubSub::new(
|
let pubsub_con = redis::Client::open(REDIS_URI.to_string()).unwrap().get_async_connection().await.unwrap().into_pubsub();
|
||||||
|
|
||||||
|
let hive = RedisPubSub::new(
|
||||||
|ids, notification: ClientboundNotification| {
|
|ids, notification: ClientboundNotification| {
|
||||||
let notif = notification.clone();
|
let notif = notification.clone();
|
||||||
async_std::task::spawn(async move {
|
async_std::task::spawn(async move {
|
||||||
@@ -26,7 +31,8 @@ pub async fn init_hive() {
|
|||||||
error!("Failed to serialise notification.");
|
error!("Failed to serialise notification.");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
get_collection("pubsub"),
|
get_pool(),
|
||||||
|
Arc::new(Mutex::new(pubsub_con))
|
||||||
);
|
);
|
||||||
|
|
||||||
if HIVE.set(hive).is_err() {
|
if HIVE.set(hive).is_err() {
|
||||||
@@ -37,6 +43,7 @@ pub async fn init_hive() {
|
|||||||
pub async fn listen() {
|
pub async fn listen() {
|
||||||
HIVE.get()
|
HIVE.get()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
.clone()
|
||||||
.listen()
|
.listen()
|
||||||
.fuse()
|
.fuse()
|
||||||
.await
|
.await
|
||||||
@@ -61,6 +68,6 @@ pub fn subscribe_if_exists(user: String, topic: String) -> Result<(), String> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_hive() -> &'static Hive {
|
pub fn get_hive() -> &'static Hive<'static> {
|
||||||
HIVE.get().unwrap()
|
HIVE.get().unwrap()
|
||||||
}
|
}
|
||||||
@@ -19,7 +19,11 @@ pub async fn generate_ready(mut user: User) -> Result<ClientboundNotification> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let server_ids = user.fetch_server_ids().await?;
|
let members = User::fetch_memberships(&user.id).await?;
|
||||||
|
let server_ids: Vec<String> = members.iter()
|
||||||
|
.map(|x| x.id.server.clone())
|
||||||
|
.collect();
|
||||||
|
|
||||||
let mut cursor = get_collection("servers")
|
let mut cursor = get_collection("servers")
|
||||||
.find(
|
.find(
|
||||||
doc! {
|
doc! {
|
||||||
@@ -108,11 +112,13 @@ pub async fn generate_ready(mut user: User) -> Result<ClientboundNotification> {
|
|||||||
|
|
||||||
user.relationship = Some(RelationshipStatus::User);
|
user.relationship = Some(RelationshipStatus::User);
|
||||||
user.online = Some(true);
|
user.online = Some(true);
|
||||||
users.push(user);
|
|
||||||
|
users.push(user.apply_badges());
|
||||||
|
|
||||||
Ok(ClientboundNotification::Ready {
|
Ok(ClientboundNotification::Ready {
|
||||||
users,
|
users,
|
||||||
servers,
|
servers,
|
||||||
channels,
|
channels,
|
||||||
|
members
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -17,8 +17,7 @@ pub async fn generate_subscriptions(user: &User) -> Result<(), String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let server_ids = user
|
let server_ids = User::fetch_server_ids(&user.id)
|
||||||
.fetch_server_ids()
|
|
||||||
.await
|
.await
|
||||||
.map_err(|_| "Failed to fetch memberships.".to_string())?;
|
.map_err(|_| "Failed to fetch memberships.".to_string())?;
|
||||||
|
|
||||||
|
|||||||
+162
-45
@@ -1,24 +1,28 @@
|
|||||||
use crate::database::*;
|
use crate::database::*;
|
||||||
|
use crate::notifications::events::Ping;
|
||||||
use crate::util::variables::WS_HOST;
|
use crate::util::variables::WS_HOST;
|
||||||
|
|
||||||
use super::subscriptions;
|
use super::subscriptions;
|
||||||
|
|
||||||
use async_std::net::{TcpListener, TcpStream};
|
use async_std::net::{TcpListener, TcpStream};
|
||||||
use async_std::task;
|
use async_std::task;
|
||||||
use async_tungstenite::tungstenite::Message;
|
use async_tungstenite::tungstenite::{handshake::server, Message};
|
||||||
use futures::channel::mpsc::{unbounded, UnboundedSender};
|
use futures::channel::{
|
||||||
|
mpsc::{unbounded, UnboundedSender},
|
||||||
|
oneshot,
|
||||||
|
};
|
||||||
use futures::stream::TryStreamExt;
|
use futures::stream::TryStreamExt;
|
||||||
use futures::{pin_mut, prelude::*};
|
use futures::{pin_mut, prelude::*};
|
||||||
use hive_pubsub::PubSub;
|
use hive_pubsub::PubSub;
|
||||||
use log::{debug, info};
|
use log::{debug, info};
|
||||||
use many_to_many::ManyToMany;
|
use many_to_many::ManyToMany;
|
||||||
use rauth::{
|
use mongodb::bson::doc;
|
||||||
auth::{Auth, Session},
|
use rauth::entities::{Model, Session};
|
||||||
options::Options,
|
use rmp_serde;
|
||||||
};
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::sync::{Arc, Mutex, RwLock};
|
use std::sync::{Arc, Mutex, RwLock};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
events::{ClientboundNotification, ServerboundNotification, WebSocketError},
|
events::{ClientboundNotification, ServerboundNotification, WebSocketError},
|
||||||
@@ -26,7 +30,7 @@ use super::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
type Tx = UnboundedSender<Message>;
|
type Tx = UnboundedSender<Message>;
|
||||||
type PeerMap = Arc<Mutex<HashMap<SocketAddr, Tx>>>;
|
type PeerMap = Arc<Mutex<HashMap<SocketAddr, (Tx, MSGFormat)>>>;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref CONNECTIONS: PeerMap = Arc::new(Mutex::new(HashMap::new()));
|
static ref CONNECTIONS: PeerMap = Arc::new(Mutex::new(HashMap::new()));
|
||||||
@@ -44,37 +48,117 @@ pub async fn launch_server() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum MSGFormat {
|
||||||
|
JSON,
|
||||||
|
MSGPACK,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct HeaderCallback {
|
||||||
|
sender: oneshot::Sender<MSGFormat>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl server::Callback for HeaderCallback {
|
||||||
|
fn on_request(
|
||||||
|
self,
|
||||||
|
request: &server::Request,
|
||||||
|
response: server::Response,
|
||||||
|
) -> Result<server::Response, server::ErrorResponse> {
|
||||||
|
// we dont get some of the data sometimes so im generating a fake url with the only data we actually need
|
||||||
|
let url = format!(
|
||||||
|
"ws://example.com?{}",
|
||||||
|
request.uri().query().unwrap_or("?format=json")
|
||||||
|
);
|
||||||
|
let mut query: HashMap<_, _> = url
|
||||||
|
.parse::<Url>()
|
||||||
|
.unwrap()
|
||||||
|
.query_pairs()
|
||||||
|
.into_owned()
|
||||||
|
.collect(); // should be safe to use unwrap here as we just made the url ourself
|
||||||
|
let format_query: Option<String> = query.remove("format");
|
||||||
|
|
||||||
|
let format = match format_query.as_deref().unwrap_or("json") {
|
||||||
|
"msgpack" => MSGFormat::MSGPACK,
|
||||||
|
"json" => MSGFormat::JSON,
|
||||||
|
_ => MSGFormat::JSON, // Fallback to JSON.
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.sender.send(format).is_ok() {
|
||||||
|
Ok(response)
|
||||||
|
} else {
|
||||||
|
Err(server::ErrorResponse::new(None))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn accept(stream: TcpStream) {
|
async fn accept(stream: TcpStream) {
|
||||||
let addr = stream
|
let addr = stream
|
||||||
.peer_addr()
|
.peer_addr()
|
||||||
.expect("Connected streams should have a peer address.");
|
.expect("Connected streams should have a peer address.");
|
||||||
let ws_stream = async_tungstenite::accept_async(stream)
|
let (sender, receiver) = oneshot::channel::<MSGFormat>();
|
||||||
|
|
||||||
|
let ws_stream =
|
||||||
|
async_tungstenite::accept_hdr_async_with_config(stream, HeaderCallback { sender }, None)
|
||||||
.await
|
.await
|
||||||
.expect("Error during websocket handshake.");
|
.expect("Error during websocket handshake.");
|
||||||
|
|
||||||
|
let msg_format = receiver.await.unwrap(); // TODO: not use unwrap
|
||||||
|
|
||||||
info!("User established WebSocket connection from {}.", &addr);
|
info!("User established WebSocket connection from {}.", &addr);
|
||||||
|
|
||||||
let (write, read) = ws_stream.split();
|
let (write, read) = ws_stream.split();
|
||||||
let (tx, rx) = unbounded();
|
let (tx, rx) = unbounded();
|
||||||
CONNECTIONS.lock().unwrap().insert(addr, tx.clone());
|
CONNECTIONS.lock().unwrap().insert(addr, (tx.clone(), msg_format.clone()));
|
||||||
|
|
||||||
let send = |notification: ClientboundNotification| {
|
let send = |notification: ClientboundNotification| {
|
||||||
if let Ok(response) = serde_json::to_string(¬ification) {
|
let res = match msg_format {
|
||||||
if let Err(_) = tx.unbounded_send(Message::Text(response)) {
|
MSGFormat::JSON => match serde_json::to_string(¬ification) {
|
||||||
debug!("Failed unbounded_send to websocket stream.");
|
Ok(s) => Message::Text(s),
|
||||||
|
Err(_) => return
|
||||||
}
|
}
|
||||||
|
MSGFormat::MSGPACK => match rmp_serde::to_vec_named(¬ification) {
|
||||||
|
Ok(v) => Message::Binary(v),
|
||||||
|
Err(_) => return,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(_) = tx.unbounded_send(res) {
|
||||||
|
debug!("Failed unbounded_send to websocket stream.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let session: Arc<Mutex<Option<Session>>> = Arc::new(Mutex::new(None));
|
let user_id: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));
|
||||||
let mutex_generator = || session.clone();
|
let mutex_generator = || user_id.clone();
|
||||||
let fwd = rx.map(Ok).forward(write);
|
let fwd = rx.map(Ok).forward(write);
|
||||||
let incoming = read.try_for_each(async move |msg| {
|
let incoming = read.try_for_each(async move |msg| {
|
||||||
let mutex = mutex_generator();
|
let mutex = mutex_generator();
|
||||||
if let Message::Text(text) = msg {
|
|
||||||
if let Ok(notification) = serde_json::from_str::<ServerboundNotification>(&text) {
|
let maybe_decoded = match msg {
|
||||||
|
Message::Text(text) => {
|
||||||
|
serde_json::from_str::<ServerboundNotification>(&text).map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
Message::Binary(vec) => {
|
||||||
|
rmp_serde::decode::from_read::<&[u8], ServerboundNotification>(vec.as_slice())
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
Message::Ping(vec) => Ok(ServerboundNotification::Ping { data: Ping::Binary(vec), responded: Some(()) }),
|
||||||
|
_ => return Ok(()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let notification = match maybe_decoded {
|
||||||
|
Err(why) => {
|
||||||
|
send(ClientboundNotification::Error(
|
||||||
|
WebSocketError::MalformedData {
|
||||||
|
msg: why.to_string(),
|
||||||
|
},
|
||||||
|
));
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
Ok(n) => n,
|
||||||
|
};
|
||||||
|
|
||||||
match notification {
|
match notification {
|
||||||
ServerboundNotification::Authenticate(new_session) => {
|
ServerboundNotification::Authenticate(auth) => {
|
||||||
{
|
{
|
||||||
if mutex.lock().unwrap().is_some() {
|
if mutex.lock().unwrap().is_some() {
|
||||||
send(ClientboundNotification::Error(
|
send(ClientboundNotification::Error(
|
||||||
@@ -85,14 +169,33 @@ async fn accept(stream: TcpStream) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(validated_session) =
|
let id = if let Ok(Some(session)) =
|
||||||
Auth::new(get_collection("accounts"), Options::new())
|
Session::find_one(&get_db(), doc! { "token": &auth.token }, None).await
|
||||||
.verify_session(new_session)
|
{
|
||||||
|
Some(session.user_id)
|
||||||
|
} else if let Ok(Some(bot)) = get_collection("bots")
|
||||||
|
.find_one(doc! { "token": auth.token }, None)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
let id = validated_session.user_id.clone();
|
Some(bot.get_str("_id").unwrap().to_string())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(id) = id {
|
||||||
if let Ok(user) = (Ref { id: id.clone() }).fetch_user().await {
|
if let Ok(user) = (Ref { id: id.clone() }).fetch_user().await {
|
||||||
|
let is_invisible = if let Some(status) = &user.status {
|
||||||
|
if let Some(presence) = &status.presence {
|
||||||
|
presence == &Presence::Invisible
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
let was_online = is_online(&id);
|
let was_online = is_online(&id);
|
||||||
|
|
||||||
{
|
{
|
||||||
match USERS.write() {
|
match USERS.write() {
|
||||||
Ok(mut map) => {
|
Ok(mut map) => {
|
||||||
@@ -110,7 +213,7 @@ async fn accept(stream: TcpStream) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
*mutex.lock().unwrap() = Some(validated_session);
|
*mutex.lock().unwrap() = Some(id.clone());
|
||||||
|
|
||||||
if let Err(_) = subscriptions::generate_subscriptions(&user).await {
|
if let Err(_) = subscriptions::generate_subscriptions(&user).await {
|
||||||
send(ClientboundNotification::Error(
|
send(ClientboundNotification::Error(
|
||||||
@@ -128,12 +231,15 @@ async fn accept(stream: TcpStream) {
|
|||||||
Ok(payload) => {
|
Ok(payload) => {
|
||||||
send(payload);
|
send(payload);
|
||||||
|
|
||||||
if !was_online {
|
if !was_online && !is_invisible {
|
||||||
ClientboundNotification::UserPresence {
|
ClientboundNotification::UserUpdate {
|
||||||
id: id.clone(),
|
id: id.clone(),
|
||||||
online: true,
|
data: json!({
|
||||||
|
"online": true
|
||||||
|
}),
|
||||||
|
clear: None,
|
||||||
}
|
}
|
||||||
.publish(id);
|
.publish_as_user(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
@@ -163,8 +269,7 @@ async fn accept(stream: TcpStream) {
|
|||||||
if mutex.lock().unwrap().is_some() {
|
if mutex.lock().unwrap().is_some() {
|
||||||
let user = {
|
let user = {
|
||||||
let mutex = mutex.lock().unwrap();
|
let mutex = mutex.lock().unwrap();
|
||||||
let session = mutex.as_ref().unwrap();
|
mutex.as_ref().unwrap().clone()
|
||||||
session.user_id.clone()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
ClientboundNotification::ChannelStartTyping {
|
ClientboundNotification::ChannelStartTyping {
|
||||||
@@ -184,8 +289,7 @@ async fn accept(stream: TcpStream) {
|
|||||||
if mutex.lock().unwrap().is_some() {
|
if mutex.lock().unwrap().is_some() {
|
||||||
let user = {
|
let user = {
|
||||||
let mutex = mutex.lock().unwrap();
|
let mutex = mutex.lock().unwrap();
|
||||||
let session = mutex.as_ref().unwrap();
|
mutex.as_ref().unwrap().clone()
|
||||||
session.user_id.clone()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
ClientboundNotification::ChannelStopTyping {
|
ClientboundNotification::ChannelStopTyping {
|
||||||
@@ -201,10 +305,14 @@ async fn accept(stream: TcpStream) {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
ServerboundNotification::Ping { data, responded } => {
|
||||||
}
|
debug!("Ping received from connection {}. Payload: {:?}", &addr, data);
|
||||||
}
|
|
||||||
|
|
||||||
|
if responded.is_none() {
|
||||||
|
send(ClientboundNotification::Pong { data });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -216,23 +324,26 @@ async fn accept(stream: TcpStream) {
|
|||||||
|
|
||||||
let mut offline = None;
|
let mut offline = None;
|
||||||
{
|
{
|
||||||
let session = session.lock().unwrap();
|
let user_id = user_id.lock().unwrap();
|
||||||
if let Some(session) = session.as_ref() {
|
if let Some(user_id) = user_id.as_ref() {
|
||||||
let mut users = USERS.write().unwrap();
|
let mut users = USERS.write().unwrap();
|
||||||
users.remove(&session.user_id, &addr);
|
users.remove(&user_id, &addr);
|
||||||
if users.get_left(&session.user_id).is_none() {
|
if users.get_left(&user_id).is_none() {
|
||||||
get_hive().drop_client(&session.user_id).unwrap();
|
get_hive().drop_client(&user_id).unwrap();
|
||||||
offline = Some(session.user_id.clone());
|
offline = Some(user_id.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(id) = offline {
|
if let Some(id) = offline {
|
||||||
ClientboundNotification::UserPresence {
|
ClientboundNotification::UserUpdate {
|
||||||
id: id.clone(),
|
id: id.clone(),
|
||||||
online: false,
|
data: json!({
|
||||||
|
"online": false
|
||||||
|
}),
|
||||||
|
clear: None,
|
||||||
}
|
}
|
||||||
.publish(id);
|
.publish_as_user(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,12 +370,18 @@ pub fn publish(ids: Vec<String>, notification: ClientboundNotification) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let msg = Message::Text(serde_json::to_string(¬ification).unwrap());
|
let json_msg = Message::Text(serde_json::to_string(¬ification).unwrap());
|
||||||
|
let msgpack_msg = Message::Binary(rmp_serde::to_vec_named(¬ification).unwrap());
|
||||||
|
|
||||||
let connections = CONNECTIONS.lock().unwrap();
|
let connections = CONNECTIONS.lock().unwrap();
|
||||||
for target in targets {
|
for target in targets {
|
||||||
if let Some(conn) = connections.get(&target) {
|
if let Some((conn, msg_format)) = connections.get(&target) {
|
||||||
if let Err(_) = conn.unbounded_send(msg.clone()) {
|
let msg = match msg_format {
|
||||||
|
MSGFormat::JSON => json_msg.clone(),
|
||||||
|
MSGFormat::MSGPACK => msgpack_msg.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(_) = conn.unbounded_send(msg) {
|
||||||
debug!("Failed unbounded_send.");
|
debug!("Failed unbounded_send.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
use crate::util::variables::REDIS_URI;
|
||||||
|
|
||||||
|
use mobc::Pool;
|
||||||
|
use mobc_redis::RedisConnectionManager;
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
|
|
||||||
|
static REDISPOOL: OnceCell<Pool<RedisConnectionManager>> = OnceCell::new();
|
||||||
|
|
||||||
|
pub async fn connect() {
|
||||||
|
let client = mobc_redis::redis::Client::open(REDIS_URI.to_string()).unwrap();
|
||||||
|
let manager = mobc_redis::RedisConnectionManager::new(client);
|
||||||
|
let pool = mobc::Pool::builder().max_open(100).build(manager);
|
||||||
|
REDISPOOL.set(pool).ok().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_pool() -> &'static Pool<RedisConnectionManager> {
|
||||||
|
REDISPOOL.get().unwrap()
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
use crate::database::*;
|
||||||
|
use crate::util::result::{Error, Result};
|
||||||
|
use crate::util::variables::MAX_BOT_COUNT;
|
||||||
|
use crate::util::regex::RE_USERNAME;
|
||||||
|
|
||||||
|
use mongodb::bson::{doc, to_document};
|
||||||
|
use rocket::serde::json::{Json, Value};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use ulid::Ulid;
|
||||||
|
use nanoid::nanoid;
|
||||||
|
use validator::Validate;
|
||||||
|
|
||||||
|
#[derive(Validate, Serialize, Deserialize)]
|
||||||
|
pub struct Data {
|
||||||
|
#[validate(length(min = 2, max = 32), regex = "RE_USERNAME")]
|
||||||
|
name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/create", data = "<info>")]
|
||||||
|
pub async fn create_bot(user: User, info: Json<Data>) -> Result<Value> {
|
||||||
|
if user.bot.is_some() {
|
||||||
|
return Err(Error::IsBot)
|
||||||
|
}
|
||||||
|
|
||||||
|
let info = info.into_inner();
|
||||||
|
info.validate()
|
||||||
|
.map_err(|error| Error::FailedValidation { error })?;
|
||||||
|
|
||||||
|
if get_collection("bots")
|
||||||
|
.count_documents(
|
||||||
|
doc! {
|
||||||
|
"owner": &user.id
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "count_documents",
|
||||||
|
with: "bots",
|
||||||
|
})? as usize >= *MAX_BOT_COUNT {
|
||||||
|
return Err(Error::ReachedMaximumBots)
|
||||||
|
}
|
||||||
|
|
||||||
|
let id = Ulid::new().to_string();
|
||||||
|
let token = nanoid!(64);
|
||||||
|
let bot = Bot {
|
||||||
|
id: id.clone(),
|
||||||
|
owner: user.id.clone(),
|
||||||
|
token,
|
||||||
|
public: false,
|
||||||
|
interactions_url: None
|
||||||
|
};
|
||||||
|
|
||||||
|
if User::is_username_taken(&info.name).await? {
|
||||||
|
return Err(Error::UsernameTaken);
|
||||||
|
}
|
||||||
|
|
||||||
|
get_collection("users")
|
||||||
|
.insert_one(
|
||||||
|
doc! {
|
||||||
|
"_id": &id,
|
||||||
|
"username": &info.name,
|
||||||
|
"bot": {
|
||||||
|
"owner": &user.id
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "insert_one",
|
||||||
|
with: "user",
|
||||||
|
})?;
|
||||||
|
|
||||||
|
get_collection("bots")
|
||||||
|
.insert_one(
|
||||||
|
to_document(&bot).map_err(|_| Error::DatabaseError { with: "bot", operation: "to_document" })?,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "insert_one",
|
||||||
|
with: "user",
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(json!(bot))
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
use crate::database::*;
|
||||||
|
use crate::notifications::events::ClientboundNotification;
|
||||||
|
use crate::util::result::{Error, EmptyResponse, Result};
|
||||||
|
|
||||||
|
use mongodb::bson::doc;
|
||||||
|
|
||||||
|
#[delete("/<target>")]
|
||||||
|
pub async fn delete_bot(user: User, target: Ref) -> Result<EmptyResponse> {
|
||||||
|
if user.bot.is_some() {
|
||||||
|
return Err(Error::IsBot)
|
||||||
|
}
|
||||||
|
|
||||||
|
let bot = target.fetch_bot().await?;
|
||||||
|
if bot.owner != user.id {
|
||||||
|
return Err(Error::MissingPermission);
|
||||||
|
}
|
||||||
|
|
||||||
|
let username = format!("Deleted User {}", &bot.id);
|
||||||
|
get_collection("users")
|
||||||
|
.update_one(
|
||||||
|
doc! {
|
||||||
|
"_id": &bot.id
|
||||||
|
},
|
||||||
|
doc! {
|
||||||
|
"$set": {
|
||||||
|
"username": &username,
|
||||||
|
"flags": 2
|
||||||
|
},
|
||||||
|
"$unset": {
|
||||||
|
"avatar": 1,
|
||||||
|
"status": 1,
|
||||||
|
"profile": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
with: "user",
|
||||||
|
operation: "update_one"
|
||||||
|
})?;
|
||||||
|
|
||||||
|
ClientboundNotification::UserUpdate {
|
||||||
|
id: target.id.clone(),
|
||||||
|
data: json!({
|
||||||
|
"username": username,
|
||||||
|
"flags": 2
|
||||||
|
}),
|
||||||
|
clear: None,
|
||||||
|
}
|
||||||
|
.publish_as_user(target.id.clone());
|
||||||
|
|
||||||
|
get_collection("bots")
|
||||||
|
.delete_one(
|
||||||
|
doc! {
|
||||||
|
"_id": &bot.id
|
||||||
|
},
|
||||||
|
None
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
with: "bot",
|
||||||
|
operation: "delete_one"
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(EmptyResponse {})
|
||||||
|
}
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
use crate::notifications::events::ClientboundNotification;
|
||||||
|
use crate::util::result::{Error, Result, EmptyResponse};
|
||||||
|
use crate::{database::*, notifications::events::RemoveBotField};
|
||||||
|
use crate::util::regex::RE_USERNAME;
|
||||||
|
|
||||||
|
use mongodb::bson::doc;
|
||||||
|
use rocket::serde::json::Json;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use validator::Validate;
|
||||||
|
|
||||||
|
#[derive(Validate, Serialize, Deserialize)]
|
||||||
|
pub struct Data {
|
||||||
|
#[validate(length(min = 2, max = 32), regex = "RE_USERNAME")]
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
name: Option<String>,
|
||||||
|
public: Option<bool>,
|
||||||
|
interactions_url: Option<String>,
|
||||||
|
remove: Option<RemoveBotField>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[patch("/<target>", data = "<data>")]
|
||||||
|
pub async fn edit_bot(user: User, target: Ref, data: Json<Data>) -> Result<EmptyResponse> {
|
||||||
|
if user.bot.is_some() {
|
||||||
|
return Err(Error::IsBot)
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = data.into_inner();
|
||||||
|
data.validate()
|
||||||
|
.map_err(|error| Error::FailedValidation { error })?;
|
||||||
|
|
||||||
|
if data.name.is_none()
|
||||||
|
&& data.public.is_none()
|
||||||
|
&& data.interactions_url.is_none()
|
||||||
|
&& data.remove.is_none()
|
||||||
|
{
|
||||||
|
return Ok(EmptyResponse {});
|
||||||
|
}
|
||||||
|
|
||||||
|
let bot = target.fetch_bot().await?;
|
||||||
|
if bot.owner != user.id {
|
||||||
|
return Err(Error::MissingPermission);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(name) = &data.name {
|
||||||
|
if User::is_username_taken(&name).await? {
|
||||||
|
return Err(Error::UsernameTaken);
|
||||||
|
}
|
||||||
|
|
||||||
|
get_collection("users")
|
||||||
|
.update_one(
|
||||||
|
doc! { "_id": &target.id },
|
||||||
|
doc! {
|
||||||
|
"$set": {
|
||||||
|
"username": name
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "update_one",
|
||||||
|
with: "user",
|
||||||
|
})?;
|
||||||
|
|
||||||
|
ClientboundNotification::UserUpdate {
|
||||||
|
id: target.id.clone(),
|
||||||
|
data: json!({
|
||||||
|
"username": name
|
||||||
|
}),
|
||||||
|
clear: None,
|
||||||
|
}
|
||||||
|
.publish_as_user(target.id.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut set = doc! {};
|
||||||
|
let mut unset = doc! {};
|
||||||
|
|
||||||
|
if let Some(remove) = &data.remove {
|
||||||
|
match remove {
|
||||||
|
RemoveBotField::InteractionsURL => {
|
||||||
|
unset.insert("interactions_url", 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(public) = &data.public {
|
||||||
|
set.insert("public", public);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(interactions_url) = &data.interactions_url {
|
||||||
|
set.insert("interactions_url", interactions_url);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut operations = doc! {};
|
||||||
|
if set.len() > 0 {
|
||||||
|
operations.insert("$set", &set);
|
||||||
|
}
|
||||||
|
|
||||||
|
if unset.len() > 0 {
|
||||||
|
operations.insert("$unset", unset);
|
||||||
|
}
|
||||||
|
|
||||||
|
if operations.len() > 0 {
|
||||||
|
get_collection("bots")
|
||||||
|
.update_one(doc! { "_id": &target.id }, operations, None)
|
||||||
|
.await
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "update_one",
|
||||||
|
with: "bot",
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(EmptyResponse {})
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
use crate::database::*;
|
||||||
|
use crate::util::result::{Error, Result};
|
||||||
|
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
#[get("/<target>")]
|
||||||
|
pub async fn fetch_bot(user: User, target: Ref) -> Result<Value> {
|
||||||
|
if user.bot.is_some() {
|
||||||
|
return Err(Error::IsBot)
|
||||||
|
}
|
||||||
|
|
||||||
|
let bot = target.fetch_bot().await?;
|
||||||
|
|
||||||
|
if !bot.public {
|
||||||
|
if bot.owner != user.id {
|
||||||
|
return Err(Error::BotIsPrivate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let user = Ref::from_unchecked(bot.id.clone()).fetch_user().await?;
|
||||||
|
|
||||||
|
Ok(json!({
|
||||||
|
"bot": bot,
|
||||||
|
"user": user
|
||||||
|
}))
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
use crate::database::*;
|
||||||
|
use crate::util::result::{Error, Result};
|
||||||
|
|
||||||
|
use futures::StreamExt;
|
||||||
|
use mongodb::bson::{Document, doc, from_document};
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
#[get("/@me")]
|
||||||
|
pub async fn fetch_owned_bots(user: User) -> Result<Value> {
|
||||||
|
if user.bot.is_some() {
|
||||||
|
return Err(Error::IsBot)
|
||||||
|
}
|
||||||
|
|
||||||
|
let bots = get_collection("bots")
|
||||||
|
.find(
|
||||||
|
doc! {
|
||||||
|
"owner": &user.id
|
||||||
|
},
|
||||||
|
None
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
with: "bots",
|
||||||
|
operation: "find"
|
||||||
|
})?
|
||||||
|
.filter_map(async move |s| s.ok())
|
||||||
|
.collect::<Vec<Document>>()
|
||||||
|
.await
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|x| from_document(x).ok())
|
||||||
|
.collect::<Vec<Bot>>();
|
||||||
|
|
||||||
|
let users = get_collection("users")
|
||||||
|
.find(
|
||||||
|
doc! {
|
||||||
|
"bot.owner": &user.id
|
||||||
|
},
|
||||||
|
None
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
with: "users",
|
||||||
|
operation: "find"
|
||||||
|
})?
|
||||||
|
.filter_map(async move |s| s.ok())
|
||||||
|
.collect::<Vec<Document>>()
|
||||||
|
.await
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|x| from_document(x).ok())
|
||||||
|
.collect::<Vec<User>>();
|
||||||
|
|
||||||
|
Ok(json!({
|
||||||
|
"bots": bots,
|
||||||
|
"users": users
|
||||||
|
}))
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
use crate::database::*;
|
||||||
|
use crate::util::result::{Error, Result};
|
||||||
|
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
#[get("/<target>/invite")]
|
||||||
|
pub async fn fetch_public_bot(user: User, target: Ref) -> Result<Value> {
|
||||||
|
if user.bot.is_some() {
|
||||||
|
return Err(Error::IsBot)
|
||||||
|
}
|
||||||
|
|
||||||
|
let bot = target.fetch_bot().await?;
|
||||||
|
|
||||||
|
if !bot.public {
|
||||||
|
if bot.owner != user.id {
|
||||||
|
return Err(Error::BotIsPrivate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let user = Ref::from_unchecked(bot.id.clone()).fetch_user().await?;
|
||||||
|
|
||||||
|
Ok(json!({
|
||||||
|
"_id": bot.id,
|
||||||
|
"username": user.username,
|
||||||
|
"avatar": user.avatar,
|
||||||
|
"description": user.profile.map(|p| p.content)
|
||||||
|
}))
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
use crate::database::*;
|
||||||
|
use crate::util::result::{Error, Result, EmptyResponse};
|
||||||
|
|
||||||
|
use rocket::serde::json::Json;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct ServerId {
|
||||||
|
server: String
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct GroupId {
|
||||||
|
group: String
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum Destination {
|
||||||
|
Server(ServerId),
|
||||||
|
Group(GroupId)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/<target>/invite", data = "<dest>")]
|
||||||
|
pub async fn invite_bot(user: User, target: Ref, dest: Json<Destination>) -> Result<EmptyResponse> {
|
||||||
|
if user.bot.is_some() {
|
||||||
|
return Err(Error::IsBot)
|
||||||
|
}
|
||||||
|
|
||||||
|
let bot = target.fetch_bot().await?;
|
||||||
|
|
||||||
|
if !bot.public {
|
||||||
|
if bot.owner != user.id {
|
||||||
|
return Err(Error::BotIsPrivate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match dest.into_inner() {
|
||||||
|
Destination::Server(ServerId { server }) => {
|
||||||
|
let server = Ref::from(server)?.fetch_server().await?;
|
||||||
|
|
||||||
|
let perm = permissions::PermissionCalculator::new(&user)
|
||||||
|
.with_server(&server)
|
||||||
|
.for_server()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if !perm.get_manage_server() {
|
||||||
|
Err(Error::MissingPermission)?
|
||||||
|
}
|
||||||
|
|
||||||
|
server.join_member(&bot.id).await?;
|
||||||
|
Ok(EmptyResponse {})
|
||||||
|
}
|
||||||
|
Destination::Group(GroupId { group }) => {
|
||||||
|
let channel = Ref::from(group)?.fetch_channel().await?;
|
||||||
|
|
||||||
|
let perm = permissions::PermissionCalculator::new(&user)
|
||||||
|
.with_channel(&channel)
|
||||||
|
.for_channel()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if !perm.get_invite_others() {
|
||||||
|
Err(Error::MissingPermission)?
|
||||||
|
}
|
||||||
|
|
||||||
|
channel.add_to_group(bot.id, user.id).await?;
|
||||||
|
Ok(EmptyResponse {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
use rocket::Route;
|
||||||
|
|
||||||
|
mod create;
|
||||||
|
mod invite;
|
||||||
|
mod fetch_public;
|
||||||
|
mod fetch;
|
||||||
|
mod fetch_owned;
|
||||||
|
mod edit;
|
||||||
|
mod delete;
|
||||||
|
|
||||||
|
pub fn routes() -> Vec<Route> {
|
||||||
|
routes![
|
||||||
|
create::create_bot,
|
||||||
|
invite::invite_bot,
|
||||||
|
fetch_public::fetch_public_bot,
|
||||||
|
fetch::fetch_bot,
|
||||||
|
fetch_owned::fetch_owned_bots,
|
||||||
|
edit::edit_bot,
|
||||||
|
delete::delete_bot,
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,12 +1,16 @@
|
|||||||
use crate::database::*;
|
use crate::database::*;
|
||||||
use crate::notifications::events::ClientboundNotification;
|
use crate::notifications::events::ClientboundNotification;
|
||||||
use crate::util::result::{Error, Result};
|
use crate::util::result::{Error, Result, EmptyResponse};
|
||||||
|
|
||||||
use mongodb::bson::doc;
|
use mongodb::bson::doc;
|
||||||
use mongodb::options::UpdateOptions;
|
use mongodb::options::UpdateOptions;
|
||||||
|
|
||||||
#[put("/<target>/ack/<message>")]
|
#[put("/<target>/ack/<message>")]
|
||||||
pub async fn req(user: User, target: Ref, message: Ref) -> Result<()> {
|
pub async fn req(user: User, target: Ref, message: Ref) -> Result<EmptyResponse> {
|
||||||
|
if user.bot.is_some() {
|
||||||
|
return Err(Error::IsBot)
|
||||||
|
}
|
||||||
|
|
||||||
let target = target.fetch_channel().await?;
|
let target = target.fetch_channel().await?;
|
||||||
|
|
||||||
let perm = permissions::PermissionCalculator::new(&user)
|
let perm = permissions::PermissionCalculator::new(&user)
|
||||||
@@ -48,5 +52,5 @@ pub async fn req(user: User, target: Ref, message: Ref) -> Result<()> {
|
|||||||
}
|
}
|
||||||
.publish(user.id);
|
.publish(user.id);
|
||||||
|
|
||||||
Ok(())
|
Ok(EmptyResponse {})
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
use crate::util::result::{Error, Result};
|
use crate::util::result::{Error, Result, EmptyResponse};
|
||||||
use crate::{database::*, notifications::events::ClientboundNotification};
|
use crate::{database::*, notifications::events::ClientboundNotification};
|
||||||
|
|
||||||
use mongodb::bson::doc;
|
use mongodb::bson::doc;
|
||||||
|
|
||||||
#[delete("/<target>")]
|
#[delete("/<target>")]
|
||||||
pub async fn req(user: User, target: Ref) -> Result<()> {
|
pub async fn req(user: User, target: Ref) -> Result<EmptyResponse> {
|
||||||
let target = target.fetch_channel().await?;
|
let target = target.fetch_channel().await?;
|
||||||
|
|
||||||
let perm = permissions::PermissionCalculator::new(&user)
|
let perm = permissions::PermissionCalculator::new(&user)
|
||||||
@@ -37,7 +37,7 @@ pub async fn req(user: User, target: Ref) -> Result<()> {
|
|||||||
with: "channel",
|
with: "channel",
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(())
|
Ok(EmptyResponse {})
|
||||||
}
|
}
|
||||||
Channel::Group {
|
Channel::Group {
|
||||||
id,
|
id,
|
||||||
@@ -70,7 +70,8 @@ pub async fn req(user: User, target: Ref) -> Result<()> {
|
|||||||
|
|
||||||
target.publish_update(json!({ "owner": new_owner })).await?;
|
target.publish_update(json!({ "owner": new_owner })).await?;
|
||||||
} else {
|
} else {
|
||||||
return target.delete().await;
|
target.delete().await?;
|
||||||
|
return Ok(EmptyResponse {});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
get_collection("channels")
|
get_collection("channels")
|
||||||
@@ -103,11 +104,13 @@ pub async fn req(user: User, target: Ref) -> Result<()> {
|
|||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
Ok(())
|
Ok(EmptyResponse {})
|
||||||
}
|
}
|
||||||
Channel::TextChannel { .. } => {
|
Channel::TextChannel { .. } |
|
||||||
|
Channel::VoiceChannel { .. } => {
|
||||||
if perm.get_manage_channel() {
|
if perm.get_manage_channel() {
|
||||||
target.delete().await
|
target.delete().await?;
|
||||||
|
Ok(EmptyResponse {})
|
||||||
} else {
|
} else {
|
||||||
Err(Error::MissingPermission)
|
Err(Error::MissingPermission)
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
use crate::notifications::events::ClientboundNotification;
|
use crate::notifications::events::ClientboundNotification;
|
||||||
use crate::util::result::{Error, Result};
|
use crate::util::result::{Error, Result, EmptyResponse};
|
||||||
use crate::{database::*, notifications::events::RemoveChannelField};
|
use crate::{database::*, notifications::events::RemoveChannelField};
|
||||||
|
|
||||||
use mongodb::bson::{doc, to_document};
|
use mongodb::bson::{doc, to_document};
|
||||||
use rocket_contrib::json::Json;
|
use rocket::serde::json::Json;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
|
||||||
@@ -18,10 +18,12 @@ pub struct Data {
|
|||||||
#[validate(length(min = 1, max = 128))]
|
#[validate(length(min = 1, max = 128))]
|
||||||
icon: Option<String>,
|
icon: Option<String>,
|
||||||
remove: Option<RemoveChannelField>,
|
remove: Option<RemoveChannelField>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
nsfw: Option<bool>
|
||||||
}
|
}
|
||||||
|
|
||||||
#[patch("/<target>", data = "<data>")]
|
#[patch("/<target>", data = "<data>")]
|
||||||
pub async fn req(user: User, target: Ref, data: Json<Data>) -> Result<()> {
|
pub async fn req(user: User, target: Ref, data: Json<Data>) -> Result<EmptyResponse> {
|
||||||
let data = data.into_inner();
|
let data = data.into_inner();
|
||||||
data.validate()
|
data.validate()
|
||||||
.map_err(|error| Error::FailedValidation { error })?;
|
.map_err(|error| Error::FailedValidation { error })?;
|
||||||
@@ -30,8 +32,9 @@ pub async fn req(user: User, target: Ref, data: Json<Data>) -> Result<()> {
|
|||||||
&& data.description.is_none()
|
&& data.description.is_none()
|
||||||
&& data.icon.is_none()
|
&& data.icon.is_none()
|
||||||
&& data.remove.is_none()
|
&& data.remove.is_none()
|
||||||
|
&& data.nsfw.is_none()
|
||||||
{
|
{
|
||||||
return Ok(());
|
return Ok(EmptyResponse {});
|
||||||
}
|
}
|
||||||
|
|
||||||
let target = target.fetch_channel().await?;
|
let target = target.fetch_channel().await?;
|
||||||
@@ -45,7 +48,9 @@ pub async fn req(user: User, target: Ref, data: Json<Data>) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
match &target {
|
match &target {
|
||||||
Channel::Group { id, icon, .. } | Channel::TextChannel { id, icon, .. } => {
|
Channel::Group { id, icon, .. }
|
||||||
|
| Channel::TextChannel { id, icon, .. }
|
||||||
|
| Channel::VoiceChannel { id, icon, .. } => {
|
||||||
let mut set = doc! {};
|
let mut set = doc! {};
|
||||||
let mut unset = doc! {};
|
let mut unset = doc! {};
|
||||||
|
|
||||||
@@ -84,6 +89,10 @@ pub async fn req(user: User, target: Ref, data: Json<Data>) -> Result<()> {
|
|||||||
remove_icon = true;
|
remove_icon = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(nsfw) = &data.nsfw {
|
||||||
|
set.insert("nsfw", nsfw);
|
||||||
|
}
|
||||||
|
|
||||||
let mut operations = doc! {};
|
let mut operations = doc! {};
|
||||||
if set.len() > 0 {
|
if set.len() > 0 {
|
||||||
operations.insert("$set", &set);
|
operations.insert("$set", &set);
|
||||||
@@ -144,7 +153,7 @@ pub async fn req(user: User, target: Ref, data: Json<Data>) -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(EmptyResponse {})
|
||||||
}
|
}
|
||||||
_ => Err(Error::InvalidOperation),
|
_ => Err(Error::InvalidOperation),
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
use crate::database::*;
|
use crate::database::*;
|
||||||
use crate::util::result::{Error, Result};
|
use crate::util::result::{Error, Result};
|
||||||
|
|
||||||
use rocket_contrib::json::JsonValue;
|
use rocket::serde::json::Value;
|
||||||
|
|
||||||
#[get("/<target>")]
|
#[get("/<target>")]
|
||||||
pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
|
pub async fn req(user: User, target: Ref) -> Result<Value> {
|
||||||
let target = target.fetch_channel().await?;
|
let target = target.fetch_channel().await?;
|
||||||
|
|
||||||
let perm = permissions::PermissionCalculator::new(&user)
|
let perm = permissions::PermissionCalculator::new(&user)
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
use crate::util::result::{Error, Result};
|
use crate::util::result::{Error, Result, EmptyResponse};
|
||||||
use crate::util::variables::MAX_GROUP_SIZE;
|
use crate::database::*;
|
||||||
use crate::{database::*, notifications::events::ClientboundNotification};
|
|
||||||
|
|
||||||
use mongodb::bson::doc;
|
use mongodb::bson::doc;
|
||||||
|
|
||||||
#[put("/<target>/recipients/<member>")]
|
#[put("/<target>/recipients/<member>")]
|
||||||
pub async fn req(user: User, target: Ref, member: Ref) -> Result<()> {
|
pub async fn req(user: User, target: Ref, member: Ref) -> Result<EmptyResponse> {
|
||||||
if get_relationship(&user, &member.id) != RelationshipStatus::Friend {
|
if get_relationship(&user, &member.id) != RelationshipStatus::Friend {
|
||||||
Err(Error::NotFriends)?
|
Err(Error::NotFriends)?
|
||||||
}
|
}
|
||||||
@@ -20,51 +19,6 @@ pub async fn req(user: User, target: Ref, member: Ref) -> Result<()> {
|
|||||||
Err(Error::MissingPermission)?
|
Err(Error::MissingPermission)?
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Channel::Group { id, recipients, .. } = &channel {
|
channel.add_to_group(member.id, user.id).await?;
|
||||||
if recipients.len() >= *MAX_GROUP_SIZE {
|
Ok(EmptyResponse {})
|
||||||
Err(Error::GroupTooLarge {
|
|
||||||
max: *MAX_GROUP_SIZE,
|
|
||||||
})?
|
|
||||||
}
|
|
||||||
|
|
||||||
if recipients.iter().find(|x| *x == &member.id).is_some() {
|
|
||||||
Err(Error::AlreadyInGroup)?
|
|
||||||
}
|
|
||||||
|
|
||||||
get_collection("channels")
|
|
||||||
.update_one(
|
|
||||||
doc! {
|
|
||||||
"_id": &id
|
|
||||||
},
|
|
||||||
doc! {
|
|
||||||
"$push": {
|
|
||||||
"recipients": &member.id
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.map_err(|_| Error::DatabaseError {
|
|
||||||
operation: "update_one",
|
|
||||||
with: "channel",
|
|
||||||
})?;
|
|
||||||
|
|
||||||
ClientboundNotification::ChannelGroupJoin {
|
|
||||||
id: id.clone(),
|
|
||||||
user: member.id.clone(),
|
|
||||||
}
|
|
||||||
.publish(id.clone());
|
|
||||||
|
|
||||||
Content::SystemMessage(SystemMessage::UserAdded {
|
|
||||||
id: member.id,
|
|
||||||
by: user.id,
|
|
||||||
})
|
|
||||||
.send_as_system(&channel)
|
|
||||||
.await
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(Error::InvalidOperation)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,7 @@ use crate::util::result::{Error, Result};
|
|||||||
use crate::util::variables::MAX_GROUP_SIZE;
|
use crate::util::variables::MAX_GROUP_SIZE;
|
||||||
|
|
||||||
use mongodb::bson::doc;
|
use mongodb::bson::doc;
|
||||||
use rocket_contrib::json::{Json, JsonValue};
|
use rocket::serde::json::{Json, Value};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::iter::FromIterator;
|
use std::iter::FromIterator;
|
||||||
@@ -20,10 +20,16 @@ pub struct Data {
|
|||||||
#[validate(length(min = 1, max = 36))]
|
#[validate(length(min = 1, max = 36))]
|
||||||
nonce: String,
|
nonce: String,
|
||||||
users: Vec<String>,
|
users: Vec<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
nsfw: Option<bool>
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/create", data = "<info>")]
|
#[post("/create", data = "<info>")]
|
||||||
pub async fn req(user: User, info: Json<Data>) -> Result<JsonValue> {
|
pub async fn req(user: User, info: Json<Data>) -> Result<Value> {
|
||||||
|
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 })?;
|
||||||
@@ -72,7 +78,9 @@ pub async fn req(user: User, info: Json<Data>) -> Result<JsonValue> {
|
|||||||
owner: user.id,
|
owner: user.id,
|
||||||
recipients: set.into_iter().collect::<Vec<String>>(),
|
recipients: set.into_iter().collect::<Vec<String>>(),
|
||||||
icon: None,
|
icon: None,
|
||||||
last_message: None,
|
last_message_id: None,
|
||||||
|
permissions: None,
|
||||||
|
nsfw: info.nsfw.unwrap_or_default()
|
||||||
};
|
};
|
||||||
|
|
||||||
channel.clone().publish().await?;
|
channel.clone().publish().await?;
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
use crate::util::result::{Error, Result};
|
use crate::util::result::{Error, Result, EmptyResponse};
|
||||||
use crate::{database::*, notifications::events::ClientboundNotification};
|
use crate::{database::*, notifications::events::ClientboundNotification};
|
||||||
|
|
||||||
use mongodb::bson::doc;
|
use mongodb::bson::doc;
|
||||||
|
|
||||||
#[delete("/<target>/recipients/<member>")]
|
#[delete("/<target>/recipients/<member>")]
|
||||||
pub async fn req(user: User, target: Ref, member: Ref) -> Result<()> {
|
pub async fn req(user: User, target: Ref, member: Ref) -> Result<EmptyResponse> {
|
||||||
if &user.id == &member.id {
|
if &user.id == &member.id {
|
||||||
Err(Error::CannotRemoveYourself)?
|
Err(Error::CannotRemoveYourself)?
|
||||||
}
|
}
|
||||||
@@ -59,7 +59,7 @@ pub async fn req(user: User, target: Ref, member: Ref) -> Result<()> {
|
|||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
Ok(())
|
Ok(EmptyResponse {})
|
||||||
} else {
|
} else {
|
||||||
Err(Error::InvalidOperation)
|
Err(Error::InvalidOperation)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use crate::util::result::{Error, Result};
|
|||||||
|
|
||||||
use mongodb::bson::doc;
|
use mongodb::bson::doc;
|
||||||
use nanoid::nanoid;
|
use nanoid::nanoid;
|
||||||
use rocket_contrib::json::JsonValue;
|
use rocket::serde::json::Value;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref ALPHABET: [char; 54] = [
|
static ref ALPHABET: [char; 54] = [
|
||||||
@@ -14,7 +14,7 @@ lazy_static! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[post("/<target>/invites")]
|
#[post("/<target>/invites")]
|
||||||
pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
|
pub async fn req(user: User, target: Ref) -> Result<Value> {
|
||||||
let target = target.fetch_channel().await?;
|
let target = target.fetch_channel().await?;
|
||||||
let perm = permissions::PermissionCalculator::new(&user)
|
let perm = permissions::PermissionCalculator::new(&user)
|
||||||
.with_channel(&target)
|
.with_channel(&target)
|
||||||
@@ -30,7 +30,8 @@ pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
|
|||||||
Channel::Group { .. } => {
|
Channel::Group { .. } => {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
Channel::TextChannel { id, server, .. } => {
|
Channel::TextChannel { id, server, .. }
|
||||||
|
| Channel::VoiceChannel { id, server, .. } => {
|
||||||
Invite::Server {
|
Invite::Server {
|
||||||
code: code.clone(),
|
code: code.clone(),
|
||||||
creator: user.id,
|
creator: user.id,
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
use crate::database::*;
|
use crate::database::*;
|
||||||
use crate::util::result::{Error, Result};
|
use crate::util::result::{Error, Result};
|
||||||
|
|
||||||
use rocket_contrib::json::JsonValue;
|
use rocket::serde::json::Value;
|
||||||
|
|
||||||
#[get("/<target>/members")]
|
#[get("/<target>/members")]
|
||||||
pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
|
pub async fn req(user: User, target: Ref) -> Result<Value> {
|
||||||
let target = target.fetch_channel().await?;
|
let target = target.fetch_channel().await?;
|
||||||
|
|
||||||
let perm = permissions::PermissionCalculator::new(&user)
|
let perm = permissions::PermissionCalculator::new(&user)
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
use crate::database::*;
|
use crate::database::*;
|
||||||
use crate::util::result::{Error, Result};
|
use crate::util::result::{Error, Result, EmptyResponse};
|
||||||
|
|
||||||
use mongodb::bson::doc;
|
use mongodb::bson::doc;
|
||||||
|
|
||||||
#[delete("/<target>/messages/<msg>")]
|
#[delete("/<target>/messages/<msg>")]
|
||||||
pub async fn req(user: User, target: Ref, msg: Ref) -> Result<()> {
|
pub async fn req(user: User, target: Ref, msg: Ref) -> Result<EmptyResponse> {
|
||||||
let channel = target.fetch_channel().await?;
|
let channel = target.fetch_channel().await?;
|
||||||
|
channel.has_messaging()?;
|
||||||
|
|
||||||
let perm = permissions::PermissionCalculator::new(&user)
|
let perm = permissions::PermissionCalculator::new(&user)
|
||||||
.with_channel(&channel)
|
.with_channel(&channel)
|
||||||
@@ -23,5 +24,6 @@ pub async fn req(user: User, target: Ref, msg: Ref) -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
message.delete().await
|
message.delete().await?;
|
||||||
|
Ok(EmptyResponse {})
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
use crate::database::*;
|
use crate::database::*;
|
||||||
use crate::util::result::{Error, Result};
|
use crate::util::result::{Error, Result, EmptyResponse};
|
||||||
|
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use mongodb::bson::{doc, Bson, DateTime, Document};
|
use mongodb::bson::{doc, Bson, DateTime, Document};
|
||||||
use rocket_contrib::json::Json;
|
use rocket::serde::json::Json;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
|
||||||
@@ -14,11 +14,13 @@ pub struct Data {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[patch("/<target>/messages/<msg>", data = "<edit>")]
|
#[patch("/<target>/messages/<msg>", data = "<edit>")]
|
||||||
pub async fn req(user: User, target: Ref, msg: Ref, edit: Json<Data>) -> Result<()> {
|
pub async fn req(user: User, target: Ref, msg: Ref, edit: Json<Data>) -> Result<EmptyResponse> {
|
||||||
edit.validate()
|
edit.validate()
|
||||||
.map_err(|error| Error::FailedValidation { error })?;
|
.map_err(|error| Error::FailedValidation { error })?;
|
||||||
|
|
||||||
let channel = target.fetch_channel().await?;
|
let channel = target.fetch_channel().await?;
|
||||||
|
channel.has_messaging()?;
|
||||||
|
|
||||||
let perm = permissions::PermissionCalculator::new(&user)
|
let perm = permissions::PermissionCalculator::new(&user)
|
||||||
.with_channel(&channel)
|
.with_channel(&channel)
|
||||||
.for_channel()
|
.for_channel()
|
||||||
@@ -27,7 +29,7 @@ pub async fn req(user: User, target: Ref, msg: Ref, edit: Json<Data>) -> Result<
|
|||||||
Err(Error::MissingPermission)?
|
Err(Error::MissingPermission)?
|
||||||
}
|
}
|
||||||
|
|
||||||
let message = msg.fetch_message(&channel).await?;
|
let mut message = msg.fetch_message(&channel).await?;
|
||||||
if message.author != user.id {
|
if message.author != user.id {
|
||||||
Err(Error::CannotEditMessage)?
|
Err(Error::CannotEditMessage)?
|
||||||
}
|
}
|
||||||
@@ -38,6 +40,7 @@ pub async fn req(user: User, target: Ref, msg: Ref, edit: Json<Data>) -> Result<
|
|||||||
"edited": Bson::DateTime(edited)
|
"edited": Bson::DateTime(edited)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
message.content = Content::Text(edit.content.clone());
|
||||||
let mut update = json!({ "content": edit.content, "edited": DateTime(edited) });
|
let mut update = json!({ "content": edit.content, "edited": DateTime(edited) });
|
||||||
|
|
||||||
if let Some(embeds) = &message.embeds {
|
if let Some(embeds) = &message.embeds {
|
||||||
@@ -50,7 +53,7 @@ pub async fn req(user: User, target: Ref, msg: Ref, edit: Json<Data>) -> Result<
|
|||||||
}
|
}
|
||||||
|
|
||||||
let obj = update.as_object_mut().unwrap();
|
let obj = update.as_object_mut().unwrap();
|
||||||
obj.insert("embeds".to_string(), json!(new_embeds).0);
|
obj.insert("embeds".to_string(), json!(new_embeds));
|
||||||
set.insert("embeds", new_embeds);
|
set.insert("embeds", new_embeds);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,5 +73,6 @@ pub async fn req(user: User, target: Ref, msg: Ref, edit: Json<Data>) -> Result<
|
|||||||
with: "message",
|
with: "message",
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
message.publish_update(update).await
|
message.publish_update(update).await?;
|
||||||
|
Ok(EmptyResponse {})
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
use crate::database::*;
|
use crate::database::*;
|
||||||
use crate::util::result::{Error, Result};
|
use crate::util::result::{Error, Result};
|
||||||
|
|
||||||
use rocket_contrib::json::JsonValue;
|
use rocket::serde::json::Value;
|
||||||
|
|
||||||
#[get("/<target>/messages/<msg>")]
|
#[get("/<target>/messages/<msg>")]
|
||||||
pub async fn req(user: User, target: Ref, msg: Ref) -> Result<JsonValue> {
|
pub async fn req(user: User, target: Ref, msg: Ref) -> Result<Value> {
|
||||||
let channel = target.fetch_channel().await?;
|
let channel = target.fetch_channel().await?;
|
||||||
|
channel.has_messaging()?;
|
||||||
|
|
||||||
let perm = permissions::PermissionCalculator::new(&user)
|
let perm = permissions::PermissionCalculator::new(&user)
|
||||||
.with_channel(&channel)
|
.with_channel(&channel)
|
||||||
|
|||||||
@@ -3,17 +3,16 @@ use std::collections::HashSet;
|
|||||||
use crate::database::*;
|
use crate::database::*;
|
||||||
use crate::util::result::{Error, Result};
|
use crate::util::result::{Error, Result};
|
||||||
|
|
||||||
use futures::StreamExt;
|
use futures::{StreamExt, try_join};
|
||||||
use mongodb::{
|
use mongodb::{
|
||||||
bson::{doc, from_document},
|
bson::{doc, from_document},
|
||||||
options::FindOptions,
|
options::FindOptions,
|
||||||
};
|
};
|
||||||
use rocket::request::Form;
|
use rocket::serde::json::Value;
|
||||||
use rocket_contrib::json::JsonValue;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, FromFormValue)]
|
#[derive(Serialize, Deserialize, FromFormField)]
|
||||||
pub enum Sort {
|
pub enum Sort {
|
||||||
Latest,
|
Latest,
|
||||||
Oldest,
|
Oldest,
|
||||||
@@ -28,16 +27,22 @@ pub struct Options {
|
|||||||
#[validate(length(min = 26, max = 26))]
|
#[validate(length(min = 26, max = 26))]
|
||||||
after: Option<String>,
|
after: Option<String>,
|
||||||
sort: Option<Sort>,
|
sort: Option<Sort>,
|
||||||
|
// Specifying 'nearby' ignores 'before', 'after' and 'sort'.
|
||||||
|
// It will also take half of limit rounded as the limits to each side.
|
||||||
|
// It also fetches the message ID specified.
|
||||||
|
#[validate(length(min = 26, max = 26))]
|
||||||
|
nearby: Option<String>,
|
||||||
include_users: Option<bool>,
|
include_users: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/<target>/messages?<options..>")]
|
#[get("/<target>/messages?<options..>")]
|
||||||
pub async fn req(user: User, target: Ref, options: Form<Options>) -> Result<JsonValue> {
|
pub async fn req(user: User, target: Ref, options: Options) -> Result<Value> {
|
||||||
options
|
options
|
||||||
.validate()
|
.validate()
|
||||||
.map_err(|error| Error::FailedValidation { error })?;
|
.map_err(|error| Error::FailedValidation { error })?;
|
||||||
|
|
||||||
let target = target.fetch_channel().await?;
|
let target = target.fetch_channel().await?;
|
||||||
|
target.has_messaging()?;
|
||||||
|
|
||||||
let perm = permissions::PermissionCalculator::new(&user)
|
let perm = permissions::PermissionCalculator::new(&user)
|
||||||
.with_channel(&target)
|
.with_channel(&target)
|
||||||
@@ -47,8 +52,70 @@ pub async fn req(user: User, target: Ref, options: Form<Options>) -> Result<Json
|
|||||||
Err(Error::MissingPermission)?
|
Err(Error::MissingPermission)?
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut query = doc! { "channel": target.id() };
|
let mut messages = vec![];
|
||||||
|
|
||||||
|
let collection = get_collection("messages");
|
||||||
|
let limit = options.limit.unwrap_or(50);
|
||||||
|
let channel = target.id();
|
||||||
|
if let Some(nearby) = &options.nearby {
|
||||||
|
let mut cursors = try_join!(
|
||||||
|
collection.find(
|
||||||
|
doc! {
|
||||||
|
"channel": channel,
|
||||||
|
"_id": {
|
||||||
|
"$gte": &nearby
|
||||||
|
}
|
||||||
|
},
|
||||||
|
FindOptions::builder()
|
||||||
|
.limit(limit / 2 + 1)
|
||||||
|
.sort(doc! {
|
||||||
|
"_id": 1
|
||||||
|
})
|
||||||
|
.build(),
|
||||||
|
),
|
||||||
|
collection.find(
|
||||||
|
doc! {
|
||||||
|
"channel": channel,
|
||||||
|
"_id": {
|
||||||
|
"$lt": &nearby
|
||||||
|
}
|
||||||
|
},
|
||||||
|
FindOptions::builder()
|
||||||
|
.limit(limit / 2)
|
||||||
|
.sort(doc! {
|
||||||
|
"_id": -1
|
||||||
|
})
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "find",
|
||||||
|
with: "messages",
|
||||||
|
})?;
|
||||||
|
|
||||||
|
while let Some(result) = cursors.0.next().await {
|
||||||
|
if let Ok(doc) = result {
|
||||||
|
messages.push(
|
||||||
|
from_document::<Message>(doc).map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "from_document",
|
||||||
|
with: "message",
|
||||||
|
})?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while let Some(result) = cursors.1.next().await {
|
||||||
|
if let Ok(doc) = result {
|
||||||
|
messages.push(
|
||||||
|
from_document::<Message>(doc).map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "from_document",
|
||||||
|
with: "message",
|
||||||
|
})?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let mut query = doc! { "channel": target.id() };
|
||||||
if let Some(before) = &options.before {
|
if let Some(before) = &options.before {
|
||||||
query.insert("_id", doc! { "$lt": before });
|
query.insert("_id", doc! { "$lt": before });
|
||||||
}
|
}
|
||||||
@@ -57,16 +124,17 @@ pub async fn req(user: User, target: Ref, options: Form<Options>) -> Result<Json
|
|||||||
query.insert("_id", doc! { "$gt": after });
|
query.insert("_id", doc! { "$gt": after });
|
||||||
}
|
}
|
||||||
|
|
||||||
let sort = if let Sort::Latest = options.sort.as_ref().unwrap_or_else(|| &Sort::Latest) {
|
let sort: i32 = if let Sort::Latest = options.sort.as_ref().unwrap_or_else(|| &Sort::Latest) {
|
||||||
-1
|
-1
|
||||||
} else {
|
} else {
|
||||||
1
|
1
|
||||||
};
|
};
|
||||||
let mut cursor = get_collection("messages")
|
|
||||||
|
let mut cursor = collection
|
||||||
.find(
|
.find(
|
||||||
query,
|
query,
|
||||||
FindOptions::builder()
|
FindOptions::builder()
|
||||||
.limit(options.limit.unwrap_or(50))
|
.limit(limit)
|
||||||
.sort(doc! {
|
.sort(doc! {
|
||||||
"_id": sort
|
"_id": sort
|
||||||
})
|
})
|
||||||
@@ -78,7 +146,6 @@ pub async fn req(user: User, target: Ref, options: Form<Options>) -> Result<Json
|
|||||||
with: "messages",
|
with: "messages",
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let mut messages = vec![];
|
|
||||||
while let Some(result) = cursor.next().await {
|
while let Some(result) = cursor.next().await {
|
||||||
if let Ok(doc) = result {
|
if let Ok(doc) = result {
|
||||||
messages.push(
|
messages.push(
|
||||||
@@ -89,6 +156,7 @@ pub async fn req(user: User, target: Ref, options: Form<Options>) -> Result<Json
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if options.include_users.unwrap_or_else(|| false) {
|
if options.include_users.unwrap_or_else(|| false) {
|
||||||
let mut ids = HashSet::new();
|
let mut ids = HashSet::new();
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use crate::util::result::{Error, Result};
|
|||||||
|
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use mongodb::bson::{doc, from_document};
|
use mongodb::bson::{doc, from_document};
|
||||||
use rocket_contrib::json::{Json, JsonValue};
|
use rocket::serde::json::{Json, Value};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
@@ -12,12 +12,13 @@ pub struct Options {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[post("/<target>/messages/stale", data = "<data>")]
|
#[post("/<target>/messages/stale", data = "<data>")]
|
||||||
pub async fn req(user: User, target: Ref, data: Json<Options>) -> Result<JsonValue> {
|
pub async fn req(user: User, target: Ref, data: Json<Options>) -> Result<Value> {
|
||||||
if data.ids.len() > 150 {
|
if data.ids.len() > 150 {
|
||||||
return Err(Error::TooManyIds);
|
return Err(Error::TooManyIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
let target = target.fetch_channel().await?;
|
let target = target.fetch_channel().await?;
|
||||||
|
target.has_messaging()?;
|
||||||
|
|
||||||
let perm = permissions::PermissionCalculator::new(&user)
|
let perm = permissions::PermissionCalculator::new(&user)
|
||||||
.with_channel(&target)
|
.with_channel(&target)
|
||||||
|
|||||||
@@ -0,0 +1,162 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use crate::database::*;
|
||||||
|
use crate::util::result::{Error, Result};
|
||||||
|
|
||||||
|
use futures::StreamExt;
|
||||||
|
use mongodb::{
|
||||||
|
bson::{doc, from_document},
|
||||||
|
options::FindOptions,
|
||||||
|
};
|
||||||
|
use rocket::serde::json::{Json, Value};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use validator::Validate;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, FromFormField)]
|
||||||
|
pub enum Sort {
|
||||||
|
Relevance,
|
||||||
|
Latest,
|
||||||
|
Oldest,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Sort {
|
||||||
|
fn default() -> Sort {
|
||||||
|
Sort::Relevance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Validate, Serialize, Deserialize, FromForm)]
|
||||||
|
pub struct Options {
|
||||||
|
#[validate(length(min = 1, max = 64))]
|
||||||
|
query: String,
|
||||||
|
|
||||||
|
#[validate(range(min = 1, max = 100))]
|
||||||
|
limit: Option<i64>,
|
||||||
|
#[validate(length(min = 26, max = 26))]
|
||||||
|
before: Option<String>,
|
||||||
|
#[validate(length(min = 26, max = 26))]
|
||||||
|
after: Option<String>,
|
||||||
|
#[serde(default = "Sort::default")]
|
||||||
|
sort: Sort,
|
||||||
|
include_users: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/<target>/search", data = "<options>")]
|
||||||
|
pub async fn req(user: User, target: Ref, options: Json<Options>) -> Result<Value> {
|
||||||
|
options
|
||||||
|
.validate()
|
||||||
|
.map_err(|error| Error::FailedValidation { error })?;
|
||||||
|
|
||||||
|
let target = target.fetch_channel().await?;
|
||||||
|
target.has_messaging()?;
|
||||||
|
|
||||||
|
let perm = permissions::PermissionCalculator::new(&user)
|
||||||
|
.with_channel(&target)
|
||||||
|
.for_channel()
|
||||||
|
.await?;
|
||||||
|
if !perm.get_view() {
|
||||||
|
Err(Error::MissingPermission)?
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut messages = vec![];
|
||||||
|
let limit = options.limit.unwrap_or(50);
|
||||||
|
|
||||||
|
let mut filter = doc! {
|
||||||
|
"channel": target.id(),
|
||||||
|
"$text": {
|
||||||
|
"$search": &options.query
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(doc) = match (&options.before, &options.after) {
|
||||||
|
(Some(before), Some(after)) => Some(doc! {
|
||||||
|
"lt": before,
|
||||||
|
"gt": after
|
||||||
|
}),
|
||||||
|
(Some(before), _) => Some(doc! {
|
||||||
|
"lt": before
|
||||||
|
}),
|
||||||
|
(_, Some(after)) => Some(doc! {
|
||||||
|
"gt": after
|
||||||
|
}),
|
||||||
|
_ => None
|
||||||
|
} {
|
||||||
|
filter.insert("_id", doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut cursor = get_collection("messages")
|
||||||
|
.find(
|
||||||
|
filter,
|
||||||
|
FindOptions::builder()
|
||||||
|
.projection(
|
||||||
|
if let Sort::Relevance = &options.sort {
|
||||||
|
doc! {
|
||||||
|
"score": {
|
||||||
|
"$meta": "textScore"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
doc! {}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.limit(limit)
|
||||||
|
.sort(
|
||||||
|
match &options.sort {
|
||||||
|
Sort::Relevance => doc! {
|
||||||
|
"score": {
|
||||||
|
"$meta": "textScore"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Sort::Latest => doc! {
|
||||||
|
"_id": -1
|
||||||
|
},
|
||||||
|
Sort::Oldest => doc! {
|
||||||
|
"_id": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "find",
|
||||||
|
with: "messages",
|
||||||
|
})?;
|
||||||
|
|
||||||
|
while let Some(result) = cursor.next().await {
|
||||||
|
if let Ok(doc) = result {
|
||||||
|
messages.push(
|
||||||
|
from_document::<Message>(doc).map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "from_document",
|
||||||
|
with: "message",
|
||||||
|
})?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.include_users.unwrap_or_else(|| false) {
|
||||||
|
let mut ids = HashSet::new();
|
||||||
|
for message in &messages {
|
||||||
|
ids.insert(message.author.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
ids.remove(&user.id);
|
||||||
|
let user_ids = ids.into_iter().collect();
|
||||||
|
let users = user.fetch_multiple_users(user_ids).await?;
|
||||||
|
|
||||||
|
if let Channel::TextChannel { server, .. } = target {
|
||||||
|
Ok(json!({
|
||||||
|
"messages": messages,
|
||||||
|
"users": users,
|
||||||
|
"members": Server::fetch_members(&server).await?
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
Ok(json!({
|
||||||
|
"messages": messages,
|
||||||
|
"users": users,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(json!(messages))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,22 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use crate::database::*;
|
use crate::database::*;
|
||||||
|
use crate::util::ratelimit::{Ratelimiter, RatelimitResponse};
|
||||||
use crate::util::result::{Error, Result};
|
use crate::util::result::{Error, Result};
|
||||||
|
|
||||||
use mongodb::{bson::doc, options::FindOneOptions};
|
use mongodb::{bson::doc, options::FindOneOptions};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use rocket_contrib::json::{Json, JsonValue};
|
use rocket::serde::json::{Json, Value};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use ulid::Ulid;
|
use ulid::Ulid;
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct Reply {
|
||||||
|
id: String,
|
||||||
|
mention: bool
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Validate, Serialize, Deserialize)]
|
#[derive(Validate, Serialize, Deserialize)]
|
||||||
pub struct Data {
|
pub struct Data {
|
||||||
#[validate(length(min = 0, max = 2000))]
|
#[validate(length(min = 0, max = 2000))]
|
||||||
@@ -17,14 +26,17 @@ pub struct Data {
|
|||||||
nonce: String,
|
nonce: String,
|
||||||
#[validate(length(min = 1, max = 128))]
|
#[validate(length(min = 1, max = 128))]
|
||||||
attachments: Option<Vec<String>>,
|
attachments: Option<Vec<String>>,
|
||||||
|
replies: Option<Vec<Reply>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref RE_ULID: Regex = Regex::new(r"<@([0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26})>").unwrap();
|
// ignoring I L O and U is intentional
|
||||||
|
static ref RE_ULID: Regex = Regex::new(r"<@([0-9A-HJKMNP-TV-Z]{26})>").unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/<target>/messages", data = "<message>")]
|
#[post("/<target>/messages", data = "<message>")]
|
||||||
pub async fn req(user: User, target: Ref, message: Json<Data>) -> Result<JsonValue> {
|
pub async fn message_send(user: User, _r: Ratelimiter, target: Ref, message: Json<Data>) -> Result<RatelimitResponse<Value>> {
|
||||||
|
let message = message.into_inner();
|
||||||
message
|
message
|
||||||
.validate()
|
.validate()
|
||||||
.map_err(|error| Error::FailedValidation { error })?;
|
.map_err(|error| Error::FailedValidation { error })?;
|
||||||
@@ -36,13 +48,15 @@ pub async fn req(user: User, target: Ref, message: Json<Data>) -> Result<JsonVal
|
|||||||
}
|
}
|
||||||
|
|
||||||
let target = target.fetch_channel().await?;
|
let target = target.fetch_channel().await?;
|
||||||
|
target.has_messaging()?;
|
||||||
|
|
||||||
let perm = permissions::PermissionCalculator::new(&user)
|
let perm = permissions::PermissionCalculator::new(&user)
|
||||||
.with_channel(&target)
|
.with_channel(&target)
|
||||||
.for_channel()
|
.for_channel()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if !perm.get_send_message() {
|
if !perm.get_send_message() {
|
||||||
Err(Error::MissingPermission)?
|
return Err(Error::MissingPermission)
|
||||||
}
|
}
|
||||||
|
|
||||||
if get_collection("messages")
|
if get_collection("messages")
|
||||||
@@ -65,48 +79,75 @@ pub async fn req(user: User, target: Ref, message: Json<Data>) -> Result<JsonVal
|
|||||||
}
|
}
|
||||||
|
|
||||||
let id = Ulid::new().to_string();
|
let id = Ulid::new().to_string();
|
||||||
let attachments = if let Some(ids) = &message.attachments {
|
|
||||||
|
let mut mentions = HashSet::new();
|
||||||
|
for capture in RE_ULID.captures_iter(&message.content) {
|
||||||
|
if let Some(mention) = capture.get(1) {
|
||||||
|
mentions.insert(mention.as_str().to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut replies = HashSet::new();
|
||||||
|
if let Some(entries) = message.replies {
|
||||||
|
// ! FIXME: move this to app config
|
||||||
|
if entries.len() >= 5 {
|
||||||
|
return Err(Error::TooManyReplies)
|
||||||
|
}
|
||||||
|
|
||||||
|
for Reply { id, mention } in entries {
|
||||||
|
let message = Ref::from_unchecked(id)
|
||||||
|
.fetch_message(&target)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
replies.insert(message.id);
|
||||||
|
|
||||||
|
if mention {
|
||||||
|
mentions.insert(message.author);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut attachments = vec![];
|
||||||
|
if let Some(ids) = &message.attachments {
|
||||||
|
if ids.len() > 0 && !perm.get_upload_files() {
|
||||||
|
return Err(Error::MissingPermission)
|
||||||
|
}
|
||||||
|
|
||||||
// ! FIXME: move this to app config
|
// ! FIXME: move this to app config
|
||||||
if ids.len() >= 5 {
|
if ids.len() >= 5 {
|
||||||
return Err(Error::TooManyAttachments)
|
return Err(Error::TooManyAttachments)
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut attachments = vec![];
|
|
||||||
for attachment_id in ids {
|
for attachment_id in ids {
|
||||||
attachments
|
attachments
|
||||||
.push(File::find_and_use(attachment_id, "attachments", "message", &id).await?);
|
.push(File::find_and_use(attachment_id, "attachments", "message", &id).await?);
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(attachments)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut mentions = vec![];
|
|
||||||
if let Some(captures) = RE_ULID.captures_iter(&message.content).next() {
|
|
||||||
// ! FIXME: in the future, verify in group so we can send out push
|
|
||||||
mentions.push(captures[1].to_string());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let msg = Message {
|
let msg = Message {
|
||||||
id,
|
id,
|
||||||
channel: target.id().to_string(),
|
channel: target.id().to_string(),
|
||||||
author: user.id,
|
author: user.id,
|
||||||
|
|
||||||
content: Content::Text(message.content.clone()),
|
content: Content::Text(message.content.clone()),
|
||||||
attachments,
|
|
||||||
nonce: Some(message.nonce.clone()),
|
nonce: Some(message.nonce.clone()),
|
||||||
edited: None,
|
edited: None,
|
||||||
embeds: None,
|
embeds: None,
|
||||||
|
|
||||||
|
attachments: if attachments.len() > 0 { Some(attachments) } else { None },
|
||||||
mentions: if mentions.len() > 0 {
|
mentions: if mentions.len() > 0 {
|
||||||
Some(mentions)
|
Some(mentions.into_iter().collect::<Vec<String>>())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
replies: if replies.len() > 0 {
|
||||||
|
Some(replies.into_iter().collect::<Vec<String>>())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
msg.clone().publish(&target).await?;
|
msg.clone().publish(&target, perm.get_embed_links()).await?;
|
||||||
|
|
||||||
Ok(json!(msg))
|
Ok(RatelimitResponse(json!(msg)))
|
||||||
}
|
}
|
||||||
+17
-11
@@ -1,32 +1,36 @@
|
|||||||
use rocket::Route;
|
use rocket::Route;
|
||||||
|
|
||||||
mod channel_ack;
|
mod channel_ack;
|
||||||
mod delete_channel;
|
mod channel_delete;
|
||||||
mod edit_channel;
|
mod channel_edit;
|
||||||
mod fetch_channel;
|
mod channel_fetch;
|
||||||
mod fetch_members;
|
mod members_fetch;
|
||||||
mod group_add_member;
|
mod group_add_member;
|
||||||
mod group_create;
|
mod group_create;
|
||||||
mod group_remove_member;
|
mod group_remove_member;
|
||||||
mod invite_create;
|
mod invite_create;
|
||||||
mod join_call;
|
mod voice_join;
|
||||||
mod message_delete;
|
mod message_delete;
|
||||||
mod message_edit;
|
mod message_edit;
|
||||||
mod message_fetch;
|
mod message_fetch;
|
||||||
mod message_query;
|
mod message_query;
|
||||||
|
mod message_search;
|
||||||
mod message_query_stale;
|
mod message_query_stale;
|
||||||
mod message_send;
|
mod message_send;
|
||||||
|
mod permissions_set;
|
||||||
|
mod permissions_set_default;
|
||||||
|
|
||||||
pub fn routes() -> Vec<Route> {
|
pub fn routes() -> Vec<Route> {
|
||||||
routes![
|
routes![
|
||||||
channel_ack::req,
|
channel_ack::req,
|
||||||
fetch_channel::req,
|
channel_fetch::req,
|
||||||
fetch_members::req,
|
members_fetch::req,
|
||||||
delete_channel::req,
|
channel_delete::req,
|
||||||
edit_channel::req,
|
channel_edit::req,
|
||||||
invite_create::req,
|
invite_create::req,
|
||||||
message_send::req,
|
message_send::message_send,
|
||||||
message_query::req,
|
message_query::req,
|
||||||
|
message_search::req,
|
||||||
message_query_stale::req,
|
message_query_stale::req,
|
||||||
message_fetch::req,
|
message_fetch::req,
|
||||||
message_edit::req,
|
message_edit::req,
|
||||||
@@ -34,6 +38,8 @@ pub fn routes() -> Vec<Route> {
|
|||||||
group_create::req,
|
group_create::req,
|
||||||
group_add_member::req,
|
group_add_member::req,
|
||||||
group_remove_member::req,
|
group_remove_member::req,
|
||||||
join_call::req,
|
voice_join::req,
|
||||||
|
permissions_set::req,
|
||||||
|
permissions_set_default::req,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
use mongodb::bson::doc;
|
||||||
|
use rocket::serde::json::Json;
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use validator::Contains;
|
||||||
|
|
||||||
|
use crate::database::*;
|
||||||
|
use crate::database::permissions::channel::ChannelPermission;
|
||||||
|
use crate::notifications::events::ClientboundNotification;
|
||||||
|
use crate::util::result::{Error, Result, EmptyResponse};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct Data {
|
||||||
|
permissions: u32
|
||||||
|
}
|
||||||
|
|
||||||
|
#[put("/<target>/permissions/<role>", data = "<data>", rank = 2)]
|
||||||
|
pub async fn req(user: User, target: Ref, role: String, data: Json<Data>) -> Result<EmptyResponse> {
|
||||||
|
let target = target.fetch_channel().await?;
|
||||||
|
|
||||||
|
match target {
|
||||||
|
Channel::TextChannel { id, server, mut role_permissions, .. }
|
||||||
|
| Channel::VoiceChannel { id, server, mut role_permissions, .. } => {
|
||||||
|
let target = Ref::from_unchecked(server).fetch_server().await?;
|
||||||
|
let perm = permissions::PermissionCalculator::new(&user)
|
||||||
|
.with_server(&target)
|
||||||
|
.for_server()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if !perm.get_manage_roles() {
|
||||||
|
return Err(Error::MissingPermission);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !target.roles.has_element(&role) {
|
||||||
|
return Err(Error::NotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
let permissions: u32 = ChannelPermission::View as u32 | data.permissions;
|
||||||
|
|
||||||
|
get_collection("channels")
|
||||||
|
.update_one(
|
||||||
|
doc! { "_id": &id },
|
||||||
|
doc! {
|
||||||
|
"$set": {
|
||||||
|
"role_permissions.".to_owned() + &role: permissions as i32
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "update_one",
|
||||||
|
with: "channel"
|
||||||
|
})?;
|
||||||
|
|
||||||
|
role_permissions.insert(role, permissions as i32);
|
||||||
|
ClientboundNotification::ChannelUpdate {
|
||||||
|
id: id.clone(),
|
||||||
|
data: json!({
|
||||||
|
"role_permissions": role_permissions
|
||||||
|
}),
|
||||||
|
clear: None
|
||||||
|
}
|
||||||
|
.publish(id);
|
||||||
|
|
||||||
|
Ok(EmptyResponse {})
|
||||||
|
}
|
||||||
|
_ => Err(Error::InvalidOperation)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
use mongodb::bson::doc;
|
||||||
|
use rocket::serde::json::Json;
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
|
use crate::database::*;
|
||||||
|
use crate::database::permissions::channel::{ ChannelPermission, DEFAULT_PERMISSION_DM };
|
||||||
|
use crate::notifications::events::ClientboundNotification;
|
||||||
|
use crate::util::result::{Error, Result, EmptyResponse};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct Data {
|
||||||
|
permissions: u32
|
||||||
|
}
|
||||||
|
|
||||||
|
#[put("/<target>/permissions/default", data = "<data>", rank = 1)]
|
||||||
|
pub async fn req(user: User, target: Ref, data: Json<Data>) -> Result<EmptyResponse> {
|
||||||
|
let target = target.fetch_channel().await?;
|
||||||
|
|
||||||
|
match target {
|
||||||
|
Channel::Group { id, owner, .. } => {
|
||||||
|
if user.id == owner {
|
||||||
|
let permissions: u32 = ChannelPermission::View as u32 | (data.permissions & *DEFAULT_PERMISSION_DM);
|
||||||
|
|
||||||
|
get_collection("channels")
|
||||||
|
.update_one(
|
||||||
|
doc! { "_id": &id },
|
||||||
|
doc! {
|
||||||
|
"$set": {
|
||||||
|
"permissions": permissions as i32
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "update_one",
|
||||||
|
with: "channel"
|
||||||
|
})?;
|
||||||
|
|
||||||
|
ClientboundNotification::ChannelUpdate {
|
||||||
|
id: id.clone(),
|
||||||
|
data: json!({
|
||||||
|
"permissions": permissions as i32
|
||||||
|
}),
|
||||||
|
clear: None
|
||||||
|
}
|
||||||
|
.publish(id);
|
||||||
|
|
||||||
|
Ok(EmptyResponse {})
|
||||||
|
} else {
|
||||||
|
Err(Error::MissingPermission)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Channel::TextChannel { id, server, .. }
|
||||||
|
| Channel::VoiceChannel { id, server, .. } => {
|
||||||
|
let target = Ref::from_unchecked(server).fetch_server().await?;
|
||||||
|
let perm = permissions::PermissionCalculator::new(&user)
|
||||||
|
.with_server(&target)
|
||||||
|
.for_server()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if !perm.get_manage_roles() {
|
||||||
|
return Err(Error::MissingPermission);
|
||||||
|
}
|
||||||
|
|
||||||
|
let permissions: u32 = ChannelPermission::View as u32 | data.permissions;
|
||||||
|
|
||||||
|
get_collection("channels")
|
||||||
|
.update_one(
|
||||||
|
doc! { "_id": &id },
|
||||||
|
doc! {
|
||||||
|
"$set": {
|
||||||
|
"default_permissions": permissions as i32
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "update_one",
|
||||||
|
with: "channel"
|
||||||
|
})?;
|
||||||
|
|
||||||
|
ClientboundNotification::ChannelUpdate {
|
||||||
|
id: id.clone(),
|
||||||
|
data: json!({
|
||||||
|
"default_permissions": permissions as i32
|
||||||
|
}),
|
||||||
|
clear: None
|
||||||
|
}
|
||||||
|
.publish(id);
|
||||||
|
|
||||||
|
Ok(EmptyResponse {})
|
||||||
|
}
|
||||||
|
_ => Err(Error::InvalidOperation)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ use crate::database::*;
|
|||||||
use crate::util::result::{Error, Result};
|
use crate::util::result::{Error, Result};
|
||||||
use crate::util::variables::{USE_VOSO, VOSO_MANAGE_TOKEN, VOSO_URL};
|
use crate::util::variables::{USE_VOSO, VOSO_MANAGE_TOKEN, VOSO_URL};
|
||||||
|
|
||||||
use rocket_contrib::json::JsonValue;
|
use rocket::serde::json::Value;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
@@ -11,7 +11,7 @@ struct CreateUserResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[post("/<target>/join_call")]
|
#[post("/<target>/join_call")]
|
||||||
pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
|
pub async fn req(user: User, target: Ref) -> Result<Value> {
|
||||||
if !*USE_VOSO {
|
if !*USE_VOSO {
|
||||||
return Err(Error::VosoUnavailable);
|
return Err(Error::VosoUnavailable);
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
use crate::database::*;
|
use crate::database::*;
|
||||||
use crate::util::result::{Error, Result};
|
use crate::util::result::{Error, Result, EmptyResponse};
|
||||||
|
|
||||||
#[delete("/<target>")]
|
#[delete("/<target>")]
|
||||||
pub async fn req(user: User, target: Ref) -> Result<()> {
|
pub async fn req(user: User, target: Ref) -> Result<EmptyResponse> {
|
||||||
let target = target.fetch_invite().await?;
|
let target = target.fetch_invite().await?;
|
||||||
|
|
||||||
if target.creator() == &user.id {
|
if target.creator() == &user.id {
|
||||||
target.delete().await
|
target.delete().await?;
|
||||||
} else {
|
} else {
|
||||||
match &target {
|
match &target {
|
||||||
Invite::Server { server, .. } => {
|
Invite::Server { server, .. } => {
|
||||||
@@ -20,9 +20,11 @@ pub async fn req(user: User, target: Ref) -> Result<()> {
|
|||||||
return Err(Error::MissingPermission);
|
return Err(Error::MissingPermission);
|
||||||
}
|
}
|
||||||
|
|
||||||
target.delete().await
|
target.delete().await?;
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(EmptyResponse {})
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::database::*;
|
use crate::database::*;
|
||||||
use crate::util::result::Result;
|
use crate::util::result::Result;
|
||||||
|
|
||||||
use rocket_contrib::json::JsonValue;
|
use rocket::serde::json::Value;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
#[derive(Serialize, Debug, Clone)]
|
#[derive(Serialize, Debug, Clone)]
|
||||||
@@ -21,11 +21,12 @@ pub enum InviteResponse {
|
|||||||
user_name: String,
|
user_name: String,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
user_avatar: Option<File>,
|
user_avatar: Option<File>,
|
||||||
|
member_count: i64,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/<target>")]
|
#[get("/<target>")]
|
||||||
pub async fn req(target: Ref) -> Result<JsonValue> {
|
pub async fn req(target: Ref) -> Result<Value> {
|
||||||
let target = target.fetch_invite().await?;
|
let target = target.fetch_invite().await?;
|
||||||
|
|
||||||
match target {
|
match target {
|
||||||
@@ -35,17 +36,13 @@ pub async fn req(target: Ref) -> Result<JsonValue> {
|
|||||||
let channel = Ref::from_unchecked(channel).fetch_channel().await?;
|
let channel = Ref::from_unchecked(channel).fetch_channel().await?;
|
||||||
let creator = Ref::from_unchecked(creator).fetch_user().await?;
|
let creator = Ref::from_unchecked(creator).fetch_user().await?;
|
||||||
|
|
||||||
if let Channel::TextChannel {
|
match channel {
|
||||||
id,
|
Channel::TextChannel { id, server, name, description, .. }
|
||||||
server,
|
| Channel::VoiceChannel { id, server, name, description, .. } => {
|
||||||
name,
|
|
||||||
description,
|
|
||||||
..
|
|
||||||
} = channel
|
|
||||||
{
|
|
||||||
let server = Ref::from_unchecked(server).fetch_server().await?;
|
let server = Ref::from_unchecked(server).fetch_server().await?;
|
||||||
|
|
||||||
Ok(json!(InviteResponse::Server {
|
Ok(json!(InviteResponse::Server {
|
||||||
|
member_count: Server::get_member_count(&server.id).await?,
|
||||||
server_id: server.id,
|
server_id: server.id,
|
||||||
server_name: server.name,
|
server_name: server.name,
|
||||||
server_icon: server.icon,
|
server_icon: server.icon,
|
||||||
@@ -56,8 +53,8 @@ pub async fn req(target: Ref) -> Result<JsonValue> {
|
|||||||
user_name: creator.username,
|
user_name: creator.username,
|
||||||
user_avatar: creator.avatar
|
user_avatar: creator.avatar
|
||||||
}))
|
}))
|
||||||
} else {
|
}
|
||||||
unreachable!()
|
_ => unreachable!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
|
|||||||
@@ -1,19 +1,32 @@
|
|||||||
use crate::database::*;
|
use crate::database::*;
|
||||||
use crate::util::result::Result;
|
use crate::util::result::{Error, Result};
|
||||||
|
use crate::util::variables::MAX_SERVER_COUNT;
|
||||||
|
|
||||||
use rocket_contrib::json::JsonValue;
|
use rocket::serde::json::Value;
|
||||||
|
|
||||||
#[post("/<target>")]
|
#[post("/<target>")]
|
||||||
pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
|
pub async fn req(user: User, target: Ref) -> Result<Value> {
|
||||||
|
if user.bot.is_some() {
|
||||||
|
return Err(Error::IsBot)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !User::can_acquire_server(&user.id).await? {
|
||||||
|
Err(Error::TooManyServers {
|
||||||
|
max: *MAX_SERVER_COUNT,
|
||||||
|
})?
|
||||||
|
}
|
||||||
|
|
||||||
let target = target.fetch_invite().await?;
|
let target = target.fetch_invite().await?;
|
||||||
|
|
||||||
match target {
|
match target {
|
||||||
Invite::Server { channel, .. } => {
|
Invite::Server { channel, .. } => {
|
||||||
let channel = Ref::from_unchecked(channel).fetch_channel().await?;
|
let channel = Ref::from_unchecked(channel).fetch_channel().await?;
|
||||||
let server = if let Channel::TextChannel { server, .. } = &channel {
|
let server = match &channel {
|
||||||
|
Channel::TextChannel { server, .. }
|
||||||
|
| Channel::VoiceChannel { server, .. } => {
|
||||||
Ref::from_unchecked(server.clone()).fetch_server().await?
|
Ref::from_unchecked(server.clone()).fetch_server().await?
|
||||||
} else {
|
}
|
||||||
unreachable!()
|
_ => unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
server.join_member(&user.id).await?;
|
server.join_member(&user.id).await?;
|
||||||
|
|||||||
+5
-3
@@ -1,7 +1,8 @@
|
|||||||
pub use rocket::http::Status;
|
pub use rocket::http::Status;
|
||||||
pub use rocket::response::Redirect;
|
pub use rocket::response::Redirect;
|
||||||
use rocket::Rocket;
|
use rocket::{Build, Rocket};
|
||||||
|
|
||||||
|
mod bots;
|
||||||
mod channels;
|
mod channels;
|
||||||
mod invites;
|
mod invites;
|
||||||
mod onboard;
|
mod onboard;
|
||||||
@@ -11,13 +12,14 @@ mod servers;
|
|||||||
mod sync;
|
mod sync;
|
||||||
mod users;
|
mod users;
|
||||||
|
|
||||||
pub fn mount(rocket: Rocket) -> Rocket {
|
pub fn mount(rocket: Rocket<Build>) -> Rocket<Build> {
|
||||||
rocket
|
rocket
|
||||||
.mount("/", routes![root::root])
|
.mount("/", routes![root::root, root::ping])
|
||||||
.mount("/onboard", onboard::routes())
|
.mount("/onboard", onboard::routes())
|
||||||
.mount("/users", users::routes())
|
.mount("/users", users::routes())
|
||||||
.mount("/channels", channels::routes())
|
.mount("/channels", channels::routes())
|
||||||
.mount("/servers", servers::routes())
|
.mount("/servers", servers::routes())
|
||||||
|
.mount("/bots", bots::routes())
|
||||||
.mount("/invites", invites::routes())
|
.mount("/invites", invites::routes())
|
||||||
.mount("/push", push::routes())
|
.mount("/push", push::routes())
|
||||||
.mount("/sync", sync::routes())
|
.mount("/sync", sync::routes())
|
||||||
|
|||||||
@@ -1,17 +1,13 @@
|
|||||||
use crate::database::*;
|
use crate::database::*;
|
||||||
use crate::util::result::{Error, Result};
|
use crate::util::result::{Error, Result, EmptyResponse};
|
||||||
|
use crate::util::regex::RE_USERNAME;
|
||||||
|
|
||||||
use mongodb::bson::doc;
|
use mongodb::bson::doc;
|
||||||
use rauth::auth::Session;
|
use rauth::entities::Session;
|
||||||
use regex::Regex;
|
use rocket::serde::json::Json;
|
||||||
use rocket_contrib::json::Json;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref RE_USERNAME: Regex = Regex::new(r"^[a-zA-Z0-9_.]+$").unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Validate, Serialize, Deserialize)]
|
#[derive(Validate, Serialize, Deserialize)]
|
||||||
pub struct Data {
|
pub struct Data {
|
||||||
#[validate(length(min = 2, max = 32), regex = "RE_USERNAME")]
|
#[validate(length(min = 2, max = 32), regex = "RE_USERNAME")]
|
||||||
@@ -19,7 +15,7 @@ pub struct Data {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[post("/complete", data = "<data>")]
|
#[post("/complete", data = "<data>")]
|
||||||
pub async fn req(session: Session, user: Option<User>, data: Json<Data>) -> Result<()> {
|
pub async fn req(session: Session, user: Option<User>, data: Json<Data>) -> Result<EmptyResponse> {
|
||||||
if user.is_some() {
|
if user.is_some() {
|
||||||
Err(Error::AlreadyOnboarded)?
|
Err(Error::AlreadyOnboarded)?
|
||||||
}
|
}
|
||||||
@@ -45,5 +41,5 @@ pub async fn req(session: Session, user: Option<User>, data: Json<Data>) -> Resu
|
|||||||
with: "user",
|
with: "user",
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(())
|
Ok(EmptyResponse {})
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
use crate::database::*;
|
use crate::database::*;
|
||||||
|
|
||||||
use rauth::auth::Session;
|
use rauth::entities::Session;
|
||||||
use rocket_contrib::json::JsonValue;
|
use rocket::serde::json::Value;
|
||||||
|
|
||||||
#[get("/hello")]
|
#[get("/hello")]
|
||||||
pub async fn req(_session: Session, user: Option<User>) -> JsonValue {
|
pub async fn req(_session: Session, user: Option<User>) -> Value {
|
||||||
json!({
|
json!({
|
||||||
"onboarding": user.is_none()
|
"onboarding": user.is_none()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,37 +1,19 @@
|
|||||||
use crate::database::*;
|
use crate::database::*;
|
||||||
use crate::util::result::{Error, Result};
|
use crate::util::result::{EmptyResponse, Error, Result};
|
||||||
|
|
||||||
use mongodb::bson::{doc, to_document};
|
use mongodb::bson::doc;
|
||||||
use rauth::auth::Session;
|
use rauth::entities::{Model, Session, WebPushSubscription};
|
||||||
use rocket_contrib::json::Json;
|
use rocket::serde::json::Json;
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct Subscription {
|
|
||||||
endpoint: String,
|
|
||||||
p256dh: String,
|
|
||||||
auth: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[post("/subscribe", data = "<data>")]
|
#[post("/subscribe", data = "<data>")]
|
||||||
pub async fn req(session: Session, data: Json<Subscription>) -> Result<()> {
|
pub async fn req(mut session: Session, data: Json<WebPushSubscription>) -> Result<EmptyResponse> {
|
||||||
let data = data.into_inner();
|
session.subscription = Some(data.into_inner());
|
||||||
get_collection("accounts")
|
session
|
||||||
.update_one(
|
.save(&get_db(), None)
|
||||||
doc! {
|
|
||||||
"_id": session.user_id,
|
|
||||||
"sessions.id": session.id.unwrap()
|
|
||||||
},
|
|
||||||
doc! {
|
|
||||||
"$set": {
|
|
||||||
"sessions.$.subscription": to_document(&data)
|
|
||||||
.map_err(|_| Error::DatabaseError { operation: "to_document", with: "subscription" })?
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
.map_err(|_| Error::DatabaseError { operation: "update_one", with: "account" })?;
|
.map(|_| EmptyResponse)
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
Ok(())
|
operation: "save",
|
||||||
|
with: "session",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
@@ -1,29 +1,18 @@
|
|||||||
use crate::database::*;
|
use crate::database::*;
|
||||||
use crate::util::result::{Error, Result};
|
use crate::util::result::{EmptyResponse, Error, Result};
|
||||||
|
|
||||||
use mongodb::bson::doc;
|
use mongodb::bson::doc;
|
||||||
use rauth::auth::Session;
|
use rauth::entities::{Model, Session};
|
||||||
|
|
||||||
#[post("/unsubscribe")]
|
#[post("/unsubscribe")]
|
||||||
pub async fn req(session: Session) -> Result<()> {
|
pub async fn req(mut session: Session) -> Result<EmptyResponse> {
|
||||||
get_collection("accounts")
|
session.subscription = None;
|
||||||
.update_one(
|
session
|
||||||
doc! {
|
.save(&get_db(), None)
|
||||||
"_id": session.user_id,
|
|
||||||
"sessions.id": session.id.unwrap()
|
|
||||||
},
|
|
||||||
doc! {
|
|
||||||
"$unset": {
|
|
||||||
"sessions.$.subscription": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
|
.map(|_| EmptyResponse)
|
||||||
.map_err(|_| Error::DatabaseError {
|
.map_err(|_| Error::DatabaseError {
|
||||||
operation: "to_document",
|
operation: "save",
|
||||||
with: "subscription",
|
with: "session",
|
||||||
})?;
|
})
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
+9
-4
@@ -1,13 +1,13 @@
|
|||||||
use crate::util::variables::{
|
use crate::util::{ratelimit::Ratelimiter, variables::{
|
||||||
APP_URL, AUTUMN_URL, EXTERNAL_WS_URL, HCAPTCHA_SITEKEY, INVITE_ONLY, JANUARY_URL, USE_AUTUMN,
|
APP_URL, AUTUMN_URL, EXTERNAL_WS_URL, HCAPTCHA_SITEKEY, INVITE_ONLY, JANUARY_URL, USE_AUTUMN,
|
||||||
USE_EMAIL, USE_HCAPTCHA, USE_JANUARY, USE_VOSO, VAPID_PUBLIC_KEY, VOSO_URL, VOSO_WS_HOST,
|
USE_EMAIL, USE_HCAPTCHA, USE_JANUARY, USE_VOSO, VAPID_PUBLIC_KEY, VOSO_URL, VOSO_WS_HOST,
|
||||||
};
|
}};
|
||||||
|
|
||||||
use mongodb::bson::doc;
|
use mongodb::bson::doc;
|
||||||
use rocket_contrib::json::JsonValue;
|
use rocket::{http::Status, serde::json::Value};
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
pub async fn root() -> JsonValue {
|
pub async fn root() -> Value {
|
||||||
json!({
|
json!({
|
||||||
"revolt": crate::version::VERSION,
|
"revolt": crate::version::VERSION,
|
||||||
"features": {
|
"features": {
|
||||||
@@ -36,3 +36,8 @@ pub async fn root() -> JsonValue {
|
|||||||
"vapid": *VAPID_PUBLIC_KEY
|
"vapid": *VAPID_PUBLIC_KEY
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[get("/ping")]
|
||||||
|
pub async fn ping(_limitguard: Ratelimiter) -> Status {
|
||||||
|
Status::Ok
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
use crate::database::*;
|
use crate::database::*;
|
||||||
use crate::util::result::{Error, Result};
|
use crate::util::result::{Error, Result, EmptyResponse};
|
||||||
|
|
||||||
use mongodb::bson::doc;
|
use mongodb::bson::doc;
|
||||||
use rocket_contrib::json::Json;
|
use rocket::serde::json::Json;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ pub struct Data {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[put("/<server>/bans/<target>", data = "<data>")]
|
#[put("/<server>/bans/<target>", data = "<data>")]
|
||||||
pub async fn req(user: User, server: Ref, target: Ref, data: Json<Data>) -> Result<()> {
|
pub async fn req(user: User, server: Ref, target: Ref, data: Json<Data>) -> Result<EmptyResponse> {
|
||||||
let data = data.into_inner();
|
let data = data.into_inner();
|
||||||
data.validate()
|
data.validate()
|
||||||
.map_err(|error| Error::FailedValidation { error })?;
|
.map_err(|error| Error::FailedValidation { error })?;
|
||||||
@@ -57,5 +57,6 @@ pub async fn req(user: User, server: Ref, target: Ref, data: Json<Data>) -> Resu
|
|||||||
with: "server_ban",
|
with: "server_ban",
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
server.remove_member(&target.id, RemoveMember::Ban).await
|
server.remove_member(&target.id, RemoveMember::Ban).await?;
|
||||||
|
Ok(EmptyResponse {})
|
||||||
}
|
}
|
||||||
@@ -2,11 +2,20 @@ use crate::database::*;
|
|||||||
use crate::util::result::{Error, Result};
|
use crate::util::result::{Error, Result};
|
||||||
|
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
|
use mongodb::options::FindOptions;
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use rocket::serde::json::Value;
|
||||||
use mongodb::bson::{doc, from_document};
|
use mongodb::bson::{doc, from_document};
|
||||||
use rocket_contrib::json::JsonValue;
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct BannedUser {
|
||||||
|
_id: String,
|
||||||
|
username: String,
|
||||||
|
avatar: Option<File>
|
||||||
|
}
|
||||||
|
|
||||||
#[get("/<target>/bans")]
|
#[get("/<target>/bans")]
|
||||||
pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
|
pub async fn req(user: User, target: Ref) -> Result<Value> {
|
||||||
let target = target.fetch_server().await?;
|
let target = target.fetch_server().await?;
|
||||||
|
|
||||||
let perm = permissions::PermissionCalculator::new(&user)
|
let perm = permissions::PermissionCalculator::new(&user)
|
||||||
@@ -14,8 +23,8 @@ pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
|
|||||||
.for_server()
|
.for_server()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if !perm.get_manage_members() {
|
if !perm.get_ban_members() {
|
||||||
Err(Error::MissingPermission)?
|
return Err(Error::MissingPermission);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut cursor = get_collection("server_bans")
|
let mut cursor = get_collection("server_bans")
|
||||||
@@ -32,13 +41,47 @@ pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
let mut bans = vec![];
|
let mut bans = vec![];
|
||||||
|
let mut user_ids = vec![];
|
||||||
while let Some(result) = cursor.next().await {
|
while let Some(result) = cursor.next().await {
|
||||||
if let Ok(doc) = result {
|
if let Ok(doc) = result {
|
||||||
if let Ok(ban) = from_document::<Ban>(doc) {
|
if let Ok(ban) = from_document::<Ban>(doc) {
|
||||||
|
user_ids.push(ban.id.user.clone());
|
||||||
bans.push(ban);
|
bans.push(ban);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(json!(bans))
|
let mut cursor = get_collection("users")
|
||||||
|
.find(
|
||||||
|
doc! {
|
||||||
|
"_id": {
|
||||||
|
"$in": user_ids
|
||||||
|
}
|
||||||
|
},
|
||||||
|
FindOptions::builder()
|
||||||
|
.projection(doc! {
|
||||||
|
"username": 1,
|
||||||
|
"avatar": 1
|
||||||
|
})
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "find",
|
||||||
|
with: "users",
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let mut users = vec![];
|
||||||
|
while let Some(result) = cursor.next().await {
|
||||||
|
if let Ok(doc) = result {
|
||||||
|
if let Ok(user) = from_document::<BannedUser>(doc) {
|
||||||
|
users.push(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(json!({
|
||||||
|
"users": users,
|
||||||
|
"bans": bans
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
use crate::database::*;
|
use crate::database::*;
|
||||||
use crate::util::result::{Error, Result};
|
use crate::util::result::{Error, Result, EmptyResponse};
|
||||||
|
|
||||||
use mongodb::bson::doc;
|
use mongodb::bson::doc;
|
||||||
|
|
||||||
#[delete("/<server>/bans/<target>")]
|
#[delete("/<server>/bans/<target>")]
|
||||||
pub async fn req(user: User, server: Ref, target: Ref) -> Result<()> {
|
pub async fn req(user: User, server: Ref, target: Ref) -> Result<EmptyResponse> {
|
||||||
let server = server.fetch_server().await?;
|
let server = server.fetch_server().await?;
|
||||||
|
|
||||||
let perm = permissions::PermissionCalculator::new(&user)
|
let perm = permissions::PermissionCalculator::new(&user)
|
||||||
@@ -39,5 +39,5 @@ pub async fn req(user: User, server: Ref, target: Ref) -> Result<()> {
|
|||||||
with: "server_ban",
|
with: "server_ban",
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(())
|
Ok(EmptyResponse {})
|
||||||
}
|
}
|
||||||
@@ -1,14 +1,30 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::database::*;
|
use crate::database::*;
|
||||||
use crate::util::result::{Error, Result};
|
use crate::util::result::{Error, Result};
|
||||||
|
|
||||||
use mongodb::bson::doc;
|
use mongodb::bson::doc;
|
||||||
use rocket_contrib::json::{Json, JsonValue};
|
use rocket::serde::json::{Json, Value};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use ulid::Ulid;
|
use ulid::Ulid;
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
enum ChannelType {
|
||||||
|
Text,
|
||||||
|
Voice
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ChannelType {
|
||||||
|
fn default() -> Self {
|
||||||
|
ChannelType::Text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Validate, Serialize, Deserialize)]
|
#[derive(Validate, Serialize, Deserialize)]
|
||||||
pub struct Data {
|
pub struct Data {
|
||||||
|
#[serde(rename = "type", default = "ChannelType::default")]
|
||||||
|
channel_type: ChannelType,
|
||||||
#[validate(length(min = 1, max = 32))]
|
#[validate(length(min = 1, max = 32))]
|
||||||
name: String,
|
name: String,
|
||||||
#[validate(length(min = 0, max = 1024))]
|
#[validate(length(min = 0, max = 1024))]
|
||||||
@@ -16,10 +32,12 @@ pub struct Data {
|
|||||||
// Maximum length of 36 allows both ULIDs and UUIDs.
|
// Maximum length of 36 allows both ULIDs and UUIDs.
|
||||||
#[validate(length(min = 1, max = 36))]
|
#[validate(length(min = 1, max = 36))]
|
||||||
nonce: String,
|
nonce: String,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
nsfw: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/<target>/channels", data = "<info>")]
|
#[post("/<target>/channels", data = "<info>")]
|
||||||
pub async fn req(user: User, target: Ref, info: Json<Data>) -> Result<JsonValue> {
|
pub async fn req(user: User, target: Ref, info: Json<Data>) -> Result<Value> {
|
||||||
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 })?;
|
||||||
@@ -30,7 +48,7 @@ pub async fn req(user: User, target: Ref, info: Json<Data>) -> Result<JsonValue>
|
|||||||
.for_server()
|
.for_server()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if !perm.get_manage_server() {
|
if !perm.get_manage_channels() {
|
||||||
Err(Error::MissingPermission)?
|
Err(Error::MissingPermission)?
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +70,8 @@ pub async fn req(user: User, target: Ref, info: Json<Data>) -> Result<JsonValue>
|
|||||||
}
|
}
|
||||||
|
|
||||||
let id = Ulid::new().to_string();
|
let id = Ulid::new().to_string();
|
||||||
let channel = Channel::TextChannel {
|
let channel = match info.channel_type {
|
||||||
|
ChannelType::Text => Channel::TextChannel {
|
||||||
id: id.clone(),
|
id: id.clone(),
|
||||||
server: target.id.clone(),
|
server: target.id.clone(),
|
||||||
nonce: Some(info.nonce),
|
nonce: Some(info.nonce),
|
||||||
@@ -60,7 +79,27 @@ pub async fn req(user: User, target: Ref, info: Json<Data>) -> Result<JsonValue>
|
|||||||
name: info.name,
|
name: info.name,
|
||||||
description: info.description,
|
description: info.description,
|
||||||
icon: None,
|
icon: None,
|
||||||
last_message: None,
|
last_message_id: None,
|
||||||
|
|
||||||
|
default_permissions: None,
|
||||||
|
role_permissions: HashMap::new(),
|
||||||
|
|
||||||
|
nsfw: info.nsfw.unwrap_or_default(),
|
||||||
|
},
|
||||||
|
ChannelType::Voice => Channel::VoiceChannel {
|
||||||
|
id: id.clone(),
|
||||||
|
server: target.id.clone(),
|
||||||
|
nonce: Some(info.nonce),
|
||||||
|
|
||||||
|
name: info.name,
|
||||||
|
description: info.description,
|
||||||
|
icon: None,
|
||||||
|
|
||||||
|
default_permissions: None,
|
||||||
|
role_permissions: HashMap::new(),
|
||||||
|
|
||||||
|
nsfw: info.nsfw.unwrap_or_default()
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
channel.clone().publish().await?;
|
channel.clone().publish().await?;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use crate::util::result::{Error, Result};
|
|||||||
|
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use mongodb::bson::{doc, from_document};
|
use mongodb::bson::{doc, from_document};
|
||||||
use rocket_contrib::json::JsonValue;
|
use rocket::serde::json::Value;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
@@ -15,7 +15,7 @@ pub struct ServerInvite {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("/<target>/invites")]
|
#[get("/<target>/invites")]
|
||||||
pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
|
pub async fn req(user: User, target: Ref) -> Result<Value> {
|
||||||
let target = target.fetch_server().await?;
|
let target = target.fetch_server().await?;
|
||||||
|
|
||||||
let perm = permissions::PermissionCalculator::new(&user)
|
let perm = permissions::PermissionCalculator::new(&user)
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use crate::notifications::events::ClientboundNotification;
|
use crate::notifications::events::ClientboundNotification;
|
||||||
use crate::util::result::{Error, Result};
|
use crate::util::result::{Error, Result, EmptyResponse};
|
||||||
use crate::{database::*, notifications::events::RemoveMemberField};
|
use crate::{database::*, notifications::events::RemoveMemberField};
|
||||||
|
|
||||||
use mongodb::bson::{doc, to_document};
|
use mongodb::bson::{doc, to_document};
|
||||||
use rocket_contrib::json::Json;
|
use rocket::serde::json::Json;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
|
||||||
@@ -12,17 +14,18 @@ pub struct Data {
|
|||||||
#[validate(length(min = 1, max = 32))]
|
#[validate(length(min = 1, max = 32))]
|
||||||
nickname: Option<String>,
|
nickname: Option<String>,
|
||||||
avatar: Option<String>,
|
avatar: Option<String>,
|
||||||
|
roles: Option<Vec<String>>,
|
||||||
remove: Option<RemoveMemberField>,
|
remove: Option<RemoveMemberField>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[patch("/<server>/members/<target>", data = "<data>")]
|
#[patch("/<server>/members/<target>", data = "<data>")]
|
||||||
pub async fn req(user: User, server: Ref, target: String, data: Json<Data>) -> Result<()> {
|
pub async fn req(user: User, server: Ref, target: String, data: Json<Data>) -> Result<EmptyResponse> {
|
||||||
let data = data.into_inner();
|
let data = data.into_inner();
|
||||||
data.validate()
|
data.validate()
|
||||||
.map_err(|error| Error::FailedValidation { error })?;
|
.map_err(|error| Error::FailedValidation { error })?;
|
||||||
|
|
||||||
if data.nickname.is_none() && data.avatar.is_none() && data.remove.is_none() {
|
if data.nickname.is_none() && data.avatar.is_none() && data.roles.is_none() && data.remove.is_none() {
|
||||||
return Ok(());
|
return Ok(EmptyResponse {});
|
||||||
}
|
}
|
||||||
|
|
||||||
let server = server.fetch_server().await?;
|
let server = server.fetch_server().await?;
|
||||||
@@ -33,6 +36,10 @@ pub async fn req(user: User, server: Ref, target: String, data: Json<Data>) -> R
|
|||||||
.for_server()
|
.for_server()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
if data.roles.is_some() && !perm.get_manage_roles() {
|
||||||
|
return Err(Error::MissingPermission);
|
||||||
|
}
|
||||||
|
|
||||||
if target.id.user == user.id {
|
if target.id.user == user.id {
|
||||||
if (data.nickname.is_some() && !perm.get_change_nickname())
|
if (data.nickname.is_some() && !perm.get_change_nickname())
|
||||||
|| (data.avatar.is_some() && !perm.get_change_avatar())
|
|| (data.avatar.is_some() && !perm.get_change_avatar())
|
||||||
@@ -97,6 +104,18 @@ pub async fn req(user: User, server: Ref, target: String, data: Json<Data>) -> R
|
|||||||
remove_avatar = true;
|
remove_avatar = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(role_ids) = &data.roles {
|
||||||
|
let mut ids = HashSet::new();
|
||||||
|
|
||||||
|
for role in role_ids {
|
||||||
|
if server.roles.contains_key(role) {
|
||||||
|
ids.insert(role.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set.insert("roles", ids.into_iter().collect::<Vec<String>>());
|
||||||
|
}
|
||||||
|
|
||||||
let mut operations = doc! {};
|
let mut operations = doc! {};
|
||||||
if set.len() > 0 {
|
if set.len() > 0 {
|
||||||
operations.insert("$set", &set);
|
operations.insert("$set", &set);
|
||||||
@@ -135,5 +154,5 @@ pub async fn req(user: User, server: Ref, target: String, data: Json<Data>) -> R
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(EmptyResponse {})
|
||||||
}
|
}
|
||||||
@@ -2,10 +2,10 @@ use crate::database::*;
|
|||||||
use crate::util::result::{Error, Result};
|
use crate::util::result::{Error, Result};
|
||||||
|
|
||||||
use mongodb::bson::doc;
|
use mongodb::bson::doc;
|
||||||
use rocket_contrib::json::JsonValue;
|
use rocket::serde::json::Value;
|
||||||
|
|
||||||
#[get("/<target>/members/<member>")]
|
#[get("/<target>/members/<member>")]
|
||||||
pub async fn req(user: User, target: Ref, member: String) -> Result<JsonValue> {
|
pub async fn req(user: User, target: Ref, member: String) -> Result<Value> {
|
||||||
let target = target.fetch_server().await?;
|
let target = target.fetch_server().await?;
|
||||||
|
|
||||||
let perm = permissions::PermissionCalculator::new(&user)
|
let perm = permissions::PermissionCalculator::new(&user)
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ use crate::util::result::{Error, Result};
|
|||||||
|
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use mongodb::bson::{doc, from_document, Document};
|
use mongodb::bson::{doc, from_document, Document};
|
||||||
use rocket_contrib::json::JsonValue;
|
use rocket::serde::json::Value;
|
||||||
|
|
||||||
// ! FIXME: this is a temporary route while permissions are being worked on.
|
// ! FIXME: this is a temporary route while permissions are being worked on.
|
||||||
|
|
||||||
#[get("/<target>/members")]
|
#[get("/<target>/members")]
|
||||||
pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
|
pub async fn req(user: User, target: Ref) -> Result<Value> {
|
||||||
let target = target.fetch_server().await?;
|
let target = target.fetch_server().await?;
|
||||||
|
|
||||||
let perm = permissions::PermissionCalculator::new(&user)
|
let perm = permissions::PermissionCalculator::new(&user)
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
use crate::database::*;
|
use crate::database::*;
|
||||||
use crate::util::result::{Error, Result};
|
use crate::util::result::{Error, Result, EmptyResponse};
|
||||||
|
|
||||||
use mongodb::bson::doc;
|
use mongodb::bson::doc;
|
||||||
|
|
||||||
#[delete("/<target>/members/<member>")]
|
#[delete("/<target>/members/<member>")]
|
||||||
pub async fn req(user: User, target: Ref, member: String) -> Result<()> {
|
pub async fn req(user: User, target: Ref, member: String) -> Result<EmptyResponse> {
|
||||||
let target = target.fetch_server().await?;
|
let target = target.fetch_server().await?;
|
||||||
|
|
||||||
let perm = permissions::PermissionCalculator::new(&user)
|
let perm = permissions::PermissionCalculator::new(&user)
|
||||||
@@ -12,7 +12,7 @@ pub async fn req(user: User, target: Ref, member: String) -> Result<()> {
|
|||||||
.for_server()
|
.for_server()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if !perm.get_manage_members() {
|
if !perm.get_kick_members() {
|
||||||
return Err(Error::MissingPermission);
|
return Err(Error::MissingPermission);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,5 +27,7 @@ pub async fn req(user: User, target: Ref, member: String) -> Result<()> {
|
|||||||
|
|
||||||
target
|
target
|
||||||
.remove_member(&member.id.user, RemoveMember::Kick)
|
.remove_member(&member.id.user, RemoveMember::Kick)
|
||||||
.await
|
.await?;
|
||||||
|
|
||||||
|
Ok(EmptyResponse {})
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,7 @@ mod server_create;
|
|||||||
mod server_delete;
|
mod server_delete;
|
||||||
mod server_edit;
|
mod server_edit;
|
||||||
mod server_fetch;
|
mod server_fetch;
|
||||||
|
mod server_ack;
|
||||||
|
|
||||||
mod channel_create;
|
mod channel_create;
|
||||||
|
|
||||||
@@ -18,12 +19,19 @@ mod ban_remove;
|
|||||||
|
|
||||||
mod invites_fetch;
|
mod invites_fetch;
|
||||||
|
|
||||||
|
mod roles_create;
|
||||||
|
mod roles_edit;
|
||||||
|
mod roles_delete;
|
||||||
|
mod permissions_set;
|
||||||
|
mod permissions_set_default;
|
||||||
|
|
||||||
pub fn routes() -> Vec<Route> {
|
pub fn routes() -> Vec<Route> {
|
||||||
routes![
|
routes![
|
||||||
server_create::req,
|
server_create::req,
|
||||||
server_delete::req,
|
server_delete::req,
|
||||||
server_fetch::req,
|
server_fetch::req,
|
||||||
server_edit::req,
|
server_edit::req,
|
||||||
|
server_ack::req,
|
||||||
channel_create::req,
|
channel_create::req,
|
||||||
member_fetch_all::req,
|
member_fetch_all::req,
|
||||||
member_remove::req,
|
member_remove::req,
|
||||||
@@ -32,6 +40,11 @@ pub fn routes() -> Vec<Route> {
|
|||||||
ban_create::req,
|
ban_create::req,
|
||||||
ban_remove::req,
|
ban_remove::req,
|
||||||
ban_list::req,
|
ban_list::req,
|
||||||
invites_fetch::req
|
invites_fetch::req,
|
||||||
|
roles_create::req,
|
||||||
|
roles_edit::req,
|
||||||
|
roles_delete::req,
|
||||||
|
permissions_set::req,
|
||||||
|
permissions_set_default::req
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
use mongodb::bson::doc;
|
||||||
|
use rocket::serde::json::Json;
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
|
use crate::database::*;
|
||||||
|
use crate::database::permissions::channel::ChannelPermission;
|
||||||
|
use crate::database::permissions::server::ServerPermission;
|
||||||
|
use crate::notifications::events::ClientboundNotification;
|
||||||
|
use crate::util::result::{Error, Result, EmptyResponse};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct Values {
|
||||||
|
server: u32,
|
||||||
|
channel: u32
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct Data {
|
||||||
|
permissions: Values
|
||||||
|
}
|
||||||
|
|
||||||
|
#[put("/<target>/permissions/<role_id>", data = "<data>", rank = 2)]
|
||||||
|
pub async fn req(user: User, target: Ref, role_id: String, data: Json<Data>) -> Result<EmptyResponse> {
|
||||||
|
let target = target.fetch_server().await?;
|
||||||
|
|
||||||
|
let perm = permissions::PermissionCalculator::new(&user)
|
||||||
|
.with_server(&target)
|
||||||
|
.for_server()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if !perm.get_manage_roles() {
|
||||||
|
return Err(Error::MissingPermission);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !target.roles.contains_key(&role_id) {
|
||||||
|
return Err(Error::NotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
let server_permissions: u32 = ServerPermission::View as u32 | data.permissions.server;
|
||||||
|
let channel_permissions: u32 = ChannelPermission::View as u32 | data.permissions.channel;
|
||||||
|
|
||||||
|
get_collection("servers")
|
||||||
|
.update_one(
|
||||||
|
doc! { "_id": &target.id },
|
||||||
|
doc! {
|
||||||
|
"$set": {
|
||||||
|
"roles.".to_owned() + &role_id + &".permissions": [
|
||||||
|
server_permissions as i32,
|
||||||
|
channel_permissions as i32
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "update_one",
|
||||||
|
with: "server"
|
||||||
|
})?;
|
||||||
|
|
||||||
|
ClientboundNotification::ServerRoleUpdate {
|
||||||
|
id: target.id.clone(),
|
||||||
|
role_id,
|
||||||
|
data: json!({
|
||||||
|
"permissions": [
|
||||||
|
server_permissions as i32,
|
||||||
|
channel_permissions as i32
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
clear: None
|
||||||
|
}
|
||||||
|
.publish(target.id);
|
||||||
|
|
||||||
|
Ok(EmptyResponse)
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
use mongodb::bson::doc;
|
||||||
|
use rocket::serde::json::Json;
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
|
use crate::database::*;
|
||||||
|
use crate::database::permissions::channel::ChannelPermission;
|
||||||
|
use crate::database::permissions::server::ServerPermission;
|
||||||
|
use crate::notifications::events::ClientboundNotification;
|
||||||
|
use crate::util::result::{Error, Result, EmptyResponse};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct Values {
|
||||||
|
server: u32,
|
||||||
|
channel: u32
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct Data {
|
||||||
|
permissions: Values
|
||||||
|
}
|
||||||
|
|
||||||
|
#[put("/<target>/permissions/default", data = "<data>", rank = 1)]
|
||||||
|
pub async fn req(user: User, target: Ref, data: Json<Data>) -> Result<EmptyResponse> {
|
||||||
|
let target = target.fetch_server().await?;
|
||||||
|
|
||||||
|
let perm = permissions::PermissionCalculator::new(&user)
|
||||||
|
.with_server(&target)
|
||||||
|
.for_server()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if !perm.get_manage_roles() {
|
||||||
|
return Err(Error::MissingPermission);
|
||||||
|
}
|
||||||
|
|
||||||
|
let server_permissions: u32 = ServerPermission::View as u32 | data.permissions.server;
|
||||||
|
let channel_permissions: u32 = ChannelPermission::View as u32 | data.permissions.channel;
|
||||||
|
|
||||||
|
get_collection("servers")
|
||||||
|
.update_one(
|
||||||
|
doc! { "_id": &target.id },
|
||||||
|
doc! {
|
||||||
|
"$set": {
|
||||||
|
"default_permissions": [
|
||||||
|
server_permissions as i32,
|
||||||
|
channel_permissions as i32
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "update_one",
|
||||||
|
with: "server"
|
||||||
|
})?;
|
||||||
|
|
||||||
|
ClientboundNotification::ServerUpdate {
|
||||||
|
id: target.id.clone(),
|
||||||
|
data: json!({
|
||||||
|
"default_permissions": [
|
||||||
|
server_permissions as i32,
|
||||||
|
channel_permissions as i32
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
clear: None
|
||||||
|
}
|
||||||
|
.publish(target.id);
|
||||||
|
|
||||||
|
Ok(EmptyResponse {})
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
use crate::database::*;
|
||||||
|
use crate::notifications::events::ClientboundNotification;
|
||||||
|
use crate::util::result::{Error, Result};
|
||||||
|
|
||||||
|
use ulid::Ulid;
|
||||||
|
use mongodb::bson::doc;
|
||||||
|
use validator::Validate;
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use rocket::serde::json::{Json, Value};
|
||||||
|
|
||||||
|
#[derive(Validate, Serialize, Deserialize)]
|
||||||
|
pub struct Data {
|
||||||
|
#[validate(length(min = 1, max = 32))]
|
||||||
|
name: String
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/<target>/roles", data = "<data>")]
|
||||||
|
pub async fn req(user: User, target: Ref, data: Json<Data>) -> Result<Value> {
|
||||||
|
let data = data.into_inner();
|
||||||
|
data.validate()
|
||||||
|
.map_err(|error| Error::FailedValidation { error })?;
|
||||||
|
|
||||||
|
let target = target.fetch_server().await?;
|
||||||
|
|
||||||
|
let perm = permissions::PermissionCalculator::new(&user)
|
||||||
|
.with_server(&target)
|
||||||
|
.for_server()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if !perm.get_manage_roles() {
|
||||||
|
Err(Error::MissingPermission)?
|
||||||
|
}
|
||||||
|
|
||||||
|
let id = Ulid::new().to_string();
|
||||||
|
let perm_tuple = (
|
||||||
|
*permissions::server::DEFAULT_PERMISSION as i32,
|
||||||
|
*permissions::channel::DEFAULT_PERMISSION_SERVER as i32
|
||||||
|
);
|
||||||
|
|
||||||
|
get_collection("servers")
|
||||||
|
.update_one(
|
||||||
|
doc! {
|
||||||
|
"_id": &target.id
|
||||||
|
},
|
||||||
|
doc! {
|
||||||
|
"$set": {
|
||||||
|
"roles.".to_owned() + &id: {
|
||||||
|
"name": &data.name,
|
||||||
|
"permissions": [
|
||||||
|
&perm_tuple.0,
|
||||||
|
&perm_tuple.1
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "update_one",
|
||||||
|
with: "servers"
|
||||||
|
})?;
|
||||||
|
|
||||||
|
ClientboundNotification::ServerRoleUpdate {
|
||||||
|
id: target.id.clone(),
|
||||||
|
role_id: id.clone(),
|
||||||
|
data: json!({
|
||||||
|
"name": data.name,
|
||||||
|
"permissions": &perm_tuple
|
||||||
|
}),
|
||||||
|
clear: None
|
||||||
|
}
|
||||||
|
.publish(target.id);
|
||||||
|
|
||||||
|
Ok(json!({ "id": id, "permissions": perm_tuple }))
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
use crate::database::*;
|
||||||
|
use crate::notifications::events::ClientboundNotification;
|
||||||
|
use crate::util::result::{Error, Result, EmptyResponse};
|
||||||
|
|
||||||
|
use mongodb::bson::doc;
|
||||||
|
|
||||||
|
#[delete("/<target>/roles/<role_id>")]
|
||||||
|
pub async fn req(user: User, target: Ref, role_id: String) -> Result<EmptyResponse> {
|
||||||
|
let target = target.fetch_server().await?;
|
||||||
|
|
||||||
|
let perm = permissions::PermissionCalculator::new(&user)
|
||||||
|
.with_server(&target)
|
||||||
|
.for_server()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if !perm.get_manage_roles() {
|
||||||
|
Err(Error::MissingPermission)?
|
||||||
|
}
|
||||||
|
|
||||||
|
get_collection("servers")
|
||||||
|
.update_one(
|
||||||
|
doc! {
|
||||||
|
"_id": &target.id
|
||||||
|
},
|
||||||
|
doc! {
|
||||||
|
"$unset": {
|
||||||
|
"roles.".to_owned() + &role_id: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "update_one",
|
||||||
|
with: "servers"
|
||||||
|
})?;
|
||||||
|
|
||||||
|
get_collection("channels")
|
||||||
|
.update_one(
|
||||||
|
doc! {
|
||||||
|
"server": &target.id
|
||||||
|
},
|
||||||
|
doc! {
|
||||||
|
"$unset": {
|
||||||
|
"role_permissions.".to_owned() + &role_id: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "update_one",
|
||||||
|
with: "channels"
|
||||||
|
})?;
|
||||||
|
|
||||||
|
get_collection("server_members")
|
||||||
|
.update_many(
|
||||||
|
doc! {
|
||||||
|
"_id.server": &target.id
|
||||||
|
},
|
||||||
|
doc! {
|
||||||
|
"$pull": {
|
||||||
|
"roles": &role_id
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "update_many",
|
||||||
|
with: "server_members"
|
||||||
|
})?;
|
||||||
|
|
||||||
|
ClientboundNotification::ServerRoleDelete {
|
||||||
|
id: target.id.clone(),
|
||||||
|
role_id
|
||||||
|
}
|
||||||
|
.publish(target.id);
|
||||||
|
|
||||||
|
Ok(EmptyResponse {})
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
use crate::notifications::events::ClientboundNotification;
|
||||||
|
use crate::util::result::{Error, Result, EmptyResponse};
|
||||||
|
use crate::{database::*, notifications::events::RemoveRoleField};
|
||||||
|
|
||||||
|
use mongodb::bson::doc;
|
||||||
|
use rocket::serde::json::Json;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use validator::Validate;
|
||||||
|
|
||||||
|
#[derive(Validate, Serialize, Deserialize)]
|
||||||
|
pub struct Data {
|
||||||
|
#[validate(length(min = 1, max = 32))]
|
||||||
|
name: Option<String>,
|
||||||
|
#[validate(length(min = 1, max = 32))]
|
||||||
|
colour: Option<String>,
|
||||||
|
hoist: Option<bool>,
|
||||||
|
rank: Option<i64>,
|
||||||
|
remove: Option<RemoveRoleField>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[patch("/<target>/roles/<role_id>", data = "<data>")]
|
||||||
|
pub async fn req(user: User, target: Ref, role_id: String, data: Json<Data>) -> Result<EmptyResponse> {
|
||||||
|
let data = data.into_inner();
|
||||||
|
data.validate()
|
||||||
|
.map_err(|error| Error::FailedValidation { error })?;
|
||||||
|
|
||||||
|
if data.name.is_none() && data.colour.is_none() && data.hoist.is_none() && data.rank.is_none() && data.remove.is_none()
|
||||||
|
{
|
||||||
|
return Ok(EmptyResponse {});
|
||||||
|
}
|
||||||
|
|
||||||
|
let target = target.fetch_server().await?;
|
||||||
|
let perm = permissions::PermissionCalculator::new(&user)
|
||||||
|
.with_server(&target)
|
||||||
|
.for_server()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if !perm.get_manage_roles() {
|
||||||
|
return Err(Error::MissingPermission)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !target.roles.contains_key(&role_id) {
|
||||||
|
return Err(Error::InvalidRole)
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut set = doc! {};
|
||||||
|
let mut unset = doc! {};
|
||||||
|
|
||||||
|
// ! FIXME: we should probably just require clients to support basic MQL incl. $set / $unset
|
||||||
|
let mut set_update = doc! {};
|
||||||
|
|
||||||
|
let role_key = "roles.".to_owned() + &role_id;
|
||||||
|
|
||||||
|
if let Some(remove) = &data.remove {
|
||||||
|
match remove {
|
||||||
|
RemoveRoleField::Colour => {
|
||||||
|
unset.insert(role_key.clone() + ".colour", 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(name) = &data.name {
|
||||||
|
set.insert(role_key.clone() + ".name", name);
|
||||||
|
set_update.insert("name", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(colour) = &data.colour {
|
||||||
|
set.insert(role_key.clone() + ".colour", colour);
|
||||||
|
set_update.insert("colour", colour);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(hoist) = &data.hoist {
|
||||||
|
set.insert(role_key.clone() + ".hoist", hoist);
|
||||||
|
set_update.insert("hoist", hoist);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(rank) = &data.rank {
|
||||||
|
set.insert(role_key.clone() + ".rank", rank);
|
||||||
|
set_update.insert("rank", rank);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut operations = doc! {};
|
||||||
|
if set.len() > 0 {
|
||||||
|
operations.insert("$set", &set);
|
||||||
|
}
|
||||||
|
|
||||||
|
if unset.len() > 0 {
|
||||||
|
operations.insert("$unset", unset);
|
||||||
|
}
|
||||||
|
|
||||||
|
if operations.len() > 0 {
|
||||||
|
get_collection("servers")
|
||||||
|
.update_one(doc! { "_id": &target.id }, operations, None)
|
||||||
|
.await
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
|
operation: "update_one",
|
||||||
|
with: "server",
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientboundNotification::ServerRoleUpdate {
|
||||||
|
id: target.id.clone(),
|
||||||
|
role_id,
|
||||||
|
data: json!(set_update),
|
||||||
|
clear: data.remove,
|
||||||
|
}
|
||||||
|
.publish(target.id.clone());
|
||||||
|
|
||||||
|
Ok(EmptyResponse {})
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
use crate::database::*;
|
||||||
|
use crate::util::result::{Error, Result, EmptyResponse};
|
||||||
|
|
||||||
|
#[put("/<target>/ack")]
|
||||||
|
pub async fn req(user: User, target: Ref) -> Result<EmptyResponse> {
|
||||||
|
if user.bot.is_some() {
|
||||||
|
return Err(Error::IsBot)
|
||||||
|
}
|
||||||
|
|
||||||
|
let target = target.fetch_server().await?;
|
||||||
|
|
||||||
|
let perm = permissions::PermissionCalculator::new(&user)
|
||||||
|
.with_server(&target)
|
||||||
|
.for_server()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if !perm.get_view() {
|
||||||
|
Err(Error::MissingPermission)?
|
||||||
|
}
|
||||||
|
|
||||||
|
target.mark_as_read(&user.id).await?;
|
||||||
|
Ok(EmptyResponse {})
|
||||||
|
}
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::database::*;
|
use crate::database::*;
|
||||||
use crate::util::result::{Error, Result};
|
use crate::util::result::{Error, Result};
|
||||||
|
use crate::util::variables::MAX_SERVER_COUNT;
|
||||||
|
|
||||||
use mongodb::bson::doc;
|
use mongodb::bson::doc;
|
||||||
use rocket_contrib::json::{Json, JsonValue};
|
use rocket::serde::json::{Json, Value};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use ulid::Ulid;
|
use ulid::Ulid;
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
@@ -16,10 +19,22 @@ pub struct Data {
|
|||||||
// Maximum length of 36 allows both ULIDs and UUIDs.
|
// Maximum length of 36 allows both ULIDs and UUIDs.
|
||||||
#[validate(length(min = 1, max = 36))]
|
#[validate(length(min = 1, max = 36))]
|
||||||
nonce: String,
|
nonce: String,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
nsfw: Option<bool>
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/create", data = "<info>")]
|
#[post("/create", data = "<info>")]
|
||||||
pub async fn req(user: User, info: Json<Data>) -> Result<JsonValue> {
|
pub async fn req(user: User, info: Json<Data>) -> Result<Value> {
|
||||||
|
if user.bot.is_some() {
|
||||||
|
return Err(Error::IsBot)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !User::can_acquire_server(&user.id).await? {
|
||||||
|
Err(Error::TooManyServers {
|
||||||
|
max: *MAX_SERVER_COUNT,
|
||||||
|
})?
|
||||||
|
}
|
||||||
|
|
||||||
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 })?;
|
||||||
@@ -51,7 +66,9 @@ pub async fn req(user: User, info: Json<Data>) -> Result<JsonValue> {
|
|||||||
|
|
||||||
name: info.name,
|
name: info.name,
|
||||||
description: info.description,
|
description: info.description,
|
||||||
|
|
||||||
channels: vec![cid.clone()],
|
channels: vec![cid.clone()],
|
||||||
|
categories: None,
|
||||||
system_messages: Some(SystemMessageChannels {
|
system_messages: Some(SystemMessageChannels {
|
||||||
user_joined: Some(cid.clone()),
|
user_joined: Some(cid.clone()),
|
||||||
user_left: Some(cid.clone()),
|
user_left: Some(cid.clone()),
|
||||||
@@ -59,8 +76,17 @@ pub async fn req(user: User, info: Json<Data>) -> Result<JsonValue> {
|
|||||||
user_banned: Some(cid.clone()),
|
user_banned: Some(cid.clone()),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
roles: HashMap::new(),
|
||||||
|
default_permissions: (
|
||||||
|
*permissions::server::DEFAULT_PERMISSION as i32,
|
||||||
|
*permissions::channel::DEFAULT_PERMISSION_SERVER as i32
|
||||||
|
),
|
||||||
|
|
||||||
icon: None,
|
icon: None,
|
||||||
banner: None,
|
banner: None,
|
||||||
|
|
||||||
|
flags: None,
|
||||||
|
nsfw: info.nsfw.unwrap_or_default()
|
||||||
};
|
};
|
||||||
|
|
||||||
Channel::TextChannel {
|
Channel::TextChannel {
|
||||||
@@ -69,8 +95,13 @@ pub async fn req(user: User, info: Json<Data>) -> Result<JsonValue> {
|
|||||||
nonce: Some(info.nonce),
|
nonce: Some(info.nonce),
|
||||||
name: "general".to_string(),
|
name: "general".to_string(),
|
||||||
description: None,
|
description: None,
|
||||||
|
|
||||||
icon: None,
|
icon: None,
|
||||||
last_message: None,
|
last_message_id: None,
|
||||||
|
|
||||||
|
default_permissions: None,
|
||||||
|
role_permissions: HashMap::new(),
|
||||||
|
nsfw: false
|
||||||
}
|
}
|
||||||
.publish()
|
.publish()
|
||||||
.await?;
|
.await?;
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
use crate::database::*;
|
use crate::database::*;
|
||||||
use crate::util::result::{Error, Result};
|
use crate::util::result::{Error, Result, EmptyResponse};
|
||||||
|
|
||||||
use mongodb::bson::doc;
|
use mongodb::bson::doc;
|
||||||
|
|
||||||
#[delete("/<target>")]
|
#[delete("/<target>")]
|
||||||
pub async fn req(user: User, target: Ref) -> Result<()> {
|
pub async fn req(user: User, target: Ref) -> Result<EmptyResponse> {
|
||||||
let target = target.fetch_server().await?;
|
let target = target.fetch_server().await?;
|
||||||
let perm = permissions::PermissionCalculator::new(&user)
|
let perm = permissions::PermissionCalculator::new(&user)
|
||||||
.with_server(&target)
|
.with_server(&target)
|
||||||
@@ -16,8 +16,10 @@ pub async fn req(user: User, target: Ref) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if user.id == target.owner {
|
if user.id == target.owner {
|
||||||
target.delete().await
|
target.delete().await?;
|
||||||
} else {
|
} else {
|
||||||
target.remove_member(&user.id, RemoveMember::Leave).await
|
target.remove_member(&user.id, RemoveMember::Leave).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(EmptyResponse {})
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
use crate::notifications::events::ClientboundNotification;
|
use crate::notifications::events::ClientboundNotification;
|
||||||
use crate::util::result::{Error, Result};
|
use crate::util::result::{Error, Result, EmptyResponse};
|
||||||
use crate::{database::*, notifications::events::RemoveServerField};
|
use crate::{database::*, notifications::events::RemoveServerField};
|
||||||
|
|
||||||
use mongodb::bson::{doc, to_document};
|
use mongodb::bson::{doc, to_bson, to_document};
|
||||||
use rocket_contrib::json::Json;
|
use rocket::serde::json::Json;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
|
||||||
@@ -15,18 +15,22 @@ pub struct Data {
|
|||||||
description: Option<String>,
|
description: Option<String>,
|
||||||
icon: Option<String>,
|
icon: Option<String>,
|
||||||
banner: Option<String>,
|
banner: Option<String>,
|
||||||
|
categories: Option<Vec<Category>>,
|
||||||
|
system_messages: Option<SystemMessageChannels>,
|
||||||
remove: Option<RemoveServerField>,
|
remove: Option<RemoveServerField>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
nsfw: Option<bool>
|
||||||
}
|
}
|
||||||
|
|
||||||
#[patch("/<target>", data = "<data>")]
|
#[patch("/<target>", data = "<data>")]
|
||||||
pub async fn req(user: User, target: Ref, data: Json<Data>) -> Result<()> {
|
pub async fn req(user: User, target: Ref, data: Json<Data>) -> Result<EmptyResponse> {
|
||||||
let data = data.into_inner();
|
let data = data.into_inner();
|
||||||
data.validate()
|
data.validate()
|
||||||
.map_err(|error| Error::FailedValidation { error })?;
|
.map_err(|error| Error::FailedValidation { error })?;
|
||||||
|
|
||||||
if data.name.is_none() && data.icon.is_none() && data.banner.is_none() && data.remove.is_none()
|
if data.name.is_none() && data.description.is_none() && data.icon.is_none() && data.banner.is_none() && data.remove.is_none() && data.categories.is_none() && data.system_messages.is_none()
|
||||||
{
|
{
|
||||||
return Ok(());
|
return Ok(EmptyResponse {});
|
||||||
}
|
}
|
||||||
|
|
||||||
let target = target.fetch_server().await?;
|
let target = target.fetch_server().await?;
|
||||||
@@ -95,6 +99,18 @@ pub async fn req(user: User, target: Ref, data: Json<Data>) -> Result<()> {
|
|||||||
remove_banner = true;
|
remove_banner = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(categories) = &data.categories {
|
||||||
|
set.insert("categories", to_bson(&categories).map_err(|_| Error::DatabaseError { operation: "to_document", with: "categories" })?);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(system_messages) = &data.system_messages {
|
||||||
|
set.insert("system_messages", to_bson(&system_messages).map_err(|_| Error::DatabaseError { operation: "to_document", with: "system_messages" })?);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(nsfw) = &data.nsfw {
|
||||||
|
set.insert("nsfw", nsfw);
|
||||||
|
}
|
||||||
|
|
||||||
let mut operations = doc! {};
|
let mut operations = doc! {};
|
||||||
if set.len() > 0 {
|
if set.len() > 0 {
|
||||||
operations.insert("$set", &set);
|
operations.insert("$set", &set);
|
||||||
@@ -135,5 +151,5 @@ pub async fn req(user: User, target: Ref, data: Json<Data>) -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(EmptyResponse {})
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
use crate::database::*;
|
use crate::database::*;
|
||||||
use crate::util::result::{Error, Result};
|
use crate::util::result::{Error, Result};
|
||||||
|
|
||||||
use rocket_contrib::json::JsonValue;
|
use rocket::serde::json::Value;
|
||||||
|
|
||||||
#[get("/<target>")]
|
#[get("/<target>")]
|
||||||
pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
|
pub async fn req(user: User, target: Ref) -> Result<Value> {
|
||||||
let target = target.fetch_server().await?;
|
let target = target.fetch_server().await?;
|
||||||
|
|
||||||
let perm = permissions::PermissionCalculator::new(&user)
|
let perm = permissions::PermissionCalculator::new(&user)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use crate::util::result::{Error, Result};
|
|||||||
|
|
||||||
use mongodb::bson::doc;
|
use mongodb::bson::doc;
|
||||||
use mongodb::options::FindOneOptions;
|
use mongodb::options::FindOneOptions;
|
||||||
use rocket_contrib::json::{Json, JsonValue};
|
use rocket::serde::json::{Json, Value};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
@@ -12,7 +12,11 @@ pub struct Options {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[post("/settings/fetch", data = "<options>")]
|
#[post("/settings/fetch", data = "<options>")]
|
||||||
pub async fn req(user: User, options: Json<Options>) -> Result<JsonValue> {
|
pub async fn req(user: User, options: Json<Options>) -> Result<Value> {
|
||||||
|
if user.bot.is_some() {
|
||||||
|
return Err(Error::IsBot);
|
||||||
|
}
|
||||||
|
|
||||||
let options = options.into_inner();
|
let options = options.into_inner();
|
||||||
let mut projection = doc! {
|
let mut projection = doc! {
|
||||||
"_id": 0,
|
"_id": 0,
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
use crate::database::*;
|
use crate::database::*;
|
||||||
use crate::util::result::Result;
|
use crate::util::result::{Error, Result};
|
||||||
|
|
||||||
use mongodb::bson::doc;
|
use mongodb::bson::doc;
|
||||||
use rocket_contrib::json::JsonValue;
|
use rocket::serde::json::Value;
|
||||||
|
|
||||||
#[get("/unreads")]
|
#[get("/unreads")]
|
||||||
pub async fn req(user: User) -> Result<JsonValue> {
|
pub async fn req(user: User) -> Result<Value> {
|
||||||
Ok(json!(user.fetch_unreads().await?))
|
if user.bot.is_some() {
|
||||||
|
return Err(Error::IsBot);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(json!(User::fetch_unreads(&user.id).await?))
|
||||||
}
|
}
|
||||||
@@ -1,24 +1,28 @@
|
|||||||
use crate::database::*;
|
use crate::database::*;
|
||||||
use crate::notifications::events::ClientboundNotification;
|
use crate::notifications::events::ClientboundNotification;
|
||||||
use crate::util::result::{Error, Result};
|
use crate::util::result::{EmptyResponse, Error, Result};
|
||||||
|
|
||||||
use chrono::prelude::*;
|
use chrono::prelude::*;
|
||||||
use mongodb::bson::{doc, to_bson};
|
use mongodb::bson::{doc, to_bson};
|
||||||
use mongodb::options::UpdateOptions;
|
use mongodb::options::UpdateOptions;
|
||||||
use rocket::request::Form;
|
use rocket::serde::json::Json;
|
||||||
use rocket_contrib::json::Json;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json::json;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
type Data = HashMap<String, String>;
|
type Data = HashMap<String, String>;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, FromForm)]
|
#[derive(FromForm, Serialize, Deserialize)]
|
||||||
pub struct Options {
|
pub struct Options {
|
||||||
timestamp: Option<i64>,
|
timestamp: Option<i64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/settings/set?<options..>", data = "<data>")]
|
#[post("/settings/set?<options..>", data = "<data>")]
|
||||||
pub async fn req(user: User, data: Json<Data>, options: Form<Options>) -> Result<()> {
|
pub async fn req(user: User, data: Json<Data>, options: Options) -> Result<EmptyResponse> {
|
||||||
|
if user.bot.is_some() {
|
||||||
|
return Err(Error::IsBot);
|
||||||
|
}
|
||||||
|
|
||||||
let data = data.into_inner();
|
let data = data.into_inner();
|
||||||
let current_time = Utc::now().timestamp_millis();
|
let current_time = Utc::now().timestamp_millis();
|
||||||
let timestamp = if let Some(timestamp) = options.timestamp {
|
let timestamp = if let Some(timestamp) = options.timestamp {
|
||||||
@@ -66,5 +70,5 @@ pub async fn req(user: User, data: Json<Data>, options: Form<Options>) -> Result
|
|||||||
}
|
}
|
||||||
.publish(user.id);
|
.publish(user.id);
|
||||||
|
|
||||||
Ok(())
|
Ok(EmptyResponse {})
|
||||||
}
|
}
|
||||||
@@ -5,10 +5,14 @@ use crate::util::result::{Error, Result};
|
|||||||
use futures::try_join;
|
use futures::try_join;
|
||||||
use mongodb::bson::doc;
|
use mongodb::bson::doc;
|
||||||
use mongodb::options::{Collation, FindOneOptions};
|
use mongodb::options::{Collation, FindOneOptions};
|
||||||
use rocket_contrib::json::JsonValue;
|
use rocket::serde::json::Value;
|
||||||
|
|
||||||
#[put("/<username>/friend")]
|
#[put("/<username>/friend")]
|
||||||
pub async fn req(user: User, username: String) -> Result<JsonValue> {
|
pub async fn req(user: User, username: String) -> Result<Value> {
|
||||||
|
if user.bot.is_some() {
|
||||||
|
return Err(Error::IsBot)
|
||||||
|
}
|
||||||
|
|
||||||
let col = get_collection("users");
|
let col = get_collection("users");
|
||||||
let doc = col
|
let doc = col
|
||||||
.find_one(
|
.find_one(
|
||||||
@@ -32,6 +36,9 @@ pub async fn req(user: User, username: String) -> Result<JsonValue> {
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
let target_user = Ref::from(target_id.to_string())?.fetch_user().await?;
|
let target_user = Ref::from(target_id.to_string())?.fetch_user().await?;
|
||||||
|
if target_user.bot.is_some() {
|
||||||
|
return Err(Error::IsBot)
|
||||||
|
}
|
||||||
|
|
||||||
match get_relationship(&user, &target_id) {
|
match get_relationship(&user, &target_id) {
|
||||||
RelationshipStatus::User => return Err(Error::NoEffect),
|
RelationshipStatus::User => return Err(Error::NoEffect),
|
||||||
|
|||||||
Loaded 100 of 121 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user