Compare commits

..
88 Commits
Author SHA1 Message Date
Zomatree 5c7eec68d0 chore: add debug logging to voice ingress 2025-10-12 01:12:08 +01:00
Zomatree 94d7320e70 fix(ci): add voice-ingress to compose 2025-10-11 23:46:13 +01:00
Zomatree a42ddcbbfa Merge branch 'main' into feat/livekit 2025-10-11 23:09:07 +01:00
Zomatree 530264f747 chore: add error logging to internal errors 2025-10-11 22:22:11 +01:00
Zomatree ae1d4dd264 fix: dont presume channel has a node 2025-10-09 01:32:17 +01:00
izzy 4789dd4847 fix: include voice-ingress in build 2025-09-30 14:55:31 -05:00
izzy f91d3bbfdd merge: branch 'main' into feat/livekit 2025-09-30 14:52:24 -05:00
Zomatree 5b50532d15 feat: track join time 2025-09-20 05:26:58 +01:00
Zomatree 938480ccec Signed-off-by: Angelo Kontaxis <me@zomatree.live> 2025-09-20 01:58:57 +01:00
Zomatree f422e8e917 Merge remote-tracking branch 'origin' into feat/livekit 2025-09-07 05:39:51 +01:00
Zomatree 1632e3dacf feat: allow users to specify who is notified when starting a call 2025-09-07 05:01:14 +01:00
Zomatree cc9a68b9c5 chore: cleanup 2025-09-07 02:27:50 +01:00
IAmTomahawkx 77a23239ea feat: Send push notifications for dm call start/end 2025-09-06 15:14:48 -07:00
Zomatree d03e6be2eb fix: update migration 2025-08-19 18:56:17 +01:00
Zomatree 0a4d77ffd3 fix: remove VoiceChannel channel type
fix: calculate user overwrites correctly
fix: dont include personal info in livekit user metadata
fix: revoke video permissons on denied publish
fix: add video to default permissions
2025-08-19 17:37:09 +01:00
Zomatree c6f121f585 Merge remote-tracking branch 'origin/main' into feat/livekit 2025-08-18 07:44:52 +01:00
Zomatree 96ddeb79d2 fix: dont set max participants in livekit 2025-08-18 07:28:09 +01:00
Zomatree a36b138978 fix: move call system message to daemon,
check max participants when creating a token to avoid giving tokens but erroring when attempting to join,
check if the channel actually supports voice
2025-08-18 04:58:06 +01:00
Zomatree 860c47fbad feat: - track call length
- move voice and video limits to config
- seperate VoiceInformation into model and db model
- fix build scripts
2025-08-12 01:48:57 +01:00
Zomatree a770d5822a chore: cleanup code 2025-08-04 03:20:08 +01:00
Zomatree 10c9682bba feat: ability to force disconnect existing voice connection 2025-08-04 03:19:53 +01:00
Zomatree eb48975428 chore: bump livekit dep 2025-07-01 20:22:53 +01:00
Zomatree 28ed85a779 Merge branch 'main' into feat/livekit 2025-07-01 20:22:34 +01:00
Zomatree 95ca735b7b fix: get_voice_state has incorrect unique_key and allowed_sources should be lowercase 2025-06-08 02:33:35 +01:00
Zomatree 901ac56154 fix: remove node from channel voice state 2025-06-05 17:26:37 +01:00
Zomatree 3dbf3bb7a0 feat: private livekit nodes 2025-06-05 14:14:19 +01:00
Zomatree 45df5724b3 Merge remote-tracking branch 'origin/main' into livekit 2025-06-02 02:20:04 +01:00
Zomatree ea1c165d80 Merge remote-tracking branch 'origin/main' into livekit 2025-06-02 02:11:51 +01:00
Zomatree 48e0b48ea9 fix: update branch from main 2025-05-01 16:58:35 +01:00
Zomatree 61038ddcf1 Merge remote-tracking branch 'origin/main' into livekit 2025-05-01 16:42:13 +01:00
Zomatree b96f07e615 fix: update branch from main 2025-04-29 15:52:03 +01:00
Zomatree d830fb0911 Merge branch 'main' into livekit 2025-04-29 15:47:16 +01:00
Zomatree cc9ff9a632 fix: add back missing early adopter badge 2025-04-29 15:40:41 +01:00
Zomatree 02f18705f4 fix: dont leak invisible presence to others 2025-04-29 15:40:10 +01:00
Zomatree a841df3b36 fix: show full branch name on github webhook messages 2025-04-29 15:40:10 +01:00
Tom 2a36f5912d feat: Add Mass Mentions to the backend (#394)
* feat: create base of push daemon

Signed-off-by: IAmTomahawkx <iamtomahawkx@gmail.com>

* Add outbound senders

* Make web_push send to rabbit instead (temp stuff)

* feat: stability and friend requests

* make vapid fr stuff not suck

* swap naming of queue

* move pushd into daemons folder

* fix cargo file for move into daemons folder

* feat: probably working fcm push notifs

* comment out fcm webpush stuff since the config keys dont exist

* fix fcm, name queues according to their prod status and configure routing keys

* add pushd to docker

* mix: Remove old code, add stuff to pushd

* fix: lockfile

* feat: update rocket to 5.0.1

* fix: fix queues and ack bugs

* Move rabbit messsage processing into ack queue

* chore: update readme

* chore: optimizations for ack database hits

* pushd flowchart

* misc: update flowchart

* exit dependancy hell

* add rocket_impl flag to authifier

* make the tests file of delta actually compile

* fix: don't silence every push message

* fix: don't silence all messages

* add debug logging for sending data to rabbit from message events

* validate mentions at a server membership level

* put back that import that was actually important

* minor fix to lockfile

* update delta authifier

* feat: proper permissions for push notifications

* add unit test for mention sanitization

* remove local file dependancy on authifier

* update ports to proper defaults

* fixTM the node bindings

* Theoretically configure docker releases for pushd

Signed-off-by: IAmTomahawkx <iamtomahawkx@gmail.com>

* declare exchange in pushd and delta

* fix createbuckets script

Signed-off-by: IAmTomahawkx <iamtomahawkx@gmail.com>

* fix: reference db implementation

Signed-off-by: IAmTomahawkx <iamtomahawkx@gmail.com>

* fix: remove finally redundant code

Signed-off-by: IAmTomahawkx <iamtomahawkx@gmail.com>

* fix: changes

Signed-off-by: IAmTomahawkx <iamtomahawkx@gmail.com>

* fix: other changes

Signed-off-by: IAmTomahawkx <iamtomahawkx@gmail.com>

* fix: make channel name return result

Signed-off-by: IAmTomahawkx <iamtomahawkx@gmail.com>

* Add role mention parsing

* feat: update to mongo 3.1, add member generator.

* integrate mass mentions into pushd

* patch redis-rs with updated versions

* feat: chunk role mentions

* move permission bits to 37/38 to avoid livekit conflict

* change role mention format to <%id>

* fix the lockfile from merge

* fix: PR change requests

* feat: add tests

* fix: i am a dumbass

* fix: tests, again

---------

Signed-off-by: IAmTomahawkx <iamtomahawkx@gmail.com>
2025-04-29 15:40:09 +01:00
izzy a110a087be ci: downgrade lockfile 2025-04-29 15:39:12 +01:00
izzy 21b9164ee7 chore: bump version to 0.8.4
chore: enable webhooks in test builds
2025-04-29 15:39:12 +01:00
ShaksterNano 2f3d1f0c7e feat: add option to send message with missing replies
Signed-off-by: ShaksterNano <54268387+shaksternano@users.noreply.github.com>
2025-04-29 15:39:12 +01:00
Builderb 62204fe50b feat: allow bots to manage emojis (#407)
* Remove bot check on emoji create

Signed-off-by: Builderb <builderbgamer@gmail.com>

* Remove bot check on emoji delete

Signed-off-by: Builderb <builderbgamer@gmail.com>

---------

Signed-off-by: Builderb <builderbgamer@gmail.com>
2025-04-29 15:39:12 +01:00
Bradlee Barnes 773b890012 fix: remove SavedMessages and Group due to given permissions
Signed-off-by: Bradlee Barnes <69256931+StupidRepo@users.noreply.github.com>
2025-04-29 15:39:12 +01:00
Bradlee Barnes 9c3b2fb364 fix: allow message pinning and unpinning for DM-like channels
Signed-off-by: Bradlee Barnes <69256931+StupidRepo@users.noreply.github.com>
2025-04-29 15:39:11 +01:00
Nils Ponsard 5fe19d9815 feat: allow to set an icon when creating a group
Signed-off-by: Nils Ponsard <nilsponsard@gmail.com>
2025-04-29 15:39:11 +01:00
phazeschift c1c5b0fd29 test: add delete server channel test
Signed-off-by: phazeschift <51278042+phazeschift@users.noreply.github.com>
2025-04-29 15:39:11 +01:00
phazeschift 6737100b4b test: add utilities for common setup
Signed-off-by: phazeschift <51278042+phazeschift@users.noreply.github.com>
2025-04-29 15:39:11 +01:00
phazeschift 6f0e80eef9 test: Add tests for invite/fetch
Signed-off-by: phazeschift <51278042+phazeschift@users.noreply.github.com>
2025-04-29 15:39:11 +01:00
IAmTomahawkx bfc6f50ee5 chore: ignore python venv and dev script for user generation
Signed-off-by: IAmTomahawkx <iamtomahawkx@gmail.com>
2025-04-29 15:39:11 +01:00
Paul Makles 7abbad700c fix: include production default value for config 2025-04-29 15:39:11 +01:00
Paul Makles a671ba6308 fix: don't bump the lockfile version 2025-04-29 15:39:11 +01:00
Paul Makles 1d385ef2bc chore: bump version to 0.8.3 2025-04-29 15:39:11 +01:00
Paul Makles 6558e9f7c4 feat: add company information to email footers 2025-04-29 15:39:10 +01:00
Paul Makles 92f4b44cb3 chore: clarify cargo deny / tomls 2025-04-29 15:39:10 +01:00
Paul Makles 370442bf4e feat: add crond container definitions 2025-04-29 15:39:10 +01:00
Paul Makles d54c5e80fa chore: fix version references 2025-04-29 15:39:10 +01:00
Paul Makles a42746c55b feat: file deletion implementation 2025-04-29 15:39:10 +01:00
Paul Makles 9a98c53aff feat: init crond crate 2025-04-29 15:38:13 +01:00
Paul Makles 4ea08a189d fix: match new error type for status code 2025-04-29 15:37:42 +01:00
Paul Makles ae15a0b4c7 fix: don't allow users to time themselves out
closes #376
2025-04-29 15:37:42 +01:00
Paul Makles 9e5fbec9b0 chore: disable LTO because it likely overloads GitHub worker 2025-04-29 15:36:56 +01:00
Paul Makles 0b02c84f04 chore: bump version to 0.8.2 2025-04-29 15:36:56 +01:00
Paul Makles 8bfe3ec6b0 fix: ensure ratelimiter adjusts for version prefix 2025-04-29 15:36:56 +01:00
Paul Makles a7345a7b90 chore: mount API at /0.8/ as well 2025-04-29 15:36:56 +01:00
Paul Makles 12854d0f1b fix: change permission check for fetching channel webhooks 2025-04-29 15:36:56 +01:00
Paul Makles 9a2a7901e7 fix: ensure limit is always at least 1 when sent to database 2025-04-29 15:36:56 +01:00
Paul Makles e8cb0bb88a docs: add some notes on overrides; fix defaults 2025-04-29 15:36:55 +01:00
Paul Makles 343b90b9af refactor: add error messages for Rabbit issues 2025-04-29 15:36:55 +01:00
Paul Makles a4246f39e4 chore: add pushd to debug image script 2025-04-29 15:36:55 +01:00
Zomatree 38380db6e7 refactor: Remove VoiceChannel 2025-04-29 15:05:18 +01:00
Zomatree 091aa4705b fix: use existing node if someone is already in the voice channel 2025-03-25 20:37:06 +00:00
Zomatree 8b5e085232 feat: being moved between voice channels 2025-03-01 22:09:40 +00:00
Zomatree 9c2cc8736c fix: respond with the url when joining a voice channel 2025-02-17 02:11:32 +00:00
Zomatree 951128032b fix: expose list of nodes 2025-02-17 01:48:34 +00:00
Zomatree 367ac887f4 feat: support multiple voice nodes 2025-02-17 01:39:00 +00:00
Zomatree 060b4c43f4 feat: make Channel::server return a reference 2025-02-07 21:35:51 +00:00
Zomatree 2593a4272b chore: switch to ids for parameter 2025-02-02 13:46:45 +00:00
Zomatree 742eb3bb7d fix: remove local testing values from config 2025-01-28 16:06:58 +00:00
Zomatree 41a6de67cd fix: remove locked rocket version 2025-01-28 15:59:58 +00:00
Zomatree c8ae34ba73 feat: implement missing permission syncs 2025-01-28 15:55:21 +00:00
Zomatree c55b5bf75f fix: include voice states in servercreate event
feat: call started system message in dms
2025-01-27 20:46:29 +00:00
Zomatree 9de87a01ab Merge remote-tracking branch 'origin/main' into livekit 2025-01-26 20:36:40 +00:00
Zomatree 2f7294705e feat(livekit): push unfinished code 2024-10-13 20:23:00 +01:00
Zomatree 120ca449b8 feat(livekit): more permission handling 2024-07-23 04:07:34 +01:00
Zomatree ffbc899792 split voice ops into its own library 2024-05-13 14:09:08 +01:00
Zomatree 675c7d6c0d fix redis key inconsistancy 2024-04-24 02:29:31 +01:00
Zomatree 67569f932e more stuff 2024-04-24 02:23:52 +01:00
Zomatree 7a4421089c track current state 2024-04-11 01:07:03 +01:00
Zomatree ae87bc0b67 move voice states to global 2024-04-09 22:36:48 +01:00
Zomatree ad0dcad497 initial livekit support
fix up code

undo changes to compose file

add back .env.example
2024-04-09 21:21:52 +01:00
128 changed files with 894 additions and 32335 deletions
+37
View File
@@ -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
+4 -7
View File
@@ -5,22 +5,19 @@ on:
tags: tags:
- "*" - "*"
pull_request: pull_request:
branches:
- "main"
paths: paths:
- "Dockerfile" - "Dockerfile"
workflow_dispatch:
permissions: permissions:
contents: read contents: read
packages: write packages: write
concurrency:
group: ${{ github.head_ref || github.ref }}
cancel-in-progress: true
jobs: jobs:
base: base:
name: Test base image build name: Test base image build
runs-on: arc-runner-set runs-on: ubuntu-latest
if: github.event_name == 'pull_request' if: github.event_name == 'pull_request'
steps: steps:
# Configure build environment # Configure build environment
@@ -41,7 +38,7 @@ jobs:
cache-to: type=gha,scope=buildx-base-multi-arch,mode=max cache-to: type=gha,scope=buildx-base-multi-arch,mode=max
publish: publish:
runs-on: arc-runner-set runs-on: self-hosted
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
name: Publish Docker images name: Publish Docker images
steps: steps:
-29
View File
@@ -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
-54
View File
@@ -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
-60
View File
@@ -1,60 +0,0 @@
name: Release Please
on:
push:
branches: [main] # updates/opens the release PR when commits land on main
workflow_dispatch:
permissions:
contents: write
pull-requests: write
id-token: write
concurrency:
group: release-please
cancel-in-progress: true
jobs:
release-please:
name: Release Please
runs-on: ubuntu-latest
outputs:
release_created: ${{ steps.rp.outputs.release_created }}
tag_name: ${{ steps.rp.outputs.tag_name }}
steps:
- id: app-token
uses: actions/create-github-app-token@v2
with:
app-id: ${{ secrets.GH_STOAT_RELEASE_APP_ID }}
private-key: ${{ secrets.GH_STOAT_RELEASE_APP_PRIVATE_KEY }}
- id: rp
uses: googleapis/release-please-action@v4
with:
token: ${{ steps.app-token.outputs.token }}
config-file: release-please-config.json
publish-please:
name: Publish Please
needs: release-please
if: needs.release-please.outputs.release_created == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- id: app-token
uses: actions/create-github-app-token@v2
with:
app-id: ${{ secrets.GH_STOAT_RELEASE_APP_ID }}
private-key: ${{ secrets.GH_STOAT_RELEASE_APP_PRIVATE_KEY }}
- name: Send release notification webhook
run: |
RELEASE_URL="https://github.com/revoltchat/backend/releases/tag/${{ needs.release-please.outputs.tag_name }}"
curl -X POST "${{ secrets.STOAT_WEBHOOK_UPDATES_URL }}" \
-H "Content-Type: application/json" \
-d "{\"content\": \"$RELEASE_URL\"}"
- name: Publish crates
uses: katyo/publish-crates@v2
with:
registry-token: ${{ secrets.CRATES_IO_PUBLISH_TOKEN }}
+26 -25
View File
@@ -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 || github.ref }}
cancel-in-progress: true
env: env:
CARGO_TERM_COLOR: always CARGO_TERM_COLOR: always
@@ -15,28 +19,25 @@ env:
jobs: jobs:
check: check:
name: Rust project name: Rust project
runs-on: arc-runner-set runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - uses: actions/checkout@v2
# Using our own runners for now:
# - 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: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1 uses: actions-rs/toolchain@v1
with: with:
toolchain: stable toolchain: stable
override: true
components: rustfmt, clippy components: rustfmt, clippy
- name: Install cargo-nextest - name: Install cargo-nextest
uses: taiki-e/install-action@a58ae9526b2c3acee9ad8e1926b950b7863305d4 # 2.65.16 uses: baptiste0928/cargo-install@v1
with: with:
tool: cargo-nextest@0.9.119 crate: cargo-nextest
locked: true
- name: Run cargo build - name: Run cargo build
run: cargo build uses: actions-rs/cargo@v1
with:
command: build
- name: Run services in background - name: Run services in background
run: | run: |
@@ -64,17 +65,17 @@ jobs:
- name: Wait for API to go up - name: Wait for API to go up
if: github.event_name != 'pull_request' && github.ref_name == 'main' if: github.event_name != 'pull_request' && github.ref_name == 'main'
uses: nev7n/wait_for_response@7fef3c1a6e8939d0b09062f14fec50d3c5d15fa1 # v1.0.1 uses: nev7n/wait_for_response@v1
with: with:
url: "http://localhost:14702/" url: "http://localhost:14702/"
- name: Checkout API repository - name: Checkout API repository
if: github.event_name != 'pull_request' && github.ref_name == 'main' if: github.event_name != 'pull_request' && github.ref_name == 'main'
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 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'
@@ -82,10 +83,10 @@ jobs:
- name: Commit changes - name: Commit changes
if: github.event_name != 'pull_request' && github.ref_name == 'main' if: github.event_name != 'pull_request' && github.ref_name == 'main'
uses: EndBug/add-and-commit@a94899bca583c204427a224a7af87c02f9b325d5 # v9.1.4 uses: EndBug/add-and-commit@v4
with: with:
cwd: "api" cwd: "api"
add: "*.json" add: "*.json"
author_name: Stoat CI author_name: Revolt CI
author_email: stoat-ci@users.noreply.github.com author_email: revolt-ci@users.noreply.github.com
message: "chore: generate OpenAPI specification" message: "chore: generate OpenAPI specification"
+54
View File
@@ -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
+79
View File
@@ -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
-20
View File
@@ -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
View File
@@ -12,4 +12,5 @@ venv/
.DS_Store .DS_Store
livekit.yml livekit.yml
.idea .idea
start
-3
View File
@@ -1,3 +0,0 @@
{
".": "0.9.1"
}
-51
View File
@@ -1,51 +0,0 @@
# Changelog
## [0.9.1](https://github.com/stoatchat/stoatchat/compare/v0.9.0...v0.9.1) (2026-01-10)
### Bug Fixes
* **ci:** pipeline fixes (marked as fix to force release) ([#483](https://github.com/stoatchat/stoatchat/issues/483)) ([303e52b](https://github.com/stoatchat/stoatchat/commit/303e52b476585eea81c33837f1b01506ce387684))
## [0.9.0](https://github.com/stoatchat/stoatchat/compare/v0.8.8...v0.9.0) (2026-01-10)
### Features
* add id field to role ([#470](https://github.com/stoatchat/stoatchat/issues/470)) ([2afea56](https://github.com/stoatchat/stoatchat/commit/2afea56e56017f02de98e67316b4457568ad5b26))
* add ratelimits to gifbox ([1542047](https://github.com/stoatchat/stoatchat/commit/154204742d21cbeff6e2577b00f50b495ea44631))
* include groups and dms in fetch mutuals ([caa8607](https://github.com/stoatchat/stoatchat/commit/caa86074680d46223cebc20f41e9c91c41ec825d))
* include member payload in ServerMemberJoin event ([480f210](https://github.com/stoatchat/stoatchat/commit/480f210ce85271e13d1dac58a5dae08de108579d))
* initial work on tenor gif searching ([b0c977b](https://github.com/stoatchat/stoatchat/commit/b0c977b324b8144c1152589546eb8fec5954c3e7))
* make message lexer use unowned string ([1561481](https://github.com/stoatchat/stoatchat/commit/1561481eb4cdc0f385fbf0a81e4950408050e11f))
* ready payload field customisation ([db57706](https://github.com/stoatchat/stoatchat/commit/db577067948f13e830b5fb773034e9713a1abaff))
* require auth for search ([b5cd5e3](https://github.com/stoatchat/stoatchat/commit/b5cd5e30ef7d5e56e8964fb7c543965fa6bf5a4a))
* trending and categories routes ([5885e06](https://github.com/stoatchat/stoatchat/commit/5885e067a627b8fff1c8ce2bf9e852ff8cf3f07a))
* voice chats v2 ([#414](https://github.com/stoatchat/stoatchat/issues/414)) ([d567155](https://github.com/stoatchat/stoatchat/commit/d567155f124e4da74115b1a8f810062f7c6559d9))
### Bug Fixes
* add license to revolt-parser ([5335124](https://github.com/stoatchat/stoatchat/commit/53351243064cac8d499dd74284be73928fa78a43))
* allow for disabling default features ([65fbd36](https://github.com/stoatchat/stoatchat/commit/65fbd3662462aed1333b79e59155fa6377e83fcc))
* apple music to use original url instead of metadata url ([bfe4018](https://github.com/stoatchat/stoatchat/commit/bfe4018e436a4075bae780dd4d35a9b58315e12f))
* apply uname fix to january and autumn ([8f9015a](https://github.com/stoatchat/stoatchat/commit/8f9015a6ff181d208d9269ab8691bd417d39811a))
* **ci:** publish images under stoatchat and remove docker hub ([d65c1a1](https://github.com/stoatchat/stoatchat/commit/d65c1a1ab3bdc7e5684b03f280af77d881661a3d))
* correct miniz_oxide in lockfile ([#478](https://github.com/stoatchat/stoatchat/issues/478)) ([5d27a91](https://github.com/stoatchat/stoatchat/commit/5d27a91e901dd2ea3e860aeaed8468db6c5f3214))
* correct shebang for try-tag-and-release ([050ba16](https://github.com/stoatchat/stoatchat/commit/050ba16d4adad5d0fb247867aa3e94e3d42bd12d))
* correct string_cache in lockfile ([#479](https://github.com/stoatchat/stoatchat/issues/479)) ([0b178fc](https://github.com/stoatchat/stoatchat/commit/0b178fc791583064bf9ca94b1d39b42d021e1d79))
* don't remove timeouts when a member leaves a server ([#409](https://github.com/stoatchat/stoatchat/issues/409)) ([e635bc2](https://github.com/stoatchat/stoatchat/commit/e635bc23ec857d648d5705e1a3875d7bc3402b0d))
* don't update the same field while trying to remove it ([f4ee35f](https://github.com/stoatchat/stoatchat/commit/f4ee35fb093ca49f0a64ff4b17fd61587df28145)), closes [#392](https://github.com/stoatchat/stoatchat/issues/392)
* github webhook incorrect payload and formatting ([#468](https://github.com/stoatchat/stoatchat/issues/468)) ([dc9c82a](https://github.com/stoatchat/stoatchat/commit/dc9c82aa4e9667ea6639256c65ac8de37a24d1f7))
* implement Serialize to ClientMessage ([dea0f67](https://github.com/stoatchat/stoatchat/commit/dea0f675dde7a63c7a59b38d469f878b7a8a3af4))
* newly created roles should be ranked the lowest ([947eb15](https://github.com/stoatchat/stoatchat/commit/947eb15771ed6785b3dcd16c354c03ded5e4cbe0))
* permit empty `remove` array in edit requests ([6ad3da5](https://github.com/stoatchat/stoatchat/commit/6ad3da5f35f989a2e7d8e29718b98374248e76af))
* preserve order of replies in message ([#447](https://github.com/stoatchat/stoatchat/issues/447)) ([657a3f0](https://github.com/stoatchat/stoatchat/commit/657a3f08e5d652814bbf0647e089ed9ebb139bbf))
* prevent timing out members which have TimeoutMembers permission ([e36fc97](https://github.com/stoatchat/stoatchat/commit/e36fc9738bac0de4f3fcbccba521f1e3754f7ae7))
* relax settings name regex ([3a34159](https://github.com/stoatchat/stoatchat/commit/3a3415915f0d0fdce1499d47a2b7fa097f5946ea))
* remove authentication tag bytes from attachment download ([32e6600](https://github.com/stoatchat/stoatchat/commit/32e6600272b885c595c094f0bc69459250220dcb))
* rename openapi operation ids ([6048587](https://github.com/stoatchat/stoatchat/commit/6048587d348fbca0dc3a9b47690c56df8fece576)), closes [#406](https://github.com/stoatchat/stoatchat/issues/406)
* respond with 201 if no body in requests ([#465](https://github.com/stoatchat/stoatchat/issues/465)) ([24fedf8](https://github.com/stoatchat/stoatchat/commit/24fedf8c4d9cd3160bdec97aa451520f8beaa739))
* swap to using reqwest for query building ([38dd4d1](https://github.com/stoatchat/stoatchat/commit/38dd4d10797b3e6e397fc219e818f379bdff19f2))
* use `trust_cloudflare` config value instead of env var ([cc7a796](https://github.com/stoatchat/stoatchat/commit/cc7a7962a882e1627fcd0bc75858a017415e8cfc))
* use our own result types instead of tenors types ([a92152d](https://github.com/stoatchat/stoatchat/commit/a92152d86da136997817e797c7af8e38731cdde8))
Generated
+17 -18
View File
@@ -6519,7 +6519,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-autumn" name = "revolt-autumn"
version = "0.8.8" version = "0.8.9"
dependencies = [ dependencies = [
"axum", "axum",
"axum-macros", "axum-macros",
@@ -6557,7 +6557,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-bonfire" name = "revolt-bonfire"
version = "0.8.8" version = "0.8.9"
dependencies = [ dependencies = [
"async-channel 2.5.0", "async-channel 2.5.0",
"async-std", "async-std",
@@ -6588,7 +6588,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-coalesced" name = "revolt-coalesced"
version = "0.8.8" version = "0.8.9"
dependencies = [ dependencies = [
"indexmap 2.11.4", "indexmap 2.11.4",
"lru 0.7.8", "lru 0.7.8",
@@ -6597,7 +6597,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-config" name = "revolt-config"
version = "0.8.8" version = "0.8.9"
dependencies = [ dependencies = [
"async-std", "async-std",
"cached", "cached",
@@ -6614,7 +6614,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-crond" name = "revolt-crond"
version = "0.8.8" version = "0.8.9"
dependencies = [ dependencies = [
"log", "log",
"revolt-config", "revolt-config",
@@ -6626,7 +6626,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-database" name = "revolt-database"
version = "0.8.8" version = "0.8.9"
dependencies = [ dependencies = [
"amqprs", "amqprs",
"async-lock 2.8.0", "async-lock 2.8.0",
@@ -6676,7 +6676,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-delta" name = "revolt-delta"
version = "0.8.8" version = "0.8.9"
dependencies = [ dependencies = [
"amqprs", "amqprs",
"async-channel 1.9.0", "async-channel 1.9.0",
@@ -6726,7 +6726,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-files" name = "revolt-files"
version = "0.8.8" version = "0.8.9"
dependencies = [ dependencies = [
"aes-gcm", "aes-gcm",
"aws-config", "aws-config",
@@ -6749,7 +6749,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-gifbox" name = "revolt-gifbox"
version = "0.8.8" version = "0.8.9"
dependencies = [ dependencies = [
"axum", "axum",
"axum-extra", "axum-extra",
@@ -6771,7 +6771,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-january" name = "revolt-january"
version = "0.8.8" version = "0.8.9"
dependencies = [ dependencies = [
"async-recursion", "async-recursion",
"axum", "axum",
@@ -6799,7 +6799,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-models" name = "revolt-models"
version = "0.8.8" version = "0.8.9"
dependencies = [ dependencies = [
"indexmap 1.9.3", "indexmap 1.9.3",
"iso8601-timestamp", "iso8601-timestamp",
@@ -6818,14 +6818,14 @@ dependencies = [
[[package]] [[package]]
name = "revolt-parser" name = "revolt-parser"
version = "0.8.8" version = "0.8.9"
dependencies = [ dependencies = [
"logos", "logos",
] ]
[[package]] [[package]]
name = "revolt-permissions" name = "revolt-permissions"
version = "0.8.8" version = "0.8.9"
dependencies = [ dependencies = [
"async-std", "async-std",
"async-trait", "async-trait",
@@ -6840,7 +6840,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-presence" name = "revolt-presence"
version = "0.8.8" version = "0.8.9"
dependencies = [ dependencies = [
"async-std", "async-std",
"log", "log",
@@ -6852,7 +6852,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-pushd" name = "revolt-pushd"
version = "0.8.8" version = "0.8.9"
dependencies = [ dependencies = [
"amqprs", "amqprs",
"anyhow", "anyhow",
@@ -6880,7 +6880,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-ratelimits" name = "revolt-ratelimits"
version = "0.8.8" version = "0.8.9"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"authifier", "authifier",
@@ -6897,7 +6897,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-result" name = "revolt-result"
version = "0.8.8" version = "0.8.9"
dependencies = [ dependencies = [
"axum", "axum",
"log", "log",
@@ -6905,7 +6905,6 @@ dependencies = [
"revolt_rocket_okapi", "revolt_rocket_okapi",
"rocket", "rocket",
"schemars 0.8.22", "schemars 0.8.22",
"sentry",
"serde", "serde",
"serde_json", "serde_json",
"utoipa", "utoipa",
+3
View File
@@ -37,6 +37,9 @@ The services and libraries that power the Revolt service.<br/>
Rust 1.86.0 or higher. Rust 1.86.0 or higher.
> [!CAUTION]
> The events server has a significant performance regression between Rust 1.77.2 and 1.78.0 onwards, see [issue #341](https://github.com/revoltchat/backend/issues/341). This is currently solved by build time options but we are looking for a proper fix.
## Development Guide ## Development Guide
Before contributing, make yourself familiar with [our contribution guidelines](https://developers.revolt.chat/contrib.html) and the [technical documentation for this project](https://revoltchat.github.io/backend/). Before contributing, make yourself familiar with [our contribution guidelines](https://developers.revolt.chat/contrib.html) and the [technical documentation for this project](https://revoltchat.github.io/backend/).
+1 -1
View File
@@ -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]
+73
View File
@@ -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.
-7
View File
@@ -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
+3 -3
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-bonfire" name = "revolt-bonfire"
version = "0.9.1" version = "0.8.9"
license = "AGPL-3.0-or-later" license = "AGPL-3.0-or-later"
edition = "2021" edition = "2021"
@@ -41,8 +41,8 @@ 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 = { 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"] }
# redis # redis
+29 -53
View File
@@ -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
+14 -1
View File
@@ -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());
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-coalesced" name = "revolt-coalesced"
version = "0.9.1" version = "0.8.9"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
authors = ["Paul Makles <me@insrt.uk>", "Zomatree <me@zomatree.live>"] authors = ["Paul Makles <me@insrt.uk>", "Zomatree <me@zomatree.live>"]
+2 -2
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-config" name = "revolt-config"
version = "0.9.1" version = "0.8.9"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
authors = ["Paul Makles <me@insrt.uk>"] authors = ["Paul Makles <me@insrt.uk>"]
@@ -37,4 +37,4 @@ sentry = { version = "0.31.5", optional = true }
sentry-anyhow = { version = "0.38.1", optional = true } sentry-anyhow = { version = "0.38.1", optional = true }
# Core # Core
revolt-result = { version = "0.9.1", path = "../result", optional = true } revolt-result = { version = "0.8.9", path = "../result", optional = true }
+1 -1
View File
@@ -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
+10 -11
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-database" name = "revolt-database"
version = "0.9.1" version = "0.8.9"
edition = "2021" edition = "2021"
license = "AGPL-3.0-or-later" license = "AGPL-3.0-or-later"
authors = ["Paul Makles <me@insrt.uk>"] authors = ["Paul Makles <me@insrt.uk>"]
@@ -18,26 +18,25 @@ async-std-runtime = ["async-std", "authifier/async-std-runtime"]
rocket-impl = ["rocket", "schemars", "revolt_okapi", "revolt_rocket_okapi", "authifier/rocket_impl"] 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"]
[dependencies] [dependencies]
# Core # Core
revolt-config = { version = "0.9.1", path = "../config", features = [ revolt-config = { version = "0.8.9", path = "../config", features = [
"report-macros", "report-macros",
] } ] }
revolt-result = { version = "0.9.1", path = "../result" } revolt-result = { version = "0.8.9", path = "../result" }
revolt-models = { version = "0.9.1", path = "../models", features = [ revolt-models = { version = "0.8.9", path = "../models", features = [
"validator", "validator",
] } ] }
revolt-presence = { version = "0.9.1", path = "../presence" } revolt-presence = { version = "0.8.9", path = "../presence" }
revolt-permissions = { version = "0.9.1", path = "../permissions", features = [ revolt-permissions = { version = "0.8.9", path = "../permissions", features = [
"serde", "serde",
"bson", "bson",
] } ] }
revolt-parser = { version = "0.9.1", path = "../parser" } revolt-parser = { version = "0.8.9", path = "../parser" }
# Utility # Utility
log = "0.4" log = "0.4"
@@ -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"] }
@@ -47,7 +47,6 @@
], ],
"roles": { "roles": {
"__ID:5__": { "__ID:5__": {
"_id": "__ID:5__",
"name": "Moderator", "name": "Moderator",
"permissions": { "permissions": {
"a": 545270208, "a": 545270208,
@@ -56,7 +55,6 @@
"rank": 1 "rank": 1
}, },
"__ID:6__": { "__ID:6__": {
"_id": "__ID:6__",
"name": "Owner", "name": "Owner",
"permissions": { "permissions": {
"a": 0, "a": 0,
+1 -3
View File
@@ -295,9 +295,7 @@ pub enum EventV1 {
}, },
UserMoveVoiceChannel { UserMoveVoiceChannel {
node: String, node: String,
from: String, token: String
to: String,
token: String,
} }
} }
-1
View File
@@ -112,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;
@@ -25,7 +25,7 @@ struct MigrationInfo {
revision: i32, revision: i32,
} }
pub const LATEST_REVISION: i32 = 50; // MUST BE +1 to last migration pub const LATEST_REVISION: i32 = 48; // MUST BE +1 to last migration
pub async fn migrate_database(db: &MongoDb) { pub async fn migrate_database(db: &MongoDb) {
let migrations = db.col::<Document>("migrations"); let migrations = db.col::<Document>("migrations");
@@ -1246,8 +1246,8 @@ pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 {
.expect("Failed to update voice channels"); .expect("Failed to update voice channels");
}; };
if revision <= 48 { if revision <= 47 {
info!("Running migration [revision 48 / 22-10-2025]: Add Video + Listen to default permissions"); info!("Running migration [revision 47 / 29-04-2025]: Add Video to default permissions");
db.col::<Document>("servers") db.col::<Document>("servers")
.update_many( .update_many(
@@ -1255,7 +1255,7 @@ pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 {
doc! { doc! {
"$bit": { "$bit": {
"default_permissions": { "default_permissions": {
"or": (ChannelPermission::Video + ChannelPermission::Speak + ChannelPermission::Listen) as i64 "or": ChannelPermission::Video as i64
}, },
} }
} }
@@ -1264,48 +1264,6 @@ pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 {
.expect("Failed to update default_permissions"); .expect("Failed to update default_permissions");
}; };
if revision <= 49 {
info!("Running migration [revision 49 / 12-12-2025]: Add _id key to roles");
#[derive(Serialize, Deserialize, Clone)]
struct Server {
#[serde(rename = "_id")]
pub id: String,
#[serde(default = "HashMap::<String, Document>::new")]
pub roles: HashMap<String, Document>,
}
let mut servers = db
.db()
.collection::<Server>("servers")
.find(doc! {
"roles": {
"$exists": true,
"$ne": {}
}
})
.await
.unwrap()
.map(|res| res.expect("Failed to decode Server { id, roles }"));
while let Some(server) = servers.next().await {
let mut doc = doc! {};
for id in server.roles.keys() {
doc.insert(
format!("roles.{id}._id"),
id,
);
}
db.db()
.collection::<Server>("servers")
.update_one(doc! { "_id": &server.id }, doc! { "$set": doc })
.await
.unwrap();
}
};
// Reminder to update LATEST_REVISION when adding new migrations. // Reminder to update LATEST_REVISION when adding new migrations.
LATEST_REVISION.max(revision) LATEST_REVISION.max(revision)
} }
@@ -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
@@ -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,
@@ -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",
}) })
} }
} }
@@ -68,9 +68,6 @@ auto_derived_partial!(
auto_derived_partial!( auto_derived_partial!(
/// Role /// Role
pub struct Role { pub struct Role {
/// Unique Id
#[serde(rename = "_id")]
pub id: String,
/// Role name /// Role name
pub name: String, pub name: String,
/// Permissions available to this role /// Permissions available to this role
@@ -249,6 +246,7 @@ impl Server {
role.update( role.update(
db, db,
&self.id, &self.id,
role_id,
PartialRole { PartialRole {
permissions: Some(permissions), permissions: Some(permissions),
..Default::default() ..Default::default()
@@ -299,7 +297,6 @@ impl Role {
/// Into optional struct /// Into optional struct
pub fn into_optional(self) -> PartialRole { pub fn into_optional(self) -> PartialRole {
PartialRole { PartialRole {
id: Some(self.id),
name: Some(self.name), name: Some(self.name),
permissions: Some(self.permissions), permissions: Some(self.permissions),
colour: self.colour, colour: self.colour,
@@ -309,29 +306,20 @@ impl Role {
} }
/// Create a role /// Create a role
pub async fn create(db: &Database, server: &Server, name: String) -> Result<Self> { pub async fn create(&self, db: &Database, server_id: &str) -> Result<String> {
let role = Role { let role_id = Ulid::new().to_string();
id: Ulid::new().to_string(), db.insert_role(server_id, &role_id, self).await?;
name,
// Rank of the new role should be below the lowest role
rank: server.roles.len() as i64,
colour: None,
hoist: false,
permissions: Default::default(),
};
db.insert_role(&server.id, &role).await?;
EventV1::ServerRoleUpdate { EventV1::ServerRoleUpdate {
id: server.id.clone(), id: server_id.to_string(),
role_id: role.id.clone(), role_id: role_id.to_string(),
data: role.clone().into_optional().into(), data: self.clone().into_optional().into(),
clear: vec![], clear: vec![],
} }
.p(server.id.clone()) .p(server_id.to_string())
.await; .await;
Ok(role) Ok(role_id)
} }
/// Update server data /// Update server data
@@ -339,6 +327,7 @@ impl Role {
&mut self, &mut self,
db: &Database, db: &Database,
server_id: &str, server_id: &str,
role_id: &str,
partial: PartialRole, partial: PartialRole,
remove: Vec<FieldsRole>, remove: Vec<FieldsRole>,
) -> Result<()> { ) -> Result<()> {
@@ -348,14 +337,14 @@ impl Role {
self.apply_options(partial.clone()); self.apply_options(partial.clone());
db.update_role(server_id, &self.id, &partial, remove.clone()) db.update_role(server_id, role_id, &partial, remove.clone())
.await?; .await?;
EventV1::ServerRoleUpdate { EventV1::ServerRoleUpdate {
id: server_id.to_string(), id: server_id.to_string(),
role_id: self.id.clone(), role_id: role_id.to_string(),
data: partial.into(), data: partial.into(),
clear: remove.into_iter().map(Into::into).collect(), clear: vec![],
} }
.p(server_id.to_string()) .p(server_id.to_string())
.await; .await;
@@ -371,15 +360,15 @@ impl Role {
} }
/// Delete a role /// Delete a role
pub async fn delete(self, db: &Database, server_id: &str) -> Result<()> { pub async fn delete(self, db: &Database, server_id: &str, role_id: &str) -> Result<()> {
EventV1::ServerRoleDelete { EventV1::ServerRoleDelete {
id: server_id.to_string(), id: server_id.to_string(),
role_id: self.id.clone(), role_id: role_id.to_string(),
} }
.p(server_id.to_string()) .p(server_id.to_string())
.await; .await;
db.delete_role(server_id, &self.id).await db.delete_role(server_id, role_id).await
} }
} }
@@ -29,7 +29,7 @@ pub trait AbstractServers: Sync + Send {
async fn delete_server(&self, id: &str) -> Result<()>; async fn delete_server(&self, id: &str) -> Result<()>;
/// Insert a new role into server object /// Insert a new role into server object
async fn insert_role(&self, server_id: &str, role: &Role) -> Result<()>; async fn insert_role(&self, server_id: &str, role_id: &str, role: &Role) -> Result<()>;
/// Update an existing role on a server /// Update an existing role on a server
async fn update_role( async fn update_role(
@@ -69,7 +69,7 @@ impl AbstractServers for MongoDb {
} }
/// Insert a new role into server object /// Insert a new role into server object
async fn insert_role(&self, server_id: &str, role: &Role) -> Result<()> { async fn insert_role(&self, server_id: &str, role_id: &str, role: &Role) -> Result<()> {
self.col::<Document>(COL) self.col::<Document>(COL)
.update_one( .update_one(
doc! { doc! {
@@ -77,7 +77,7 @@ impl AbstractServers for MongoDb {
}, },
doc! { doc! {
"$set": { "$set": {
"roles.".to_owned() + &role.id: to_document(role) "roles.".to_owned() + role_id: to_document(role)
.map_err(|_| create_database_error!("to_document", "role"))? .map_err(|_| create_database_error!("to_document", "role"))?
} }
}, },
@@ -72,10 +72,10 @@ impl AbstractServers for ReferenceDb {
} }
/// Insert a new role into server object /// Insert a new role into server object
async fn insert_role(&self, server_id: &str, role: &Role) -> Result<()> { async fn insert_role(&self, server_id: &str, role_id: &str, role: &Role) -> Result<()> {
let mut servers = self.servers.lock().await; let mut servers = self.servers.lock().await;
if let Some(server) = servers.get_mut(server_id) { if let Some(server) = servers.get_mut(server_id) {
server.roles.insert(role.id.clone(), role.clone()); server.roles.insert(role_id.to_string(), role.clone());
Ok(()) Ok(())
} else { } else {
Err(create_error!(NotFound)) Err(create_error!(NotFound))
@@ -896,7 +896,6 @@ impl From<SystemMessageChannels> for crate::SystemMessageChannels {
impl From<crate::Role> for Role { impl From<crate::Role> for Role {
fn from(value: crate::Role) -> Self { fn from(value: crate::Role) -> Self {
Role { Role {
id: value.id,
name: value.name, name: value.name,
permissions: value.permissions, permissions: value.permissions,
colour: value.colour, colour: value.colour,
@@ -909,7 +908,6 @@ impl From<crate::Role> for Role {
impl From<Role> for crate::Role { impl From<Role> for crate::Role {
fn from(value: Role) -> crate::Role { fn from(value: Role) -> crate::Role {
crate::Role { crate::Role {
id: value.id,
name: value.name, name: value.name,
permissions: value.permissions, permissions: value.permissions,
colour: value.colour, colour: value.colour,
@@ -922,7 +920,6 @@ impl From<Role> for crate::Role {
impl From<crate::PartialRole> for PartialRole { impl From<crate::PartialRole> for PartialRole {
fn from(value: crate::PartialRole) -> Self { fn from(value: crate::PartialRole) -> Self {
PartialRole { PartialRole {
id: value.id,
name: value.name, name: value.name,
permissions: value.permissions, permissions: value.permissions,
colour: value.colour, colour: value.colour,
@@ -935,7 +932,6 @@ impl From<crate::PartialRole> for PartialRole {
impl From<PartialRole> for crate::PartialRole { impl From<PartialRole> for crate::PartialRole {
fn from(value: PartialRole) -> crate::PartialRole { fn from(value: PartialRole) -> crate::PartialRole {
crate::PartialRole { crate::PartialRole {
id: value.id,
name: value.name, name: value.name,
permissions: value.permissions, permissions: value.permissions,
colour: value.colour, colour: value.colour,
+2 -34
View File
@@ -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<()> {
@@ -35,7 +35,7 @@ pub async fn raise_if_in_voice(user: &User, channel_id: &str) -> Result<()> {
.to_internal_error()? .to_internal_error()?
> 0 > 0
{ {
Err(create_error!(AlreadyConnected)) Err(create_error!(NotConnected))
} else { } else {
Ok(()) Ok(())
} }
@@ -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(())
}
+3 -3
View File
@@ -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>
+1 -1
View File
@@ -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.
+3 -3
View File
@@ -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>
+1 -1
View File
@@ -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.
+3 -3
View File
@@ -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>
+1 -1
View File
@@ -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.
+3 -3
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-files" name = "revolt-files"
version = "0.9.1" version = "0.8.9"
edition = "2021" edition = "2021"
license = "AGPL-3.0-or-later" license = "AGPL-3.0-or-later"
authors = ["Paul Makles <me@insrt.uk>"] authors = ["Paul Makles <me@insrt.uk>"]
@@ -20,10 +20,10 @@ typenum = "1.17.0"
aws-config = "1.5.5" aws-config = "1.5.5"
aws-sdk-s3 = { version = "1.46.0", features = ["behavior-version-latest"] } aws-sdk-s3 = { version = "1.46.0", features = ["behavior-version-latest"] }
revolt-config = { version = "0.9.1", path = "../config", features = [ revolt-config = { version = "0.8.9", path = "../config", features = [
"report-macros", "report-macros",
] } ] }
revolt-result = { version = "0.9.1", path = "../result" } revolt-result = { version = "0.8.9", path = "../result" }
# image processing # image processing
jxl-oxide = "0.8.1" jxl-oxide = "0.8.1"
+3 -3
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-models" name = "revolt-models"
version = "0.9.1" version = "0.8.9"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
authors = ["Paul Makles <me@insrt.uk>"] authors = ["Paul Makles <me@insrt.uk>"]
@@ -20,8 +20,8 @@ default = ["serde", "partials", "rocket"]
[dependencies] [dependencies]
# Core # Core
revolt-config = { version = "0.9.1", path = "../config" } revolt-config = { version = "0.8.9", path = "../config" }
revolt-permissions = { version = "0.9.1", path = "../permissions" } revolt-permissions = { version = "0.8.9", path = "../permissions" }
# Utility # Utility
regex = "1.11" regex = "1.11"
-4
View File
@@ -85,9 +85,6 @@ auto_derived_partial!(
auto_derived_partial!( auto_derived_partial!(
/// Role /// Role
pub struct Role { pub struct Role {
/// Unique Id
#[cfg_attr(feature = "serde", serde(rename = "_id"))]
pub id: String,
/// Role name /// Role name
pub name: String, pub name: String,
/// Permissions available to this role /// Permissions available to this role
@@ -184,7 +181,6 @@ auto_derived!(
} }
/// Response after creating new role /// Response after creating new role
// TODO: remove this in favor of just Role
pub struct NewRoleResponse { pub struct NewRoleResponse {
/// Id of the role /// Id of the role
pub id: String, pub id: String,
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-parser" name = "revolt-parser"
version = "0.9.1" version = "0.8.9"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
authors = ["Zomatree <me@zomatree.live>", "Paul Makles <me@insrt.uk>"] authors = ["Zomatree <me@zomatree.live>", "Paul Makles <me@insrt.uk>"]
+2 -2
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-permissions" name = "revolt-permissions"
version = "0.9.1" version = "0.8.9"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
authors = ["Paul Makles <me@insrt.uk>"] authors = ["Paul Makles <me@insrt.uk>"]
@@ -21,7 +21,7 @@ async-std = { version = "1.8.0", features = ["attributes"] }
[dependencies] [dependencies]
# Core # Core
revolt-result = { version = "0.9.1", path = "../result" } revolt-result = { version = "0.8.9", path = "../result" }
# Utility # Utility
auto_ops = "0.3.0" auto_ops = "0.3.0"
+2 -2
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-presence" name = "revolt-presence"
version = "0.9.1" version = "0.8.9"
edition = "2021" edition = "2021"
license = "AGPL-3.0-or-later" license = "AGPL-3.0-or-later"
authors = ["Paul Makles <me@insrt.uk>"] authors = ["Paul Makles <me@insrt.uk>"]
@@ -16,7 +16,7 @@ redis-is-patched = []
async-std = { version = "1.8.0", features = ["attributes"] } async-std = { version = "1.8.0", features = ["attributes"] }
# Config for loading Redis URI # Config for loading Redis URI
revolt-config = { version = "0.9.1", path = "../config" } revolt-config = { version = "0.8.9", path = "../config" }
[dependencies] [dependencies]
# Utility # Utility
+4 -4
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-ratelimits" name = "revolt-ratelimits"
version = "0.9.1" version = "0.8.9"
edition = "2024" edition = "2024"
[features] [features]
@@ -10,9 +10,9 @@ axum = ["dep:axum", "revolt-database/axum-impl"]
default = ["rocket", "axum"] default = ["rocket", "axum"]
[dependencies] [dependencies]
revolt-database = { version = "0.9.1", path = "../database"} revolt-database = { version = "0.8.9", path = "../database"}
revolt-result = { version = "0.9.1", path = "../result" } revolt-result = { version = "0.8.9", path = "../result" }
revolt-config = { version = "0.9.1", 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 } revolt_rocket_okapi = { version = "0.10.0", optional = true }
+2 -5
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-result" name = "revolt-result"
version = "0.9.1" version = "0.8.9"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
authors = ["Paul Makles <me@insrt.uk>"] authors = ["Paul Makles <me@insrt.uk>"]
@@ -15,9 +15,8 @@ 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"]
okapi = ["dep:revolt_rocket_okapi", "dep:revolt_okapi", "schemas"] okapi = ["dep:revolt_rocket_okapi", "dep:revolt_okapi", "schemas"]
sentry = ["dep:sentry"]
default = ["serde", "sentry"] default = ["serde"]
[dependencies] [dependencies]
# Serialisation # Serialisation
@@ -37,5 +36,3 @@ revolt_okapi = { version = "0.9.1", optional = true }
log = "0.4" log = "0.4"
# Axum # Axum
axum = { version = "0.7.5", optional = true } axum = { version = "0.7.5", optional = true }
sentry = { version = "0.31.5", optional = true }
-1
View File
@@ -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,
+1 -4
View File
@@ -139,7 +139,6 @@ pub enum ErrorType {
NotPrivileged, NotPrivileged,
CannotGiveMissingPermissions, CannotGiveMissingPermissions,
NotOwner, NotOwner,
IsElevated,
// ? General errors // ? General errors
DatabaseError { DatabaseError {
@@ -227,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();
@@ -235,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,
-1
View File
@@ -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,
+5 -5
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-crond" name = "revolt-crond"
version = "0.9.1" version = "0.8.9"
license = "AGPL-3.0-or-later" license = "AGPL-3.0-or-later"
authors = ["Paul Makles <me@insrt.uk>"] authors = ["Paul Makles <me@insrt.uk>"]
edition = "2021" edition = "2021"
@@ -16,7 +16,7 @@ log = "0.4"
tokio = { version = "1" } tokio = { version = "1" }
# Core # Core
revolt-database = { version = "0.9.1", path = "../../core/database" } revolt-database = { version = "0.8.9", path = "../../core/database" }
revolt-result = { version = "0.9.1", path = "../../core/result" } revolt-result = { version = "0.8.9", path = "../../core/result" }
revolt-config = { version = "0.9.1", path = "../../core/config" } revolt-config = { version = "0.8.9", path = "../../core/config" }
revolt-files = { version = "0.9.1", path = "../../core/files" } revolt-files = { version = "0.8.9", path = "../../core/files" }
+6 -6
View File
@@ -1,20 +1,20 @@
[package] [package]
name = "revolt-pushd" name = "revolt-pushd"
version = "0.9.1" version = "0.8.9"
edition = "2021" edition = "2021"
license = "AGPL-3.0-or-later" license = "AGPL-3.0-or-later"
[dependencies] [dependencies]
revolt-result = { version = "0.9.1", path = "../../core/result" } revolt-result = { version = "0.8.9", path = "../../core/result" }
revolt-config = { version = "0.9.1", path = "../../core/config", features = [ revolt-config = { version = "0.8.9", path = "../../core/config", features = [
"report-macros", "report-macros",
"anyhow" "anyhow"
] } ] }
revolt-database = { version = "0.9.1", path = "../../core/database" } revolt-database = { version = "0.8.9", path = "../../core/database" }
revolt-models = { version = "0.9.1", path = "../../core/models", features = [ revolt-models = { version = "0.8.9", path = "../../core/models", features = [
"validator", "validator",
] } ] }
revolt-presence = { version = "0.9.1", path = "../../core/presence", features = [ revolt-presence = { version = "0.8.9", path = "../../core/presence", features = [
"redis-is-patched", "redis-is-patched",
] } ] }
+2 -2
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-voice-ingress" name = "revolt-voice-ingress"
version = "0.9.1" version = "0.7.1"
license = "AGPL-3.0-or-later" license = "AGPL-3.0-or-later"
edition = "2021" edition = "2021"
@@ -36,7 +36,7 @@ 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
+117 -133
View File
@@ -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()?;
@@ -213,59 +214,42 @@ pub async fn ingress(
// forbid any size which goes over the limit and also limit the aspect ratio to stop people from making too tall or too wide and bypassing the limit. // forbid any size which goes over the limit and also limit the aspect ratio to stop people from making too tall or too wide and bypassing the limit.
// TODO: figure out how to track audio stream quality // TODO: figure out how to track audio stream quality
if event.event == "track_published" { if event.event == "track_published"
let mut disconnect = false; && (track.r#type == TrackType::Data as i32
|| (track.r#type == TrackType::Video as i32
&& (user_limits.video_resolution[0] != 0
&& user_limits.video_resolution[1] != 0
&& track.width * track.height
> user_limits.video_resolution[0]
* user_limits.video_resolution[1])
|| (user_limits.video_aspect_ratio[0]
!= user_limits.video_aspect_ratio[1]
&& !(user_limits.video_aspect_ratio[0]
..=user_limits.video_aspect_ratio[1])
.contains(&(track.width as f32 / track.height as f32)))))
{
log::debug!("Removing user {user_id} from channel {channel_id} {event:?} due to forbidden track.");
if track.r#type == TrackType::Data as i32 { voice_client.remove_user(node, user_id, channel_id).await?;
log::debug!("User published data"); delete_voice_state(channel_id, channel.server(), user_id).await?;
disconnect = true; } else {
}; let partial = update_voice_state_tracks(
channel_id,
channel.server(),
user_id,
event.event == "track_published", // to avoid duplicating this entire case twice
track.source,
)
.await?;
if track.r#type == TrackType::Video as i32 { EventV1::UserVoiceStateUpdate {
if user_limits.video_resolution[0] != 0 id: user_id.clone(),
&& user_limits.video_resolution[1] != 0 channel_id: channel_id.clone(),
&& track.width * track.height data: partial,
> user_limits.video_resolution[0] * user_limits.video_resolution[1] }
{ .p(channel_id.clone())
log::debug!("User published video with out of bounds resolution"); .await;
disconnect = true;
};
if user_limits.video_aspect_ratio[0] != user_limits.video_aspect_ratio[1]
&& !(user_limits.video_aspect_ratio[0]..=user_limits.video_aspect_ratio[1])
.contains(&(track.width as f32 / track.height as f32))
{
log::debug!("User published video with out of bounds aspect ratio");
disconnect = true;
};
};
if disconnect {
log::debug!("Removing user {user_id} from channel {channel_id} {event:?} due to forbidden track.");
let _ = voice_client.remove_user(node, user_id, channel_id).await;
delete_voice_state(channel_id, channel.server(), user_id).await?;
return Ok(EmptyResponse);
};
};
let partial = update_voice_state_tracks(
channel_id,
channel.server(),
user_id,
event.event == "track_published" || event.event == "track_unmuted", // to avoid duplicating this entire case twice
track.source,
)
.await?;
EventV1::UserVoiceStateUpdate {
id: user_id.clone(),
channel_id: channel_id.clone(),
data: partial,
} }
.p(channel_id.clone())
.await;
} }
_ => {} _ => {}
}; };
+1 -2
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-delta" name = "revolt-delta"
version = "0.9.1" version = "0.8.9"
license = "AGPL-3.0-or-later" license = "AGPL-3.0-or-later"
authors = ["Paul Makles <paulmakles@gmail.com>"] authors = ["Paul Makles <paulmakles@gmail.com>"]
edition = "2018" edition = "2018"
@@ -72,7 +72,6 @@ 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",
"voice",
] } ] }
revolt-models = { path = "../core/models", features = [ revolt-models = { path = "../core/models", features = [
"schemas", "schemas",
@@ -47,7 +47,6 @@
], ],
"roles": { "roles": {
"__ID:5__": { "__ID:5__": {
"_id": "__ID:5__",
"name": "Moderator", "name": "Moderator",
"permissions": { "permissions": {
"a": 545270216, "a": 545270216,
@@ -56,7 +55,6 @@
"rank": 1 "rank": 1
}, },
"__ID:6__": { "__ID:6__": {
"_id": "__ID:6__",
"name": "Owner", "name": "Owner",
"permissions": { "permissions": {
"a": 0, "a": 0,
@@ -65,7 +63,6 @@
"rank": 0 "rank": 0
}, },
"__ID:7__": { "__ID:7__": {
"_id": "__ID:7__",
"name": "Lower Rank 1", "name": "Lower Rank 1",
"permissions": { "permissions": {
"a": 0, "a": 0,
@@ -74,7 +71,6 @@
"rank": 2 "rank": 2
}, },
"__ID:8__": { "__ID:8__": {
"_id": "__ID:8__",
"name": "Lower Rank 2", "name": "Lower Rank 2",
"permissions": { "permissions": {
"a": 0, "a": 0,
+9 -2
View File
@@ -1,4 +1,4 @@
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;
@@ -21,7 +21,14 @@ 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? {
let node = get_channel_node(&channel_id).await?.unwrap();
let channel = Reference::from_unchecked(&channel_id).as_channel(db).await?;
voice_client.remove_user(&node, &bot.id, &channel_id).await?;
delete_voice_state(&channel_id, channel.server(), &bot.id).await?;
}
Ok(EmptyResponse) Ok(EmptyResponse)
} }
@@ -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};
@@ -55,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};
@@ -255,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()))
@@ -1,8 +1,4 @@
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};
@@ -28,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()
@@ -51,11 +44,14 @@ 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?; 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?;
}; };
Ok(EmptyResponse) Ok(EmptyResponse)
@@ -151,20 +151,29 @@ mod test {
name: "Hidden Channel".to_string(), name: "Hidden Channel".to_string(),
description: None, description: None,
nsfw: Some(false), nsfw: Some(false),
voice: None, voice: None
}, },
true, true,
) )
.await .await
.expect("Failed to make new channel"); .expect("Failed to make new channel");
let role = Role::create(&harness.db, &server, "Show Hidden Channel".to_string()) let role = Role {
name: "Show Hidden Channel".to_string(),
permissions: OverrideField { a: 0, d: 0 },
colour: None,
hoist: false,
rank: 5,
};
let role_id = role
.create(&harness.db, &server.id)
.await .await
.expect("Failed to create the role"); .expect("Failed to create the role");
let mut overrides = HashMap::new(); let mut overrides = HashMap::new();
overrides.insert( overrides.insert(
role.id.clone(), role_id.clone(),
OverrideField { OverrideField {
a: (ChannelPermission::ViewChannel) as i64, a: (ChannelPermission::ViewChannel) as i64,
d: 0, d: 0,
@@ -272,7 +281,7 @@ mod test {
"Mention failed to be scrubbed when the user cannot see the channel" "Mention failed to be scrubbed when the user cannot see the channel"
); );
let second_member_roles = vec![role.id.clone()]; let second_member_roles = vec![role_id.clone()];
let partial = PartialMember { let partial = PartialMember {
id: None, id: None,
joined_at: None, joined_at: None,
@@ -281,7 +290,7 @@ mod test {
timeout: None, timeout: None,
roles: Some(second_member_roles), roles: Some(second_member_roles),
can_publish: None, can_publish: None,
can_receive: None, can_receive: None
}; };
second_member second_member
.update(&harness.db, partial, vec![]) .update(&harness.db, partial, vec![])
@@ -487,7 +496,7 @@ mod test {
let (_, _, other_user) = harness.new_user().await; let (_, _, other_user) = harness.new_user().await;
let (server, _) = harness.new_server(&user).await; let (server, _) = harness.new_server(&user).await;
let channel = harness.new_channel(&server).await; let channel = harness.new_channel(&server).await;
let role = harness let (role_id, _role) = harness
.new_role( .new_role(
&server, &server,
1, 1,
@@ -509,7 +518,7 @@ mod test {
Some(&harness.amqp), Some(&harness.amqp),
channel.clone(), channel.clone(),
v0::DataMessageSend { v0::DataMessageSend {
content: Some(format!("Mentioning @everyone and role <%{}>", &role.id)), content: Some(format!("Mentioning @everyone and role <%{}>", &role_id)),
nonce: None, nonce: None,
attachments: None, attachments: None,
replies: None, replies: None,
@@ -554,7 +563,7 @@ mod test {
Some(&harness.amqp), Some(&harness.amqp),
channel.clone(), channel.clone(),
v0::DataMessageSend { v0::DataMessageSend {
content: Some(format!("Mentioning `@everyone` and role `<%{}>`", &role.id)), content: Some(format!("Mentioning `@everyone` and role `<%{}>`", &role_id)),
nonce: None, nonce: None,
attachments: None, attachments: None,
replies: None, replies: None,
@@ -596,7 +605,7 @@ mod test {
"Role mentions detected when inside codeblock" "Role mentions detected when inside codeblock"
); );
other_member.roles.push(role.id.clone()); other_member.roles.push(role_id.clone());
harness harness
.db .db
.update_member( .update_member(
@@ -606,10 +615,10 @@ mod test {
id: None, id: None,
joined_at: None, joined_at: None,
nickname: None, nickname: None,
roles: Some(vec![role.id.clone()]), roles: Some(vec![role_id.clone()]),
timeout: None, timeout: None,
can_publish: None, can_publish: None,
can_receive: None, can_receive: None
}, },
vec![], vec![],
) )
@@ -623,7 +632,7 @@ mod test {
Some(&harness.amqp), Some(&harness.amqp),
channel.clone(), channel.clone(),
v0::DataMessageSend { v0::DataMessageSend {
content: Some(format!("Mentioning @everyone and role <%{}>", &role.id)), content: Some(format!("Mentioning @everyone and role <%{}>", &role_id)),
nonce: None, nonce: None,
attachments: None, attachments: None,
replies: None, replies: None,
@@ -79,15 +79,15 @@ pub async fn call(
// should only ever loop once but just to cover our backs. // should only ever loop once but just to cover our backs.
for channel_id in get_user_voice_channels(&user.id).await? { for channel_id in get_user_voice_channels(&user.id).await? {
if let Some(node) = get_channel_node(&channel_id).await? { let node = get_channel_node(&channel_id).await?.unwrap();
// if this errors its just a mismatching state - ignore and proceed to still delete our state
let _ = voice_client.remove_user(&node, &user.id, &channel_id).await;
};
let channel = Reference::from_unchecked(&channel_id) let channel = Reference::from_unchecked(&channel_id)
.as_channel(db) .as_channel(db)
.await?; .await?;
voice_client
.remove_user(&node, &user.id, &channel_id)
.await?;
delete_voice_state(&channel_id, channel.server(), &user.id).await?; delete_voice_state(&channel_id, channel.server(), &user.id).await?;
} }
} else { } else {
@@ -103,7 +103,7 @@ pub async fn call(
log::debug!("Created room {}", room.name); log::debug!("Created room {}", room.name);
if let Some(recipients) = recipients { if let Some(recipients) = recipients {
if room.num_participants == 0 && !recipients.is_empty() { if room.num_participants == 0 {
set_call_notification_recipients(channel.id(), &user.id, &recipients).await?; set_call_notification_recipients(channel.id(), &user.id, &recipients).await?;
} }
} }
@@ -2,14 +2,12 @@ use revolt_database::{Database, User};
use revolt_result::Result; use revolt_result::Result;
use rocket::State; use rocket::State;
use rocket_empty::EmptyResponse;
/// # Acknowledge Policy Changes /// # Acknowledge Policy Changes
/// ///
/// Accept/acknowledge changes to platform policy. /// Accept/acknowledge changes to platform policy.
#[openapi(tag = "Policy")] #[openapi(tag = "Policy")]
#[post("/acknowledge")] #[post("/acknowledge")]
pub async fn acknowledge_policy_changes(db: &State<Database>, user: User) -> Result<EmptyResponse> { pub async fn acknowledge_policy_changes(db: &State<Database>, user: User) -> Result<()> {
db.acknowledge_policy_changes(&user.id).await?; db.acknowledge_policy_changes(&user.id).await
Ok(EmptyResponse)
} }
@@ -1,7 +1,6 @@
use revolt_database::{events::client::EventV1, Database, Report, Snapshot, SnapshotContent, User}; use revolt_database::{events::client::EventV1, Database, Report, Snapshot, SnapshotContent, User};
use revolt_models::v0::{ReportStatus, ReportedContent}; use revolt_models::v0::{ReportStatus, ReportedContent};
use revolt_result::{create_error, Result}; use revolt_result::{create_error, Result};
use rocket_empty::EmptyResponse;
use serde::Deserialize; use serde::Deserialize;
use ulid::Ulid; use ulid::Ulid;
use validator::Validate; use validator::Validate;
@@ -28,7 +27,7 @@ pub async fn report_content(
db: &State<Database>, db: &State<Database>,
user: User, user: User,
data: Json<DataReportContent>, data: Json<DataReportContent>,
) -> Result<EmptyResponse> { ) -> Result<()> {
let data = data.into_inner(); let data = data.into_inner();
data.validate().map_err(|error| { data.validate().map_err(|error| {
create_error!(FailedValidation { create_error!(FailedValidation {
@@ -130,5 +129,5 @@ pub async fn report_content(
EventV1::ReportCreate(report.into()).global().await; EventV1::ReportCreate(report.into()).global().await;
Ok(EmptyResponse) Ok(())
} }
@@ -1,6 +1,6 @@
use revolt_database::{ use revolt_database::{
util::{permissions::DatabasePermissionQuery, reference::Reference}, util::{permissions::DatabasePermissionQuery, reference::Reference},
voice::{get_user_voice_channel_in_server, remove_user_from_voice_channel, VoiceClient}, voice::{delete_voice_state, get_channel_node, get_user_voice_channel_in_server, VoiceClient},
Database, RemovalIntention, ServerBan, User, Database, RemovalIntention, ServerBan, User,
}; };
use revolt_models::v0; use revolt_models::v0;
@@ -58,8 +58,12 @@ pub async fn ban(
.await?; .await?;
// If the member is in a voice channel while banned kick them from the voice channel // If the member is in a voice channel while banned kick them from the voice channel
if let Some(channel_id) = get_user_voice_channel_in_server(&target.id, &server.id).await? { if let Some(channel_id) = get_user_voice_channel_in_server(&user.id, &server.id).await? {
remove_user_from_voice_channel(db, voice_client, &channel_id, &target.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, Some(&server.id), &user.id).await?
} }
} }
+3 -15
View File
@@ -51,10 +51,6 @@ pub async fn edit(
let mut query = DatabasePermissionQuery::new(db, &user).server(&server); let mut query = DatabasePermissionQuery::new(db, &user).server(&server);
let permissions = calculate_server_permissions(&mut query).await; let permissions = calculate_server_permissions(&mut query).await;
// Fetch target permissions
let mut target_query = DatabasePermissionQuery::new(db, &target_user).server(&server).member(&member);
let target_permissions = calculate_server_permissions(&mut target_query).await;
// Check permissions in server // Check permissions in server
if data.nickname.is_some() || data.remove.contains(&v0::FieldsMember::Nickname) { if data.nickname.is_some() || data.remove.contains(&v0::FieldsMember::Nickname) {
if user.id == member.id.user { if user.id == member.id.user {
@@ -77,14 +73,8 @@ pub async fn edit(
} }
if data.timeout.is_some() || data.remove.contains(&v0::FieldsMember::Timeout) { if data.timeout.is_some() || data.remove.contains(&v0::FieldsMember::Timeout) {
if data.timeout.is_some() { if data.timeout.is_some() && member.id.user == user.id {
if member.id.user == user.id { return Err(create_error!(CannotTimeoutYourself));
return Err(create_error!(CannotTimeoutYourself));
}
if target_permissions.has_channel_permission(ChannelPermission::TimeoutMembers) {
return Err(create_error!(IsElevated))
}
} }
permissions.throw_if_lacking_channel_permission(ChannelPermission::TimeoutMembers)?; permissions.throw_if_lacking_channel_permission(ChannelPermission::TimeoutMembers)?;
@@ -227,11 +217,9 @@ pub async fn edit(
EventV1::UserMoveVoiceChannel { EventV1::UserMoveVoiceChannel {
node: new_node, node: new_node,
from: channel,
to: new_voice_channel.id().to_string(),
token, token,
} }
.private(target_user.id.clone()) .p_user(target_user.id.clone(), db)
.await; .await;
}; };
} else if can_publish.is_some() || can_receive.is_some() || remove_contains_voice { } else if can_publish.is_some() || can_receive.is_some() || remove_contains_voice {
@@ -1,6 +1,6 @@
use revolt_database::{ use revolt_database::{
util::{permissions::DatabasePermissionQuery, reference::Reference}, util::{permissions::DatabasePermissionQuery, reference::Reference},
voice::{get_user_voice_channel_in_server, remove_user_from_voice_channel, VoiceClient}, voice::{delete_voice_state, get_channel_node, get_user_voice_channel_in_server, VoiceClient},
Database, RemovalIntention, User, Database, RemovalIntention, User,
}; };
use revolt_permissions::{calculate_server_permissions, ChannelPermission}; use revolt_permissions::{calculate_server_permissions, ChannelPermission};
@@ -46,8 +46,12 @@ pub async fn kick(
.remove(db, &server, RemovalIntention::Kick, false) .remove(db, &server, RemovalIntention::Kick, false)
.await?; .await?;
if let Some(channel_id) = get_user_voice_channel_in_server(&target.id, &server.id).await? { if let Some(channel_id) = get_user_voice_channel_in_server(&user.id, &server.id).await? {
remove_user_from_voice_channel(db, voice_client, &channel_id, &target.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, Some(&server.id), &user.id).await?;
}; };
Ok(EmptyResponse) Ok(EmptyResponse)
@@ -40,10 +40,17 @@ pub async fn create(
})); }));
}; };
let role = Role::create(db, &server, data.name).await?; let role = Role {
name: data.name,
// Rank of the new role should be below the lowest role
rank: server.roles.len() as i64,
colour: None,
hoist: false,
permissions: Default::default(),
};
Ok(Json(v0::NewRoleResponse { Ok(Json(v0::NewRoleResponse {
id: role.id.clone(), id: role.create(db, &server.id).await?,
role: role.into(), role: role.into(),
})) }))
} }
@@ -37,7 +37,7 @@ pub async fn delete(
return Err(create_error!(NotElevated)); return Err(create_error!(NotElevated));
} }
role.delete(db, &server.id).await?; role.delete(db, &server.id, &role_id).await?;
for channel_id in &server.channels { for channel_id in &server.channels {
let channel = Reference::from_unchecked(channel_id).as_channel(db).await?; let channel = Reference::from_unchecked(channel_id).as_channel(db).await?;
@@ -61,6 +61,7 @@ pub async fn edit(
role.update( role.update(
db, db,
&server.id, &server.id,
&role_id,
partial, partial,
remove.into_iter().map(Into::into).collect(), remove.into_iter().map(Into::into).collect(),
) )
@@ -1,9 +1,6 @@
use revolt_database::{ use revolt_database::{
util::reference::Reference, util::reference::Reference,
voice::{ voice::{delete_channel_voice_state, delete_voice_state, get_channel_node, get_user_voice_channel_in_server, get_voice_channel_members, VoiceClient},
delete_voice_channel, get_user_voice_channel_in_server, remove_user_from_voice_channel,
VoiceClient,
},
Database, RemovalIntention, User, Database, RemovalIntention, User,
}; };
use revolt_models::v0; use revolt_models::v0;
@@ -29,13 +26,25 @@ pub async fn delete(
if server.owner == user.id { if server.owner == user.id {
for channel_id in &server.channels { for channel_id in &server.channels {
delete_voice_channel(voice_client, channel_id, Some(&server.id)).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, Some(&server.id), &users).await?;
};
} }
server.delete(db).await server.delete(db).await
} else { } else {
if let Some(channel_id) = get_user_voice_channel_in_server(&user.id, &server.id).await? { if let Some(channel_id) = get_user_voice_channel_in_server(&user.id, &server.id).await? {
remove_user_from_voice_channel(db, voice_client, &channel_id, &user.id).await?; if server.channels.iter().any(|c| c == &channel_id) {
let node = get_channel_node(&channel_id).await?.unwrap();
voice_client.remove_user(&node, &user.id, &channel_id).await?;
delete_voice_state(&channel_id, Some(&server.id), &user.id).await?;
}
}; };
member member
@@ -217,6 +217,7 @@ pub struct GithubComment {
position: Option<u32>, position: Option<u32>,
reactions: Option<GithubReactions>, reactions: Option<GithubReactions>,
updated_at: Value, updated_at: Value,
url: String,
user: GithubUser, user: GithubUser,
} }
@@ -700,7 +701,7 @@ fn safe_from_str<T: for<'de> Deserialize<'de>>(data: &str) -> Result<T> {
match serde_json::from_str(data) { match serde_json::from_str(data) {
Ok(output) => Ok(output), Ok(output) => Ok(output),
Err(err) => { Err(err) => {
revolt_config::capture_internal_error!(err); log::error!("{err:?}");
Err(create_error!(InvalidOperation)) Err(create_error!(InvalidOperation))
} }
} }
@@ -752,12 +753,12 @@ pub async fn webhook_execute_github(
db: &State<Database>, db: &State<Database>,
amqp: &State<AMQP>, amqp: &State<AMQP>,
webhook_id: Reference<'_>, webhook_id: Reference<'_>,
token: &str, token: String,
event: EventHeader<'_>, event: EventHeader<'_>,
data: String, data: String,
) -> Result<()> { ) -> Result<()> {
let webhook = webhook_id.as_webhook(db).await?; let webhook = webhook_id.as_webhook(db).await?;
webhook.assert_token(token)?; webhook.assert_token(&token)?;
let channel = db.fetch_channel(&webhook.channel_id).await?; let channel = db.fetch_channel(&webhook.channel_id).await?;
let event = convert_event(&data, &event)?; let event = convert_event(&data, &event)?;
@@ -896,11 +897,10 @@ pub async fn webhook_execute_github(
url: Some(event.sender.html_url), url: Some(event.sender.html_url),
title: Some(event.sender.login), title: Some(event.sender.login),
description: Some(format!( description: Some(format!(
"#### [[{}] New discussion #{}: {}]({})\n{}", "#### [{}] New discussion #{}: {}\n{}",
event.repository.full_name, event.repository.full_name,
discussion.number, discussion.number,
discussion.title, discussion.title,
discussion.html_url,
shorten_text(&discussion.body, 450) shorten_text(&discussion.body, 450)
)), )),
colour: Some(LIGHT_ORANGE.to_string()), colour: Some(LIGHT_ORANGE.to_string()),
@@ -911,11 +911,10 @@ pub async fn webhook_execute_github(
url: Some(answer.comment.user.html_url), url: Some(answer.comment.user.html_url),
title: Some(answer.comment.user.login), title: Some(answer.comment.user.login),
description: Some(format!( description: Some(format!(
"#### [[{}] Discussion #{} marked answered: {}]({})\n{}", "#### [{}] discussion #{} marked answered: {}\n{}",
event.repository.full_name, event.repository.full_name,
discussion.number, discussion.number,
discussion.title, discussion.title,
answer.comment.html_url,
shorten_text(&answer.comment.body, 450) shorten_text(&answer.comment.body, 450)
)), )),
colour: Some(LIGHT_ORANGE.to_string()), colour: Some(LIGHT_ORANGE.to_string()),
@@ -931,11 +930,10 @@ pub async fn webhook_execute_github(
url: Some(comment.comment.user.html_url), url: Some(comment.comment.user.html_url),
title: Some(comment.comment.user.login), title: Some(comment.comment.user.login),
description: Some(format!( description: Some(format!(
"#### [[{}] New comment on discussion #{}: {}]({})\n{}", "[{}] New comment on discussion #{}: {}\n{}",
event.repository.full_name, event.repository.full_name,
discussion.number, discussion.number,
discussion.title, discussion.title,
comment.comment.html_url,
shorten_text(&comment.comment.body, 450) shorten_text(&comment.comment.body, 450)
)), )),
colour: Some(LIGHT_ORANGE.to_string()), colour: Some(LIGHT_ORANGE.to_string()),
@@ -1004,7 +1002,7 @@ pub async fn webhook_execute_github(
event.repository.full_name, event.repository.full_name,
issue.number, issue.number,
issue.title, issue.title,
comment.html_url, issue.html_url,
shorten_text(&comment.body, 450) shorten_text(&comment.body, 450)
)), )),
colour: Some(LIGHT_ORANGE.to_string()), colour: Some(LIGHT_ORANGE.to_string()),
+14 -19
View File
@@ -6,7 +6,7 @@ use futures::StreamExt;
use rand::Rng; use rand::Rng;
use redis_kiss::redis::aio::PubSub; use redis_kiss::redis::aio::PubSub;
use revolt_database::{ use revolt_database::{
events::client::EventV1, Channel, Database, Member, Message, PartialRole, Server, User, AMQP, events::client::EventV1, Channel, Database, Member, Message, Server, User, AMQP,
}; };
use revolt_database::{util::idempotency::IdempotencyKey, Role}; use revolt_database::{util::idempotency::IdempotencyKey, Role};
use revolt_models::v0; use revolt_models::v0;
@@ -139,26 +139,21 @@ impl TestHarness {
server: &Server, server: &Server,
rank: i64, rank: i64,
overrides: Option<OverrideField>, overrides: Option<OverrideField>,
) -> Role { ) -> (String, Role) {
let mut role = Role::create(&self.db, &server, TestHarness::rand_string()) let role = Role {
name: TestHarness::rand_string(),
permissions: overrides.unwrap_or(OverrideField { a: 0, d: 0 }),
rank,
colour: None,
hoist: false,
};
let id = role
.create(&self.db, &server.id)
.await .await
.expect("Failed to create test role"); .expect("Failed to create test role");
if let Some(overrides) = overrides { (id, role)
role.update(
&self.db,
&server.id,
PartialRole {
permissions: Some(overrides),
..Default::default()
},
Vec::new(),
)
.await
.expect("Failed to set test role overrides");
};
role
} }
pub async fn new_channel(&self, server: &Server) -> Channel { pub async fn new_channel(&self, server: &Server) -> Channel {
@@ -170,7 +165,7 @@ impl TestHarness {
name: "Test Channel".to_string(), name: "Test Channel".to_string(),
description: None, description: None,
nsfw: Some(false), nsfw: Some(false),
voice: None, voice: None
}, },
true, true,
) )
+6 -6
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-autumn" name = "revolt-autumn"
version = "0.9.1" version = "0.8.9"
edition = "2021" edition = "2021"
license = "AGPL-3.0-or-later" license = "AGPL-3.0-or-later"
@@ -43,16 +43,16 @@ tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing-subscriber = { version = "0.3", features = ["env-filter"] }
# Core crates # Core crates
revolt-files = { version = "0.9.1", path = "../../core/files" } revolt-files = { version = "0.8.9", path = "../../core/files" }
revolt-config = { version = "0.9.1", path = "../../core/config" } revolt-config = { version = "0.8.9", path = "../../core/config" }
revolt-database = { version = "0.9.1", path = "../../core/database", features = [ revolt-database = { version = "0.8.9", path = "../../core/database", features = [
"axum-impl", "axum-impl",
] } ] }
revolt-result = { version = "0.9.1", path = "../../core/result", features = [ revolt-result = { version = "0.8.9", path = "../../core/result", features = [
"utoipa", "utoipa",
"axum", "axum",
] } ] }
revolt-ratelimits = { version = "0.9.1", path = "../../core/ratelimits", features = ["axum"] } revolt-ratelimits = { version = "0.8.9", path = "../../core/ratelimits", features = ["axum"] }
# Axum / web server # Axum / web server
tempfile = "3.12.0" tempfile = "3.12.0"
+7 -7
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-gifbox" name = "revolt-gifbox"
version = "0.9.1" version = "0.8.9"
edition = "2021" edition = "2021"
license = "AGPL-3.0-or-later" license = "AGPL-3.0-or-later"
@@ -16,19 +16,19 @@ tokio = { version = "1.0", features = ["full"] }
reqwest = { version = "0.12", features = ["json"] } reqwest = { version = "0.12", features = ["json"] }
# Core crates # Core crates
revolt-config = { version = "0.9.1", path = "../../core/config" } revolt-config = { version = "0.8.9", path = "../../core/config" }
revolt-models = { version = "0.9.1", path = "../../core/models" } revolt-models = { version = "0.8.9", path = "../../core/models" }
revolt-result = { version = "0.9.1", path = "../../core/result", features = [ revolt-result = { version = "0.8.9", path = "../../core/result", features = [
"utoipa", "utoipa",
"axum", "axum",
] } ] }
revolt-coalesced = { version = "0.9.1", path = "../../core/coalesced", features = [ revolt-coalesced = { version = "0.8.9", path = "../../core/coalesced", features = [
"queue", "queue",
] } ] }
revolt-database = { version = "0.9.1", path = "../../core/database", features = [ revolt-database = { version = "0.8.9", path = "../../core/database", features = [
"axum-impl", "axum-impl",
] } ] }
revolt-ratelimits = { version = "0.9.1", path = "../../core/ratelimits", features = [ revolt-ratelimits = { version = "0.8.9", path = "../../core/ratelimits", features = [
"axum", "axum",
] } ] }
+5 -5
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-january" name = "revolt-january"
version = "0.9.1" version = "0.8.9"
edition = "2021" edition = "2021"
license = "AGPL-3.0-or-later" license = "AGPL-3.0-or-later"
@@ -32,13 +32,13 @@ tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing-subscriber = { version = "0.3", features = ["env-filter"] }
# Core crates # Core crates
revolt-config = { version = "0.9.1", path = "../../core/config" } revolt-config = { version = "0.8.9", path = "../../core/config" }
revolt-models = { version = "0.9.1", path = "../../core/models" } revolt-models = { version = "0.8.9", path = "../../core/models" }
revolt-result = { version = "0.9.1", path = "../../core/result", features = [ revolt-result = { version = "0.8.9", path = "../../core/result", features = [
"utoipa", "utoipa",
"axum", "axum",
] } ] }
revolt-files = { version = "0.9.1", path = "../../core/files" } revolt-files = { version = "0.8.9", path = "../../core/files" }
# Axum / web server # Axum / web server
axum = { version = "0.7.5" } axum = { version = "0.7.5" }
+1
View File
@@ -0,0 +1 @@
book
+6
View File
@@ -0,0 +1,6 @@
[book]
authors = []
language = "en"
multilingual = false
src = "src"
title = "Revolt Backend"
+8
View File
@@ -0,0 +1,8 @@
# Summary
- [Introduction](./hello.md)
- [Project Structure]()
- [Creating new API features](./new_features.md)
- [Testing]()
- [Writing a new database test]()
- [Writing a new API test]()
+5
View File
@@ -0,0 +1,5 @@
# Revolt Backend
Welcome to the developer documentation for the Revolt backend.
This is very much incomplete and needs more work!
@@ -1,4 +1,4 @@
# Adding new API features # New API features
New API features must be documented where appropriate, this document aims to cover everywhere you need to update for new features. New API features must be documented where appropriate, this document aims to cover everywhere you need to update for new features.
-20
View File
@@ -1,20 +0,0 @@
# Dependencies
/node_modules
# Production
/build
# Generated files
.docusaurus
.cache-loader
# Misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
-41
View File
@@ -1,41 +0,0 @@
# Website
This website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator.
## Installation
```bash
yarn
```
## Local Development
```bash
yarn start
```
This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
## Build
```bash
yarn build
```
This command generates static content into the `build` directory and can be served using any static contents hosting service.
## Deployment
Using SSH:
```bash
USE_SSH=true yarn deploy
```
Not using SSH:
```bash
GIT_USER=<Your GitHub username> yarn deploy
```
If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.
-11
View File
@@ -1,11 +0,0 @@
{
pkgs ? import <nixpkgs> { },
}:
with pkgs;
pkgs.mkShell {
buildInputs = [
nodejs
nodejs.pkgs.pnpm
];
}
-10
View File
@@ -1,10 +0,0 @@
{
"label": "API",
"position": 2,
"link": {
"type": "generated-index",
"description": "Connecting and consuming the Stoat API"
},
"collapsed": false,
"collapsible": true
}
@@ -1,15 +0,0 @@
# Authentication
To authenticate with the API, you must first acquire a bot token or user token:
- **Bot:** create one from user settings in the client
- **User:** copy one from client or authenticate through API
Then you may provide these through either:
| Type | Header |
| :---: | :---------------: |
| Bot | `X-Bot-Token` |
| User | `X-Session-Token` |
When dealing with an authenticated route.

Some files were not shown because too many files have changed in this diff Show More