Compare commits
92
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ba48a0197 | ||
|
|
47002ec81c | ||
|
|
6ab264eb36 | ||
|
|
59af536e29 | ||
|
|
5c7eec68d0 | ||
|
|
94d7320e70 | ||
|
|
a42ddcbbfa | ||
|
|
530264f747 | ||
|
|
ae1d4dd264 | ||
|
|
4789dd4847 | ||
|
|
f91d3bbfdd | ||
|
|
5b50532d15 | ||
|
|
938480ccec | ||
|
|
f422e8e917 | ||
|
|
1632e3dacf | ||
|
|
cc9a68b9c5 | ||
|
|
77a23239ea | ||
|
|
d03e6be2eb | ||
|
|
0a4d77ffd3 | ||
|
|
c6f121f585 | ||
|
|
96ddeb79d2 | ||
|
|
a36b138978 | ||
|
|
860c47fbad | ||
|
|
a770d5822a | ||
|
|
10c9682bba | ||
|
|
eb48975428 | ||
|
|
28ed85a779 | ||
|
|
95ca735b7b | ||
|
|
901ac56154 | ||
|
|
3dbf3bb7a0 | ||
|
|
45df5724b3 | ||
|
|
ea1c165d80 | ||
|
|
48e0b48ea9 | ||
|
|
61038ddcf1 | ||
|
|
b96f07e615 | ||
|
|
d830fb0911 | ||
|
|
cc9ff9a632 | ||
|
|
02f18705f4 | ||
|
|
a841df3b36 | ||
|
|
2a36f5912d | ||
|
|
a110a087be | ||
|
|
21b9164ee7 | ||
|
|
2f3d1f0c7e | ||
|
|
62204fe50b | ||
|
|
773b890012 | ||
|
|
9c3b2fb364 | ||
|
|
5fe19d9815 | ||
|
|
c1c5b0fd29 | ||
|
|
6737100b4b | ||
|
|
6f0e80eef9 | ||
|
|
bfc6f50ee5 | ||
|
|
7abbad700c | ||
|
|
a671ba6308 | ||
|
|
1d385ef2bc | ||
|
|
6558e9f7c4 | ||
|
|
92f4b44cb3 | ||
|
|
370442bf4e | ||
|
|
d54c5e80fa | ||
|
|
a42746c55b | ||
|
|
9a98c53aff | ||
|
|
4ea08a189d | ||
|
|
ae15a0b4c7 | ||
|
|
9e5fbec9b0 | ||
|
|
0b02c84f04 | ||
|
|
8bfe3ec6b0 | ||
|
|
a7345a7b90 | ||
|
|
12854d0f1b | ||
|
|
9a2a7901e7 | ||
|
|
e8cb0bb88a | ||
|
|
343b90b9af | ||
|
|
a4246f39e4 | ||
|
|
38380db6e7 | ||
|
|
091aa4705b | ||
|
|
8b5e085232 | ||
|
|
9c2cc8736c | ||
|
|
951128032b | ||
|
|
367ac887f4 | ||
|
|
060b4c43f4 | ||
|
|
2593a4272b | ||
|
|
742eb3bb7d | ||
|
|
41a6de67cd | ||
|
|
c8ae34ba73 | ||
|
|
c55b5bf75f | ||
|
|
9de87a01ab | ||
|
|
2f7294705e | ||
|
|
120ca449b8 | ||
|
|
ffbc899792 | ||
|
|
675c7d6c0d | ||
|
|
67569f932e | ||
|
|
7a4421089c | ||
|
|
ae87bc0b67 | ||
|
|
ad0dcad497 |
@@ -0,0 +1,37 @@
|
|||||||
|
name: Build documentation
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write # To push a branch
|
||||||
|
pages: write # To push to a GitHub Pages site
|
||||||
|
id-token: write # To update the deployment status
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Install latest mdbook
|
||||||
|
run: |
|
||||||
|
tag=$(curl 'https://api.github.com/repos/rust-lang/mdbook/releases/latest' | jq -r '.tag_name')
|
||||||
|
url="https://github.com/rust-lang/mdbook/releases/download/${tag}/mdbook-${tag}-x86_64-unknown-linux-gnu.tar.gz"
|
||||||
|
mkdir mdbook
|
||||||
|
curl -sSL $url | tar -xz --directory=./mdbook
|
||||||
|
echo `pwd`/mdbook >> $GITHUB_PATH
|
||||||
|
- name: Build Book
|
||||||
|
run: |
|
||||||
|
cd doc
|
||||||
|
mdbook build
|
||||||
|
- name: Setup Pages
|
||||||
|
uses: actions/configure-pages@v4
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-pages-artifact@v3
|
||||||
|
with:
|
||||||
|
path: "doc/book"
|
||||||
|
- name: Deploy to GitHub Pages
|
||||||
|
id: deployment
|
||||||
|
uses: actions/deploy-pages@v4
|
||||||
@@ -5,6 +5,8 @@ on:
|
|||||||
tags:
|
tags:
|
||||||
- "*"
|
- "*"
|
||||||
pull_request:
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- "main"
|
||||||
paths:
|
paths:
|
||||||
- "Dockerfile"
|
- "Dockerfile"
|
||||||
|
|
||||||
@@ -12,10 +14,6 @@ permissions:
|
|||||||
contents: read
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.head_ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
base:
|
base:
|
||||||
name: Test base image build
|
name: Test base image build
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
name: Documentation (test)
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
test-deploy:
|
|
||||||
name: Test deployment
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
working-directory: ./docs
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- uses: pnpm/action-setup@v4
|
|
||||||
with:
|
|
||||||
package_json_file: ./docs/package.json
|
|
||||||
- uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: lts/*
|
|
||||||
cache: pnpm
|
|
||||||
cache-dependency-path: ./docs/pnpm-lock.yaml
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: pnpm install
|
|
||||||
- name: Test build website
|
|
||||||
run: pnpm run build
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
name: Documentation
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Build Docusaurus
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
working-directory: ./docs
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- uses: pnpm/action-setup@v4
|
|
||||||
with:
|
|
||||||
package_json_file: ./docs/package.json
|
|
||||||
- uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: lts/*
|
|
||||||
cache: pnpm
|
|
||||||
cache-dependency-path: ./docs/pnpm-lock.yaml
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: pnpm install
|
|
||||||
- name: Build website
|
|
||||||
run: pnpm run build
|
|
||||||
|
|
||||||
- name: Upload Build Artifact
|
|
||||||
uses: actions/upload-pages-artifact@v3
|
|
||||||
with:
|
|
||||||
path: ./docs/build
|
|
||||||
|
|
||||||
deploy:
|
|
||||||
name: Deploy to GitHub Pages
|
|
||||||
needs: build
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
pages: write # to deploy to Pages
|
|
||||||
id-token: write # to verify the deployment originates from an appropriate source
|
|
||||||
|
|
||||||
environment:
|
|
||||||
name: github-pages
|
|
||||||
url: ${{ steps.deployment.outputs.page_url }}
|
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Deploy to GitHub Pages
|
|
||||||
id: deployment
|
|
||||||
uses: actions/deploy-pages@v4
|
|
||||||
+12
-12
@@ -2,12 +2,16 @@ name: Rust build, test, and generate specification
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [main]
|
paths-ignore:
|
||||||
|
- ".github/**"
|
||||||
|
- "!.github/workflows/rust.yaml"
|
||||||
|
- ".vscode/**"
|
||||||
|
- "doc/**"
|
||||||
|
- ".gitignore"
|
||||||
|
- "LICENSE"
|
||||||
|
- "README"
|
||||||
pull_request:
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
concurrency:
|
|
||||||
group: ${{ github.head_ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
env:
|
env:
|
||||||
CARGO_TERM_COLOR: always
|
CARGO_TERM_COLOR: always
|
||||||
@@ -18,10 +22,6 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Free up disk space
|
|
||||||
run: |
|
|
||||||
sudo rm -rf /usr/local/lib/android /usr/share/dotnet /opt/ghc
|
|
||||||
|
|
||||||
- name: Install latest stable
|
- name: Install latest stable
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
@@ -32,7 +32,7 @@ jobs:
|
|||||||
uses: baptiste0928/cargo-install@v1
|
uses: baptiste0928/cargo-install@v1
|
||||||
with:
|
with:
|
||||||
crate: cargo-nextest
|
crate: cargo-nextest
|
||||||
args: --locked
|
locked: true
|
||||||
|
|
||||||
- name: Run cargo build
|
- name: Run cargo build
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
@@ -73,9 +73,9 @@ jobs:
|
|||||||
if: github.event_name != 'pull_request' && github.ref_name == 'main'
|
if: github.event_name != 'pull_request' && github.ref_name == 'main'
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
repository: stoatchat/javascript-client-api
|
repository: stoatchat/api
|
||||||
path: api
|
path: api
|
||||||
ssh-key: ${{ secrets.DEPLOY_KEY_JAVASCRIPT_CLIENT_API }}
|
token: ${{ secrets.PAT }}
|
||||||
|
|
||||||
- name: Download OpenAPI specification
|
- name: Download OpenAPI specification
|
||||||
if: github.event_name != 'pull_request' && github.ref_name == 'main'
|
if: github.event_name != 'pull_request' && github.ref_name == 'main'
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
name: Add Issue to Board
|
||||||
|
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types: [opened]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
track_issue:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Get project data
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.PAT }}
|
||||||
|
run: |
|
||||||
|
gh api graphql -f query='
|
||||||
|
query {
|
||||||
|
organization(login: "stoatchat"){
|
||||||
|
projectV2(number: 3) {
|
||||||
|
id
|
||||||
|
fields(first:20) {
|
||||||
|
nodes {
|
||||||
|
... on ProjectV2SingleSelectField {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
options {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}' > project_data.json
|
||||||
|
|
||||||
|
echo 'PROJECT_ID='$(jq '.data.organization.projectV2.id' project_data.json) >> $GITHUB_ENV
|
||||||
|
echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV
|
||||||
|
echo 'TODO_OPTION_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") | .options[] | select(.name=="Todo") |.id' project_data.json) >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Add issue to project
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.PAT }}
|
||||||
|
ISSUE_ID: ${{ github.event.issue.node_id }}
|
||||||
|
run: |
|
||||||
|
item_id="$( gh api graphql -f query='
|
||||||
|
mutation($project:ID!, $issue:ID!) {
|
||||||
|
addProjectV2ItemById(input: {projectId: $project, contentId: $issue}) {
|
||||||
|
item {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}' -f project=$PROJECT_ID -f issue=$ISSUE_ID --jq '.data.addProjectV2ItemById.item.id')"
|
||||||
|
|
||||||
|
echo 'ITEM_ID='$item_id >> $GITHUB_ENV
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
name: Add PR to Board
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request_target:
|
||||||
|
types: [opened, synchronize, ready_for_review, review_requested]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
track_pr:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Get project data
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.PAT }}
|
||||||
|
run: |
|
||||||
|
gh api graphql -f query='
|
||||||
|
query {
|
||||||
|
organization(login: "stoatchat"){
|
||||||
|
projectV2(number: 5) {
|
||||||
|
id
|
||||||
|
fields(first:20) {
|
||||||
|
nodes {
|
||||||
|
... on ProjectV2SingleSelectField {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
options {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}' > project_data.json
|
||||||
|
|
||||||
|
echo 'PROJECT_ID='$(jq '.data.organization.projectV2.id' project_data.json) >> $GITHUB_ENV
|
||||||
|
echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV
|
||||||
|
echo 'INCOMING_OPTION_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") | .options[] | select(.name=="🆕 Untriaged") |.id' project_data.json) >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Add PR to project
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.PAT }}
|
||||||
|
PR_ID: ${{ github.event.pull_request.node_id }}
|
||||||
|
run: |
|
||||||
|
item_id="$( gh api graphql -f query='
|
||||||
|
mutation($project:ID!, $pr:ID!) {
|
||||||
|
addProjectV2ItemById(input: {projectId: $project, contentId: $pr}) {
|
||||||
|
item {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}' -f project=$PROJECT_ID -f pr=$PR_ID --jq '.data.addProjectV2ItemById.item.id')"
|
||||||
|
|
||||||
|
echo 'ITEM_ID='$item_id >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Set fields
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.PAT }}
|
||||||
|
run: |
|
||||||
|
gh api graphql -f query='
|
||||||
|
mutation (
|
||||||
|
$project: ID!
|
||||||
|
$item: ID!
|
||||||
|
$status_field: ID!
|
||||||
|
$status_value: String!
|
||||||
|
) {
|
||||||
|
set_status: updateProjectV2ItemFieldValue(input: {
|
||||||
|
projectId: $project
|
||||||
|
itemId: $item
|
||||||
|
fieldId: $status_field
|
||||||
|
value: {
|
||||||
|
singleSelectOptionId: $status_value
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
projectV2Item {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}' -f project=$PROJECT_ID -f item=$ITEM_ID -f status_field=$STATUS_FIELD_ID -f status_value=${{ env.INCOMING_OPTION_ID }} --silent
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
name: "Lint PR"
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request_target:
|
|
||||||
types:
|
|
||||||
- opened
|
|
||||||
- reopened
|
|
||||||
- edited
|
|
||||||
- synchronize
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
main:
|
|
||||||
name: Validate PR title
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
pull-requests: read
|
|
||||||
steps:
|
|
||||||
- uses: amannn/action-semantic-pull-request@v6
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
+2
-1
@@ -12,4 +12,5 @@ venv/
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
livekit.yml
|
livekit.yml
|
||||||
.idea
|
.idea
|
||||||
|
start
|
||||||
Generated
+193
-312
File diff suppressed because it is too large
Load Diff
+2
-3
@@ -11,9 +11,8 @@ members = [
|
|||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
redis23 = { package = "redis", version = "0.23.3", git = "https://github.com/revoltchat/redis-rs", rev = "523b2937367e17bd0073722bf6e23d06042cb4e4" }
|
redis23 = { package = "redis", version = "0.23.3", git = "https://github.com/revoltchat/redis-rs", rev = "523b2937367e17bd0073722bf6e23d06042cb4e4" }
|
||||||
authifier = { package = "authifier", version = "1.0.10", path = "../authifier/crates/authifier" }
|
#authifier = { package = "authifier", version = "1.0.10", path = "../authifier/crates/authifier" }
|
||||||
rocket_authifier = { package = "rocket_authifier", version = "1.0.10", path = "../authifier/crates/rocket_authifier" }
|
#rocket_authifier = { package = "rocket_authifier", version = "1.0.10", path = "../authifier/crates/rocket_authifier" }
|
||||||
iso8601-timestamp = { path = "../iso8601-timestamp" }
|
|
||||||
|
|
||||||
# I'm 99% sure this is overloading the GitHub worker
|
# I'm 99% sure this is overloading the GitHub worker
|
||||||
# hence builds have been failing since, let's just
|
# hence builds have been failing since, let's just
|
||||||
|
|||||||
+1
-1
@@ -54,7 +54,7 @@ use_starttls = false
|
|||||||
url = "http://livekit"
|
url = "http://livekit"
|
||||||
lat = 0.0
|
lat = 0.0
|
||||||
lon = 0.0
|
lon = 0.0
|
||||||
key = "worldwide"
|
key = "worldwide_key"
|
||||||
secret = "ZjCofRlfm6GGtjlifmNpCDkcQbEIIVC0"
|
secret = "ZjCofRlfm6GGtjlifmNpCDkcQbEIIVC0"
|
||||||
|
|
||||||
[files.s3]
|
[files.s3]
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
# Code Style Guide
|
||||||
|
|
||||||
|
Beyond using Cargo format and Clippy, there are some specific code style guidelines laid out in this document for different parts of the project.
|
||||||
|
|
||||||
|
## Writing Style
|
||||||
|
|
||||||
|
- Shorten "identifier" to "Id" with that exact casing, i.e. Server Id.
|
||||||
|
|
||||||
|
## `core/database` crate
|
||||||
|
|
||||||
|
w.r.t. `model.rs` files
|
||||||
|
|
||||||
|
- All struct definitions must be commented.
|
||||||
|
```rust
|
||||||
|
/// Server
|
||||||
|
pub struct Server {
|
||||||
|
/// Name of the server
|
||||||
|
pub name: String,
|
||||||
|
```
|
||||||
|
- Struct definitions should not include derives unless necessary (if additional traits such as Hash are required) and instead use `auto_derived!` and `auto_derived_partial!`.
|
||||||
|
```rust
|
||||||
|
auto_derived_partial!(
|
||||||
|
/// Server
|
||||||
|
pub struct Server { .. },
|
||||||
|
"PartialServer"
|
||||||
|
);
|
||||||
|
```
|
||||||
|
- `auto_derived!` macro accepts multiple entries and should be used as such:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
auto_derived!(
|
||||||
|
/// Optional fields on server object
|
||||||
|
pub enum FieldsServer { .. }
|
||||||
|
|
||||||
|
/// Optional fields on server object
|
||||||
|
pub enum FieldsRole { .. }
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
- If special serialisation conditions are required, such as checking if a boolean is false, use the existing definitions for these functions from the crate root:
|
||||||
|
```rust
|
||||||
|
#[serde(skip_serializing_if = "crate::if_false", default)]
|
||||||
|
```
|
||||||
|
- `impl` blocks may be defined below the struct definitions and should be ordered in the same order of definition. Methods in the block must follow the same guidelines as traits where-in: methods are ordered in terms of CRUD, there are empty line breaks, and methods are commented.
|
||||||
|
|
||||||
|
w.r.t. `ops` module for models
|
||||||
|
|
||||||
|
- All traits must use a the name format `AbstractPlural` where Plural is the plural form of the collection. e.g. Servers
|
||||||
|
- Traits defined must follow these guidelines:
|
||||||
|
|
||||||
|
- Methods are ordered in terms of CRUD, create-read-update-delete ordering.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[async_trait]
|
||||||
|
pub trait AbstractServerMembers: Sync + Send {
|
||||||
|
/// Insert a new server member into the database
|
||||||
|
async fn insert_member(&self, member: &Member) -> Result<()>;
|
||||||
|
|
||||||
|
/// Fetch a server member by their id
|
||||||
|
async fn fetch_member(&self, server_id: &str, user_id: &str) -> Result<Member>;
|
||||||
|
|
||||||
|
/// Update information for a server member
|
||||||
|
async fn update_member(&self, .. ) -> Result<()>;
|
||||||
|
|
||||||
|
/// Delete a server member by their id
|
||||||
|
async fn delete_member(&self, id: &MemberCompositeKey) -> Result<()>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- There should be an empty line break between each method declaration.
|
||||||
|
- All methods must have an appropriate comment.
|
||||||
|
|
||||||
|
- When implementing the trait defined in `ops.rs` with each driver, the method declaration style should be the same for ease of searching: same ordering, same comments, same line breaks.
|
||||||
@@ -64,10 +64,3 @@ services:
|
|||||||
MAILDEV_WEB_PORT: 8080
|
MAILDEV_WEB_PORT: 8080
|
||||||
MAILDEV_INCOMING_USER: smtp
|
MAILDEV_INCOMING_USER: smtp
|
||||||
MAILDEV_INCOMING_PASS: smtp
|
MAILDEV_INCOMING_PASS: smtp
|
||||||
|
|
||||||
livekit:
|
|
||||||
image: ghcr.io/stoatchat/livekit-server:v1.9.9
|
|
||||||
command: --config /etc/livekit.yml
|
|
||||||
network_mode: "host"
|
|
||||||
volumes:
|
|
||||||
- ./livekit.yml:/etc/livekit.yml
|
|
||||||
@@ -41,7 +41,7 @@ authifier = { version = "1.0.15" }
|
|||||||
revolt-result = { path = "../core/result" }
|
revolt-result = { path = "../core/result" }
|
||||||
revolt-models = { path = "../core/models" }
|
revolt-models = { path = "../core/models" }
|
||||||
revolt-config = { path = "../core/config" }
|
revolt-config = { path = "../core/config" }
|
||||||
revolt-database = { path = "../core/database", features = ["voice"] }
|
revolt-database = { path = "../core/database" }
|
||||||
revolt-permissions = { version = "0.8.9", path = "../core/permissions" }
|
revolt-permissions = { version = "0.8.9", path = "../core/permissions" }
|
||||||
revolt-presence = { path = "../core/presence", features = ["redis-is-patched"] }
|
revolt-presence = { path = "../core/presence", features = ["redis-is-patched"] }
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use futures::future::join_all;
|
use futures::future::join_all;
|
||||||
use revolt_database::{
|
use revolt_database::{
|
||||||
@@ -124,7 +124,12 @@ impl State {
|
|||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
// Fetch all memberships with their corresponding servers.
|
// Fetch all memberships with their corresponding servers.
|
||||||
let mut members: Vec<Member> = db.fetch_all_memberships(&user.id).await?;
|
let members: Vec<Member> = db.fetch_all_memberships(&user.id).await?;
|
||||||
|
self.cache.members = members
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.map(|x| (x.id.server.clone(), x))
|
||||||
|
.collect();
|
||||||
|
|
||||||
let server_ids: Vec<String> = members.iter().map(|x| x.id.server.clone()).collect();
|
let server_ids: Vec<String> = members.iter().map(|x| x.id.server.clone()).collect();
|
||||||
let servers = db.fetch_servers(&server_ids).await?;
|
let servers = db.fetch_servers(&server_ids).await?;
|
||||||
@@ -153,51 +158,6 @@ impl State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let voice_states = if fields.voice_states {
|
|
||||||
let mut voice_state_server_members: HashMap<String, HashSet<String>> = HashMap::new();
|
|
||||||
|
|
||||||
// fetch voice states for all the channels we can see
|
|
||||||
let mut voice_states = Vec::new();
|
|
||||||
|
|
||||||
for channel in channels.iter().filter(|c| {
|
|
||||||
matches!(
|
|
||||||
c,
|
|
||||||
Channel::DirectMessage { .. }
|
|
||||||
| Channel::Group { .. }
|
|
||||||
| Channel::TextChannel { voice: Some(_), .. }
|
|
||||||
)
|
|
||||||
}) {
|
|
||||||
if let Ok(Some(voice_state)) = get_channel_voice_state(channel).await {
|
|
||||||
if let Some(server) = channel.server() {
|
|
||||||
let set = voice_state_server_members.entry(server.to_string()).or_default();
|
|
||||||
|
|
||||||
for participant in &voice_state.participants {
|
|
||||||
user_ids.insert(participant.id.clone());
|
|
||||||
set.insert(participant.id.clone());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for participant in &voice_state.participants {
|
|
||||||
user_ids.insert(participant.id.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
voice_states.push(voice_state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch all the members for for the participants who are in a server
|
|
||||||
for (server, user_ids) in voice_state_server_members {
|
|
||||||
let user_ids = user_ids.into_iter().collect::<Vec<_>>();
|
|
||||||
let voice_members = db.fetch_members(&server, &user_ids).await?;
|
|
||||||
|
|
||||||
members.extend(voice_members);
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(voice_states)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
// Fetch presence data for known users.
|
// Fetch presence data for known users.
|
||||||
let online_ids = filter_online(&user_ids.iter().cloned().collect::<Vec<String>>()).await;
|
let online_ids = filter_online(&user_ids.iter().cloned().collect::<Vec<String>>()).await;
|
||||||
|
|
||||||
@@ -211,12 +171,6 @@ impl State {
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
self.cache.members = members
|
|
||||||
.iter()
|
|
||||||
.cloned()
|
|
||||||
.map(|x| (x.id.server.clone(), x))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// Fetch customisations.
|
// Fetch customisations.
|
||||||
let emojis = if fields.emojis {
|
let emojis = if fields.emojis {
|
||||||
Some(
|
Some(
|
||||||
@@ -258,6 +212,28 @@ impl State {
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let voice_states = if fields.voice_states {
|
||||||
|
// fetch voice states for all the channels we can see
|
||||||
|
let mut voice_states = Vec::new();
|
||||||
|
|
||||||
|
for channel in channels.iter().filter(|c| {
|
||||||
|
matches!(
|
||||||
|
c,
|
||||||
|
Channel::DirectMessage { .. }
|
||||||
|
| Channel::Group { .. }
|
||||||
|
| Channel::TextChannel { voice: Some(_), .. }
|
||||||
|
)
|
||||||
|
}) {
|
||||||
|
if let Ok(Some(voice_state)) = get_channel_voice_state(channel).await {
|
||||||
|
voice_states.push(voice_state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(voice_states)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
// Copy data into local state cache.
|
// Copy data into local state cache.
|
||||||
self.cache.users = users.iter().cloned().map(|x| (x.id.clone(), x)).collect();
|
self.cache.users = users.iter().cloned().map(|x| (x.id.clone(), x)).collect();
|
||||||
self.cache
|
self.cache
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
use std::env;
|
use std::{env, sync::Arc};
|
||||||
|
|
||||||
use async_std::net::TcpListener;
|
use async_std::net::TcpListener;
|
||||||
use revolt_presence::clear_region;
|
use revolt_presence::clear_region;
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
|
use revolt_database::voice::VoiceClient;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
@@ -12,6 +14,15 @@ pub mod events;
|
|||||||
mod database;
|
mod database;
|
||||||
mod websocket;
|
mod websocket;
|
||||||
|
|
||||||
|
pub static VOICE_CLIENT: OnceCell<Arc<VoiceClient>> = OnceCell::new();
|
||||||
|
|
||||||
|
pub fn get_voice_client() -> Arc<VoiceClient> {
|
||||||
|
VOICE_CLIENT
|
||||||
|
.get()
|
||||||
|
.expect("get_voice_client called before set")
|
||||||
|
.clone()
|
||||||
|
}
|
||||||
|
|
||||||
#[async_std::main]
|
#[async_std::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
// Configure requirements for Bonfire.
|
// Configure requirements for Bonfire.
|
||||||
@@ -24,6 +35,8 @@ async fn main() {
|
|||||||
clear_region(None).await;
|
clear_region(None).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VOICE_CLIENT.set(Arc::new(VoiceClient::from_revolt_config().await)).unwrap();
|
||||||
|
|
||||||
// Setup a TCP listener to accept WebSocket connections on.
|
// Setup a TCP listener to accept WebSocket connections on.
|
||||||
// By default, we bind to port 14703 on all interfaces.
|
// By default, we bind to port 14703 on all interfaces.
|
||||||
let bind = env::var("HOST").unwrap_or_else(|_| "0.0.0.0:14703".into());
|
let bind = env::var("HOST").unwrap_or_else(|_| "0.0.0.0:14703".into());
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ use crate::{CoalescionServiceConfig, Error};
|
|||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
/// Coalescion service
|
/// # Coalescion service
|
||||||
///
|
///
|
||||||
/// See module description for example usage.
|
/// See module description for example usage.
|
||||||
pub struct CoalescionService<Id: Hash + Clone + Eq> {
|
pub struct CoalescionService<Id: Hash + Clone + Eq> {
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ message_queue = "notifications.origin.message"
|
|||||||
mass_mention_queue = "notifications.origin.mass_mention" # handles messages that contain role or everyone mentions
|
mass_mention_queue = "notifications.origin.mass_mention" # handles messages that contain role or everyone mentions
|
||||||
fr_accepted_queue = "notifications.ingest.fr_accepted" # friend request accepted
|
fr_accepted_queue = "notifications.ingest.fr_accepted" # friend request accepted
|
||||||
fr_received_queue = "notifications.ingest.fr_received" # friend request received
|
fr_received_queue = "notifications.ingest.fr_received" # friend request received
|
||||||
dm_call_queue = "notifications.ingest.dm_call" # direct message voice call
|
dm_call_queue = "notifications.ingest.dm_call" # friend request received
|
||||||
generic_queue = "notifications.ingest.generic" # generic messages (title + body)
|
generic_queue = "notifications.ingest.generic" # generic messages (title + body)
|
||||||
ack_queue = "notifications.process.ack" # updates badges for apple devices
|
ack_queue = "notifications.process.ack" # updates badges for apple devices
|
||||||
|
|
||||||
|
|||||||
@@ -15,10 +15,9 @@ mongodb = ["dep:mongodb", "bson", "authifier/database-mongodb"]
|
|||||||
# ... Other
|
# ... Other
|
||||||
tasks = ["isahc", "linkify", "url-escape"]
|
tasks = ["isahc", "linkify", "url-escape"]
|
||||||
async-std-runtime = ["async-std", "authifier/async-std-runtime"]
|
async-std-runtime = ["async-std", "authifier/async-std-runtime"]
|
||||||
rocket-impl = ["rocket"]
|
rocket-impl = ["rocket", "schemars", "revolt_okapi", "revolt_rocket_okapi", "authifier/rocket_impl"]
|
||||||
axum-impl = ["axum"]
|
axum-impl = ["axum"]
|
||||||
redis-is-patched = ["revolt-presence/redis-is-patched"]
|
redis-is-patched = ["revolt-presence/redis-is-patched"]
|
||||||
voice = ["livekit-api", "livekit-protocol", "livekit-runtime"]
|
|
||||||
|
|
||||||
# Default Features
|
# Default Features
|
||||||
default = ["mongodb", "async-std-runtime", "tasks"]
|
default = ["mongodb", "async-std-runtime", "tasks"]
|
||||||
@@ -47,7 +46,7 @@ ulid = "1.0.0"
|
|||||||
nanoid = "0.4.0"
|
nanoid = "0.4.0"
|
||||||
base64 = "0.21.3"
|
base64 = "0.21.3"
|
||||||
once_cell = "1.17"
|
once_cell = "1.17"
|
||||||
indexmap = "2.12.0"
|
indexmap = "1.9.1"
|
||||||
decancer = "1.6.2"
|
decancer = "1.6.2"
|
||||||
deadqueue = "0.2.4"
|
deadqueue = "0.2.4"
|
||||||
linkify = { optional = true, version = "0.8.1" }
|
linkify = { optional = true, version = "0.8.1" }
|
||||||
@@ -59,7 +58,7 @@ isahc = { optional = true, version = "1.7", features = ["json"] }
|
|||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
revolt_optional_struct = "0.2.0"
|
revolt_optional_struct = "0.2.0"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
iso8601-timestamp = { version = "0.4.0", features = ["serde", "bson"] }
|
iso8601-timestamp = { version = "0.2.10", features = ["serde", "bson"] }
|
||||||
|
|
||||||
# Events
|
# Events
|
||||||
redis-kiss = { version = "0.1.4" }
|
redis-kiss = { version = "0.1.4" }
|
||||||
@@ -82,15 +81,15 @@ async-recursion = "1.0.4"
|
|||||||
async-std = { version = "1.8.0", features = ["attributes"], optional = true }
|
async-std = { version = "1.8.0", features = ["attributes"], optional = true }
|
||||||
|
|
||||||
# Axum Impl
|
# Axum Impl
|
||||||
axum = { version = "0.8.6", optional = true }
|
axum = { version = "0.7.5", optional = true }
|
||||||
|
|
||||||
# Rocket Impl
|
# Rocket Impl
|
||||||
|
schemars = { version = "0.8.8", optional = true }
|
||||||
rocket = { version = "0.5.1", default-features = false, features = [
|
rocket = { version = "0.5.1", default-features = false, features = [
|
||||||
"json",
|
"json",
|
||||||
], optional = true }
|
], optional = true }
|
||||||
|
revolt_okapi = { version = "0.9.1", optional = true }
|
||||||
# Openapi Schema
|
revolt_rocket_okapi = { version = "0.10.0", optional = true }
|
||||||
utoipa = { version = "5.4.0", optional = true }
|
|
||||||
|
|
||||||
# Authifier
|
# Authifier
|
||||||
authifier = { version = "1.0.15" }
|
authifier = { version = "1.0.15" }
|
||||||
@@ -99,6 +98,6 @@ authifier = { version = "1.0.15" }
|
|||||||
amqprs = { version = "1.7.0" }
|
amqprs = { version = "1.7.0" }
|
||||||
|
|
||||||
# Voice
|
# Voice
|
||||||
livekit-api = { version = "=0.4.4", optional = true}
|
livekit-api = "0.4.4"
|
||||||
livekit-protocol = { version = "=0.4.0", optional = true }
|
livekit-protocol = "0.4.0"
|
||||||
livekit-runtime = { version = "=0.3.1", features = ["tokio"], optional = true }
|
livekit-runtime = { version = "0.3.1", features = ["tokio"] }
|
||||||
|
|||||||
@@ -295,9 +295,7 @@ pub enum EventV1 {
|
|||||||
},
|
},
|
||||||
UserMoveVoiceChannel {
|
UserMoveVoiceChannel {
|
||||||
node: String,
|
node: String,
|
||||||
from: String,
|
token: String
|
||||||
to: String,
|
|
||||||
token: String,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,10 +25,6 @@ pub use mongodb;
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate bson;
|
extern crate bson;
|
||||||
|
|
||||||
#[cfg(feature = "utoipa")]
|
|
||||||
#[macro_use]
|
|
||||||
extern crate utoipa;
|
|
||||||
|
|
||||||
#[cfg(not(feature = "async-std-runtime"))]
|
#[cfg(not(feature = "async-std-runtime"))]
|
||||||
compile_error!("async-std-runtime feature must be enabled.");
|
compile_error!("async-std-runtime feature must be enabled.");
|
||||||
|
|
||||||
@@ -64,7 +60,7 @@ macro_rules! database_derived {
|
|||||||
macro_rules! auto_derived {
|
macro_rules! auto_derived {
|
||||||
( $( $item:item )+ ) => {
|
( $( $item:item )+ ) => {
|
||||||
$(
|
$(
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
|
||||||
$item
|
$item
|
||||||
)+
|
)+
|
||||||
};
|
};
|
||||||
@@ -72,8 +68,8 @@ macro_rules! auto_derived {
|
|||||||
|
|
||||||
macro_rules! auto_derived_partial {
|
macro_rules! auto_derived_partial {
|
||||||
( $item:item, $name:expr ) => {
|
( $item:item, $name:expr ) => {
|
||||||
#[derive(OptionalStruct, Serialize, Deserialize, Debug, Clone, PartialEq)]
|
#[derive(OptionalStruct, Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
|
||||||
#[optional_derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq)]
|
#[optional_derive(Serialize, Deserialize, Debug, Clone, Default, Eq, PartialEq)]
|
||||||
#[optional_name = $name]
|
#[optional_name = $name]
|
||||||
#[opt_skip_serializing_none]
|
#[opt_skip_serializing_none]
|
||||||
#[opt_some_priority]
|
#[opt_some_priority]
|
||||||
@@ -116,7 +112,6 @@ pub mod tasks;
|
|||||||
mod amqp;
|
mod amqp;
|
||||||
pub use amqp::amqp::AMQP;
|
pub use amqp::amqp::AMQP;
|
||||||
|
|
||||||
#[cfg(feature = "voice")]
|
|
||||||
pub mod voice;
|
pub mod voice;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ auto_derived!(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Composite primary key consisting of channel and user id
|
/// Composite primary key consisting of channel and user id
|
||||||
#[derive(Hash, Eq)]
|
#[derive(Hash)]
|
||||||
pub struct ChannelCompositeKey {
|
pub struct ChannelCompositeKey {
|
||||||
/// Channel Id
|
/// Channel Id
|
||||||
pub channel: String,
|
pub channel: String,
|
||||||
|
|||||||
@@ -443,8 +443,7 @@ impl Message {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify replies are valid.
|
// Verify replies are valid.
|
||||||
let mut replies = Vec::new();
|
let mut replies = HashSet::new();
|
||||||
|
|
||||||
if let Some(entries) = data.replies {
|
if let Some(entries) = data.replies {
|
||||||
if entries.len() > config.features.limits.global.message_replies {
|
if entries.len() > config.features.limits.global.message_replies {
|
||||||
return Err(create_error!(TooManyReplies {
|
return Err(create_error!(TooManyReplies {
|
||||||
@@ -452,8 +451,6 @@ impl Message {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
replies.reserve(entries.len());
|
|
||||||
|
|
||||||
for ReplyIntent {
|
for ReplyIntent {
|
||||||
id,
|
id,
|
||||||
mention,
|
mention,
|
||||||
@@ -467,12 +464,7 @@ impl Message {
|
|||||||
user_mentions.insert(message.author.to_owned());
|
user_mentions.insert(message.author.to_owned());
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is O(n^2), but this is faster than a HashSet
|
replies.insert(message.id);
|
||||||
// when n < 20; as long as the message_replies limit
|
|
||||||
// is reasonable, this will be fast.
|
|
||||||
if !replies.contains(&message.id) {
|
|
||||||
replies.push(message.id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// If the referenced message doesn't exist and fail_if_not_exists
|
// If the referenced message doesn't exist and fail_if_not_exists
|
||||||
// is set to false, send the message without the reply.
|
// is set to false, send the message without the reply.
|
||||||
@@ -545,7 +537,9 @@ impl Message {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !replies.is_empty() {
|
if !replies.is_empty() {
|
||||||
message.replies.replace(replies);
|
message
|
||||||
|
.replies
|
||||||
|
.replace(replies.into_iter().collect::<Vec<String>>());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate final message flags
|
// Calculate final message flags
|
||||||
|
|||||||
@@ -247,7 +247,7 @@ impl AbstractMessages for ReferenceDb {
|
|||||||
let mut messages = self.messages.lock().await;
|
let mut messages = self.messages.lock().await;
|
||||||
if let Some(message) = messages.get_mut(id) {
|
if let Some(message) = messages.get_mut(id) {
|
||||||
if let Some(users) = message.reactions.get_mut(emoji) {
|
if let Some(users) = message.reactions.get_mut(emoji) {
|
||||||
users.swap_remove(&user.to_string());
|
users.remove(&user.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -260,7 +260,7 @@ impl AbstractMessages for ReferenceDb {
|
|||||||
async fn clear_reaction(&self, id: &str, emoji: &str) -> Result<()> {
|
async fn clear_reaction(&self, id: &str, emoji: &str) -> Result<()> {
|
||||||
let mut messages = self.messages.lock().await;
|
let mut messages = self.messages.lock().await;
|
||||||
if let Some(message) = messages.get_mut(id) {
|
if let Some(message) = messages.get_mut(id) {
|
||||||
message.reactions.swap_remove(emoji);
|
message.reactions.remove(emoji);
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(create_error!(NotFound))
|
Err(create_error!(NotFound))
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use iso8601_timestamp::Timestamp;
|
use iso8601_timestamp::Timestamp;
|
||||||
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
|
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
|
||||||
use revolt_result::{create_error, Result};
|
use revolt_result::{create_error, Result};
|
||||||
|
use crate::voice::get_channel_voice_state;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
events::client::EventV1, util::permissions::DatabasePermissionQuery, Channel,
|
events::client::EventV1, util::permissions::DatabasePermissionQuery, Channel,
|
||||||
@@ -55,7 +56,7 @@ auto_derived_partial!(
|
|||||||
|
|
||||||
auto_derived!(
|
auto_derived!(
|
||||||
/// Composite primary key consisting of server and user id
|
/// Composite primary key consisting of server and user id
|
||||||
#[derive(Hash, Default, Eq)]
|
#[derive(Hash, Default)]
|
||||||
pub struct MemberCompositeKey {
|
pub struct MemberCompositeKey {
|
||||||
/// Server Id
|
/// Server Id
|
||||||
pub server: String,
|
pub server: String,
|
||||||
@@ -147,12 +148,10 @@ impl Member {
|
|||||||
|
|
||||||
let emojis = db.fetch_emoji_by_parent_id(&server.id).await?;
|
let emojis = db.fetch_emoji_by_parent_id(&server.id).await?;
|
||||||
|
|
||||||
#[allow(unused_mut)]
|
|
||||||
let mut voice_states = Vec::new();
|
let mut voice_states = Vec::new();
|
||||||
|
|
||||||
#[cfg(feature = "voice")]
|
|
||||||
for channel in &channels {
|
for channel in &channels {
|
||||||
if let Ok(Some(voice_state)) = crate::voice::get_channel_voice_state(channel).await {
|
if let Ok(Some(voice_state)) = get_channel_voice_state(channel).await {
|
||||||
voice_states.push(voice_state)
|
voice_states.push(voice_state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -336,8 +336,8 @@ impl IntoDocumentPath for FieldsMember {
|
|||||||
FieldsMember::Nickname => "nickname",
|
FieldsMember::Nickname => "nickname",
|
||||||
FieldsMember::Roles => "roles",
|
FieldsMember::Roles => "roles",
|
||||||
FieldsMember::Timeout => "timeout",
|
FieldsMember::Timeout => "timeout",
|
||||||
FieldsMember::CanPublish => "can_publish",
|
FieldsMember::CanPublish => "is_publishing",
|
||||||
FieldsMember::CanReceive => "can_receive",
|
FieldsMember::CanReceive => "is_receiving",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use revolt_result::{create_error, Error, Result};
|
|||||||
|
|
||||||
use crate::{Database, User};
|
use crate::{Database, User};
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
impl<S> FromRequestParts<S> for User
|
impl<S> FromRequestParts<S> for User
|
||||||
where
|
where
|
||||||
Database: FromRef<S>,
|
Database: FromRef<S>,
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ mod model;
|
|||||||
mod ops;
|
mod ops;
|
||||||
#[cfg(feature = "rocket-impl")]
|
#[cfg(feature = "rocket-impl")]
|
||||||
mod rocket;
|
mod rocket;
|
||||||
|
#[cfg(feature = "rocket-impl")]
|
||||||
|
mod schema;
|
||||||
|
|
||||||
pub use model::*;
|
pub use model::*;
|
||||||
pub use ops::*;
|
pub use ops::*;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ use serde_json::json;
|
|||||||
use ulid::Ulid;
|
use ulid::Ulid;
|
||||||
|
|
||||||
auto_derived_partial!(
|
auto_derived_partial!(
|
||||||
/// User
|
/// # User
|
||||||
pub struct User {
|
pub struct User {
|
||||||
/// Unique Id
|
/// Unique Id
|
||||||
#[serde(rename = "_id")]
|
#[serde(rename = "_id")]
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
use revolt_okapi::openapi3::{SecurityScheme, SecuritySchemeData};
|
||||||
|
use revolt_rocket_okapi::{
|
||||||
|
gen::OpenApiGenerator,
|
||||||
|
request::{OpenApiFromRequest, RequestHeaderInput},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::User;
|
||||||
|
|
||||||
|
impl OpenApiFromRequest<'_> for User {
|
||||||
|
fn from_request_input(
|
||||||
|
_gen: &mut OpenApiGenerator,
|
||||||
|
_name: String,
|
||||||
|
_required: bool,
|
||||||
|
) -> revolt_rocket_okapi::Result<RequestHeaderInput> {
|
||||||
|
let mut requirements = schemars::Map::new();
|
||||||
|
requirements.insert("Session Token".to_owned(), vec![]);
|
||||||
|
|
||||||
|
Ok(RequestHeaderInput::Security(
|
||||||
|
"Session Token".to_owned(),
|
||||||
|
SecurityScheme {
|
||||||
|
data: SecuritySchemeData::ApiKey {
|
||||||
|
name: "x-session-token".to_owned(),
|
||||||
|
location: "header".to_owned(),
|
||||||
|
},
|
||||||
|
description: Some("Used to authenticate as a user.".to_owned()),
|
||||||
|
extensions: schemars::Map::new(),
|
||||||
|
},
|
||||||
|
requirements,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,7 +17,7 @@ use super::DelayedTask;
|
|||||||
use crate::Channel::TextChannel;
|
use crate::Channel::TextChannel;
|
||||||
|
|
||||||
/// Enumeration of possible events
|
/// Enumeration of possible events
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
pub enum AckEvent {
|
pub enum AckEvent {
|
||||||
/// Add mentions for a channel
|
/// Add mentions for a channel
|
||||||
ProcessMessage {
|
ProcessMessage {
|
||||||
|
|||||||
@@ -10,16 +10,16 @@ use once_cell::sync::Lazy;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
#[cfg_attr(feature = "utoipa", derive(IntoParams))]
|
pub struct IdempotencyKey {
|
||||||
#[cfg_attr(feature = "utoipa", into_params(names("Idempotency-Key"), parameter_in=Header))]
|
key: String,
|
||||||
pub struct IdempotencyKey(String);
|
}
|
||||||
|
|
||||||
static TOKEN_CACHE: Lazy<Mutex<lru::LruCache<String, ()>>> =
|
static TOKEN_CACHE: Lazy<Mutex<lru::LruCache<String, ()>>> =
|
||||||
Lazy::new(|| Mutex::new(lru::LruCache::new(NonZeroUsize::new(1000).unwrap())));
|
Lazy::new(|| Mutex::new(lru::LruCache::new(NonZeroUsize::new(1000).unwrap())));
|
||||||
|
|
||||||
impl IdempotencyKey {
|
impl IdempotencyKey {
|
||||||
pub fn unchecked_from_string(key: String) -> Self {
|
pub fn unchecked_from_string(key: String) -> Self {
|
||||||
Self(key)
|
Self { key }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Backwards compatibility.
|
// Backwards compatibility.
|
||||||
@@ -32,56 +32,56 @@ impl IdempotencyKey {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cache.put(v.clone(), ());
|
cache.put(v.clone(), ());
|
||||||
self.0 = v;
|
self.key = v;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn into_key(self) -> String {
|
pub fn into_key(self) -> String {
|
||||||
self.0
|
self.key
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[cfg(feature = "rocket-impl")]
|
#[cfg(feature = "rocket-impl")]
|
||||||
// use revolt_rocket_okapi::{
|
use revolt_rocket_okapi::{
|
||||||
// gen::OpenApiGenerator,
|
gen::OpenApiGenerator,
|
||||||
// request::{OpenApiFromRequest, RequestHeaderInput},
|
request::{OpenApiFromRequest, RequestHeaderInput},
|
||||||
// revolt_okapi::openapi3::{Parameter, ParameterValue},
|
revolt_okapi::openapi3::{Parameter, ParameterValue},
|
||||||
// };
|
};
|
||||||
|
|
||||||
// #[cfg(feature = "rocket-impl")]
|
#[cfg(feature = "rocket-impl")]
|
||||||
// use schemars::schema::{InstanceType, SchemaObject, SingleOrVec};
|
use schemars::schema::{InstanceType, SchemaObject, SingleOrVec};
|
||||||
|
|
||||||
// #[cfg(feature = "rocket-impl")]
|
#[cfg(feature = "rocket-impl")]
|
||||||
// impl OpenApiFromRequest<'_> for IdempotencyKey {
|
impl OpenApiFromRequest<'_> for IdempotencyKey {
|
||||||
// fn from_request_input(
|
fn from_request_input(
|
||||||
// _gen: &mut OpenApiGenerator,
|
_gen: &mut OpenApiGenerator,
|
||||||
// _name: String,
|
_name: String,
|
||||||
// _required: bool,
|
_required: bool,
|
||||||
// ) -> revolt_rocket_okapi::Result<RequestHeaderInput> {
|
) -> revolt_rocket_okapi::Result<RequestHeaderInput> {
|
||||||
// Ok(RequestHeaderInput::Parameter(Parameter {
|
Ok(RequestHeaderInput::Parameter(Parameter {
|
||||||
// name: "Idempotency-Key".to_string(),
|
name: "Idempotency-Key".to_string(),
|
||||||
// description: Some("Unique key to prevent duplicate requests".to_string()),
|
description: Some("Unique key to prevent duplicate requests".to_string()),
|
||||||
// allow_empty_value: false,
|
allow_empty_value: false,
|
||||||
// required: false,
|
required: false,
|
||||||
// deprecated: false,
|
deprecated: false,
|
||||||
// extensions: schemars::Map::new(),
|
extensions: schemars::Map::new(),
|
||||||
// location: "header".to_string(),
|
location: "header".to_string(),
|
||||||
// value: ParameterValue::Schema {
|
value: ParameterValue::Schema {
|
||||||
// allow_reserved: false,
|
allow_reserved: false,
|
||||||
// example: None,
|
example: None,
|
||||||
// examples: None,
|
examples: None,
|
||||||
// explode: None,
|
explode: None,
|
||||||
// style: None,
|
style: None,
|
||||||
// schema: SchemaObject {
|
schema: SchemaObject {
|
||||||
// instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))),
|
instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))),
|
||||||
// ..Default::default()
|
..Default::default()
|
||||||
// },
|
},
|
||||||
// },
|
},
|
||||||
// }))
|
}))
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
#[cfg(feature = "rocket-impl")]
|
#[cfg(feature = "rocket-impl")]
|
||||||
use rocket::{
|
use rocket::{
|
||||||
@@ -110,16 +110,18 @@ impl<'r> FromRequest<'r> for IdempotencyKey {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let idempotency = IdempotencyKey(key);
|
let idempotency = IdempotencyKey { key };
|
||||||
let mut cache = TOKEN_CACHE.lock().await;
|
let mut cache = TOKEN_CACHE.lock().await;
|
||||||
if cache.get(&idempotency.0).is_some() {
|
if cache.get(&idempotency.key).is_some() {
|
||||||
return Outcome::Error((Status::Conflict, create_error!(DuplicateNonce)));
|
return Outcome::Error((Status::Conflict, create_error!(DuplicateNonce)));
|
||||||
}
|
}
|
||||||
|
|
||||||
cache.put(idempotency.0.clone(), ());
|
cache.put(idempotency.key.clone(), ());
|
||||||
return Outcome::Success(idempotency);
|
return Outcome::Success(idempotency);
|
||||||
}
|
}
|
||||||
|
|
||||||
Outcome::Success(IdempotencyKey(ulid::Ulid::new().to_string()))
|
Outcome::Success(IdempotencyKey {
|
||||||
|
key: ulid::Ulid::new().to_string(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,5 @@ pub mod idempotency;
|
|||||||
pub mod permissions;
|
pub mod permissions;
|
||||||
pub mod reference;
|
pub mod reference;
|
||||||
pub mod test_fixtures;
|
pub mod test_fixtures;
|
||||||
#[cfg(feature = "utoipa")]
|
|
||||||
pub mod utoipa;
|
|
||||||
|
|
||||||
pub use funcs::*;
|
pub use funcs::*;
|
||||||
|
|||||||
@@ -3,6 +3,11 @@ use std::str::FromStr;
|
|||||||
use revolt_result::Result;
|
use revolt_result::Result;
|
||||||
#[cfg(feature = "rocket-impl")]
|
#[cfg(feature = "rocket-impl")]
|
||||||
use rocket::request::FromParam;
|
use rocket::request::FromParam;
|
||||||
|
#[cfg(feature = "rocket-impl")]
|
||||||
|
use schemars::{
|
||||||
|
schema::{InstanceType, Schema, SchemaObject, SingleOrVec},
|
||||||
|
JsonSchema,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Bot, Channel, Database, Emoji, Invite, Member, Message, Server, ServerBan, User, Webhook,
|
Bot, Channel, Database, Emoji, Invite, Member, Message, Server, ServerBan, User, Webhook,
|
||||||
@@ -107,3 +112,17 @@ impl<'r> FromParam<'r> for Reference<'r> {
|
|||||||
Ok(Reference::from_unchecked(param))
|
Ok(Reference::from_unchecked(param))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "rocket-impl")]
|
||||||
|
impl<'a> JsonSchema for Reference<'a> {
|
||||||
|
fn schema_name() -> String {
|
||||||
|
"Id".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> Schema {
|
||||||
|
Schema::Object(SchemaObject {
|
||||||
|
instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
use utoipa::{
|
|
||||||
openapi::{
|
|
||||||
schema::SchemaType,
|
|
||||||
security::{ApiKey, ApiKeyValue, SecurityScheme},
|
|
||||||
ObjectBuilder, OpenApi, RefOr, Schema, Type,
|
|
||||||
},
|
|
||||||
Modify, PartialSchema, ToSchema,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::util::reference::Reference;
|
|
||||||
|
|
||||||
pub struct TokenSecurity;
|
|
||||||
|
|
||||||
impl Modify for TokenSecurity {
|
|
||||||
fn modify(&self, openapi: &mut OpenApi) {
|
|
||||||
let components = openapi.components.get_or_insert_default();
|
|
||||||
|
|
||||||
components.add_security_scheme(
|
|
||||||
"Session-Token",
|
|
||||||
SecurityScheme::ApiKey(ApiKey::Header(ApiKeyValue::new(
|
|
||||||
"X-Session-Token".to_string(),
|
|
||||||
))),
|
|
||||||
);
|
|
||||||
|
|
||||||
components.add_security_scheme(
|
|
||||||
"Bot-Token",
|
|
||||||
SecurityScheme::ApiKey(ApiKey::Header(ApiKeyValue::new("X-Bot-Ticket".to_string()))),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToSchema for Reference<'_> {
|
|
||||||
fn name() -> std::borrow::Cow<'static, str> {
|
|
||||||
std::borrow::Cow::Borrowed("Reference")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialSchema for Reference<'_> {
|
|
||||||
fn schema() -> RefOr<Schema> {
|
|
||||||
RefOr::T(
|
|
||||||
ObjectBuilder::new()
|
|
||||||
.description(Some("An id referencing a stoat model."))
|
|
||||||
.schema_type(SchemaType::Type(Type::String))
|
|
||||||
.examples(["01FD58YK5W7QRV5H3D64KTQYX3"])
|
|
||||||
.build()
|
|
||||||
.into(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -16,7 +16,7 @@ mod voice_client;
|
|||||||
pub use voice_client::VoiceClient;
|
pub use voice_client::VoiceClient;
|
||||||
|
|
||||||
async fn get_connection() -> Result<Conn> {
|
async fn get_connection() -> Result<Conn> {
|
||||||
_get_connection().await.map_err(|_| create_error!(InternalError))
|
_get_connection().await.to_internal_error()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn raise_if_in_voice(user: &User, channel_id: &str) -> Result<()> {
|
pub async fn raise_if_in_voice(user: &User, channel_id: &str) -> Result<()> {
|
||||||
@@ -564,35 +564,3 @@ pub async fn get_call_notification_recipients(
|
|||||||
.await
|
.await
|
||||||
.to_internal_error()
|
.to_internal_error()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn remove_user_from_voice_channels(db: &Database, voice_client: &VoiceClient, user_id: &str) -> Result<()> {
|
|
||||||
for channel_id in get_user_voice_channels(user_id).await? {
|
|
||||||
remove_user_from_voice_channel(db, voice_client, &channel_id, user_id).await?;
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn remove_user_from_voice_channel(db: &Database, voice_client: &VoiceClient, channel_id: &str, user_id: &str) -> Result<()> {
|
|
||||||
if let Some(node) = get_channel_node(channel_id).await? {
|
|
||||||
let _ = voice_client.remove_user(&node, user_id, channel_id).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
let channel = Reference::from_unchecked(channel_id).as_channel(db).await?;
|
|
||||||
|
|
||||||
delete_voice_state(channel_id, channel.server(), user_id).await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn delete_voice_channel(voice_client: &VoiceClient, channel_id: &str, server_id: Option<&str>) -> Result<()> {
|
|
||||||
if let Some(users) = get_voice_channel_members(channel_id).await? {
|
|
||||||
let node = get_channel_node(channel_id).await?.unwrap();
|
|
||||||
|
|
||||||
voice_client.delete_room(&node, channel_id).await?;
|
|
||||||
|
|
||||||
delete_channel_voice_state(channel_id, server_id, &users).await?;
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<!-- Compiled with Bootstrap Email version: 1.6.0 --><meta http-equiv="x-ua-compatible" content="ie=edge">
|
<!-- Compiled with Bootstrap Email version: 1.5.1 --><meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||||
<meta name="x-apple-disable-message-reformatting">
|
<meta name="x-apple-disable-message-reformatting">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<meta name="format-detection" content="telephone=no, date=no, address=no, email=no">
|
<meta name="format-detection" content="telephone=no, date=no, address=no, email=no">
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td style="line-height: 24px; font-size: 16px; margin: 0;" align="left">
|
<td style="line-height: 24px; font-size: 16px; margin: 0;" align="left">
|
||||||
<img alt="Stoat Logo" class="w-24" src="https://stoat.chat/favicon.svg" style="height: auto; line-height: 100%; outline: none; text-decoration: none; display: block; width: 96px; border-style: none; border-width: 0;" width="96">
|
<img alt="Revolt Logo" class="w-24" src="https://app.revolt.chat/assets/logo_round.png" style="height: auto; line-height: 100%; outline: none; text-decoration: none; display: block; width: 96px; border-style: none; border-width: 0;" width="96">
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -107,7 +107,7 @@
|
|||||||
</table>
|
</table>
|
||||||
<div class="text-muted text-center" style="color: #718096;" align="center">
|
<div class="text-muted text-center" style="color: #718096;" align="center">
|
||||||
This email is intended for {{email}}<br>
|
This email is intended for {{email}}<br>
|
||||||
Sent from Stoat<br>
|
Sent from Revolt<br>
|
||||||
Made in Europe
|
Made in Europe
|
||||||
</div>
|
</div>
|
||||||
<table class="s-6 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
|
<table class="s-6 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
|
||||||
|
|||||||
@@ -1,37 +1,38 @@
|
|||||||
<html>
|
<html>
|
||||||
|
<head>
|
||||||
<head>
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
<style>
|
||||||
<style>
|
/* Add custom classes and styles that you want inlined here */
|
||||||
/* Add custom classes and styles that you want inlined here */
|
</style>
|
||||||
</style>
|
</head>
|
||||||
</head>
|
<body class="bg-light">
|
||||||
|
<div class="container">
|
||||||
<body class="bg-light">
|
<img
|
||||||
<div class="container">
|
alt="Revolt Logo"
|
||||||
<img alt="Stoat Logo" class="ax-center my-10 w-24" src="https://stoat.chat/favicon.svg" />
|
class="ax-center my-10 w-24"
|
||||||
<div class="card p-6 p-lg-10 space-y-4">
|
src="https://app.revolt.chat/assets/logo_round.png"
|
||||||
<h1 class="h3 fw-700">Account Deletion</h1>
|
/>
|
||||||
<p>
|
<div class="card p-6 p-lg-10 space-y-4">
|
||||||
You requested to have your account deleted, if you did not perform
|
<h1 class="h3 fw-700">Account Deletion</h1>
|
||||||
this action please take measures to secure your account immediately.
|
<p>
|
||||||
</p>
|
You requested to have your account deleted, if you did not perform
|
||||||
<a class="btn btn-primary p-3 fw-700" href="{{url}}">Confirm</a>
|
this action please take measures to secure your account immediately.
|
||||||
|
</p>
|
||||||
|
<a class="btn btn-primary p-3 fw-700" href="{{url}}">Confirm</a>
|
||||||
|
</div>
|
||||||
|
<div class="text-muted text-center my-6">
|
||||||
|
This email is intended for {{email}}<br />
|
||||||
|
Sent from Revolt<br />
|
||||||
|
Made in Europe
|
||||||
|
</div>
|
||||||
|
<div class="text-muted text-xs my-6 ax-center">
|
||||||
|
Revolt Platforms Ltd. is a company incorporated and registered under the
|
||||||
|
laws of England and Wales.<br />
|
||||||
|
Registered Company Number: 16260658<br />
|
||||||
|
Registered Office:<br />
|
||||||
|
Suite 5703 Unit 3A, 34-35 Hatton Garden,<br />
|
||||||
|
Holborn, United Kingdom, EC1N 8DX
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-muted text-center my-6">
|
</body>
|
||||||
This email is intended for {{email}}<br />
|
</html>
|
||||||
Sent from Stoat<br />
|
|
||||||
Made in Europe
|
|
||||||
</div>
|
|
||||||
<div class="text-muted text-xs my-6 ax-center">
|
|
||||||
Revolt Platforms Ltd. is a company incorporated and registered under the
|
|
||||||
laws of England and Wales.<br />
|
|
||||||
Registered Company Number: 16260658<br />
|
|
||||||
Registered Office:<br />
|
|
||||||
Suite 5703 Unit 3A, 34-35 Hatton Garden,<br />
|
|
||||||
Holborn, United Kingdom, EC1N 8DX
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ You requested to have your account deleted, if you did not perform this action p
|
|||||||
Please navigate to: {{url}}
|
Please navigate to: {{url}}
|
||||||
|
|
||||||
This email is intended for {{email}}
|
This email is intended for {{email}}
|
||||||
Sent by Stoat
|
Sent by Revolt
|
||||||
Made in Europe
|
Made in Europe
|
||||||
|
|
||||||
Revolt Platforms Ltd. is a company incorporated and registered under the laws of England and Wales.
|
Revolt Platforms Ltd. is a company incorporated and registered under the laws of England and Wales.
|
||||||
|
|||||||
@@ -4,6 +4,6 @@ Please navigate to: {{url}}
|
|||||||
|
|
||||||
This email is intended for {{email}}
|
This email is intended for {{email}}
|
||||||
|
|
||||||
This email has no association with Stoat or Revolt Platforms Ltd.
|
This email has no association with Revolt or Revolt Platforms Ltd.
|
||||||
Learn more about third party instances here:
|
Learn more about third party instances here:
|
||||||
https://developers.stoat.chat/faq.html
|
https://developers.revolt.chat/faq.html
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<!-- Compiled with Bootstrap Email version: 1.6.0 --><meta http-equiv="x-ua-compatible" content="ie=edge">
|
<!-- Compiled with Bootstrap Email version: 1.5.1 --><meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||||
<meta name="x-apple-disable-message-reformatting">
|
<meta name="x-apple-disable-message-reformatting">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<meta name="format-detection" content="telephone=no, date=no, address=no, email=no">
|
<meta name="format-detection" content="telephone=no, date=no, address=no, email=no">
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td style="line-height: 24px; font-size: 16px; margin: 0;" align="left">
|
<td style="line-height: 24px; font-size: 16px; margin: 0;" align="left">
|
||||||
<img class="w-24" src="https://stoat.chat/favicon.svg" alt="Stoat Logo" style="height: auto; line-height: 100%; outline: none; text-decoration: none; display: block; width: 96px; border-style: none; border-width: 0;" width="96">
|
<img class="w-24" src="https://app.revolt.chat/assets/logo_round.png" style="height: auto; line-height: 100%; outline: none; text-decoration: none; display: block; width: 96px; border-style: none; border-width: 0;" width="96">
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -120,7 +120,7 @@
|
|||||||
</table>
|
</table>
|
||||||
<div class="text-muted text-center" style="color: #718096;" align="center">
|
<div class="text-muted text-center" style="color: #718096;" align="center">
|
||||||
This email is intended for {{email}}<br>
|
This email is intended for {{email}}<br>
|
||||||
Sent from Stoat<br>
|
Sent from Revolt<br>
|
||||||
Made in Europe
|
Made in Europe
|
||||||
</div>
|
</div>
|
||||||
<table class="s-6 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
|
<table class="s-6 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
|
||||||
|
|||||||
@@ -9,8 +9,7 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<img
|
<img
|
||||||
class="ax-center my-10 w-24"
|
class="ax-center my-10 w-24"
|
||||||
src="https://stoat.chat/favicon.svg"
|
src="https://app.revolt.chat/assets/logo_round.png"
|
||||||
alt="Stoat Logo"
|
|
||||||
/>
|
/>
|
||||||
<div class="card p-6 p-lg-10 space-y-4">
|
<div class="card p-6 p-lg-10 space-y-4">
|
||||||
<h1 class="h3 fw-700">Password Reset</h1>
|
<h1 class="h3 fw-700">Password Reset</h1>
|
||||||
@@ -26,7 +25,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="text-muted text-center my-6">
|
<div class="text-muted text-center my-6">
|
||||||
This email is intended for {{email}}<br />
|
This email is intended for {{email}}<br />
|
||||||
Sent from Stoat<br />
|
Sent from Revolt<br />
|
||||||
Made in Europe
|
Made in Europe
|
||||||
</div>
|
</div>
|
||||||
<div class="text-muted text-xs my-6 ax-center">
|
<div class="text-muted text-xs my-6 ax-center">
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ password on it, click below to continue.
|
|||||||
Please navigate to: {{url}}
|
Please navigate to: {{url}}
|
||||||
|
|
||||||
This email is intended for {{email}}
|
This email is intended for {{email}}
|
||||||
Sent by Stoat
|
Sent by Revolt
|
||||||
Made in Europe
|
Made in Europe
|
||||||
|
|
||||||
Revolt Platforms Ltd. is a company incorporated and registered under the laws of England and Wales.
|
Revolt Platforms Ltd. is a company incorporated and registered under the laws of England and Wales.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<!-- Compiled with Bootstrap Email version: 1.6.0 --><meta http-equiv="x-ua-compatible" content="ie=edge">
|
<!-- Compiled with Bootstrap Email version: 1.5.1 --><meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||||
<meta name="x-apple-disable-message-reformatting">
|
<meta name="x-apple-disable-message-reformatting">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<meta name="format-detection" content="telephone=no, date=no, address=no, email=no">
|
<meta name="format-detection" content="telephone=no, date=no, address=no, email=no">
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td style="line-height: 24px; font-size: 16px; margin: 0;" align="left">
|
<td style="line-height: 24px; font-size: 16px; margin: 0;" align="left">
|
||||||
<img class="w-24" src="https://stoat.chat/favicon.svg" alt="Stoat Logo" style="height: auto; line-height: 100%; outline: none; text-decoration: none; display: block; width: 96px; border-style: none; border-width: 0;" width="96">
|
<img class="w-24" src="https://app.revolt.chat/assets/logo_round.png" style="height: auto; line-height: 100%; outline: none; text-decoration: none; display: block; width: 96px; border-style: none; border-width: 0;" width="96">
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -104,7 +104,7 @@
|
|||||||
</table>
|
</table>
|
||||||
<div class="text-muted text-center" style="color: #718096;" align="center">
|
<div class="text-muted text-center" style="color: #718096;" align="center">
|
||||||
This email is intended for {{email}}<br>
|
This email is intended for {{email}}<br>
|
||||||
Sent from Stoat<br>
|
Sent from Revolt<br>
|
||||||
Made in Europe
|
Made in Europe
|
||||||
</div>
|
</div>
|
||||||
<table class="s-6 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
|
<table class="s-6 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
|
||||||
|
|||||||
@@ -1,34 +1,34 @@
|
|||||||
<html>
|
<html>
|
||||||
|
<head>
|
||||||
<head>
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
<style>
|
||||||
<style>
|
/* Add custom classes and styles that you want inlined here */
|
||||||
/* Add custom classes and styles that you want inlined here */
|
</style>
|
||||||
</style>
|
</head>
|
||||||
</head>
|
<body class="bg-light">
|
||||||
|
<div class="container">
|
||||||
<body class="bg-light">
|
<img
|
||||||
<div class="container">
|
class="ax-center my-10 w-24"
|
||||||
<img class="ax-center my-10 w-24" src="https://stoat.chat/favicon.svg" alt="Stoat Logo" />
|
src="https://app.revolt.chat/assets/logo_round.png"
|
||||||
<div class="card p-6 p-lg-10 space-y-4">
|
/>
|
||||||
<h1 class="h3 fw-700">Password Reset</h1>
|
<div class="card p-6 p-lg-10 space-y-4">
|
||||||
<p>You requested a password reset, click below to continue.</p>
|
<h1 class="h3 fw-700">Password Reset</h1>
|
||||||
<a class="btn btn-primary p-3 fw-700" href="{{url}}">Reset</a>
|
<p>You requested a password reset, click below to continue.</p>
|
||||||
|
<a class="btn btn-primary p-3 fw-700" href="{{url}}">Reset</a>
|
||||||
|
</div>
|
||||||
|
<div class="text-muted text-center my-6">
|
||||||
|
This email is intended for {{email}}<br />
|
||||||
|
Sent from Revolt<br />
|
||||||
|
Made in Europe
|
||||||
|
</div>
|
||||||
|
<div class="text-muted text-xs my-6 ax-center">
|
||||||
|
Revolt Platforms Ltd. is a company incorporated and registered under the
|
||||||
|
laws of England and Wales.<br />
|
||||||
|
Registered Company Number: 16260658<br />
|
||||||
|
Registered Office:<br />
|
||||||
|
Suite 5703 Unit 3A, 34-35 Hatton Garden,<br />
|
||||||
|
Holborn, United Kingdom, EC1N 8DX
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-muted text-center my-6">
|
</body>
|
||||||
This email is intended for {{email}}<br />
|
</html>
|
||||||
Sent from Stoat<br />
|
|
||||||
Made in Europe
|
|
||||||
</div>
|
|
||||||
<div class="text-muted text-xs my-6 ax-center">
|
|
||||||
Revolt Platforms Ltd. is a company incorporated and registered under the
|
|
||||||
laws of England and Wales.<br />
|
|
||||||
Registered Company Number: 16260658<br />
|
|
||||||
Registered Office:<br />
|
|
||||||
Suite 5703 Unit 3A, 34-35 Hatton Garden,<br />
|
|
||||||
Holborn, United Kingdom, EC1N 8DX
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ You requested a password reset, if you did not perform this action you can safel
|
|||||||
Please navigate to: {{url}}
|
Please navigate to: {{url}}
|
||||||
|
|
||||||
This email is intended for {{email}}
|
This email is intended for {{email}}
|
||||||
Sent by Stoat
|
Sent by Revolt
|
||||||
Made in Europe
|
Made in Europe
|
||||||
|
|
||||||
Revolt Platforms Ltd. is a company incorporated and registered under the laws of England and Wales.
|
Revolt Platforms Ltd. is a company incorporated and registered under the laws of England and Wales.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<!-- Compiled with Bootstrap Email version: 1.6.0 --><meta http-equiv="x-ua-compatible" content="ie=edge">
|
<!-- Compiled with Bootstrap Email version: 1.5.1 --><meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||||
<meta name="x-apple-disable-message-reformatting">
|
<meta name="x-apple-disable-message-reformatting">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<meta name="format-detection" content="telephone=no, date=no, address=no, email=no">
|
<meta name="format-detection" content="telephone=no, date=no, address=no, email=no">
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td style="line-height: 24px; font-size: 16px; margin: 0;" align="left">
|
<td style="line-height: 24px; font-size: 16px; margin: 0;" align="left">
|
||||||
<img class="w-24" src="https://stoat.chat/favicon.svg" alt="Stoat Logo" style="height: auto; line-height: 100%; outline: none; text-decoration: none; display: block; width: 96px; border-style: none; border-width: 0;" width="96">
|
<img alt="Revolt Logo" class="w-24" src="https://app.revolt.chat/assets/logo_round.png" style="height: auto; line-height: 100%; outline: none; text-decoration: none; display: block; width: 96px; border-style: none; border-width: 0;" width="96">
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -134,7 +134,7 @@
|
|||||||
</table>
|
</table>
|
||||||
<div class="text-muted text-center" style="color: #718096;" align="center">
|
<div class="text-muted text-center" style="color: #718096;" align="center">
|
||||||
This email is intended for {{email}}<br>
|
This email is intended for {{email}}<br>
|
||||||
Sent from Stoat<br>
|
Sent from Revolt<br>
|
||||||
Made in Europe
|
Made in Europe
|
||||||
</div>
|
</div>
|
||||||
<table class="s-6 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
|
<table class="s-6 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
|
||||||
|
|||||||
@@ -1,45 +1,46 @@
|
|||||||
<html>
|
<html>
|
||||||
|
<head>
|
||||||
<head>
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
<style>
|
||||||
<style>
|
/* Add custom classes and styles that you want inlined here */
|
||||||
/* Add custom classes and styles that you want inlined here */
|
</style>
|
||||||
</style>
|
</head>
|
||||||
</head>
|
<body class="bg-light">
|
||||||
|
<div class="container">
|
||||||
<body class="bg-light">
|
<img
|
||||||
<div class="container">
|
alt="Revolt Logo"
|
||||||
<img class="ax-center my-10 w-24" src="https://stoat.chat/favicon.svg" alt="Stoat Logo" />
|
class="ax-center my-10 w-24"
|
||||||
<div class="card p-6 p-lg-10 space-y-4">
|
src="https://app.revolt.chat/assets/logo_round.png"
|
||||||
<h1 class="h3 fw-700">Account Suspended</h1>
|
/>
|
||||||
<p>Your account has been suspended, for one or more reasons:</p>
|
<div class="card p-6 p-lg-10 space-y-4">
|
||||||
<ul>
|
<h1 class="h3 fw-700">Account Suspended</h1>
|
||||||
{{list}}
|
<p>Your account has been suspended, for one or more reasons:</p>
|
||||||
</ul>
|
<ul>
|
||||||
<p style="display:{{duration_display}}">
|
{{list}}
|
||||||
You will be able to use your account again in {{duration}} days.
|
</ul>
|
||||||
</p>
|
<p style="display:{{duration_display}}">
|
||||||
<p>
|
You will be able to use your account again in {{duration}} days.
|
||||||
Further violations may result in a permanent ban depending on
|
</p>
|
||||||
severity, please abide by the
|
<p>
|
||||||
<a href="https://revolt.chat/aup">Acceptable Usage Policy</a>.
|
Further violations may result in a permanent ban depending on
|
||||||
</p>
|
severity, please abide by the
|
||||||
<p>Ban evasion is prohibited and will be dealt with accordingly.</p>
|
<a href="https://revolt.chat/aup">Acceptable Usage Policy</a>.
|
||||||
|
</p>
|
||||||
|
<p>Ban evasion is prohibited and will be dealt with accordingly.</p>
|
||||||
|
</div>
|
||||||
|
<div class="text-muted text-center my-6">
|
||||||
|
This email is intended for {{email}}<br />
|
||||||
|
Sent from Revolt<br />
|
||||||
|
Made in Europe
|
||||||
|
</div>
|
||||||
|
<div class="text-muted text-xs my-6 ax-center">
|
||||||
|
Revolt Platforms Ltd. is a company incorporated and registered under the
|
||||||
|
laws of England and Wales.<br />
|
||||||
|
Registered Company Number: 16260658<br />
|
||||||
|
Registered Office:<br />
|
||||||
|
Suite 5703 Unit 3A, 34-35 Hatton Garden,<br />
|
||||||
|
Holborn, United Kingdom, EC1N 8DX
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-muted text-center my-6">
|
</body>
|
||||||
This email is intended for {{email}}<br />
|
</html>
|
||||||
Sent from Stoat<br />
|
|
||||||
Made in Europe
|
|
||||||
</div>
|
|
||||||
<div class="text-muted text-xs my-6 ax-center">
|
|
||||||
Revolt Platforms Ltd. is a company incorporated and registered under the
|
|
||||||
laws of England and Wales.<br />
|
|
||||||
Registered Company Number: 16260658<br />
|
|
||||||
Registered Office:<br />
|
|
||||||
Suite 5703 Unit 3A, 34-35 Hatton Garden,<br />
|
|
||||||
Holborn, United Kingdom, EC1N 8DX
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ Further violations may result in a permanent ban depending on severity, please a
|
|||||||
Ban evasion is prohibited and will be dealt with accordingly.
|
Ban evasion is prohibited and will be dealt with accordingly.
|
||||||
|
|
||||||
This email is intended for {{email}}
|
This email is intended for {{email}}
|
||||||
Sent by Stoat
|
Sent by Revolt
|
||||||
Made in Europe
|
Made in Europe
|
||||||
|
|
||||||
Revolt Platforms Ltd. is a company incorporated and registered under the laws of England and Wales.
|
Revolt Platforms Ltd. is a company incorporated and registered under the laws of England and Wales.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<!-- Compiled with Bootstrap Email version: 1.6.0 --><meta http-equiv="x-ua-compatible" content="ie=edge">
|
<!-- Compiled with Bootstrap Email version: 1.5.1 --><meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||||
<meta name="x-apple-disable-message-reformatting">
|
<meta name="x-apple-disable-message-reformatting">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<meta name="format-detection" content="telephone=no, date=no, address=no, email=no">
|
<meta name="format-detection" content="telephone=no, date=no, address=no, email=no">
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td style="line-height: 24px; font-size: 16px; margin: 0;" align="left">
|
<td style="line-height: 24px; font-size: 16px; margin: 0;" align="left">
|
||||||
<img class="w-24" src="https://stoat.chat/favicon.svg" alt="Stoat Logo" style="height: auto; line-height: 100%; outline: none; text-decoration: none; display: block; width: 96px; border-style: none; border-width: 0;" width="96">
|
<img alt="Revolt Logo" class="w-24" src="https://app.revolt.chat/assets/logo_round.png" style="height: auto; line-height: 100%; outline: none; text-decoration: none; display: block; width: 96px; border-style: none; border-width: 0;" width="96">
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -104,7 +104,7 @@
|
|||||||
</table>
|
</table>
|
||||||
<div class="text-muted text-center" style="color: #718096;" align="center">
|
<div class="text-muted text-center" style="color: #718096;" align="center">
|
||||||
This email is intended for {{email}}<br>
|
This email is intended for {{email}}<br>
|
||||||
Sent from Stoat<br>
|
Sent from Revolt<br>
|
||||||
Made in Europe
|
Made in Europe
|
||||||
</div>
|
</div>
|
||||||
<table class="s-6 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
|
<table class="s-6 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
|
||||||
|
|||||||
@@ -1,34 +1,35 @@
|
|||||||
<html>
|
<html>
|
||||||
|
<head>
|
||||||
<head>
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
<style>
|
||||||
<style>
|
/* Add custom classes and styles that you want inlined here */
|
||||||
/* Add custom classes and styles that you want inlined here */
|
</style>
|
||||||
</style>
|
</head>
|
||||||
</head>
|
<body class="bg-light">
|
||||||
|
<div class="container">
|
||||||
<body class="bg-light">
|
<img
|
||||||
<div class="container">
|
alt="Revolt Logo"
|
||||||
<img class="ax-center my-10 w-24" src="https://stoat.chat/favicon.svg" alt="Stoat Logo" />
|
class="ax-center my-10 w-24"
|
||||||
<div class="card p-6 p-lg-10 space-y-4">
|
src="https://app.revolt.chat/assets/logo_round.png"
|
||||||
<h1 class="h3 fw-700">Almost there!</h1>
|
/>
|
||||||
<p>To complete your sign up, we just need to verify your email.</p>
|
<div class="card p-6 p-lg-10 space-y-4">
|
||||||
<a class="btn btn-primary p-3 fw-700" href="{{url}}">Confirm</a>
|
<h1 class="h3 fw-700">Almost there!</h1>
|
||||||
|
<p>To complete your sign up, we just need to verify your email.</p>
|
||||||
|
<a class="btn btn-primary p-3 fw-700" href="{{url}}">Confirm</a>
|
||||||
|
</div>
|
||||||
|
<div class="text-muted text-center my-6">
|
||||||
|
This email is intended for {{email}}<br />
|
||||||
|
Sent from Revolt<br />
|
||||||
|
Made in Europe
|
||||||
|
</div>
|
||||||
|
<div class="text-muted text-xs my-6 ax-center">
|
||||||
|
Revolt Platforms Ltd. is a company incorporated and registered under the
|
||||||
|
laws of England and Wales.<br />
|
||||||
|
Registered Company Number: 16260658<br />
|
||||||
|
Registered Office:<br />
|
||||||
|
Suite 5703 Unit 3A, 34-35 Hatton Garden,<br />
|
||||||
|
Holborn, United Kingdom, EC1N 8DX
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-muted text-center my-6">
|
</body>
|
||||||
This email is intended for {{email}}<br />
|
</html>
|
||||||
Sent from Stoat<br />
|
|
||||||
Made in Europe
|
|
||||||
</div>
|
|
||||||
<div class="text-muted text-xs my-6 ax-center">
|
|
||||||
Revolt Platforms Ltd. is a company incorporated and registered under the
|
|
||||||
laws of England and Wales.<br />
|
|
||||||
Registered Company Number: 16260658<br />
|
|
||||||
Registered Office:<br />
|
|
||||||
Suite 5703 Unit 3A, 34-35 Hatton Garden,<br />
|
|
||||||
Holborn, United Kingdom, EC1N 8DX
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ To complete your sign up, we just need to verify your email.
|
|||||||
Please navigate to: {{url}}
|
Please navigate to: {{url}}
|
||||||
|
|
||||||
This email is intended for {{email}}
|
This email is intended for {{email}}
|
||||||
Sent by Stoat
|
Sent by Revolt
|
||||||
Made in Europe
|
Made in Europe
|
||||||
|
|
||||||
Revolt Platforms Ltd. is a company incorporated and registered under the laws of England and Wales.
|
Revolt Platforms Ltd. is a company incorporated and registered under the laws of England and Wales.
|
||||||
|
|||||||
@@ -10,22 +10,22 @@ description = "Revolt Backend: API Models"
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
serde = ["dep:serde", "revolt-permissions/serde", "indexmap/serde"]
|
serde = ["dep:serde", "revolt-permissions/serde", "indexmap/serde"]
|
||||||
|
schemas = ["dep:schemars", "revolt-permissions/schemas"]
|
||||||
|
utoipa = ["dep:utoipa"]
|
||||||
validator = ["dep:validator"]
|
validator = ["dep:validator"]
|
||||||
rocket = ["dep:rocket"]
|
rocket = ["dep:rocket"]
|
||||||
partials = ["dep:revolt_optional_struct", "serde", "utoipa"]
|
partials = ["dep:revolt_optional_struct", "serde", "schemas", "utoipa"]
|
||||||
|
|
||||||
default = ["serde", "partials", "rocket"]
|
default = ["serde", "partials", "rocket"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# Core
|
# Core
|
||||||
revolt-config = { version = "0.8.9", path = "../config" }
|
revolt-config = { version = "0.8.9", path = "../config" }
|
||||||
revolt-permissions = { version = "0.8.9", path = "../permissions", features = [
|
revolt-permissions = { version = "0.8.9", path = "../permissions" }
|
||||||
"utoipa",
|
|
||||||
] }
|
|
||||||
|
|
||||||
# Utility
|
# Utility
|
||||||
regex = "1.11"
|
regex = "1.11"
|
||||||
indexmap = "2.12.0"
|
indexmap = "1.9.3"
|
||||||
once_cell = "1.17.1"
|
once_cell = "1.17.1"
|
||||||
num_enum = "0.6.1"
|
num_enum = "0.6.1"
|
||||||
|
|
||||||
@@ -35,10 +35,11 @@ rocket = { optional = true, version = "0.5.0-rc.2", default-features = false }
|
|||||||
# Serialisation
|
# Serialisation
|
||||||
revolt_optional_struct = { version = "0.2.0", optional = true }
|
revolt_optional_struct = { version = "0.2.0", optional = true }
|
||||||
serde = { version = "1", features = ["derive"], optional = true }
|
serde = { version = "1", features = ["derive"], optional = true }
|
||||||
iso8601-timestamp = { version = "0.4.0", features = ["utoipa", "bson"] }
|
iso8601-timestamp = { version = "0.2.11", features = ["schema", "bson"] }
|
||||||
|
|
||||||
# Spec Generation
|
# Spec Generation
|
||||||
utoipa = { version = "5.4.0", features = ["indexmap"], optional = true }
|
schemars = { version = "0.8.8", optional = true, features = ["indexmap1"] }
|
||||||
|
utoipa = { version = "4.2.3", optional = true }
|
||||||
|
|
||||||
# Validation
|
# Validation
|
||||||
validator = { version = "0.16.0", optional = true, features = ["derive"] }
|
validator = { version = "0.16.0", optional = true, features = ["derive"] }
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde;
|
extern crate serde;
|
||||||
|
|
||||||
|
#[cfg(feature = "schemas")]
|
||||||
|
#[macro_use]
|
||||||
|
extern crate schemars;
|
||||||
|
|
||||||
#[cfg(feature = "utoipa")]
|
#[cfg(feature = "utoipa")]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate utoipa;
|
extern crate utoipa;
|
||||||
@@ -17,8 +21,9 @@ macro_rules! auto_derived {
|
|||||||
( $( $item:item )+ ) => {
|
( $( $item:item )+ ) => {
|
||||||
$(
|
$(
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
#[cfg_attr(feature = "schemas", derive(JsonSchema))]
|
||||||
#[cfg_attr(feature = "utoipa", derive(ToSchema))]
|
#[cfg_attr(feature = "utoipa", derive(ToSchema))]
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
$item
|
$item
|
||||||
)+
|
)+
|
||||||
};
|
};
|
||||||
@@ -27,8 +32,19 @@ macro_rules! auto_derived {
|
|||||||
#[cfg(feature = "partials")]
|
#[cfg(feature = "partials")]
|
||||||
macro_rules! auto_derived_partial {
|
macro_rules! auto_derived_partial {
|
||||||
( $item:item, $name:expr ) => {
|
( $item:item, $name:expr ) => {
|
||||||
#[derive(OptionalStruct, Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema)]
|
#[derive(
|
||||||
#[optional_derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema, Default)]
|
OptionalStruct, Debug, Clone, Eq, PartialEq, Serialize, Deserialize, JsonSchema,
|
||||||
|
)]
|
||||||
|
#[optional_derive(
|
||||||
|
Debug,
|
||||||
|
Clone,
|
||||||
|
Eq,
|
||||||
|
PartialEq,
|
||||||
|
Serialize,
|
||||||
|
Deserialize,
|
||||||
|
JsonSchema,
|
||||||
|
Default
|
||||||
|
)]
|
||||||
#[optional_name = $name]
|
#[optional_name = $name]
|
||||||
#[opt_skip_serializing_none]
|
#[opt_skip_serializing_none]
|
||||||
#[opt_some_priority]
|
#[opt_some_priority]
|
||||||
@@ -39,7 +55,7 @@ macro_rules! auto_derived_partial {
|
|||||||
#[cfg(not(feature = "partials"))]
|
#[cfg(not(feature = "partials"))]
|
||||||
macro_rules! auto_derived_partial {
|
macro_rules! auto_derived_partial {
|
||||||
( $item:item, $name:expr ) => {
|
( $item:item, $name:expr ) => {
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema)]
|
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
|
||||||
$item
|
$item
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,10 +86,10 @@ auto_derived!(
|
|||||||
/// Information for the webhook
|
/// Information for the webhook
|
||||||
#[cfg_attr(feature = "validator", derive(Validate))]
|
#[cfg_attr(feature = "validator", derive(Validate))]
|
||||||
pub struct CreateWebhookBody {
|
pub struct CreateWebhookBody {
|
||||||
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 32)))]
|
#[validate(length(min = 1, max = 32))]
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|
||||||
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 128)))]
|
#[validate(length(min = 1, max = 128))]
|
||||||
pub avatar: Option<String>,
|
pub avatar: Option<String>,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -271,7 +271,6 @@ auto_derived!(
|
|||||||
|
|
||||||
/// Options when deleting a channel
|
/// Options when deleting a channel
|
||||||
#[cfg_attr(feature = "rocket", derive(FromForm))]
|
#[cfg_attr(feature = "rocket", derive(FromForm))]
|
||||||
#[cfg_attr(feature = "utoipa", derive(IntoParams))]
|
|
||||||
pub struct OptionsChannelDelete {
|
pub struct OptionsChannelDelete {
|
||||||
/// Whether to not send a leave message
|
/// Whether to not send a leave message
|
||||||
pub leave_silently: Option<bool>,
|
pub leave_silently: Option<bool>,
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
auto_derived!(
|
|
||||||
/// hCaptcha Configuration
|
|
||||||
pub struct CaptchaFeature {
|
|
||||||
/// Whether captcha is enabled
|
|
||||||
pub enabled: bool,
|
|
||||||
/// Client key used for solving captcha
|
|
||||||
pub key: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generic Service Configuration
|
|
||||||
pub struct Feature {
|
|
||||||
/// Whether the service is enabled
|
|
||||||
pub enabled: bool,
|
|
||||||
/// URL pointing to the service
|
|
||||||
pub url: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// # Information about a livekit node
|
|
||||||
pub struct VoiceNode {
|
|
||||||
pub name: String,
|
|
||||||
pub lat: f64,
|
|
||||||
pub lon: f64,
|
|
||||||
pub public_url: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// # Voice Server Configuration
|
|
||||||
pub struct VoiceFeature {
|
|
||||||
/// Whether voice is enabled
|
|
||||||
pub enabled: bool,
|
|
||||||
/// All livekit nodes
|
|
||||||
pub nodes: Vec<VoiceNode>,
|
|
||||||
}
|
|
||||||
/// Feature Configuration
|
|
||||||
pub struct RevoltFeatures {
|
|
||||||
/// hCaptcha configuration
|
|
||||||
pub captcha: CaptchaFeature,
|
|
||||||
/// Whether email verification is enabled
|
|
||||||
pub email: bool,
|
|
||||||
/// Whether this server is invite only
|
|
||||||
pub invite_only: bool,
|
|
||||||
/// File server service configuration
|
|
||||||
pub autumn: Feature,
|
|
||||||
/// Proxy service configuration
|
|
||||||
pub january: Feature,
|
|
||||||
/// Voice server configuration
|
|
||||||
pub livekit: VoiceFeature,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Build Information
|
|
||||||
pub struct BuildInformation {
|
|
||||||
/// Commit Hash
|
|
||||||
pub commit_sha: String,
|
|
||||||
/// Commit Timestamp
|
|
||||||
pub commit_timestamp: String,
|
|
||||||
/// Git Semver
|
|
||||||
pub semver: String,
|
|
||||||
/// Git Origin URL
|
|
||||||
pub origin_url: String,
|
|
||||||
/// Build Timestamp
|
|
||||||
pub timestamp: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Server Configuration
|
|
||||||
pub struct RevoltConfig {
|
|
||||||
/// Revolt API Version
|
|
||||||
pub revolt: String,
|
|
||||||
/// Features enabled on this Revolt node
|
|
||||||
pub features: RevoltFeatures,
|
|
||||||
/// WebSocket URL
|
|
||||||
pub ws: String,
|
|
||||||
/// URL pointing to the client serving this node
|
|
||||||
pub app: String,
|
|
||||||
/// Web Push VAPID public key
|
|
||||||
pub vapid: String,
|
|
||||||
/// Build information
|
|
||||||
pub build: BuildInformation,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
@@ -46,7 +46,7 @@ auto_derived!(
|
|||||||
#[cfg_attr(feature = "validator", derive(Validate))]
|
#[cfg_attr(feature = "validator", derive(Validate))]
|
||||||
pub struct DataCreateEmoji {
|
pub struct DataCreateEmoji {
|
||||||
/// Server name
|
/// Server name
|
||||||
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 32), regex = "RE_EMOJI"))]
|
#[validate(length(min = 1, max = 32), regex = "RE_EMOJI")]
|
||||||
pub name: String,
|
pub name: String,
|
||||||
/// Parent information
|
/// Parent information
|
||||||
pub parent: EmojiParent,
|
pub parent: EmojiParent,
|
||||||
|
|||||||
@@ -141,17 +141,17 @@ auto_derived!(
|
|||||||
pub struct Masquerade {
|
pub struct Masquerade {
|
||||||
/// Replace the display name shown on this message
|
/// Replace the display name shown on this message
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 32)))]
|
#[validate(length(min = 1, max = 32))]
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
/// Replace the avatar shown on this message (URL to image file)
|
/// Replace the avatar shown on this message (URL to image file)
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 256)))]
|
#[validate(length(min = 1, max = 256))]
|
||||||
pub avatar: Option<String>,
|
pub avatar: Option<String>,
|
||||||
/// Replace the display role colour shown on this message
|
/// Replace the display role colour shown on this message
|
||||||
///
|
///
|
||||||
/// Must have `ManageRole` permission to use
|
/// Must have `ManageRole` permission to use
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 128), regex = "RE_COLOUR"))]
|
#[validate(length(min = 1, max = 128), regex = "RE_COLOUR")]
|
||||||
pub colour: Option<String>,
|
pub colour: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,7 +281,6 @@ auto_derived!(
|
|||||||
/// Options for querying messages
|
/// Options for querying messages
|
||||||
#[cfg_attr(feature = "validator", derive(Validate))]
|
#[cfg_attr(feature = "validator", derive(Validate))]
|
||||||
#[cfg_attr(feature = "rocket", derive(FromForm))]
|
#[cfg_attr(feature = "rocket", derive(FromForm))]
|
||||||
#[cfg_attr(feature = "utoipa", derive(IntoParams))]
|
|
||||||
pub struct OptionsQueryMessages {
|
pub struct OptionsQueryMessages {
|
||||||
/// Maximum number of messages to fetch
|
/// Maximum number of messages to fetch
|
||||||
///
|
///
|
||||||
@@ -354,13 +353,12 @@ auto_derived!(
|
|||||||
)]
|
)]
|
||||||
pub struct OptionsBulkDelete {
|
pub struct OptionsBulkDelete {
|
||||||
/// Message IDs
|
/// Message IDs
|
||||||
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 100)))]
|
#[validate(length(min = 1, max = 100))]
|
||||||
pub ids: Vec<String>,
|
pub ids: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Options for removing reaction
|
/// Options for removing reaction
|
||||||
#[cfg_attr(feature = "rocket", derive(FromForm))]
|
#[cfg_attr(feature = "rocket", derive(FromForm))]
|
||||||
#[cfg_attr(feature = "utoipa", derive(IntoParams))]
|
|
||||||
pub struct OptionsUnreact {
|
pub struct OptionsUnreact {
|
||||||
/// Remove a specific user's reaction
|
/// Remove a specific user's reaction
|
||||||
pub user_id: Option<String>,
|
pub user_id: Option<String>,
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ mod channel_invites;
|
|||||||
mod channel_unreads;
|
mod channel_unreads;
|
||||||
mod channel_webhooks;
|
mod channel_webhooks;
|
||||||
mod channels;
|
mod channels;
|
||||||
mod config;
|
|
||||||
mod embeds;
|
mod embeds;
|
||||||
mod emojis;
|
mod emojis;
|
||||||
mod files;
|
mod files;
|
||||||
@@ -21,7 +20,6 @@ pub use channel_invites::*;
|
|||||||
pub use channel_unreads::*;
|
pub use channel_unreads::*;
|
||||||
pub use channel_webhooks::*;
|
pub use channel_webhooks::*;
|
||||||
pub use channels::*;
|
pub use channels::*;
|
||||||
pub use config::*;
|
|
||||||
pub use embeds::*;
|
pub use embeds::*;
|
||||||
pub use emojis::*;
|
pub use emojis::*;
|
||||||
pub use files::*;
|
pub use files::*;
|
||||||
|
|||||||
@@ -116,7 +116,6 @@ auto_derived!(
|
|||||||
|
|
||||||
/// Options for fetching all members
|
/// Options for fetching all members
|
||||||
#[cfg_attr(feature = "rocket", derive(FromForm))]
|
#[cfg_attr(feature = "rocket", derive(FromForm))]
|
||||||
#[cfg_attr(feature = "utoipa", derive(IntoParams))]
|
|
||||||
pub struct OptionsFetchAllMembers {
|
pub struct OptionsFetchAllMembers {
|
||||||
/// Whether to exclude offline users
|
/// Whether to exclude offline users
|
||||||
pub exclude_offline: Option<bool>,
|
pub exclude_offline: Option<bool>,
|
||||||
|
|||||||
@@ -198,7 +198,6 @@ auto_derived!(
|
|||||||
|
|
||||||
/// Options when fetching server
|
/// Options when fetching server
|
||||||
#[cfg_attr(feature = "rocket", derive(FromForm))]
|
#[cfg_attr(feature = "rocket", derive(FromForm))]
|
||||||
#[cfg_attr(feature = "utoipa", derive(IntoParams))]
|
|
||||||
pub struct OptionsFetchServer {
|
pub struct OptionsFetchServer {
|
||||||
/// Whether to include channels
|
/// Whether to include channels
|
||||||
pub include_channels: Option<bool>,
|
pub include_channels: Option<bool>,
|
||||||
@@ -285,7 +284,6 @@ auto_derived!(
|
|||||||
|
|
||||||
/// Options when leaving a server
|
/// Options when leaving a server
|
||||||
#[cfg_attr(feature = "rocket", derive(FromForm))]
|
#[cfg_attr(feature = "rocket", derive(FromForm))]
|
||||||
#[cfg_attr(feature = "utoipa", derive(IntoParams))]
|
|
||||||
pub struct OptionsServerDelete {
|
pub struct OptionsServerDelete {
|
||||||
/// Whether to not send a leave message
|
/// Whether to not send a leave message
|
||||||
pub leave_silently: Option<bool>,
|
pub leave_silently: Option<bool>,
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ auto_derived!(
|
|||||||
|
|
||||||
/// Additional options for inserting settings
|
/// Additional options for inserting settings
|
||||||
#[cfg_attr(feature = "rocket", derive(FromForm))]
|
#[cfg_attr(feature = "rocket", derive(FromForm))]
|
||||||
#[cfg_attr(feature = "utoipa", derive(IntoParams))]
|
|
||||||
pub struct OptionsSetSettings {
|
pub struct OptionsSetSettings {
|
||||||
/// Timestamp of settings change.
|
/// Timestamp of settings change.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ auto_derived!(
|
|||||||
#[cfg_attr(feature = "validator", derive(Validate))]
|
#[cfg_attr(feature = "validator", derive(Validate))]
|
||||||
pub struct UserStatus {
|
pub struct UserStatus {
|
||||||
/// Custom status text
|
/// Custom status text
|
||||||
#[cfg_attr(feature = "validator", validate(length(min = 0, max = 128)))]
|
#[validate(length(min = 0, max = 128))]
|
||||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub text: Option<String>,
|
pub text: Option<String>,
|
||||||
/// Current presence option
|
/// Current presence option
|
||||||
@@ -155,7 +155,7 @@ auto_derived!(
|
|||||||
#[cfg_attr(feature = "validator", derive(Validate))]
|
#[cfg_attr(feature = "validator", derive(Validate))]
|
||||||
pub struct UserProfile {
|
pub struct UserProfile {
|
||||||
/// Text content on user's profile
|
/// Text content on user's profile
|
||||||
#[cfg_attr(feature = "validator", validate(length(min = 0, max = 2000)))]
|
#[validate(length(min = 0, max = 2000))]
|
||||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub content: Option<String>,
|
pub content: Option<String>,
|
||||||
/// Background visible on user's profile
|
/// Background visible on user's profile
|
||||||
|
|||||||
@@ -11,8 +11,10 @@ description = "Revolt Backend: Permission Logic"
|
|||||||
[features]
|
[features]
|
||||||
bson = ["dep:bson"]
|
bson = ["dep:bson"]
|
||||||
serde = ["dep:serde"]
|
serde = ["dep:serde"]
|
||||||
|
schemas = ["dep:schemars"]
|
||||||
try-from-primitive = ["dep:num_enum"]
|
try-from-primitive = ["dep:num_enum"]
|
||||||
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
# Async
|
# Async
|
||||||
async-std = { version = "1.8.0", features = ["attributes"] }
|
async-std = { version = "1.8.0", features = ["attributes"] }
|
||||||
@@ -34,4 +36,4 @@ serde = { version = "1", features = ["derive"], optional = true }
|
|||||||
bson = { version = "2.1.0", optional = true }
|
bson = { version = "2.1.0", optional = true }
|
||||||
|
|
||||||
# Spec Generation
|
# Spec Generation
|
||||||
utoipa = { version = "5.4.0", optional = true }
|
schemars = { version = "0.8.8", optional = true }
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
#[cfg(feature = "utoipa")]
|
#[cfg(feature = "schemas")]
|
||||||
use utoipa::ToSchema;
|
use schemars::JsonSchema;
|
||||||
|
|
||||||
/// Representation of a single permission override
|
/// Representation of a single permission override
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Default)]
|
#[derive(Debug, Clone, Eq, PartialEq, Default)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[cfg_attr(feature = "utoipa", derive(ToSchema))]
|
#[cfg_attr(feature = "schemas", derive(JsonSchema))]
|
||||||
pub struct Override {
|
pub struct Override {
|
||||||
/// Allow bit flags
|
/// Allow bit flags
|
||||||
pub allow: u64,
|
pub allow: u64,
|
||||||
@@ -15,7 +15,7 @@ pub struct Override {
|
|||||||
/// Data permissions Field - contains both allow and deny
|
/// Data permissions Field - contains both allow and deny
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[cfg_attr(feature = "utoipa", derive(ToSchema))]
|
#[cfg_attr(feature = "schemas", derive(JsonSchema))]
|
||||||
pub struct DataPermissionsField {
|
pub struct DataPermissionsField {
|
||||||
pub permissions: Override,
|
pub permissions: Override,
|
||||||
}
|
}
|
||||||
@@ -23,7 +23,7 @@ pub struct DataPermissionsField {
|
|||||||
/// Data permissions Value - contains allow
|
/// Data permissions Value - contains allow
|
||||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[cfg_attr(feature = "utoipa", derive(ToSchema))]
|
#[cfg_attr(feature = "schemas", derive(JsonSchema))]
|
||||||
pub struct DataPermissionsValue {
|
pub struct DataPermissionsValue {
|
||||||
pub permissions: u64,
|
pub permissions: u64,
|
||||||
}
|
}
|
||||||
@@ -31,7 +31,7 @@ pub struct DataPermissionsValue {
|
|||||||
/// Data permissions Poly - can contain either Value or Field
|
/// Data permissions Poly - can contain either Value or Field
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[cfg_attr(feature = "utoipa", derive(ToSchema))]
|
#[cfg_attr(feature = "schemas", derive(JsonSchema))]
|
||||||
#[cfg_attr(feature = "serde", serde(untagged))]
|
#[cfg_attr(feature = "serde", serde(untagged))]
|
||||||
pub enum DataPermissionPoly {
|
pub enum DataPermissionPoly {
|
||||||
Value {
|
Value {
|
||||||
@@ -48,7 +48,7 @@ pub enum DataPermissionPoly {
|
|||||||
/// as it appears on models and in the database
|
/// as it appears on models and in the database
|
||||||
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
|
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[cfg_attr(feature = "utoipa", derive(ToSchema))]
|
#[cfg_attr(feature = "schemas", derive(JsonSchema))]
|
||||||
pub struct OverrideField {
|
pub struct OverrideField {
|
||||||
/// Allow bit flags
|
/// Allow bit flags
|
||||||
pub a: i64,
|
pub a: i64,
|
||||||
|
|||||||
@@ -4,22 +4,20 @@ version = "0.8.9"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
rocket = [
|
rocket = ["dep:rocket", "dep:revolt_rocket_okapi", "revolt-database/rocket-impl"]
|
||||||
"dep:rocket",
|
|
||||||
"revolt-database/rocket-impl",
|
|
||||||
]
|
|
||||||
axum = ["dep:axum", "revolt-database/axum-impl"]
|
axum = ["dep:axum", "revolt-database/axum-impl"]
|
||||||
|
|
||||||
default = ["rocket", "axum"]
|
default = ["rocket", "axum"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
revolt-database = { version = "0.8.9", path = "../database" }
|
revolt-database = { version = "0.8.9", path = "../database"}
|
||||||
revolt-result = { version = "0.8.9", path = "../result" }
|
revolt-result = { version = "0.8.9", path = "../result" }
|
||||||
revolt-config = { version = "0.8.9", path = "../config" }
|
revolt-config = { version = "0.8.9", path = "../config" }
|
||||||
|
|
||||||
rocket = { version = "0.5.1", optional = true }
|
rocket = { version = "0.5.1", optional = true }
|
||||||
|
revolt_rocket_okapi = { version = "0.10.0", optional = true }
|
||||||
|
|
||||||
axum = { version = "0.8.6", optional = true, features = ["macros"] }
|
axum = { version = "0.7.5", optional = true, features = ["macros"] }
|
||||||
|
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
authifier = { version = "1.0.15" }
|
authifier = { version = "1.0.15" }
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
use axum::{
|
use axum::{
|
||||||
Json, RequestPartsExt, Router,
|
Json, RequestPartsExt, Router,
|
||||||
body::Body,
|
body::Body,
|
||||||
@@ -43,6 +44,7 @@ async fn to_real_ip(parts: &Parts) -> String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
impl<S: Send + Sync> FromRequestParts<S> for Ratelimiter
|
impl<S: Send + Sync> FromRequestParts<S> for Ratelimiter
|
||||||
where
|
where
|
||||||
Database: FromRef<S>,
|
Database: FromRef<S>,
|
||||||
@@ -80,6 +82,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
impl<S: Send + Sync> FromRequestParts<S> for RatelimitInformation
|
impl<S: Send + Sync> FromRequestParts<S> for RatelimitInformation
|
||||||
where
|
where
|
||||||
Database: FromRef<S>,
|
Database: FromRef<S>,
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ use rocket::serde::json::Json;
|
|||||||
use rocket::{Data, Request, Response, State};
|
use rocket::{Data, Request, Response, State};
|
||||||
use revolt_config::config;
|
use revolt_config::config;
|
||||||
|
|
||||||
|
use revolt_rocket_okapi::r#gen::OpenApiGenerator;
|
||||||
|
use revolt_rocket_okapi::request::{OpenApiFromRequest, RequestHeaderInput};
|
||||||
|
|
||||||
use authifier::models::Session;
|
use authifier::models::Session;
|
||||||
|
|
||||||
use crate::ratelimiter::RequestKind;
|
use crate::ratelimiter::RequestKind;
|
||||||
@@ -75,6 +78,16 @@ impl<'r> FromRequest<'r> for Ratelimiter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl OpenApiFromRequest<'_> for Ratelimiter {
|
||||||
|
fn from_request_input(
|
||||||
|
_gen: &mut OpenApiGenerator,
|
||||||
|
_name: String,
|
||||||
|
_required: bool,
|
||||||
|
) -> revolt_rocket_okapi::Result<RequestHeaderInput> {
|
||||||
|
Ok(RequestHeaderInput::None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Attach ratelimiter to the Rocket application
|
/// Attach ratelimiter to the Rocket application
|
||||||
pub struct RatelimitFairing;
|
pub struct RatelimitFairing;
|
||||||
|
|
||||||
|
|||||||
@@ -10,12 +10,13 @@ description = "Revolt Backend: Result and Error types"
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
serde = ["dep:serde"]
|
serde = ["dep:serde"]
|
||||||
|
schemas = ["dep:schemars"]
|
||||||
utoipa = ["dep:utoipa"]
|
utoipa = ["dep:utoipa"]
|
||||||
rocket = ["dep:rocket", "dep:serde_json"]
|
rocket = ["dep:rocket", "dep:serde_json"]
|
||||||
axum = ["dep:axum", "dep:serde_json"]
|
axum = ["dep:axum", "dep:serde_json"]
|
||||||
sentry = ["dep:sentry"]
|
okapi = ["dep:revolt_rocket_okapi", "dep:revolt_okapi", "schemas"]
|
||||||
|
|
||||||
default = ["serde", "sentry"]
|
default = ["serde"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# Serialisation
|
# Serialisation
|
||||||
@@ -23,14 +24,15 @@ serde_json = { version = "1", optional = true }
|
|||||||
serde = { version = "1", features = ["derive"], optional = true }
|
serde = { version = "1", features = ["derive"], optional = true }
|
||||||
|
|
||||||
# Spec Generation
|
# Spec Generation
|
||||||
utoipa = { version = "5.4.0", optional = true }
|
schemars = { version = "0.8.8", optional = true }
|
||||||
|
utoipa = { version = "4.2.3", optional = true }
|
||||||
|
|
||||||
# Rocket
|
# Rocket
|
||||||
rocket = { optional = true, version = "0.5.0-rc.2", default-features = false }
|
rocket = { optional = true, version = "0.5.0-rc.2", default-features = false }
|
||||||
|
revolt_rocket_okapi = { version = "0.10.0", optional = true }
|
||||||
|
revolt_okapi = { version = "0.9.1", optional = true }
|
||||||
|
|
||||||
# utilities
|
# utilities
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
# Axum
|
# Axum
|
||||||
axum = { version = "0.8.6", optional = true }
|
axum = { version = "0.7.5", optional = true }
|
||||||
|
|
||||||
sentry = { version = "0.31.5", optional = true }
|
|
||||||
|
|||||||
@@ -62,7 +62,6 @@ impl IntoResponse for Error {
|
|||||||
ErrorType::NotPrivileged => StatusCode::FORBIDDEN,
|
ErrorType::NotPrivileged => StatusCode::FORBIDDEN,
|
||||||
ErrorType::CannotGiveMissingPermissions => StatusCode::FORBIDDEN,
|
ErrorType::CannotGiveMissingPermissions => StatusCode::FORBIDDEN,
|
||||||
ErrorType::NotOwner => StatusCode::FORBIDDEN,
|
ErrorType::NotOwner => StatusCode::FORBIDDEN,
|
||||||
ErrorType::IsElevated => StatusCode::FORBIDDEN,
|
|
||||||
|
|
||||||
ErrorType::DatabaseError { .. } => StatusCode::INTERNAL_SERVER_ERROR,
|
ErrorType::DatabaseError { .. } => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
ErrorType::InternalError => StatusCode::INTERNAL_SERVER_ERROR,
|
ErrorType::InternalError => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
|||||||
@@ -5,10 +5,13 @@ use std::fmt::Display;
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde;
|
extern crate serde;
|
||||||
|
|
||||||
|
#[cfg(feature = "schemas")]
|
||||||
|
#[macro_use]
|
||||||
|
extern crate schemars;
|
||||||
|
|
||||||
#[cfg(feature = "utoipa")]
|
#[cfg(feature = "utoipa")]
|
||||||
mod utoipa_impl;
|
#[macro_use]
|
||||||
#[cfg(feature = "utoipa")]
|
extern crate utoipa;
|
||||||
pub use crate::utoipa_impl::*;
|
|
||||||
|
|
||||||
#[cfg(feature = "rocket")]
|
#[cfg(feature = "rocket")]
|
||||||
pub mod rocket;
|
pub mod rocket;
|
||||||
@@ -16,12 +19,16 @@ pub mod rocket;
|
|||||||
#[cfg(feature = "axum")]
|
#[cfg(feature = "axum")]
|
||||||
pub mod axum;
|
pub mod axum;
|
||||||
|
|
||||||
|
#[cfg(feature = "okapi")]
|
||||||
|
pub mod okapi;
|
||||||
|
|
||||||
/// Result type with custom Error
|
/// Result type with custom Error
|
||||||
pub type Result<T, E = Error> = std::result::Result<T, E>;
|
pub type Result<T, E = Error> = std::result::Result<T, E>;
|
||||||
|
|
||||||
/// Error information
|
/// Error information
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
|
#[cfg_attr(feature = "schemas", derive(JsonSchema))]
|
||||||
|
#[cfg_attr(feature = "utoipa", derive(ToSchema))]
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Error {
|
pub struct Error {
|
||||||
/// Type of error and additional information
|
/// Type of error and additional information
|
||||||
@@ -43,7 +50,8 @@ impl std::error::Error for Error {}
|
|||||||
/// Possible error types
|
/// Possible error types
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
#[cfg_attr(feature = "serde", serde(tag = "type"))]
|
#[cfg_attr(feature = "serde", serde(tag = "type"))]
|
||||||
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
|
#[cfg_attr(feature = "schemas", derive(JsonSchema))]
|
||||||
|
#[cfg_attr(feature = "utoipa", derive(ToSchema))]
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum ErrorType {
|
pub enum ErrorType {
|
||||||
/// This error was not labeled :(
|
/// This error was not labeled :(
|
||||||
@@ -131,7 +139,6 @@ pub enum ErrorType {
|
|||||||
NotPrivileged,
|
NotPrivileged,
|
||||||
CannotGiveMissingPermissions,
|
CannotGiveMissingPermissions,
|
||||||
NotOwner,
|
NotOwner,
|
||||||
IsElevated,
|
|
||||||
|
|
||||||
// ? General errors
|
// ? General errors
|
||||||
DatabaseError {
|
DatabaseError {
|
||||||
@@ -219,7 +226,7 @@ pub trait ToRevoltError<T> {
|
|||||||
fn to_internal_error(self) -> Result<T, Error>;
|
fn to_internal_error(self) -> Result<T, Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, E: std::fmt::Debug + std::error::Error> ToRevoltError<T> for Result<T, E> {
|
impl<T, E: std::fmt::Debug> ToRevoltError<T> for Result<T, E> {
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn to_internal_error(self) -> Result<T, Error> {
|
fn to_internal_error(self) -> Result<T, Error> {
|
||||||
let loc = Location::caller();
|
let loc = Location::caller();
|
||||||
@@ -227,8 +234,6 @@ impl<T, E: std::fmt::Debug + std::error::Error> ToRevoltError<T> for Result<T, E
|
|||||||
self
|
self
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
log::error!("{e:?}");
|
log::error!("{e:?}");
|
||||||
#[cfg(feature = "sentry")]
|
|
||||||
sentry::capture_error(&e);
|
|
||||||
|
|
||||||
Error {
|
Error {
|
||||||
error_type: ErrorType::InternalError,
|
error_type: ErrorType::InternalError,
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
use revolt_okapi::openapi3::SchemaObject;
|
||||||
|
use revolt_rocket_okapi::revolt_okapi::openapi3;
|
||||||
|
use schemars::schema::Schema;
|
||||||
|
|
||||||
|
use crate::Error;
|
||||||
|
|
||||||
|
impl revolt_rocket_okapi::response::OpenApiResponderInner for Error {
|
||||||
|
fn responses(
|
||||||
|
gen: &mut revolt_rocket_okapi::gen::OpenApiGenerator,
|
||||||
|
) -> std::result::Result<openapi3::Responses, revolt_rocket_okapi::OpenApiError> {
|
||||||
|
let mut content = revolt_okapi::Map::new();
|
||||||
|
|
||||||
|
let settings = schemars::gen::SchemaSettings::default().with(|s| {
|
||||||
|
s.option_nullable = true;
|
||||||
|
s.option_add_null_type = false;
|
||||||
|
s.definitions_path = "#/components/schemas/".to_string();
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut schema_generator = settings.into_generator();
|
||||||
|
let schema = schema_generator.root_schema_for::<Error>();
|
||||||
|
|
||||||
|
let definitions = gen.schema_generator().definitions_mut();
|
||||||
|
for (key, value) in schema.definitions {
|
||||||
|
definitions.insert(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
definitions.insert("Error".to_string(), Schema::Object(schema.schema));
|
||||||
|
|
||||||
|
content.insert(
|
||||||
|
"application/json".to_string(),
|
||||||
|
openapi3::MediaType {
|
||||||
|
schema: Some(SchemaObject {
|
||||||
|
reference: Some("#/components/schemas/Error".to_string()),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(openapi3::Responses {
|
||||||
|
default: Some(openapi3::RefOr::Object(openapi3::Response {
|
||||||
|
content,
|
||||||
|
description: "An error occurred.".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
})),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -69,7 +69,6 @@ impl<'r> Responder<'r, 'static> for Error {
|
|||||||
ErrorType::NotPrivileged => Status::Forbidden,
|
ErrorType::NotPrivileged => Status::Forbidden,
|
||||||
ErrorType::CannotGiveMissingPermissions => Status::Forbidden,
|
ErrorType::CannotGiveMissingPermissions => Status::Forbidden,
|
||||||
ErrorType::NotOwner => Status::Forbidden,
|
ErrorType::NotOwner => Status::Forbidden,
|
||||||
ErrorType::IsElevated => Status::Forbidden,
|
|
||||||
|
|
||||||
ErrorType::DatabaseError { .. } => Status::InternalServerError,
|
ErrorType::DatabaseError { .. } => Status::InternalServerError,
|
||||||
ErrorType::InternalError => Status::InternalServerError,
|
ErrorType::InternalError => Status::InternalServerError,
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
use utoipa::{Modify, openapi::{Content, OpenApi, Ref, RefOr, ResponseBuilder}};
|
|
||||||
|
|
||||||
pub struct ErrorAddon;
|
|
||||||
|
|
||||||
impl Modify for ErrorAddon {
|
|
||||||
fn modify(&self, utoipa: &mut OpenApi) {
|
|
||||||
let response = ResponseBuilder::new()
|
|
||||||
.description("An error occurred")
|
|
||||||
.content(
|
|
||||||
"application/json",
|
|
||||||
Content::new(Some(RefOr::Ref(Ref::from_schema_name("Error")))),
|
|
||||||
)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
for path in utoipa.paths.paths.values_mut() {
|
|
||||||
if let Some(route) = path.get.as_mut() {
|
|
||||||
route
|
|
||||||
.responses
|
|
||||||
.responses
|
|
||||||
.insert("default".to_string(), RefOr::T(response.clone()));
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(route) = path.delete.as_mut() {
|
|
||||||
route
|
|
||||||
.responses
|
|
||||||
.responses
|
|
||||||
.insert("default".to_string(), RefOr::T(response.clone()));
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(route) = path.patch.as_mut() {
|
|
||||||
route
|
|
||||||
.responses
|
|
||||||
.responses
|
|
||||||
.insert("default".to_string(), RefOr::T(response.clone()));
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(route) = path.post.as_mut() {
|
|
||||||
route
|
|
||||||
.responses
|
|
||||||
.responses
|
|
||||||
.insert("default".to_string(), RefOr::T(response.clone()));
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(route) = path.put.as_mut() {
|
|
||||||
route
|
|
||||||
.responses
|
|
||||||
.responses
|
|
||||||
.insert("default".to_string(), RefOr::T(response.clone()));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -38,5 +38,5 @@ pretty_env_logger = "0.4.0"
|
|||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
revolt_optional_struct = "0.2.0"
|
revolt_optional_struct = "0.2.0"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
iso8601-timestamp = { version = "0.4.0", features = ["serde", "bson"] }
|
iso8601-timestamp = { version = "0.2.10", features = ["serde", "bson"] }
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
|
|||||||
@@ -36,13 +36,13 @@ async-std = { version = "1.8.0", features = [
|
|||||||
revolt-result = { path = "../../core/result" }
|
revolt-result = { path = "../../core/result" }
|
||||||
revolt-models = { path = "../../core/models" }
|
revolt-models = { path = "../../core/models" }
|
||||||
revolt-config = { path = "../../core/config" }
|
revolt-config = { path = "../../core/config" }
|
||||||
revolt-database = { path = "../../core/database", features = ["voice"] }
|
revolt-database = { path = "../../core/database" }
|
||||||
revolt-permissions = { path = "../../core/permissions" }
|
revolt-permissions = { path = "../../core/permissions" }
|
||||||
|
|
||||||
# Voice
|
# Voice
|
||||||
livekit-api = "=0.4.4"
|
livekit-api = "0.4.4"
|
||||||
livekit-protocol = "=0.4.0"
|
livekit-protocol = "0.4.0"
|
||||||
livekit-runtime = { version = "=0.3.1", features = ["tokio"] }
|
livekit-runtime = { version = "0.3.1", features = ["tokio"] }
|
||||||
|
|
||||||
# RabbitMQ
|
# RabbitMQ
|
||||||
amqprs = { version = "1.7.0" }
|
amqprs = { version = "1.7.0" }
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use chrono::DateTime;
|
||||||
use livekit_api::{access_token::TokenVerifier, webhooks::WebhookReceiver};
|
use livekit_api::{access_token::TokenVerifier, webhooks::WebhookReceiver};
|
||||||
use livekit_protocol::TrackType;
|
use livekit_protocol::TrackType;
|
||||||
use revolt_database::{
|
use revolt_database::{
|
||||||
@@ -5,15 +6,18 @@ use revolt_database::{
|
|||||||
iso8601_timestamp::{Duration, Timestamp},
|
iso8601_timestamp::{Duration, Timestamp},
|
||||||
util::reference::Reference,
|
util::reference::Reference,
|
||||||
voice::{
|
voice::{
|
||||||
create_voice_state, delete_voice_state,
|
create_voice_state, delete_voice_state, get_call_notification_recipients,
|
||||||
get_user_moved_from_voice, get_user_moved_to_voice,
|
get_user_moved_from_voice, get_user_moved_to_voice, get_voice_channel_members,
|
||||||
|
set_channel_call_started_system_message, take_channel_call_started_system_message,
|
||||||
update_voice_state_tracks, VoiceClient,
|
update_voice_state_tracks, VoiceClient,
|
||||||
},
|
},
|
||||||
Database, AMQP,
|
Database, PartialMessage, SystemMessage, AMQP,
|
||||||
};
|
};
|
||||||
|
use revolt_models::v0;
|
||||||
use revolt_result::{Result, ToRevoltError};
|
use revolt_result::{Result, ToRevoltError};
|
||||||
use rocket::{post, State};
|
use rocket::{post, State};
|
||||||
use rocket_empty::EmptyResponse;
|
use rocket_empty::EmptyResponse;
|
||||||
|
use ulid::Ulid;
|
||||||
|
|
||||||
use crate::guard::AuthHeader;
|
use crate::guard::AuthHeader;
|
||||||
|
|
||||||
@@ -21,7 +25,7 @@ use crate::guard::AuthHeader;
|
|||||||
pub async fn ingress(
|
pub async fn ingress(
|
||||||
db: &State<Database>,
|
db: &State<Database>,
|
||||||
voice_client: &State<VoiceClient>,
|
voice_client: &State<VoiceClient>,
|
||||||
_amqp: &State<AMQP>,
|
amqp: &State<AMQP>,
|
||||||
node: &str,
|
node: &str,
|
||||||
auth_header: AuthHeader<'_>,
|
auth_header: AuthHeader<'_>,
|
||||||
body: &str,
|
body: &str,
|
||||||
@@ -86,51 +90,50 @@ pub async fn ingress(
|
|||||||
.await;
|
.await;
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: fix `num_participants` being incorrect sometimes see (#457)
|
|
||||||
// First user who joined - send call started system message.
|
// First user who joined - send call started system message.
|
||||||
// if event.room.as_ref().unwrap().num_participants == 1 {
|
if event.room.as_ref().unwrap().num_participants == 1 {
|
||||||
// let user = Reference::from_unchecked(user_id).as_user(db).await?;
|
let user = Reference::from_unchecked(user_id).as_user(db).await?;
|
||||||
|
|
||||||
// let message_id =
|
let message_id =
|
||||||
// Ulid::from_datetime(DateTime::from_timestamp_secs(event.created_at).unwrap())
|
Ulid::from_datetime(DateTime::from_timestamp_secs(event.created_at).unwrap())
|
||||||
// .to_string();
|
.to_string();
|
||||||
|
|
||||||
// let mut call_started_message = SystemMessage::CallStarted {
|
let mut call_started_message = SystemMessage::CallStarted {
|
||||||
// by: user_id.to_string(),
|
by: user_id.to_string(),
|
||||||
// finished_at: None,
|
finished_at: None,
|
||||||
// }
|
}
|
||||||
// .into_message(channel.id().to_string());
|
.into_message(channel.id().to_string());
|
||||||
|
|
||||||
// call_started_message.id = message_id;
|
call_started_message.id = message_id;
|
||||||
|
|
||||||
// set_channel_call_started_system_message(channel.id(), &call_started_message.id)
|
set_channel_call_started_system_message(channel.id(), &call_started_message.id)
|
||||||
// .await?;
|
.await?;
|
||||||
|
|
||||||
// call_started_message
|
call_started_message
|
||||||
// .send(
|
.send(
|
||||||
// db,
|
db,
|
||||||
// Some(amqp),
|
Some(amqp),
|
||||||
// v0::MessageAuthor::System {
|
v0::MessageAuthor::System {
|
||||||
// username: &user.username,
|
username: &user.username,
|
||||||
// avatar: user.avatar.as_ref().map(|file| file.id.as_ref()),
|
avatar: user.avatar.as_ref().map(|file| file.id.as_ref()),
|
||||||
// },
|
},
|
||||||
// None,
|
None,
|
||||||
// None,
|
None,
|
||||||
// &channel,
|
&channel,
|
||||||
// false,
|
false,
|
||||||
// )
|
)
|
||||||
// .await?;
|
.await?;
|
||||||
|
|
||||||
// let recipients = get_call_notification_recipients(&channel_id, &user_id).await?;
|
let recipients = get_call_notification_recipients(&channel_id, &user_id).await?;
|
||||||
// let now = joined_at.format_short().to_string();
|
let now = joined_at.format_short().to_string();
|
||||||
|
|
||||||
// if let Err(e) = amqp
|
if let Err(e) = amqp
|
||||||
// .dm_call_updated(&user.id, channel.id(), Some(&now), false, recipients)
|
.dm_call_updated(&user.id, channel.id(), Some(&now), false, recipients)
|
||||||
// .await
|
.await
|
||||||
// {
|
{
|
||||||
// revolt_config::capture_error(&e);
|
revolt_config::capture_error(&e);
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
// User left a channel
|
// User left a channel
|
||||||
"participant_left" => {
|
"participant_left" => {
|
||||||
@@ -154,52 +157,50 @@ pub async fn ingress(
|
|||||||
.await;
|
.await;
|
||||||
};
|
};
|
||||||
|
|
||||||
// See above for why this is commented out
|
// Update CallStarted system message if everyone has left with the end time
|
||||||
|
let members = get_voice_channel_members(channel_id).await?;
|
||||||
|
|
||||||
// // Update CallStarted system message if everyone has left with the end time
|
if members.is_none_or(|m| m.is_empty()) {
|
||||||
// let members = get_voice_channel_members(channel_id).await?;
|
// The channel is empty so send out an "end" message for ringing
|
||||||
|
if let Err(e) = amqp
|
||||||
|
.dm_call_updated(user_id, channel_id, None, true, None)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
revolt_config::capture_internal_error!(&e);
|
||||||
|
}
|
||||||
|
|
||||||
// if members.is_none_or(|m| m.is_empty()) {
|
if let Some(system_message_id) =
|
||||||
// // The channel is empty so send out an "end" message for ringing
|
take_channel_call_started_system_message(channel_id).await?
|
||||||
// if let Err(e) = amqp
|
{
|
||||||
// .dm_call_updated(user_id, channel_id, None, true, None)
|
// Could have been deleted
|
||||||
// .await
|
if let Ok(mut message) = Reference::from_unchecked(&system_message_id)
|
||||||
// {
|
.as_message(db)
|
||||||
// revolt_config::capture_internal_error!(&e);
|
.await
|
||||||
// }
|
{
|
||||||
|
if let Some(SystemMessage::CallStarted { finished_at, .. }) =
|
||||||
|
&mut message.system
|
||||||
|
{
|
||||||
|
*finished_at = Some(Timestamp::now_utc());
|
||||||
|
|
||||||
// if let Some(system_message_id) =
|
message
|
||||||
// take_channel_call_started_system_message(channel_id).await?
|
.update(
|
||||||
// {
|
db,
|
||||||
// // Could have been deleted
|
PartialMessage {
|
||||||
// if let Ok(mut message) = Reference::from_unchecked(&system_message_id)
|
system: message.system.clone(),
|
||||||
// .as_message(db)
|
..Default::default()
|
||||||
// .await
|
},
|
||||||
// {
|
Vec::new(),
|
||||||
// if let Some(SystemMessage::CallStarted { finished_at, .. }) =
|
)
|
||||||
// &mut message.system
|
.await?;
|
||||||
// {
|
} else {
|
||||||
// *finished_at = Some(Timestamp::now_utc());
|
log::error!("Broken State: Call started message ID ({}) does not contain a CallStarted system message.", &message.id)
|
||||||
|
}
|
||||||
// message
|
};
|
||||||
// .update(
|
};
|
||||||
// db,
|
}
|
||||||
// PartialMessage {
|
|
||||||
// system: message.system.clone(),
|
|
||||||
// ..Default::default()
|
|
||||||
// },
|
|
||||||
// Vec::new(),
|
|
||||||
// )
|
|
||||||
// .await?;
|
|
||||||
// } else {
|
|
||||||
// log::error!("Broken State: Call started message ID ({}) does not contain a CallStarted system message.", &message.id)
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
// Audio/video track was started/stopped/unmuted/muted
|
// Audio/video track was started/stopped
|
||||||
"track_published" | "track_unpublished" | "track_unmuted" | "track_muted" => {
|
"track_published" | "track_unpublished" => {
|
||||||
let channel_id = channel_id.to_internal_error()?;
|
let channel_id = channel_id.to_internal_error()?;
|
||||||
let user_id = user_id.to_internal_error()?;
|
let user_id = user_id.to_internal_error()?;
|
||||||
let track = event.track.as_ref().to_internal_error()?;
|
let track = event.track.as_ref().to_internal_error()?;
|
||||||
@@ -254,7 +255,7 @@ pub async fn ingress(
|
|||||||
channel_id,
|
channel_id,
|
||||||
channel.server(),
|
channel.server(),
|
||||||
user_id,
|
user_id,
|
||||||
event.event == "track_published" || event.event == "track_unmuted", // to avoid duplicating this entire case twice
|
event.event == "track_published", // to avoid duplicating this entire case twice
|
||||||
track.source,
|
track.source,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|||||||
+9
-11
@@ -35,7 +35,7 @@ nanoid = "0.4.0"
|
|||||||
serde_json = "1.0.57"
|
serde_json = "1.0.57"
|
||||||
serde = { version = "1.0.115", features = ["derive"] }
|
serde = { version = "1.0.115", features = ["derive"] }
|
||||||
validator = { version = "0.16", features = ["derive"] }
|
validator = { version = "0.16", features = ["derive"] }
|
||||||
iso8601-timestamp = { version = "0.4.0", features = [] }
|
iso8601-timestamp = { version = "0.2.11", features = [] }
|
||||||
|
|
||||||
# async
|
# async
|
||||||
futures = "0.3.8"
|
futures = "0.3.8"
|
||||||
@@ -55,13 +55,13 @@ lettre = "0.10.0-alpha.4"
|
|||||||
|
|
||||||
rocket = { version = "0.5.1", default-features = false, features = ["json"] }
|
rocket = { version = "0.5.1", default-features = false, features = ["json"] }
|
||||||
rocket_cors = { git = "https://github.com/lawliet89/rocket_cors", rev = "072d90359b23e9b291df6b672c07c93de9c46011" }
|
rocket_cors = { git = "https://github.com/lawliet89/rocket_cors", rev = "072d90359b23e9b291df6b672c07c93de9c46011" }
|
||||||
rocket_empty = { version = "0.1.1" }
|
rocket_empty = { version = "0.1.1", features = ["schema"] }
|
||||||
rocket_authifier = { version = "1.0.15" }
|
rocket_authifier = { version = "1.0.15" }
|
||||||
rocket_prometheus = "0.10.0-rc.3"
|
rocket_prometheus = "0.10.0-rc.3"
|
||||||
|
|
||||||
# spec generation
|
# spec generation
|
||||||
utoipa = { version = "5.4.0", features = ["rocket_extras"] }
|
schemars = "0.8.8"
|
||||||
utoipa-scalar = { version = "0.3.0", features = ["rocket"] }
|
revolt_rocket_okapi = { version = "0.10.0", features = ["swagger"] }
|
||||||
|
|
||||||
# rabbit
|
# rabbit
|
||||||
amqprs = { version = "1.7.0" }
|
amqprs = { version = "1.7.0" }
|
||||||
@@ -72,22 +72,20 @@ revolt-config = { path = "../core/config" }
|
|||||||
revolt-database = { path = "../core/database", features = [
|
revolt-database = { path = "../core/database", features = [
|
||||||
"rocket-impl",
|
"rocket-impl",
|
||||||
"redis-is-patched",
|
"redis-is-patched",
|
||||||
"utoipa",
|
|
||||||
"voice",
|
|
||||||
] }
|
] }
|
||||||
revolt-models = { path = "../core/models", features = [
|
revolt-models = { path = "../core/models", features = [
|
||||||
"utoipa",
|
"schemas",
|
||||||
"validator",
|
"validator",
|
||||||
"rocket",
|
"rocket",
|
||||||
] }
|
] }
|
||||||
revolt-presence = { path = "../core/presence" }
|
revolt-presence = { path = "../core/presence" }
|
||||||
revolt-result = { path = "../core/result", features = ["rocket", "utoipa"] }
|
revolt-result = { path = "../core/result", features = ["rocket", "okapi"] }
|
||||||
revolt-permissions = { path = "../core/permissions", features = ["utoipa"] }
|
revolt-permissions = { path = "../core/permissions", features = ["schemas"] }
|
||||||
revolt-ratelimits = { path = "../core/ratelimits", features = ["rocket"] }
|
revolt-ratelimits = { path = "../core/ratelimits", features = ["rocket"] }
|
||||||
|
|
||||||
# voice
|
# voice
|
||||||
livekit-api = "=0.4.4"
|
livekit-api = "0.4.4"
|
||||||
livekit-protocol = "=0.4.0"
|
livekit-protocol = "0.4.0"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
vergen = "7.5.0"
|
vergen = "7.5.0"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate rocket;
|
extern crate rocket;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate utoipa;
|
extern crate revolt_rocket_okapi;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
|
|
||||||
@@ -15,8 +15,6 @@ use revolt_ratelimits::rocket as ratelimiter;
|
|||||||
use rocket::{Build, Rocket};
|
use rocket::{Build, Rocket};
|
||||||
use rocket_cors::{AllowedOrigins, CorsOptions};
|
use rocket_cors::{AllowedOrigins, CorsOptions};
|
||||||
use rocket_prometheus::PrometheusMetrics;
|
use rocket_prometheus::PrometheusMetrics;
|
||||||
use utoipa::OpenApi;
|
|
||||||
use utoipa_scalar::{Scalar, Servable};
|
|
||||||
use std::net::Ipv4Addr;
|
use std::net::Ipv4Addr;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
@@ -77,6 +75,31 @@ pub async fn web() -> Rocket<Build> {
|
|||||||
.to_cors()
|
.to_cors()
|
||||||
.expect("Failed to create CORS.");
|
.expect("Failed to create CORS.");
|
||||||
|
|
||||||
|
// Configure Swagger
|
||||||
|
let swagger = revolt_rocket_okapi::swagger_ui::make_swagger_ui(
|
||||||
|
&revolt_rocket_okapi::swagger_ui::SwaggerUIConfig {
|
||||||
|
url: "/openapi.json".to_owned(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.into();
|
||||||
|
|
||||||
|
let swagger_0_8 = revolt_rocket_okapi::swagger_ui::make_swagger_ui(
|
||||||
|
&revolt_rocket_okapi::swagger_ui::SwaggerUIConfig {
|
||||||
|
url: "/0.8/openapi.json".to_owned(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.into();
|
||||||
|
|
||||||
|
let swagger_0_8 = revolt_rocket_okapi::swagger_ui::make_swagger_ui(
|
||||||
|
&revolt_rocket_okapi::swagger_ui::SwaggerUIConfig {
|
||||||
|
url: "/0.8/openapi.json".to_owned(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.into();
|
||||||
|
|
||||||
// Voice handler
|
// Voice handler
|
||||||
let voice_client = VoiceClient::new(config.api.livekit.nodes.clone());
|
let voice_client = VoiceClient::new(config.api.livekit.nodes.clone());
|
||||||
// Configure Rabbit
|
// Configure Rabbit
|
||||||
@@ -120,7 +143,8 @@ pub async fn web() -> Rocket<Build> {
|
|||||||
.mount("/metrics", prometheus)
|
.mount("/metrics", prometheus)
|
||||||
.mount("/", rocket_cors::catch_all_options_routes())
|
.mount("/", rocket_cors::catch_all_options_routes())
|
||||||
.mount("/", ratelimiter::routes())
|
.mount("/", ratelimiter::routes())
|
||||||
.mount("/", Scalar::with_url("/scalar", routes::ApiDoc::openapi()))
|
.mount("/swagger/", swagger)
|
||||||
|
.mount("/0.8/swagger/", swagger_0_8)
|
||||||
.manage(authifier)
|
.manage(authifier)
|
||||||
.manage(db)
|
.manage(db)
|
||||||
.manage(amqp)
|
.manage(amqp)
|
||||||
|
|||||||
@@ -5,16 +5,10 @@ use rocket::serde::json::Json;
|
|||||||
use rocket::State;
|
use rocket::State;
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
|
||||||
/// Create Bot
|
/// # Create Bot
|
||||||
///
|
///
|
||||||
/// Create a new Revolt bot.
|
/// Create a new Revolt bot.
|
||||||
#[utoipa::path(
|
#[openapi(tag = "Bots")]
|
||||||
tag = "Bots",
|
|
||||||
security(("Session-Token" = [])),
|
|
||||||
responses(
|
|
||||||
(status = 200, body = v0::BotWithUserResponse),
|
|
||||||
),
|
|
||||||
)]
|
|
||||||
#[post("/create", data = "<info>")]
|
#[post("/create", data = "<info>")]
|
||||||
pub async fn create_bot(
|
pub async fn create_bot(
|
||||||
db: &State<Database>,
|
db: &State<Database>,
|
||||||
|
|||||||
@@ -1,18 +1,12 @@
|
|||||||
use revolt_database::{Database, User, util::reference::Reference, voice::{VoiceClient, remove_user_from_voice_channels}};
|
use revolt_database::{util::reference::Reference, voice::{delete_voice_state, get_channel_node, get_user_voice_channels, VoiceClient}, Database, User};
|
||||||
use revolt_result::{create_error, Result};
|
use revolt_result::{create_error, Result};
|
||||||
use rocket::State;
|
use rocket::State;
|
||||||
use rocket_empty::EmptyResponse;
|
use rocket_empty::EmptyResponse;
|
||||||
|
|
||||||
/// Delete Bot
|
/// # Delete Bot
|
||||||
///
|
///
|
||||||
/// Delete a bot by its id.
|
/// Delete a bot by its id.
|
||||||
#[utoipa::path(
|
#[openapi(tag = "Bots")]
|
||||||
tag = "Bots",
|
|
||||||
security(("Session-Token" = [])),
|
|
||||||
responses(
|
|
||||||
(status = 204),
|
|
||||||
),
|
|
||||||
)]
|
|
||||||
#[delete("/<target>")]
|
#[delete("/<target>")]
|
||||||
pub async fn delete_bot(
|
pub async fn delete_bot(
|
||||||
db: &State<Database>,
|
db: &State<Database>,
|
||||||
@@ -27,7 +21,15 @@ pub async fn delete_bot(
|
|||||||
|
|
||||||
bot.delete(db).await?;
|
bot.delete(db).await?;
|
||||||
|
|
||||||
remove_user_from_voice_channels(db, voice_client, &bot.id).await?;
|
for channel_id in get_user_voice_channels(&bot.id).await? {
|
||||||
|
if let Some(node) = get_channel_node(&channel_id).await? {
|
||||||
|
let _ = voice_client.remove_user(&node, &bot.id, &channel_id).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
let channel = Reference::from_unchecked(&channel_id).as_channel(db).await?;
|
||||||
|
|
||||||
|
delete_voice_state(&channel_id, channel.server(), &bot.id).await?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(EmptyResponse)
|
Ok(EmptyResponse)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,16 +6,10 @@ use rocket::State;
|
|||||||
use rocket::serde::json::Json;
|
use rocket::serde::json::Json;
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
|
||||||
/// Edit Bot
|
/// # Edit Bot
|
||||||
///
|
///
|
||||||
/// Edit bot details by its id.
|
/// Edit bot details by its id.
|
||||||
#[utoipa::path(
|
#[openapi(tag = "Bots")]
|
||||||
tag = "Bots",
|
|
||||||
security(("Session-Token" = [])),
|
|
||||||
responses(
|
|
||||||
(status = 200, body = v0::BotWithUserResponse),
|
|
||||||
),
|
|
||||||
)]
|
|
||||||
#[patch("/<target>", data = "<data>")]
|
#[patch("/<target>", data = "<data>")]
|
||||||
pub async fn edit_bot(
|
pub async fn edit_bot(
|
||||||
db: &State<Database>,
|
db: &State<Database>,
|
||||||
@@ -66,8 +60,15 @@ pub async fn edit_bot(
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
bot.update(db, partial, remove.into_iter().map(|v| v.into()).collect())
|
bot.update(
|
||||||
.await?;
|
db,
|
||||||
|
partial,
|
||||||
|
remove
|
||||||
|
.into_iter()
|
||||||
|
.map(|v| v.into())
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(Json(v0::BotWithUserResponse {
|
Ok(Json(v0::BotWithUserResponse {
|
||||||
bot: bot.into(),
|
bot: bot.into(),
|
||||||
|
|||||||
@@ -3,16 +3,10 @@ use revolt_models::v0::FetchBotResponse;
|
|||||||
use revolt_result::{create_error, Result};
|
use revolt_result::{create_error, Result};
|
||||||
use rocket::{serde::json::Json, State};
|
use rocket::{serde::json::Json, State};
|
||||||
|
|
||||||
/// Fetch Bot
|
/// # Fetch Bot
|
||||||
///
|
///
|
||||||
/// Fetch details of a bot you own by its id.
|
/// Fetch details of a bot you own by its id.
|
||||||
#[utoipa::path(
|
#[openapi(tag = "Bots")]
|
||||||
tag = "Bots",
|
|
||||||
security(("Session-Token" = [])),
|
|
||||||
responses(
|
|
||||||
(status = 200, body = FetchBotResponse),
|
|
||||||
),
|
|
||||||
)]
|
|
||||||
#[get("/<bot>")]
|
#[get("/<bot>")]
|
||||||
pub async fn fetch_bot(
|
pub async fn fetch_bot(
|
||||||
db: &State<Database>,
|
db: &State<Database>,
|
||||||
|
|||||||
@@ -5,16 +5,10 @@ use revolt_result::Result;
|
|||||||
use rocket::serde::json::Json;
|
use rocket::serde::json::Json;
|
||||||
use rocket::State;
|
use rocket::State;
|
||||||
|
|
||||||
/// Fetch Owned Bots
|
/// # Fetch Owned Bots
|
||||||
///
|
///
|
||||||
/// Fetch all of the bots that you have control over.
|
/// Fetch all of the bots that you have control over.
|
||||||
#[utoipa::path(
|
#[openapi(tag = "Bots")]
|
||||||
tag = "Bots",
|
|
||||||
security(("Session-Token" = [])),
|
|
||||||
responses(
|
|
||||||
(status = 200, body = OwnedBotsResponse),
|
|
||||||
),
|
|
||||||
)]
|
|
||||||
#[get("/@me")]
|
#[get("/@me")]
|
||||||
pub async fn fetch_owned_bots(db: &State<Database>, user: User) -> Result<Json<OwnedBotsResponse>> {
|
pub async fn fetch_owned_bots(db: &State<Database>, user: User) -> Result<Json<OwnedBotsResponse>> {
|
||||||
let mut bots = db.fetch_bots_by_user(&user.id).await?;
|
let mut bots = db.fetch_bots_by_user(&user.id).await?;
|
||||||
|
|||||||
@@ -5,16 +5,10 @@ use revolt_result::{create_error, Result};
|
|||||||
use rocket::serde::json::Json;
|
use rocket::serde::json::Json;
|
||||||
use rocket::State;
|
use rocket::State;
|
||||||
|
|
||||||
/// Fetch Public Bot
|
/// # Fetch Public Bot
|
||||||
///
|
///
|
||||||
/// Fetch details of a public (or owned) bot by its id.
|
/// Fetch details of a public (or owned) bot by its id.
|
||||||
#[utoipa::path(
|
#[openapi(tag = "Bots")]
|
||||||
tag = "Bots",
|
|
||||||
security(("Session-Token" = []), ()),
|
|
||||||
responses(
|
|
||||||
(status = 200, body = PublicBot),
|
|
||||||
),
|
|
||||||
)]
|
|
||||||
#[get("/<target>/invite")]
|
#[get("/<target>/invite")]
|
||||||
pub async fn fetch_public_bot(
|
pub async fn fetch_public_bot(
|
||||||
db: &State<Database>,
|
db: &State<Database>,
|
||||||
|
|||||||
@@ -11,16 +11,10 @@ use rocket::State;
|
|||||||
use rocket::serde::json::Json;
|
use rocket::serde::json::Json;
|
||||||
use rocket_empty::EmptyResponse;
|
use rocket_empty::EmptyResponse;
|
||||||
|
|
||||||
/// Invite Bot
|
/// # Invite Bot
|
||||||
///
|
///
|
||||||
/// Invite a bot to a server or group by its id.`
|
/// Invite a bot to a server or group by its id.`
|
||||||
#[utoipa::path(
|
#[openapi(tag = "Bots")]
|
||||||
tag = "Bots",
|
|
||||||
security(("Session-Token" = [])),
|
|
||||||
responses(
|
|
||||||
(status = 200, body = v0::BotWithUserResponse),
|
|
||||||
),
|
|
||||||
)]
|
|
||||||
#[post("/<target>/invite", data = "<dest>")]
|
#[post("/<target>/invite", data = "<dest>")]
|
||||||
pub async fn invite_bot(
|
pub async fn invite_bot(
|
||||||
db: &State<Database>,
|
db: &State<Database>,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use revolt_rocket_okapi::revolt_okapi::openapi3::OpenApi;
|
||||||
use rocket::Route;
|
use rocket::Route;
|
||||||
|
|
||||||
mod create;
|
mod create;
|
||||||
@@ -8,22 +9,8 @@ mod fetch_owned;
|
|||||||
mod fetch_public;
|
mod fetch_public;
|
||||||
mod invite;
|
mod invite;
|
||||||
|
|
||||||
#[derive(OpenApi)]
|
pub fn routes() -> (Vec<Route>, OpenApi) {
|
||||||
#[openapi(
|
openapi_get_routes_spec![
|
||||||
paths(
|
|
||||||
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,
|
|
||||||
)
|
|
||||||
)]
|
|
||||||
pub struct ApiDoc;
|
|
||||||
|
|
||||||
pub fn routes() -> Vec<Route> {
|
|
||||||
routes![
|
|
||||||
create::create_bot,
|
create::create_bot,
|
||||||
invite::invite_bot,
|
invite::invite_bot,
|
||||||
fetch_public::fetch_public_bot,
|
fetch_public::fetch_public_bot,
|
||||||
|
|||||||
@@ -7,20 +7,10 @@ use revolt_result::{create_error, Result};
|
|||||||
use rocket::State;
|
use rocket::State;
|
||||||
use rocket_empty::EmptyResponse;
|
use rocket_empty::EmptyResponse;
|
||||||
|
|
||||||
/// Acknowledge Message
|
/// # Acknowledge Message
|
||||||
///
|
///
|
||||||
/// Lets the server and all other clients know that we've seen this message id in this channel.
|
/// Lets the server and all other clients know that we've seen this message id in this channel.
|
||||||
#[utoipa::path(
|
#[openapi(tag = "Messaging")]
|
||||||
tag = "Messaging",
|
|
||||||
security(("Session-Token" = [])),
|
|
||||||
params(
|
|
||||||
("target" = Reference, Path),
|
|
||||||
("message" = Reference, Path),
|
|
||||||
),
|
|
||||||
responses(
|
|
||||||
(status = 204)
|
|
||||||
)
|
|
||||||
)]
|
|
||||||
#[put("/<target>/ack/<message>")]
|
#[put("/<target>/ack/<message>")]
|
||||||
pub async fn ack(
|
pub async fn ack(
|
||||||
db: &State<Database>,
|
db: &State<Database>,
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
use revolt_database::{
|
use revolt_database::{
|
||||||
AMQP, Channel, Database, PartialChannel, User, util::{permissions::DatabasePermissionQuery, reference::Reference}, voice::{
|
util::{permissions::DatabasePermissionQuery, reference::Reference},
|
||||||
VoiceClient, delete_voice_channel, is_in_voice_channel, remove_user_from_voice_channel
|
voice::{
|
||||||
}
|
delete_channel_voice_state, delete_voice_state, get_channel_node, get_voice_channel_members, is_in_voice_channel, VoiceClient
|
||||||
|
},
|
||||||
|
Channel, Database, PartialChannel, User, AMQP,
|
||||||
};
|
};
|
||||||
use revolt_models::v0;
|
use revolt_models::v0;
|
||||||
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
|
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
|
||||||
@@ -9,20 +11,10 @@ use revolt_result::{create_error, Result};
|
|||||||
use rocket::State;
|
use rocket::State;
|
||||||
use rocket_empty::EmptyResponse;
|
use rocket_empty::EmptyResponse;
|
||||||
|
|
||||||
/// Close Channel
|
/// # Close Channel
|
||||||
///
|
///
|
||||||
/// Deletes a server channel, leaves a group or closes a group.
|
/// Deletes a server channel, leaves a group or closes a group.
|
||||||
#[utoipa::path(
|
#[openapi(tag = "Channel Information")]
|
||||||
tag = "Channel Information",
|
|
||||||
security(("Session-Token" = []), ("Bot-Token" = [])),
|
|
||||||
params(
|
|
||||||
("target" = Reference, Path),
|
|
||||||
v0::OptionsChannelDelete,
|
|
||||||
),
|
|
||||||
responses(
|
|
||||||
(status = 204),
|
|
||||||
),
|
|
||||||
)]
|
|
||||||
#[delete("/<target>?<options..>")]
|
#[delete("/<target>?<options..>")]
|
||||||
pub async fn delete(
|
pub async fn delete(
|
||||||
db: &State<Database>,
|
db: &State<Database>,
|
||||||
@@ -65,14 +57,26 @@ pub async fn delete(
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if is_in_voice_channel(&user.id, channel.id()).await? {
|
if is_in_voice_channel(&user.id, channel.id()).await? {
|
||||||
remove_user_from_voice_channel(db, voice_client, channel.id(), &user.id).await?;
|
let node = get_channel_node(channel.id()).await?.unwrap();
|
||||||
|
|
||||||
|
voice_client
|
||||||
|
.remove_user(&node, &user.id, channel.id())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
delete_voice_state(channel.id(), None, &user.id).await?;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
Channel::TextChannel { .. } => {
|
Channel::TextChannel { .. } => {
|
||||||
permissions.throw_if_lacking_channel_permission(ChannelPermission::ManageChannel)?;
|
permissions.throw_if_lacking_channel_permission(ChannelPermission::ManageChannel)?;
|
||||||
channel.delete(db).await?;
|
channel.delete(db).await?;
|
||||||
|
|
||||||
delete_voice_channel(voice_client, channel.id(), channel.server()).await?;
|
if let Some(users) = get_voice_channel_members(channel.id()).await? {
|
||||||
|
let node = get_channel_node(channel.id()).await?.unwrap();
|
||||||
|
|
||||||
|
voice_client.delete_room(&node, channel.id()).await?;
|
||||||
|
|
||||||
|
delete_channel_voice_state(channel.id(), channel.server(), &users).await?;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
use revolt_database::{
|
use revolt_database::{
|
||||||
util::{permissions::DatabasePermissionQuery, reference::Reference},
|
util::{permissions::DatabasePermissionQuery, reference::Reference}, voice::{delete_channel_voice_state, get_channel_node, get_voice_channel_members, VoiceClient}, Channel, Database, File, PartialChannel, SystemMessage, User, AMQP
|
||||||
voice::{delete_voice_channel, VoiceClient},
|
|
||||||
Channel, Database, File, PartialChannel, SystemMessage, User, AMQP,
|
|
||||||
};
|
};
|
||||||
use revolt_models::v0;
|
use revolt_models::v0;
|
||||||
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
|
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
|
||||||
@@ -9,19 +7,10 @@ use revolt_result::{create_error, Result};
|
|||||||
use rocket::{serde::json::Json, State};
|
use rocket::{serde::json::Json, State};
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
|
||||||
/// Edit Channel
|
/// # Edit Channel
|
||||||
///
|
///
|
||||||
/// Edit a channel object by its id.
|
/// Edit a channel object by its id.
|
||||||
#[utoipa::path(
|
#[openapi(tag = "Channel Information")]
|
||||||
tag = "Channel Information",
|
|
||||||
security(("Session-Token" = []), ("Bot-Token" = [])),
|
|
||||||
params(
|
|
||||||
("target" = Reference, Path),
|
|
||||||
),
|
|
||||||
responses(
|
|
||||||
(status = 200, body = v0::Channel),
|
|
||||||
),
|
|
||||||
)]
|
|
||||||
#[patch("/<target>", data = "<data>")]
|
#[patch("/<target>", data = "<data>")]
|
||||||
pub async fn edit(
|
pub async fn edit(
|
||||||
db: &State<Database>,
|
db: &State<Database>,
|
||||||
@@ -264,12 +253,21 @@ pub async fn edit(
|
|||||||
.update(
|
.update(
|
||||||
db,
|
db,
|
||||||
partial,
|
partial,
|
||||||
data.remove.into_iter().map(|f| f.into()).collect(),
|
data.remove
|
||||||
|
.into_iter()
|
||||||
|
.map(|f| f.into())
|
||||||
|
.collect(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if channel.voice().is_none() {
|
if channel.voice().is_none() {
|
||||||
delete_voice_channel(voice_client, channel.id(), channel.server()).await?;
|
if let Some(users) = get_voice_channel_members(channel.id()).await? {
|
||||||
|
let node = get_channel_node(channel.id()).await?.unwrap();
|
||||||
|
|
||||||
|
voice_client.delete_room(&node, channel.id()).await?;
|
||||||
|
|
||||||
|
delete_channel_voice_state(channel.id(), channel.server(), &users).await?;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Json(channel.into()))
|
Ok(Json(channel.into()))
|
||||||
|
|||||||
@@ -8,19 +8,10 @@ use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
|
|||||||
use revolt_result::Result;
|
use revolt_result::Result;
|
||||||
use rocket::{serde::json::Json, State};
|
use rocket::{serde::json::Json, State};
|
||||||
|
|
||||||
/// Fetch Channel
|
/// # Fetch Channel
|
||||||
///
|
///
|
||||||
/// Fetch channel by its id.
|
/// Fetch channel by its id.
|
||||||
#[utoipa::path(
|
#[openapi(tag = "Channel Information")]
|
||||||
tag = "Channel Information",
|
|
||||||
security(("Session-Token" = []), ("Bot-Token" = [])),
|
|
||||||
params(
|
|
||||||
("target" = Reference, Path),
|
|
||||||
),
|
|
||||||
responses(
|
|
||||||
(status = 200, body = v0::Channel),
|
|
||||||
),
|
|
||||||
)]
|
|
||||||
#[get("/<target>")]
|
#[get("/<target>")]
|
||||||
pub async fn fetch(
|
pub async fn fetch(
|
||||||
db: &State<Database>,
|
db: &State<Database>,
|
||||||
|
|||||||
@@ -8,20 +8,10 @@ use revolt_result::{create_error, Result};
|
|||||||
use rocket::State;
|
use rocket::State;
|
||||||
use rocket_empty::EmptyResponse;
|
use rocket_empty::EmptyResponse;
|
||||||
|
|
||||||
/// Add Member to Group
|
/// # Add Member to Group
|
||||||
///
|
///
|
||||||
/// Adds another user to the group.
|
/// Adds another user to the group.
|
||||||
#[utoipa::path(
|
#[openapi(tag = "Groups")]
|
||||||
tag = "Groups",
|
|
||||||
security(("Session-Token" = [])),
|
|
||||||
params(
|
|
||||||
("group_id" = Reference, Path),
|
|
||||||
("member_id" = Reference, Path),
|
|
||||||
),
|
|
||||||
responses(
|
|
||||||
(status = 204),
|
|
||||||
),
|
|
||||||
)]
|
|
||||||
#[put("/<group_id>/recipients/<member_id>")]
|
#[put("/<group_id>/recipients/<member_id>")]
|
||||||
pub async fn add_member(
|
pub async fn add_member(
|
||||||
db: &State<Database>,
|
db: &State<Database>,
|
||||||
|
|||||||
@@ -6,16 +6,10 @@ use rocket::serde::json::Json;
|
|||||||
use rocket::State;
|
use rocket::State;
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
|
||||||
/// Create Group
|
/// # Create Group
|
||||||
///
|
///
|
||||||
/// Create a new group channel.
|
/// Create a new group channel.
|
||||||
#[utoipa::path(
|
#[openapi(tag = "Groups")]
|
||||||
tag = "Groups",
|
|
||||||
security(("Session-Token" = [])),
|
|
||||||
responses(
|
|
||||||
(status = 200, body = v0::Channel),
|
|
||||||
),
|
|
||||||
)]
|
|
||||||
#[post("/create", data = "<data>")]
|
#[post("/create", data = "<data>")]
|
||||||
pub async fn create_group(
|
pub async fn create_group(
|
||||||
db: &State<Database>,
|
db: &State<Database>,
|
||||||
|
|||||||
@@ -1,28 +1,14 @@
|
|||||||
use revolt_database::{
|
use revolt_database::{util::reference::Reference, voice::{delete_voice_state, get_channel_node, is_in_voice_channel, VoiceClient}, Channel, Database, User, AMQP};
|
||||||
util::reference::Reference,
|
|
||||||
voice::{is_in_voice_channel, remove_user_from_voice_channel, VoiceClient},
|
|
||||||
Channel, Database, User, AMQP,
|
|
||||||
};
|
|
||||||
use revolt_permissions::ChannelPermission;
|
use revolt_permissions::ChannelPermission;
|
||||||
use revolt_result::{create_error, Result};
|
use revolt_result::{create_error, Result};
|
||||||
|
|
||||||
use rocket::State;
|
use rocket::State;
|
||||||
use rocket_empty::EmptyResponse;
|
use rocket_empty::EmptyResponse;
|
||||||
|
|
||||||
/// Remove Member from Group
|
/// # Remove Member from Group
|
||||||
///
|
///
|
||||||
/// Removes a user from the group.
|
/// Removes a user from the group.
|
||||||
#[utoipa::path(
|
#[openapi(tag = "Groups")]
|
||||||
tag = "Groups",
|
|
||||||
security(("Session-Token" = [])),
|
|
||||||
params(
|
|
||||||
("target" = Reference, Path),
|
|
||||||
("member" = Reference, Path),
|
|
||||||
),
|
|
||||||
responses(
|
|
||||||
(status = 204),
|
|
||||||
),
|
|
||||||
)]
|
|
||||||
#[delete("/<target>/recipients/<member>")]
|
#[delete("/<target>/recipients/<member>")]
|
||||||
pub async fn remove_member(
|
pub async fn remove_member(
|
||||||
db: &State<Database>,
|
db: &State<Database>,
|
||||||
@@ -38,10 +24,7 @@ pub async fn remove_member(
|
|||||||
|
|
||||||
let channel = target.as_channel(db).await?;
|
let channel = target.as_channel(db).await?;
|
||||||
|
|
||||||
if let Channel::Group {
|
if let Channel::Group { owner, recipients, .. } = &channel {
|
||||||
owner, recipients, ..
|
|
||||||
} = &channel
|
|
||||||
{
|
|
||||||
if &user.id != owner {
|
if &user.id != owner {
|
||||||
return Err(create_error!(MissingPermission {
|
return Err(create_error!(MissingPermission {
|
||||||
permission: ChannelPermission::ManageChannel.to_string()
|
permission: ChannelPermission::ManageChannel.to_string()
|
||||||
@@ -61,11 +44,15 @@ pub async fn remove_member(
|
|||||||
.remove_user_from_group(db, amqp, &member, Some(&user.id), false)
|
.remove_user_from_group(db, amqp, &member, Some(&user.id), false)
|
||||||
.await?;
|
.await?;
|
||||||
} else {
|
} else {
|
||||||
return Err(create_error!(InvalidOperation));
|
return Err(create_error!(InvalidOperation))
|
||||||
};
|
};
|
||||||
|
|
||||||
if is_in_voice_channel(&member.id, channel.id()).await? {
|
if is_in_voice_channel(&user.id, channel.id()).await? {
|
||||||
remove_user_from_voice_channel(db, voice_client, channel.id(), &member.id).await?;
|
if let Some(node) = get_channel_node(channel.id()).await? {
|
||||||
|
let _ = voice_client.remove_user(&node, &user.id, channel.id()).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete_voice_state(channel.id(), None, &user.id).await?;
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(EmptyResponse)
|
Ok(EmptyResponse)
|
||||||
|
|||||||
@@ -8,21 +8,12 @@ use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
|
|||||||
use revolt_result::{create_error, Result};
|
use revolt_result::{create_error, Result};
|
||||||
use rocket::{serde::json::Json, State};
|
use rocket::{serde::json::Json, State};
|
||||||
|
|
||||||
/// Create Invite
|
/// # Create Invite
|
||||||
///
|
///
|
||||||
/// Creates an invite to this channel.
|
/// Creates an invite to this channel.
|
||||||
///
|
///
|
||||||
/// Channel must be a `TextChannel`.
|
/// Channel must be a `TextChannel`.
|
||||||
#[utoipa::path(
|
#[openapi(tag = "Channel Invites")]
|
||||||
tag = "Channel Invites",
|
|
||||||
security(("Session-Token" = [])),
|
|
||||||
params(
|
|
||||||
("target" = Reference, Path),
|
|
||||||
),
|
|
||||||
responses(
|
|
||||||
(status = 200, body = v0::Invite),
|
|
||||||
),
|
|
||||||
)]
|
|
||||||
#[post("/<target>/invites")]
|
#[post("/<target>/invites")]
|
||||||
pub async fn create_invite(
|
pub async fn create_invite(
|
||||||
db: &State<Database>,
|
db: &State<Database>,
|
||||||
|
|||||||
@@ -7,21 +7,12 @@ use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
|
|||||||
use revolt_result::{create_error, Result};
|
use revolt_result::{create_error, Result};
|
||||||
use rocket::{serde::json::Json, State};
|
use rocket::{serde::json::Json, State};
|
||||||
|
|
||||||
/// Fetch Group Members
|
/// # Fetch Group Members
|
||||||
///
|
///
|
||||||
/// Retrieves all users who are part of this group.
|
/// Retrieves all users who are part of this group.
|
||||||
///
|
///
|
||||||
/// This may not return full user information if users are not friends but have mutual connections.
|
/// This may not return full user information if users are not friends but have mutual connections.
|
||||||
#[utoipa::path(
|
#[openapi(tag = "Groups")]
|
||||||
tag = "Groups",
|
|
||||||
security(("Session-Token" = []), ("Bot-Token" = [])),
|
|
||||||
params(
|
|
||||||
("target" = Reference, Path),
|
|
||||||
),
|
|
||||||
responses(
|
|
||||||
(status = 200, body = Vec<v0::User>),
|
|
||||||
),
|
|
||||||
)]
|
|
||||||
#[get("/<target>/members")]
|
#[get("/<target>/members")]
|
||||||
pub async fn fetch_members(
|
pub async fn fetch_members(
|
||||||
db: &State<Database>,
|
db: &State<Database>,
|
||||||
|
|||||||
@@ -10,23 +10,14 @@ use rocket::{serde::json::Json, State};
|
|||||||
use rocket_empty::EmptyResponse;
|
use rocket_empty::EmptyResponse;
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
|
||||||
/// Bulk Delete Messages
|
/// # Bulk Delete Messages
|
||||||
///
|
///
|
||||||
/// Delete multiple messages you've sent or one you have permission to delete.
|
/// Delete multiple messages you've sent or one you have permission to delete.
|
||||||
///
|
///
|
||||||
/// This will always require `ManageMessages` permission regardless of whether you own the message or not.
|
/// This will always require `ManageMessages` permission regardless of whether you own the message or not.
|
||||||
///
|
///
|
||||||
/// Messages must have been sent within the past 1 week.
|
/// Messages must have been sent within the past 1 week.
|
||||||
#[utoipa::path(
|
#[openapi(tag = "Messaging")]
|
||||||
tag = "Messaging",
|
|
||||||
security(("Session-Token" = []), ("Bot-Token" = [])),
|
|
||||||
params(
|
|
||||||
("target" = Reference, Path),
|
|
||||||
),
|
|
||||||
responses(
|
|
||||||
(status = 204),
|
|
||||||
),
|
|
||||||
)]
|
|
||||||
#[delete("/<target>/messages/bulk", data = "<options>", rank = 1)]
|
#[delete("/<target>/messages/bulk", data = "<options>", rank = 1)]
|
||||||
pub async fn bulk_delete_messages(
|
pub async fn bulk_delete_messages(
|
||||||
db: &State<Database>,
|
db: &State<Database>,
|
||||||
|
|||||||
@@ -7,22 +7,12 @@ use revolt_result::Result;
|
|||||||
use rocket::State;
|
use rocket::State;
|
||||||
use rocket_empty::EmptyResponse;
|
use rocket_empty::EmptyResponse;
|
||||||
|
|
||||||
/// Remove All Reactions from Message
|
/// # Remove All Reactions from Message
|
||||||
///
|
///
|
||||||
/// Remove your own, someone else's or all of a given reaction.
|
/// Remove your own, someone else's or all of a given reaction.
|
||||||
///
|
///
|
||||||
/// Requires `ManageMessages` permission.
|
/// Requires `ManageMessages` permission.
|
||||||
#[utoipa::path(
|
#[openapi(tag = "Interactions")]
|
||||||
tag = "Interactions",
|
|
||||||
security(("Session-Token" = []), ("Bot-Token" = [])),
|
|
||||||
params(
|
|
||||||
("target" = Reference, Path),
|
|
||||||
("msg" = Reference, Path),
|
|
||||||
),
|
|
||||||
responses(
|
|
||||||
(status = 204),
|
|
||||||
),
|
|
||||||
)]
|
|
||||||
#[delete("/<target>/messages/<msg>/reactions")]
|
#[delete("/<target>/messages/<msg>/reactions")]
|
||||||
pub async fn clear_reactions(
|
pub async fn clear_reactions(
|
||||||
db: &State<Database>,
|
db: &State<Database>,
|
||||||
@@ -47,7 +37,7 @@ pub async fn clear_reactions(
|
|||||||
reactions: Some(Default::default()),
|
reactions: Some(Default::default()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
vec![],
|
vec![]
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map(|_| EmptyResponse)
|
.map(|_| EmptyResponse)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user