Compare commits

..
30 Commits
Author SHA1 Message Date
stoat-release[bot] d0ea88f1f8 chore(main): release 0.9.3 (#488)
Co-authored-by: stoat-release[bot] <245062572+stoat-release[bot]@users.noreply.github.com>
2026-01-10 16:04:28 +00:00
Paul Makles 92b466a5f0 chore: switch back to docker hub since we cache it now (#489) 2026-01-10 14:49:51 +00:00
Paul Makles aeeafebefc fix: pipeline fixes (#487) 2026-01-10 14:46:57 +00:00
stoat-tofu[bot] bf0048a052 chore: modify .github/workflows/release-webhook.yml 2026-01-10 13:19:24 +00:00
stoat-release[bot] b4ba02218f chore(main): release 0.9.2 (#486)
Co-authored-by: stoat-release[bot] <245062572+stoat-release[bot]@users.noreply.github.com>
2026-01-10 13:07:06 +00:00
Paul Makles d13609c372 fix: disable publish for services (#485) 2026-01-10 13:06:33 +00:00
stoat-release[bot] 58fb269450 chore(main): release 0.9.1 (#484)
Co-authored-by: stoat-release[bot] <245062572+stoat-release[bot]@users.noreply.github.com>
2026-01-10 12:59:50 +00:00
Paul Makles 303e52b476 fix(ci): pipeline fixes (marked as fix to force release) (#483) 2026-01-10 12:59:20 +00:00
stoat-release[bot] 868894f500 chore(main): release 0.9.0 (#475)
Co-authored-by: stoat-release[bot] <245062572+stoat-release[bot]@users.noreply.github.com>
2026-01-10 12:51:09 +00:00
Paul Makles 67d49b461f ci: use our arc runner set for Rust builds (#482) 2026-01-10 12:44:26 +00:00
Paul Makles f89ee5a370 ci: send release notification on create (#481) 2026-01-09 21:05:42 +00:00
Angelo Kontaxis 2afea56e56 feat: add id field to role (#470) 2026-01-09 20:06:16 +00:00
Paul Makles a5387b64af ci: use dev-dependency for revolt-presence (#480) 2026-01-09 19:21:45 +00:00
Paul Makles 0b178fc791 fix: correct string_cache in lockfile (#479) 2026-01-09 19:10:01 +00:00
Paul Makles 5d27a91e90 fix: correct miniz_oxide in lockfile (#478) 2026-01-09 19:05:49 +00:00
Paul Makles e2016a2ab4 ci: add the dependency chain into release please (#477) 2026-01-08 23:07:33 +00:00
Paul Makles 924c7f5b18 ci: bootstrap sha (#476) 2026-01-08 22:39:44 +00:00
Paul Makles 22e46194ab ci: switch release please to simple mode (#474) 2026-01-08 22:36:04 +00:00
Paul Makles ffdb2b6900 ci: switch api server as main package for release please (#473) 2026-01-08 22:24:45 +00:00
Paul Makles a7b870c397 chore: configure release please (#472) 2026-01-08 22:16:50 +00:00
Simon Marty b48eb28fc2 docs: remove outdated notice about perf. regression (#467) 2026-01-07 21:12:24 +00:00
Angelo Kontaxis dc9c82aa4e fix: github webhook incorrect payload and formatting (#468) 2026-01-07 21:11:42 +00:00
Paul Makles 24fedf8c4d fix: respond with 201 if no body in requests (#465)
Signed-off-by: izzy <me@insrt.uk>
2025-11-16 15:24:39 +00:00
Paul Makles dda20f44ae ci: correct build to ./docs/build (#463) 2025-11-14 22:11:21 +00:00
Paul Makles 0b7c132ace docs: initialise new docs with docusaurus (#460)
* docs: initialise new docs with docusaurus

* chore: sign off
Signed-off-by: izzy <me@insrt.uk>

* chore: lock pnpm version in package.json

Signed-off-by: izzy <me@insrt.uk>

* ci: specify package json to load pnpm version from

Signed-off-by: izzy <me@insrt.uk>

* docs: update repo. lifecycle details

Signed-off-by: izzy <me@insrt.uk>

* docs: add external links to for-web, for-android
chore: add default.nix to docs

* docs: correct headings in contrib

* ci: use cache dependency path

* fix: docusaurus configuration

---------

Signed-off-by: izzy <me@insrt.uk>
2025-11-14 22:06:20 +00:00
Paul Makles e006cdd051 ci: prevent concurrency on docker/rust actions (#461) 2025-11-14 21:41:22 +00:00
stoat-tofu[bot] ac2971a78d chore: modify .github/workflows/validate-pr-title.yml 2025-11-14 20:25:09 +00:00
Paul Makles b7c2ca84ab chore: update branding in transactional emails (#458)
Signed-off-by: izzy <me@insrt.uk>
2025-11-12 22:19:34 +00:00
Paul Makles 82b492e529 ci: normalise workflow triggers (#459)
Signed-off-by: izzy <me@insrt.uk>
2025-11-12 22:17:11 +00:00
Angelo Kontaxis d567155f12 feat: voice chats v2 (#414)
* initial livekit support

fix up code

undo changes to compose file

add back .env.example

* move voice states to global

* track current state

* more stuff

* fix redis key inconsistancy

* split voice ops into its own library

* feat(livekit): more permission handling

* feat(livekit): push unfinished code

* fix: include voice states in servercreate event
feat: call started system message in dms

* feat: implement missing permission syncs

* fix: remove locked rocket version

* fix: remove local testing values from config

* chore: switch to ids for parameter

* feat: make Channel::server return a reference

* feat: support multiple voice nodes

* fix: expose list of nodes

* fix: respond with the url when joining a voice channel

* feat: being moved between voice channels

* fix: use existing node if someone is already in the voice channel

* refactor: Remove VoiceChannel

* chore: add pushd to debug image script

* refactor: add error messages for Rabbit issues

* docs: add some notes on overrides; fix defaults

* fix: ensure limit is always at least 1 when sent to database

* fix: change permission check for fetching channel webhooks

* chore: mount API at /0.8/ as well

* fix: ensure ratelimiter adjusts for version prefix

* chore: bump version to 0.8.2

* chore: disable LTO because it likely overloads GitHub worker

* fix: don't allow users to time themselves out
closes #376

* fix: match new error type for status code

* feat: init crond crate

* feat: file deletion implementation

* chore: fix version references

* feat: add crond container definitions

* chore: clarify cargo deny / tomls

* feat: add company information to email footers

* chore: bump version to 0.8.3

* fix: don't bump the lockfile version

* fix: include `production` default value for config

* chore: ignore python venv and dev script for user generation

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

* test: Add tests for invite/fetch

Signed-off-by: phazeschift <51278042+phazeschift@users.noreply.github.com>

* test: add utilities for common setup

Signed-off-by: phazeschift <51278042+phazeschift@users.noreply.github.com>

* test: add delete server channel test

Signed-off-by: phazeschift <51278042+phazeschift@users.noreply.github.com>

* feat: allow to set an icon when creating a group

Signed-off-by: Nils Ponsard <nilsponsard@gmail.com>

* fix: allow message pinning and unpinning for DM-like channels

Signed-off-by: Bradlee Barnes <69256931+StupidRepo@users.noreply.github.com>

* fix: remove SavedMessages and Group due to given permissions

Signed-off-by: Bradlee Barnes <69256931+StupidRepo@users.noreply.github.com>

* 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>

* feat: add option to send message with missing replies

Signed-off-by: ShaksterNano <54268387+shaksternano@users.noreply.github.com>

* chore: bump version to 0.8.4
chore: enable webhooks in test builds

* ci: downgrade lockfile

* 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>

* fix: show full branch name on github webhook messages

* fix: dont leak invisible presence to others

* fix: add back missing early adopter badge

* fix: update branch from main

* fix: update branch from main

* Merge remote-tracking branch 'origin/main' into livekit

* feat: private livekit nodes

* fix: remove node from channel voice state

* fix: get_voice_state has incorrect unique_key and allowed_sources should be lowercase

* chore: bump livekit dep

* feat: ability to force disconnect existing voice connection

* chore: cleanup code

* feat: - track call length
- move voice and video limits to config
- seperate VoiceInformation into model and db model
- fix build scripts

* 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

* fix: dont set max participants in livekit

* 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

* fix: update migration

* feat: Send push notifications for dm call start/end

* chore: cleanup

* feat: allow users to specify who is notified when starting a call

* feat: track join time

* merge: branch 'main' into feat/livekit

* fix: include voice-ingress in build

* fix: dont presume channel has a node

* chore: add error logging to internal errors

* chore: add debug logging to voice ingress

* refactor: seperate out disconnect logic

* fix: return correct error if user is already in a voice channel

* fix: dont break on user not still being in channel and force disconnecting

* fix: add Speak and Listen to default permissions

* fix: include channel ids in UserMoveVoiceChannel

* chore: add temp sentry logging system

* fix: handle track_muted and track_unmuted

* fix: dont set notification recipients when empty vec is passed

* fix: only send UserMoveVoiceChannel to the user

* fix: preserve order of replies in message (#447)

Signed-off-by: Aeledfyr <aeledfyr@gmail.com>

* fix: prevent timing out members which have TimeoutMembers permission

* chore: update ci

* fix: temporarily disable call started system messages
See #457

* fix: update code from review
2025-11-12 21:27:04 +00:00
192 changed files with 2145 additions and 2566 deletions
+5 -4
View File
@@ -7,19 +7,20 @@ on:
pull_request:
paths:
- "Dockerfile"
workflow_dispatch:
permissions:
contents: read
packages: write
concurrency:
group: ${{ github.head_ref }}
concurrency:
group: ${{ github.head_ref || github.ref }}
cancel-in-progress: true
jobs:
base:
name: Test base image build
runs-on: ubuntu-latest
runs-on: arc-runner-set
if: github.event_name == 'pull_request'
steps:
# Configure build environment
@@ -40,7 +41,7 @@ jobs:
cache-to: type=gha,scope=buildx-base-multi-arch,mode=max
publish:
runs-on: self-hosted
runs-on: arc-runner-set
if: github.event_name != 'pull_request'
name: Publish Docker images
steps:
+21
View File
@@ -0,0 +1,21 @@
name: Publish Crates
on:
workflow_dispatch:
release:
types: [published]
jobs:
publish:
name: Publish Crates
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- name: Publish crates
uses: katyo/publish-crates@02cc2f1ad653fb25c7d1ff9eb590a8a50d06186b # v2
with:
registry-token: ${{ secrets.CRATES_IO_PUBLISH_TOKEN }}
+59
View File
@@ -0,0 +1,59 @@
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
- name: Install latest stable
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1
with:
toolchain: stable
- name: Update Cargo.lock
if: ${{ steps.rp.outputs.prs_created == 'true' || steps.rp.outputs.prs_updated == 'true' }}
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
PR_NUMBER=$(echo '${{ steps.rp.outputs.prs }}' | jq -r '.[0].number')
gh pr checkout "$PR_NUMBER"
cargo update -w
if git diff --quiet Cargo.lock; then
echo "No changes to Cargo.lock"
else
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add Cargo.lock
git commit -s -m "chore: update Cargo.lock"
git push
fi
+19
View File
@@ -0,0 +1,19 @@
name: Release Webhook
on:
workflow_dispatch:
release:
types: [published]
jobs:
release-webhook:
name: Send Release Webhook
runs-on: ubuntu-latest
steps:
- name: Send release notification webhook
run: |
RELEASE_URL="https://github.com/${{ github.repository }}/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\"}"
+21 -20
View File
@@ -5,8 +5,8 @@ on:
branches: [main]
pull_request:
concurrency:
group: ${{ github.head_ref }}
concurrency:
group: ${{ github.head_ref || github.ref }}
cancel-in-progress: true
env:
@@ -15,29 +15,30 @@ env:
jobs:
check:
name: Rust project
runs-on: ubuntu-latest
runs-on: arc-runner-set
steps:
- uses: actions/checkout@v2
- name: Free up disk space
run: |
sudo rm -rf /usr/local/lib/android /usr/share/dotnet /opt/ghc
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
# 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
uses: actions-rs/toolchain@v1
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1
with:
toolchain: stable
override: true
components: rustfmt, clippy
- name: Install cargo-nextest
uses: baptiste0928/cargo-install@v1
uses: taiki-e/install-action@a58ae9526b2c3acee9ad8e1926b950b7863305d4 # 2.65.16
with:
crate: cargo-nextest
args: --locked
tool: cargo-nextest@0.9.119
- name: Run cargo build
uses: actions-rs/cargo@v1
with:
command: build
run: cargo build
- name: Run services in background
run: |
@@ -65,13 +66,13 @@ jobs:
- name: Wait for API to go up
if: github.event_name != 'pull_request' && github.ref_name == 'main'
uses: nev7n/wait_for_response@v1
uses: nev7n/wait_for_response@7fef3c1a6e8939d0b09062f14fec50d3c5d15fa1 # v1.0.1
with:
url: "http://localhost:14702/"
- name: Checkout API repository
if: github.event_name != 'pull_request' && github.ref_name == 'main'
uses: actions/checkout@v3
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
repository: stoatchat/javascript-client-api
path: api
@@ -83,10 +84,10 @@ jobs:
- name: Commit changes
if: github.event_name != 'pull_request' && github.ref_name == 'main'
uses: EndBug/add-and-commit@v4
uses: EndBug/add-and-commit@a94899bca583c204427a224a7af87c02f9b325d5 # v9.1.4
with:
cwd: "api"
add: "*.json"
author_name: Revolt CI
author_email: revolt-ci@users.noreply.github.com
author_name: Stoat CI
author_email: stoat-ci@users.noreply.github.com
message: "chore: generate OpenAPI specification"
+3
View File
@@ -0,0 +1,3 @@
{
".": "0.9.3"
}
+65
View File
@@ -0,0 +1,65 @@
# Changelog
## [0.9.3](https://github.com/stoatchat/stoatchat/compare/v0.9.2...v0.9.3) (2026-01-10)
### Bug Fixes
* pipeline fixes ([#487](https://github.com/stoatchat/stoatchat/issues/487)) ([aeeafeb](https://github.com/stoatchat/stoatchat/commit/aeeafebefc36a43a656cf797c9251ca50292733c))
## [0.9.2](https://github.com/stoatchat/stoatchat/compare/v0.9.1...v0.9.2) (2026-01-10)
### Bug Fixes
* disable publish for services ([#485](https://github.com/stoatchat/stoatchat/issues/485)) ([d13609c](https://github.com/stoatchat/stoatchat/commit/d13609c37279d6a40445dcd99564e5c3dd03bac1))
## [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
+235 -333
View File
File diff suppressed because it is too large Load Diff
+2 -3
View File
@@ -11,9 +11,8 @@ members = [
[patch.crates-io]
redis23 = { package = "redis", version = "0.23.3", git = "https://github.com/revoltchat/redis-rs", rev = "523b2937367e17bd0073722bf6e23d06042cb4e4" }
authifier = { package = "authifier", version = "1.0.10", path = "../authifier/crates/authifier" }
rocket_authifier = { package = "rocket_authifier", version = "1.0.10", path = "../authifier/crates/rocket_authifier" }
iso8601-timestamp = { path = "../iso8601-timestamp" }
#authifier = { package = "authifier", version = "1.0.10", path = "../authifier/crates/authifier" }
#rocket_authifier = { package = "rocket_authifier", version = "1.0.10", path = "../authifier/crates/rocket_authifier" }
# I'm 99% sure this is overloading the GitHub worker
# hence builds have been failing since, let's just
-3
View File
@@ -37,9 +37,6 @@ The services and libraries that power the Revolt service.<br/>
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
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
@@ -55,7 +55,7 @@ services:
# Mock SMTP server
maildev:
image: soulteary/maildev
image: maildev/maildev
ports:
- "14025:25"
- "14080:8080"
+3 -2
View File
@@ -1,8 +1,9 @@
[package]
name = "revolt-bonfire"
version = "0.8.9"
version = "0.9.3"
license = "AGPL-3.0-or-later"
edition = "2021"
publish = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -42,7 +43,7 @@ revolt-result = { path = "../core/result" }
revolt-models = { path = "../core/models" }
revolt-config = { path = "../core/config" }
revolt-database = { path = "../core/database", features = ["voice"] }
revolt-permissions = { version = "0.8.9", path = "../core/permissions" }
revolt-permissions = { path = "../core/permissions" }
revolt-presence = { path = "../core/presence", features = ["redis-is-patched"] }
# redis
+3 -3
View File
@@ -1,6 +1,6 @@
[package]
name = "revolt-coalesced"
version = "0.8.9"
version = "0.9.3"
edition = "2021"
license = "MIT"
authors = ["Paul Makles <me@insrt.uk>", "Zomatree <me@zomatree.live>"]
@@ -15,8 +15,8 @@ default = ["tokio"]
[dependencies]
tokio = { version = "1.47.0", features = ["sync"], optional = true }
indexmap = { version = "*", optional = true }
lru = { version = "*", optional = true }
indexmap = { version = "2.13.0", optional = true }
lru = { version = "0.16.3", optional = true }
[dev-dependencies]
tokio = { version = "1.47.0", features = ["rt", "rt-multi-thread", "macros", "time"] }
+1 -1
View File
@@ -15,7 +15,7 @@ use crate::{CoalescionServiceConfig, Error};
#[derive(Debug, Clone)]
#[allow(clippy::type_complexity)]
/// Coalescion service
/// # Coalescion service
///
/// See module description for example usage.
pub struct CoalescionService<Id: Hash + Clone + Eq> {
+2 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "revolt-config"
version = "0.8.9"
version = "0.9.3"
edition = "2021"
license = "MIT"
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 }
# Core
revolt-result = { version = "0.8.9", path = "../result", optional = true }
revolt-result = { version = "0.9.3", path = "../result", optional = true }
+17 -17
View File
@@ -1,6 +1,6 @@
[package]
name = "revolt-database"
version = "0.8.9"
version = "0.9.3"
edition = "2021"
license = "AGPL-3.0-or-later"
authors = ["Paul Makles <me@insrt.uk>"]
@@ -15,7 +15,7 @@ mongodb = ["dep:mongodb", "bson", "authifier/database-mongodb"]
# ... Other
tasks = ["isahc", "linkify", "url-escape"]
async-std-runtime = ["async-std", "authifier/async-std-runtime"]
rocket-impl = ["rocket"]
rocket-impl = ["rocket", "schemars", "revolt_okapi", "revolt_rocket_okapi", "authifier/rocket_impl"]
axum-impl = ["axum"]
redis-is-patched = ["revolt-presence/redis-is-patched"]
voice = ["livekit-api", "livekit-protocol", "livekit-runtime"]
@@ -25,19 +25,19 @@ default = ["mongodb", "async-std-runtime", "tasks"]
[dependencies]
# Core
revolt-config = { version = "0.8.9", path = "../config", features = [
revolt-config = { version = "0.9.3", path = "../config", features = [
"report-macros",
] }
revolt-result = { version = "0.8.9", path = "../result" }
revolt-models = { version = "0.8.9", path = "../models", features = [
revolt-result = { version = "0.9.3", path = "../result" }
revolt-models = { version = "0.9.3", path = "../models", features = [
"validator",
] }
revolt-presence = { version = "0.8.9", path = "../presence" }
revolt-permissions = { version = "0.8.9", path = "../permissions", features = [
revolt-presence = { version = "0.9.3", path = "../presence" }
revolt-permissions = { version = "0.9.3", path = "../permissions", features = [
"serde",
"bson",
] }
revolt-parser = { version = "0.8.9", path = "../parser" }
revolt-parser = { version = "0.9.3", path = "../parser" }
# Utility
log = "0.4"
@@ -47,7 +47,7 @@ ulid = "1.0.0"
nanoid = "0.4.0"
base64 = "0.21.3"
once_cell = "1.17"
indexmap = "2.12.0"
indexmap = "1.9.1"
decancer = "1.6.2"
deadqueue = "0.2.4"
linkify = { optional = true, version = "0.8.1" }
@@ -59,7 +59,7 @@ isahc = { optional = true, version = "1.7", features = ["json"] }
serde_json = "1"
revolt_optional_struct = "0.2.0"
serde = { version = "1", features = ["derive"] }
iso8601-timestamp = { version = "0.4.0", features = ["serde", "bson"] }
iso8601-timestamp = { version = "0.2.10", features = ["serde", "bson"] }
# Events
redis-kiss = { version = "0.1.4" }
@@ -82,15 +82,15 @@ async-recursion = "1.0.4"
async-std = { version = "1.8.0", features = ["attributes"], optional = true }
# Axum Impl
axum = { version = "0.8.6", optional = true }
axum = { version = "0.7.5", optional = true }
# Rocket Impl
schemars = { version = "0.8.8", optional = true }
rocket = { version = "0.5.1", default-features = false, features = [
"json",
], optional = true }
# Openapi Schema
utoipa = { version = "5.4.0", optional = true }
revolt_okapi = { version = "0.9.1", optional = true }
revolt_rocket_okapi = { version = "0.10.0", optional = true }
# Authifier
authifier = { version = "1.0.15" }
@@ -99,6 +99,6 @@ authifier = { version = "1.0.15" }
amqprs = { version = "1.7.0" }
# Voice
livekit-api = { version = "=0.4.4", optional = true}
livekit-protocol = { version = "=0.4.0", optional = true }
livekit-runtime = { version = "=0.3.1", features = ["tokio"], optional = true }
livekit-api = { version = "0.4.4", optional = true}
livekit-protocol = { version = "0.4.0", optional = true }
livekit-runtime = { version = "0.3.1", features = ["tokio"], optional = true }
@@ -47,6 +47,7 @@
],
"roles": {
"__ID:5__": {
"_id": "__ID:5__",
"name": "Moderator",
"permissions": {
"a": 545270208,
@@ -55,6 +56,7 @@
"rank": 1
},
"__ID:6__": {
"_id": "__ID:6__",
"name": "Owner",
"permissions": {
"a": 0,
+3 -7
View File
@@ -25,10 +25,6 @@ pub use mongodb;
#[macro_use]
extern crate bson;
#[cfg(feature = "utoipa")]
#[macro_use]
extern crate utoipa;
#[cfg(not(feature = "async-std-runtime"))]
compile_error!("async-std-runtime feature must be enabled.");
@@ -64,7 +60,7 @@ macro_rules! database_derived {
macro_rules! auto_derived {
( $( $item:item )+ ) => {
$(
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
$item
)+
};
@@ -72,8 +68,8 @@ macro_rules! auto_derived {
macro_rules! auto_derived_partial {
( $item:item, $name:expr ) => {
#[derive(OptionalStruct, Serialize, Deserialize, Debug, Clone, PartialEq)]
#[optional_derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq)]
#[derive(OptionalStruct, Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
#[optional_derive(Serialize, Deserialize, Debug, Clone, Default, Eq, PartialEq)]
#[optional_name = $name]
#[opt_skip_serializing_none]
#[opt_some_priority]
@@ -25,7 +25,7 @@ struct MigrationInfo {
revision: i32,
}
pub const LATEST_REVISION: i32 = 49; // MUST BE +1 to last migration
pub const LATEST_REVISION: i32 = 50; // MUST BE +1 to last migration
pub async fn migrate_database(db: &MongoDb) {
let migrations = db.col::<Document>("migrations");
@@ -1246,7 +1246,7 @@ pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 {
.expect("Failed to update voice channels");
};
if revision <= 48 {
if revision <= 48 {
info!("Running migration [revision 48 / 22-10-2025]: Add Video + Listen to default permissions");
db.col::<Document>("servers")
@@ -1264,6 +1264,48 @@ pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 {
.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.
LATEST_REVISION.max(revision)
}
@@ -14,7 +14,7 @@ auto_derived!(
}
/// Composite primary key consisting of channel and user id
#[derive(Hash, Eq)]
#[derive(Hash)]
pub struct ChannelCompositeKey {
/// Channel Id
pub channel: String,
@@ -247,7 +247,7 @@ impl AbstractMessages for ReferenceDb {
let mut messages = self.messages.lock().await;
if let Some(message) = messages.get_mut(id) {
if let Some(users) = message.reactions.get_mut(emoji) {
users.swap_remove(&user.to_string());
users.remove(&user.to_string());
}
Ok(())
@@ -260,7 +260,7 @@ impl AbstractMessages for ReferenceDb {
async fn clear_reaction(&self, id: &str, emoji: &str) -> Result<()> {
let mut messages = self.messages.lock().await;
if let Some(message) = messages.get_mut(id) {
message.reactions.swap_remove(emoji);
message.reactions.remove(emoji);
Ok(())
} else {
Err(create_error!(NotFound))
@@ -55,7 +55,7 @@ auto_derived_partial!(
auto_derived!(
/// Composite primary key consisting of server and user id
#[derive(Hash, Default, Eq)]
#[derive(Hash, Default)]
pub struct MemberCompositeKey {
/// Server Id
pub server: String,
@@ -68,6 +68,9 @@ auto_derived_partial!(
auto_derived_partial!(
/// Role
pub struct Role {
/// Unique Id
#[serde(rename = "_id")]
pub id: String,
/// Role name
pub name: String,
/// Permissions available to this role
@@ -246,7 +249,6 @@ impl Server {
role.update(
db,
&self.id,
role_id,
PartialRole {
permissions: Some(permissions),
..Default::default()
@@ -297,6 +299,7 @@ impl Role {
/// Into optional struct
pub fn into_optional(self) -> PartialRole {
PartialRole {
id: Some(self.id),
name: Some(self.name),
permissions: Some(self.permissions),
colour: self.colour,
@@ -306,20 +309,29 @@ impl Role {
}
/// Create a role
pub async fn create(&self, db: &Database, server_id: &str) -> Result<String> {
let role_id = Ulid::new().to_string();
db.insert_role(server_id, &role_id, self).await?;
pub async fn create(db: &Database, server: &Server, name: String) -> Result<Self> {
let role = Role {
id: Ulid::new().to_string(),
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 {
id: server_id.to_string(),
role_id: role_id.to_string(),
data: self.clone().into_optional().into(),
id: server.id.clone(),
role_id: role.id.clone(),
data: role.clone().into_optional().into(),
clear: vec![],
}
.p(server_id.to_string())
.p(server.id.clone())
.await;
Ok(role_id)
Ok(role)
}
/// Update server data
@@ -327,7 +339,6 @@ impl Role {
&mut self,
db: &Database,
server_id: &str,
role_id: &str,
partial: PartialRole,
remove: Vec<FieldsRole>,
) -> Result<()> {
@@ -337,14 +348,14 @@ impl Role {
self.apply_options(partial.clone());
db.update_role(server_id, role_id, &partial, remove.clone())
db.update_role(server_id, &self.id, &partial, remove.clone())
.await?;
EventV1::ServerRoleUpdate {
id: server_id.to_string(),
role_id: role_id.to_string(),
role_id: self.id.clone(),
data: partial.into(),
clear: vec![],
clear: remove.into_iter().map(Into::into).collect(),
}
.p(server_id.to_string())
.await;
@@ -360,15 +371,15 @@ impl Role {
}
/// Delete a role
pub async fn delete(self, db: &Database, server_id: &str, role_id: &str) -> Result<()> {
pub async fn delete(self, db: &Database, server_id: &str) -> Result<()> {
EventV1::ServerRoleDelete {
id: server_id.to_string(),
role_id: role_id.to_string(),
role_id: self.id.clone(),
}
.p(server_id.to_string())
.await;
db.delete_role(server_id, role_id).await
db.delete_role(server_id, &self.id).await
}
}
@@ -29,7 +29,7 @@ pub trait AbstractServers: Sync + Send {
async fn delete_server(&self, id: &str) -> Result<()>;
/// Insert a new role into server object
async fn insert_role(&self, server_id: &str, role_id: &str, role: &Role) -> Result<()>;
async fn insert_role(&self, server_id: &str, role: &Role) -> Result<()>;
/// Update an existing role on a server
async fn update_role(
@@ -69,7 +69,7 @@ impl AbstractServers for MongoDb {
}
/// Insert a new role into server object
async fn insert_role(&self, server_id: &str, role_id: &str, role: &Role) -> Result<()> {
async fn insert_role(&self, server_id: &str, role: &Role) -> Result<()> {
self.col::<Document>(COL)
.update_one(
doc! {
@@ -77,7 +77,7 @@ impl AbstractServers for MongoDb {
},
doc! {
"$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"))?
}
},
@@ -72,10 +72,10 @@ impl AbstractServers for ReferenceDb {
}
/// Insert a new role into server object
async fn insert_role(&self, server_id: &str, role_id: &str, role: &Role) -> Result<()> {
async fn insert_role(&self, server_id: &str, role: &Role) -> Result<()> {
let mut servers = self.servers.lock().await;
if let Some(server) = servers.get_mut(server_id) {
server.roles.insert(role_id.to_string(), role.clone());
server.roles.insert(role.id.clone(), role.clone());
Ok(())
} else {
Err(create_error!(NotFound))
@@ -4,6 +4,7 @@ use revolt_result::{create_error, Error, Result};
use crate::{Database, User};
#[async_trait::async_trait]
impl<S> FromRequestParts<S> for User
where
Database: FromRef<S>,
@@ -4,6 +4,8 @@ mod model;
mod ops;
#[cfg(feature = "rocket-impl")]
mod rocket;
#[cfg(feature = "rocket-impl")]
mod schema;
pub use model::*;
pub use ops::*;
@@ -15,7 +15,7 @@ use serde_json::json;
use ulid::Ulid;
auto_derived_partial!(
/// User
/// # User
pub struct User {
/// Unique Id
#[serde(rename = "_id")]
@@ -0,0 +1,31 @@
use revolt_okapi::openapi3::{SecurityScheme, SecuritySchemeData};
use revolt_rocket_okapi::{
gen::OpenApiGenerator,
request::{OpenApiFromRequest, RequestHeaderInput},
};
use crate::User;
impl OpenApiFromRequest<'_> for User {
fn from_request_input(
_gen: &mut OpenApiGenerator,
_name: String,
_required: bool,
) -> revolt_rocket_okapi::Result<RequestHeaderInput> {
let mut requirements = schemars::Map::new();
requirements.insert("Session Token".to_owned(), vec![]);
Ok(RequestHeaderInput::Security(
"Session Token".to_owned(),
SecurityScheme {
data: SecuritySchemeData::ApiKey {
name: "x-session-token".to_owned(),
location: "header".to_owned(),
},
description: Some("Used to authenticate as a user.".to_owned()),
extensions: schemars::Map::new(),
},
requirements,
))
}
}
+1 -1
View File
@@ -17,7 +17,7 @@ use super::DelayedTask;
use crate::Channel::TextChannel;
/// Enumeration of possible events
#[derive(Debug, PartialEq)]
#[derive(Debug, Eq, PartialEq)]
pub enum AckEvent {
/// Add mentions for a channel
ProcessMessage {
@@ -896,6 +896,7 @@ impl From<SystemMessageChannels> for crate::SystemMessageChannels {
impl From<crate::Role> for Role {
fn from(value: crate::Role) -> Self {
Role {
id: value.id,
name: value.name,
permissions: value.permissions,
colour: value.colour,
@@ -908,6 +909,7 @@ impl From<crate::Role> for Role {
impl From<Role> for crate::Role {
fn from(value: Role) -> crate::Role {
crate::Role {
id: value.id,
name: value.name,
permissions: value.permissions,
colour: value.colour,
@@ -920,6 +922,7 @@ impl From<Role> for crate::Role {
impl From<crate::PartialRole> for PartialRole {
fn from(value: crate::PartialRole) -> Self {
PartialRole {
id: value.id,
name: value.name,
permissions: value.permissions,
colour: value.colour,
@@ -932,6 +935,7 @@ impl From<crate::PartialRole> for PartialRole {
impl From<PartialRole> for crate::PartialRole {
fn from(value: PartialRole) -> crate::PartialRole {
crate::PartialRole {
id: value.id,
name: value.name,
permissions: value.permissions,
colour: value.colour,
+49 -47
View File
@@ -10,16 +10,16 @@ use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(IntoParams))]
#[cfg_attr(feature = "utoipa", into_params(names("Idempotency-Key"), parameter_in=Header))]
pub struct IdempotencyKey(String);
pub struct IdempotencyKey {
key: String,
}
static TOKEN_CACHE: Lazy<Mutex<lru::LruCache<String, ()>>> =
Lazy::new(|| Mutex::new(lru::LruCache::new(NonZeroUsize::new(1000).unwrap())));
impl IdempotencyKey {
pub fn unchecked_from_string(key: String) -> Self {
Self(key)
Self { key }
}
// Backwards compatibility.
@@ -32,56 +32,56 @@ impl IdempotencyKey {
}
cache.put(v.clone(), ());
self.0 = v;
self.key = v;
}
Ok(())
}
pub fn into_key(self) -> String {
self.0
self.key
}
}
// #[cfg(feature = "rocket-impl")]
// use revolt_rocket_okapi::{
// gen::OpenApiGenerator,
// request::{OpenApiFromRequest, RequestHeaderInput},
// revolt_okapi::openapi3::{Parameter, ParameterValue},
// };
#[cfg(feature = "rocket-impl")]
use revolt_rocket_okapi::{
gen::OpenApiGenerator,
request::{OpenApiFromRequest, RequestHeaderInput},
revolt_okapi::openapi3::{Parameter, ParameterValue},
};
// #[cfg(feature = "rocket-impl")]
// use schemars::schema::{InstanceType, SchemaObject, SingleOrVec};
#[cfg(feature = "rocket-impl")]
use schemars::schema::{InstanceType, SchemaObject, SingleOrVec};
// #[cfg(feature = "rocket-impl")]
// impl OpenApiFromRequest<'_> for IdempotencyKey {
// fn from_request_input(
// _gen: &mut OpenApiGenerator,
// _name: String,
// _required: bool,
// ) -> revolt_rocket_okapi::Result<RequestHeaderInput> {
// Ok(RequestHeaderInput::Parameter(Parameter {
// name: "Idempotency-Key".to_string(),
// description: Some("Unique key to prevent duplicate requests".to_string()),
// allow_empty_value: false,
// required: false,
// deprecated: false,
// extensions: schemars::Map::new(),
// location: "header".to_string(),
// value: ParameterValue::Schema {
// allow_reserved: false,
// example: None,
// examples: None,
// explode: None,
// style: None,
// schema: SchemaObject {
// instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))),
// ..Default::default()
// },
// },
// }))
// }
// }
#[cfg(feature = "rocket-impl")]
impl OpenApiFromRequest<'_> for IdempotencyKey {
fn from_request_input(
_gen: &mut OpenApiGenerator,
_name: String,
_required: bool,
) -> revolt_rocket_okapi::Result<RequestHeaderInput> {
Ok(RequestHeaderInput::Parameter(Parameter {
name: "Idempotency-Key".to_string(),
description: Some("Unique key to prevent duplicate requests".to_string()),
allow_empty_value: false,
required: false,
deprecated: false,
extensions: schemars::Map::new(),
location: "header".to_string(),
value: ParameterValue::Schema {
allow_reserved: false,
example: None,
examples: None,
explode: None,
style: None,
schema: SchemaObject {
instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))),
..Default::default()
},
},
}))
}
}
#[cfg(feature = "rocket-impl")]
use rocket::{
@@ -110,16 +110,18 @@ impl<'r> FromRequest<'r> for IdempotencyKey {
));
}
let idempotency = IdempotencyKey(key);
let idempotency = IdempotencyKey { key };
let mut cache = TOKEN_CACHE.lock().await;
if cache.get(&idempotency.0).is_some() {
if cache.get(&idempotency.key).is_some() {
return Outcome::Error((Status::Conflict, create_error!(DuplicateNonce)));
}
cache.put(idempotency.0.clone(), ());
cache.put(idempotency.key.clone(), ());
return Outcome::Success(idempotency);
}
Outcome::Success(IdempotencyKey(ulid::Ulid::new().to_string()))
Outcome::Success(IdempotencyKey {
key: ulid::Ulid::new().to_string(),
})
}
}
-2
View File
@@ -5,7 +5,5 @@ pub mod idempotency;
pub mod permissions;
pub mod reference;
pub mod test_fixtures;
#[cfg(feature = "utoipa")]
pub mod utoipa;
pub use funcs::*;
@@ -3,6 +3,11 @@ use std::str::FromStr;
use revolt_result::Result;
#[cfg(feature = "rocket-impl")]
use rocket::request::FromParam;
#[cfg(feature = "rocket-impl")]
use schemars::{
schema::{InstanceType, Schema, SchemaObject, SingleOrVec},
JsonSchema,
};
use crate::{
Bot, Channel, Database, Emoji, Invite, Member, Message, Server, ServerBan, User, Webhook,
@@ -107,3 +112,17 @@ impl<'r> FromParam<'r> for Reference<'r> {
Ok(Reference::from_unchecked(param))
}
}
#[cfg(feature = "rocket-impl")]
impl<'a> JsonSchema for Reference<'a> {
fn schema_name() -> String {
"Id".to_string()
}
fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> Schema {
Schema::Object(SchemaObject {
instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))),
..Default::default()
})
}
}
-49
View File
@@ -1,49 +0,0 @@
use utoipa::{
openapi::{
schema::SchemaType,
security::{ApiKey, ApiKeyValue, SecurityScheme},
ObjectBuilder, OpenApi, RefOr, Schema, Type,
},
Modify, PartialSchema, ToSchema,
};
use crate::util::reference::Reference;
pub struct TokenSecurity;
impl Modify for TokenSecurity {
fn modify(&self, openapi: &mut OpenApi) {
let components = openapi.components.get_or_insert_default();
components.add_security_scheme(
"Session-Token",
SecurityScheme::ApiKey(ApiKey::Header(ApiKeyValue::new(
"X-Session-Token".to_string(),
))),
);
components.add_security_scheme(
"Bot-Token",
SecurityScheme::ApiKey(ApiKey::Header(ApiKeyValue::new("X-Bot-Ticket".to_string()))),
);
}
}
impl ToSchema for Reference<'_> {
fn name() -> std::borrow::Cow<'static, str> {
std::borrow::Cow::Borrowed("Reference")
}
}
impl PartialSchema for Reference<'_> {
fn schema() -> RefOr<Schema> {
RefOr::T(
ObjectBuilder::new()
.description(Some("An id referencing a stoat model."))
.schema_type(SchemaType::Type(Type::String))
.examples(["01FD58YK5W7QRV5H3D64KTQYX3"])
.build()
.into(),
)
}
}
+3 -3
View File
@@ -1,6 +1,6 @@
[package]
name = "revolt-files"
version = "0.8.9"
version = "0.9.3"
edition = "2021"
license = "AGPL-3.0-or-later"
authors = ["Paul Makles <me@insrt.uk>"]
@@ -20,10 +20,10 @@ typenum = "1.17.0"
aws-config = "1.5.5"
aws-sdk-s3 = { version = "1.46.0", features = ["behavior-version-latest"] }
revolt-config = { version = "0.8.9", path = "../config", features = [
revolt-config = { version = "0.9.3", path = "../config", features = [
"report-macros",
] }
revolt-result = { version = "0.8.9", path = "../result" }
revolt-result = { version = "0.9.3", path = "../result" }
# image processing
jxl-oxide = "0.8.1"
+10 -9
View File
@@ -1,6 +1,6 @@
[package]
name = "revolt-models"
version = "0.8.9"
version = "0.9.3"
edition = "2021"
license = "MIT"
authors = ["Paul Makles <me@insrt.uk>"]
@@ -10,22 +10,22 @@ description = "Revolt Backend: API Models"
[features]
serde = ["dep:serde", "revolt-permissions/serde", "indexmap/serde"]
schemas = ["dep:schemars", "revolt-permissions/schemas"]
utoipa = ["dep:utoipa"]
validator = ["dep:validator"]
rocket = ["dep:rocket"]
partials = ["dep:revolt_optional_struct", "serde", "utoipa"]
partials = ["dep:revolt_optional_struct", "serde", "schemas", "utoipa"]
default = ["serde", "partials", "rocket"]
[dependencies]
# Core
revolt-config = { version = "0.8.9", path = "../config" }
revolt-permissions = { version = "0.8.9", path = "../permissions", features = [
"utoipa",
] }
revolt-config = { version = "0.9.3", path = "../config" }
revolt-permissions = { version = "0.9.3", path = "../permissions" }
# Utility
regex = "1.11"
indexmap = "2.12.0"
indexmap = "1.9.3"
once_cell = "1.17.1"
num_enum = "0.6.1"
@@ -35,10 +35,11 @@ rocket = { optional = true, version = "0.5.0-rc.2", default-features = false }
# Serialisation
revolt_optional_struct = { version = "0.2.0", optional = true }
serde = { version = "1", features = ["derive"], optional = true }
iso8601-timestamp = { version = "0.4.0", features = ["utoipa", "bson"] }
iso8601-timestamp = { version = "0.2.11", features = ["schema", "bson"] }
# Spec Generation
utoipa = { version = "5.4.0", features = ["indexmap"], optional = true }
schemars = { version = "0.8.8", optional = true, features = ["indexmap1"] }
utoipa = { version = "4.2.3", optional = true }
# Validation
validator = { version = "0.16.0", optional = true, features = ["derive"] }
+20 -4
View File
@@ -2,6 +2,10 @@
#[macro_use]
extern crate serde;
#[cfg(feature = "schemas")]
#[macro_use]
extern crate schemars;
#[cfg(feature = "utoipa")]
#[macro_use]
extern crate utoipa;
@@ -17,8 +21,9 @@ macro_rules! auto_derived {
( $( $item:item )+ ) => {
$(
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "schemas", derive(JsonSchema))]
#[cfg_attr(feature = "utoipa", derive(ToSchema))]
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, Eq, PartialEq)]
$item
)+
};
@@ -27,8 +32,19 @@ macro_rules! auto_derived {
#[cfg(feature = "partials")]
macro_rules! auto_derived_partial {
( $item:item, $name:expr ) => {
#[derive(OptionalStruct, Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema)]
#[optional_derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema, Default)]
#[derive(
OptionalStruct, Debug, Clone, Eq, PartialEq, Serialize, Deserialize, JsonSchema,
)]
#[optional_derive(
Debug,
Clone,
Eq,
PartialEq,
Serialize,
Deserialize,
JsonSchema,
Default
)]
#[optional_name = $name]
#[opt_skip_serializing_none]
#[opt_some_priority]
@@ -39,7 +55,7 @@ macro_rules! auto_derived_partial {
#[cfg(not(feature = "partials"))]
macro_rules! auto_derived_partial {
( $item:item, $name:expr ) => {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema)]
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
$item
};
}
@@ -86,10 +86,10 @@ auto_derived!(
/// Information for the webhook
#[cfg_attr(feature = "validator", derive(Validate))]
pub struct CreateWebhookBody {
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 32)))]
#[validate(length(min = 1, max = 32))]
pub name: String,
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 128)))]
#[validate(length(min = 1, max = 128))]
pub avatar: Option<String>,
}
);
-1
View File
@@ -271,7 +271,6 @@ auto_derived!(
/// Options when deleting a channel
#[cfg_attr(feature = "rocket", derive(FromForm))]
#[cfg_attr(feature = "utoipa", derive(IntoParams))]
pub struct OptionsChannelDelete {
/// Whether to not send a leave message
pub leave_silently: Option<bool>,
-78
View File
@@ -1,78 +0,0 @@
auto_derived!(
/// hCaptcha Configuration
pub struct CaptchaFeature {
/// Whether captcha is enabled
pub enabled: bool,
/// Client key used for solving captcha
pub key: String,
}
/// Generic Service Configuration
pub struct Feature {
/// Whether the service is enabled
pub enabled: bool,
/// URL pointing to the service
pub url: String,
}
/// # Information about a livekit node
pub struct VoiceNode {
pub name: String,
pub lat: f64,
pub lon: f64,
pub public_url: String,
}
/// # Voice Server Configuration
pub struct VoiceFeature {
/// Whether voice is enabled
pub enabled: bool,
/// All livekit nodes
pub nodes: Vec<VoiceNode>,
}
/// Feature Configuration
pub struct RevoltFeatures {
/// hCaptcha configuration
pub captcha: CaptchaFeature,
/// Whether email verification is enabled
pub email: bool,
/// Whether this server is invite only
pub invite_only: bool,
/// File server service configuration
pub autumn: Feature,
/// Proxy service configuration
pub january: Feature,
/// Voice server configuration
pub livekit: VoiceFeature,
}
/// Build Information
pub struct BuildInformation {
/// Commit Hash
pub commit_sha: String,
/// Commit Timestamp
pub commit_timestamp: String,
/// Git Semver
pub semver: String,
/// Git Origin URL
pub origin_url: String,
/// Build Timestamp
pub timestamp: String,
}
/// Server Configuration
pub struct RevoltConfig {
/// Revolt API Version
pub revolt: String,
/// Features enabled on this Revolt node
pub features: RevoltFeatures,
/// WebSocket URL
pub ws: String,
/// URL pointing to the client serving this node
pub app: String,
/// Web Push VAPID public key
pub vapid: String,
/// Build information
pub build: BuildInformation,
}
);
+1 -1
View File
@@ -46,7 +46,7 @@ auto_derived!(
#[cfg_attr(feature = "validator", derive(Validate))]
pub struct DataCreateEmoji {
/// Server name
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 32), regex = "RE_EMOJI"))]
#[validate(length(min = 1, max = 32), regex = "RE_EMOJI")]
pub name: String,
/// Parent information
pub parent: EmojiParent,
+4 -6
View File
@@ -141,17 +141,17 @@ auto_derived!(
pub struct Masquerade {
/// Replace the display name shown on this message
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 32)))]
#[validate(length(min = 1, max = 32))]
pub name: Option<String>,
/// Replace the avatar shown on this message (URL to image file)
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 256)))]
#[validate(length(min = 1, max = 256))]
pub avatar: Option<String>,
/// Replace the display role colour shown on this message
///
/// Must have `ManageRole` permission to use
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 128), regex = "RE_COLOUR"))]
#[validate(length(min = 1, max = 128), regex = "RE_COLOUR")]
pub colour: Option<String>,
}
@@ -281,7 +281,6 @@ auto_derived!(
/// Options for querying messages
#[cfg_attr(feature = "validator", derive(Validate))]
#[cfg_attr(feature = "rocket", derive(FromForm))]
#[cfg_attr(feature = "utoipa", derive(IntoParams))]
pub struct OptionsQueryMessages {
/// Maximum number of messages to fetch
///
@@ -354,13 +353,12 @@ auto_derived!(
)]
pub struct OptionsBulkDelete {
/// Message IDs
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 100)))]
#[validate(length(min = 1, max = 100))]
pub ids: Vec<String>,
}
/// Options for removing reaction
#[cfg_attr(feature = "rocket", derive(FromForm))]
#[cfg_attr(feature = "utoipa", derive(IntoParams))]
pub struct OptionsUnreact {
/// Remove a specific user's reaction
pub user_id: Option<String>,
-2
View File
@@ -3,7 +3,6 @@ mod channel_invites;
mod channel_unreads;
mod channel_webhooks;
mod channels;
mod config;
mod embeds;
mod emojis;
mod files;
@@ -21,7 +20,6 @@ pub use channel_invites::*;
pub use channel_unreads::*;
pub use channel_webhooks::*;
pub use channels::*;
pub use config::*;
pub use embeds::*;
pub use emojis::*;
pub use files::*;
@@ -116,7 +116,6 @@ auto_derived!(
/// Options for fetching all members
#[cfg_attr(feature = "rocket", derive(FromForm))]
#[cfg_attr(feature = "utoipa", derive(IntoParams))]
pub struct OptionsFetchAllMembers {
/// Whether to exclude offline users
pub exclude_offline: Option<bool>,
+4 -2
View File
@@ -85,6 +85,9 @@ auto_derived_partial!(
auto_derived_partial!(
/// Role
pub struct Role {
/// Unique Id
#[cfg_attr(feature = "serde", serde(rename = "_id"))]
pub id: String,
/// Role name
pub name: String,
/// Permissions available to this role
@@ -181,6 +184,7 @@ auto_derived!(
}
/// Response after creating new role
// TODO: remove this in favor of just Role
pub struct NewRoleResponse {
/// Id of the role
pub id: String,
@@ -198,7 +202,6 @@ auto_derived!(
/// Options when fetching server
#[cfg_attr(feature = "rocket", derive(FromForm))]
#[cfg_attr(feature = "utoipa", derive(IntoParams))]
pub struct OptionsFetchServer {
/// Whether to include channels
pub include_channels: Option<bool>,
@@ -285,7 +288,6 @@ auto_derived!(
/// Options when leaving a server
#[cfg_attr(feature = "rocket", derive(FromForm))]
#[cfg_attr(feature = "utoipa", derive(IntoParams))]
pub struct OptionsServerDelete {
/// Whether to not send a leave message
pub leave_silently: Option<bool>,
@@ -17,7 +17,6 @@ auto_derived!(
/// Additional options for inserting settings
#[cfg_attr(feature = "rocket", derive(FromForm))]
#[cfg_attr(feature = "utoipa", derive(IntoParams))]
pub struct OptionsSetSettings {
/// Timestamp of settings change.
///
+2 -2
View File
@@ -142,7 +142,7 @@ auto_derived!(
#[cfg_attr(feature = "validator", derive(Validate))]
pub struct UserStatus {
/// Custom status text
#[cfg_attr(feature = "validator", validate(length(min = 0, max = 128)))]
#[validate(length(min = 0, max = 128))]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub text: Option<String>,
/// Current presence option
@@ -155,7 +155,7 @@ auto_derived!(
#[cfg_attr(feature = "validator", derive(Validate))]
pub struct UserProfile {
/// Text content on user's profile
#[cfg_attr(feature = "validator", validate(length(min = 0, max = 2000)))]
#[validate(length(min = 0, max = 2000))]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub content: Option<String>,
/// Background visible on user's profile
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "revolt-parser"
version = "0.8.9"
version = "0.9.3"
edition = "2021"
license = "MIT"
authors = ["Zomatree <me@zomatree.live>", "Paul Makles <me@insrt.uk>"]
+5 -3
View File
@@ -1,6 +1,6 @@
[package]
name = "revolt-permissions"
version = "0.8.9"
version = "0.9.3"
edition = "2021"
license = "MIT"
authors = ["Paul Makles <me@insrt.uk>"]
@@ -11,15 +11,17 @@ description = "Revolt Backend: Permission Logic"
[features]
bson = ["dep:bson"]
serde = ["dep:serde"]
schemas = ["dep:schemars"]
try-from-primitive = ["dep:num_enum"]
[dev-dependencies]
# Async
async-std = { version = "1.8.0", features = ["attributes"] }
[dependencies]
# Core
revolt-result = { version = "0.8.9", path = "../result" }
revolt-result = { version = "0.9.3", path = "../result" }
# Utility
auto_ops = "0.3.0"
@@ -34,4 +36,4 @@ serde = { version = "1", features = ["derive"], optional = true }
bson = { version = "2.1.0", optional = true }
# Spec Generation
utoipa = { version = "5.4.0", optional = true }
schemars = { version = "0.8.8", optional = true }
+7 -7
View File
@@ -1,10 +1,10 @@
#[cfg(feature = "utoipa")]
use utoipa::ToSchema;
#[cfg(feature = "schemas")]
use schemars::JsonSchema;
/// Representation of a single permission override
#[derive(Debug, Clone, Eq, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "utoipa", derive(ToSchema))]
#[cfg_attr(feature = "schemas", derive(JsonSchema))]
pub struct Override {
/// Allow bit flags
pub allow: u64,
@@ -15,7 +15,7 @@ pub struct Override {
/// Data permissions Field - contains both allow and deny
#[derive(Debug, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "utoipa", derive(ToSchema))]
#[cfg_attr(feature = "schemas", derive(JsonSchema))]
pub struct DataPermissionsField {
pub permissions: Override,
}
@@ -23,7 +23,7 @@ pub struct DataPermissionsField {
/// Data permissions Value - contains allow
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "utoipa", derive(ToSchema))]
#[cfg_attr(feature = "schemas", derive(JsonSchema))]
pub struct DataPermissionsValue {
pub permissions: u64,
}
@@ -31,7 +31,7 @@ pub struct DataPermissionsValue {
/// Data permissions Poly - can contain either Value or Field
#[derive(Debug, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "utoipa", derive(ToSchema))]
#[cfg_attr(feature = "schemas", derive(JsonSchema))]
#[cfg_attr(feature = "serde", serde(untagged))]
pub enum DataPermissionPoly {
Value {
@@ -48,7 +48,7 @@ pub enum DataPermissionPoly {
/// as it appears on models and in the database
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "utoipa", derive(ToSchema))]
#[cfg_attr(feature = "schemas", derive(JsonSchema))]
pub struct OverrideField {
/// Allow bit flags
pub a: i64,
+2 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "revolt-presence"
version = "0.8.9"
version = "0.9.3"
edition = "2021"
license = "AGPL-3.0-or-later"
authors = ["Paul Makles <me@insrt.uk>"]
@@ -16,7 +16,7 @@ redis-is-patched = []
async-std = { version = "1.8.0", features = ["attributes"] }
# Config for loading Redis URI
revolt-config = { version = "0.8.9", path = "../config" }
revolt-config = { version = "0.9.3", path = "../config" }
[dependencies]
# Utility
+7 -9
View File
@@ -1,25 +1,23 @@
[package]
name = "revolt-ratelimits"
version = "0.8.9"
version = "0.9.3"
edition = "2024"
[features]
rocket = [
"dep:rocket",
"revolt-database/rocket-impl",
]
rocket = ["dep:rocket", "dep:revolt_rocket_okapi", "revolt-database/rocket-impl"]
axum = ["dep:axum", "revolt-database/axum-impl"]
default = ["rocket", "axum"]
[dependencies]
revolt-database = { version = "0.8.9", path = "../database" }
revolt-result = { version = "0.8.9", path = "../result" }
revolt-config = { version = "0.8.9", path = "../config" }
revolt-database = { version = "0.9.3", path = "../database"}
revolt-result = { version = "0.9.3", path = "../result" }
revolt-config = { version = "0.9.3", path = "../config" }
rocket = { version = "0.5.1", optional = true }
revolt_rocket_okapi = { version = "0.10.0", optional = true }
axum = { version = "0.8.6", optional = true, features = ["macros"] }
axum = { version = "0.7.5", optional = true, features = ["macros"] }
serde = { version = "1", features = ["derive"] }
authifier = { version = "1.0.15" }
+3
View File
@@ -1,5 +1,6 @@
use std::net::SocketAddr;
use async_trait::async_trait;
use axum::{
Json, RequestPartsExt, Router,
body::Body,
@@ -43,6 +44,7 @@ async fn to_real_ip(parts: &Parts) -> String {
}
}
#[async_trait]
impl<S: Send + Sync> FromRequestParts<S> for Ratelimiter
where
Database: FromRef<S>,
@@ -80,6 +82,7 @@ where
}
}
#[async_trait]
impl<S: Send + Sync> FromRequestParts<S> for RatelimitInformation
where
Database: FromRef<S>,
+13
View File
@@ -8,6 +8,9 @@ use rocket::serde::json::Json;
use rocket::{Data, Request, Response, State};
use revolt_config::config;
use revolt_rocket_okapi::r#gen::OpenApiGenerator;
use revolt_rocket_okapi::request::{OpenApiFromRequest, RequestHeaderInput};
use authifier::models::Session;
use crate::ratelimiter::RequestKind;
@@ -75,6 +78,16 @@ impl<'r> FromRequest<'r> for Ratelimiter {
}
}
impl OpenApiFromRequest<'_> for Ratelimiter {
fn from_request_input(
_gen: &mut OpenApiGenerator,
_name: String,
_required: bool,
) -> revolt_rocket_okapi::Result<RequestHeaderInput> {
Ok(RequestHeaderInput::None)
}
}
/// Attach ratelimiter to the Rocket application
pub struct RatelimitFairing;
+9 -4
View File
@@ -1,6 +1,6 @@
[package]
name = "revolt-result"
version = "0.8.9"
version = "0.9.3"
edition = "2021"
license = "MIT"
authors = ["Paul Makles <me@insrt.uk>"]
@@ -10,9 +10,11 @@ description = "Revolt Backend: Result and Error types"
[features]
serde = ["dep:serde"]
schemas = ["dep:schemars"]
utoipa = ["dep:utoipa"]
rocket = ["dep:rocket", "dep:serde_json"]
axum = ["dep:axum", "dep:serde_json"]
okapi = ["dep:revolt_rocket_okapi", "dep:revolt_okapi", "schemas"]
sentry = ["dep:sentry"]
default = ["serde", "sentry"]
@@ -23,14 +25,17 @@ serde_json = { version = "1", optional = true }
serde = { version = "1", features = ["derive"], optional = true }
# Spec Generation
utoipa = { version = "5.4.0", optional = true }
schemars = { version = "0.8.8", optional = true }
utoipa = { version = "4.2.3", optional = true }
# Rocket
rocket = { optional = true, version = "0.5.0-rc.2", default-features = false }
revolt_rocket_okapi = { version = "0.10.0", optional = true }
revolt_okapi = { version = "0.9.1", optional = true }
# utilities
log = "0.4"
# Axum
axum = { version = "0.8.6", optional = true }
axum = { version = "0.7.5", optional = true }
sentry = { version = "0.31.5", optional = true }
sentry = { version = "0.31.5", optional = true }
+13 -5
View File
@@ -5,10 +5,13 @@ use std::fmt::Display;
#[macro_use]
extern crate serde;
#[cfg(feature = "schemas")]
#[macro_use]
extern crate schemars;
#[cfg(feature = "utoipa")]
mod utoipa_impl;
#[cfg(feature = "utoipa")]
pub use crate::utoipa_impl::*;
#[macro_use]
extern crate utoipa;
#[cfg(feature = "rocket")]
pub mod rocket;
@@ -16,12 +19,16 @@ pub mod rocket;
#[cfg(feature = "axum")]
pub mod axum;
#[cfg(feature = "okapi")]
pub mod okapi;
/// Result type with custom Error
pub type Result<T, E = Error> = std::result::Result<T, E>;
/// Error information
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "schemas", derive(JsonSchema))]
#[cfg_attr(feature = "utoipa", derive(ToSchema))]
#[derive(Debug, Clone)]
pub struct Error {
/// Type of error and additional information
@@ -43,7 +50,8 @@ impl std::error::Error for Error {}
/// Possible error types
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "type"))]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "schemas", derive(JsonSchema))]
#[cfg_attr(feature = "utoipa", derive(ToSchema))]
#[derive(Debug, Clone)]
pub enum ErrorType {
/// This error was not labeled :(
+49
View File
@@ -0,0 +1,49 @@
use revolt_okapi::openapi3::SchemaObject;
use revolt_rocket_okapi::revolt_okapi::openapi3;
use schemars::schema::Schema;
use crate::Error;
impl revolt_rocket_okapi::response::OpenApiResponderInner for Error {
fn responses(
gen: &mut revolt_rocket_okapi::gen::OpenApiGenerator,
) -> std::result::Result<openapi3::Responses, revolt_rocket_okapi::OpenApiError> {
let mut content = revolt_okapi::Map::new();
let settings = schemars::gen::SchemaSettings::default().with(|s| {
s.option_nullable = true;
s.option_add_null_type = false;
s.definitions_path = "#/components/schemas/".to_string();
});
let mut schema_generator = settings.into_generator();
let schema = schema_generator.root_schema_for::<Error>();
let definitions = gen.schema_generator().definitions_mut();
for (key, value) in schema.definitions {
definitions.insert(key, value);
}
definitions.insert("Error".to_string(), Schema::Object(schema.schema));
content.insert(
"application/json".to_string(),
openapi3::MediaType {
schema: Some(SchemaObject {
reference: Some("#/components/schemas/Error".to_string()),
..Default::default()
}),
..Default::default()
},
);
Ok(openapi3::Responses {
default: Some(openapi3::RefOr::Object(openapi3::Response {
content,
description: "An error occurred.".to_string(),
..Default::default()
})),
..Default::default()
})
}
}
-52
View File
@@ -1,52 +0,0 @@
use utoipa::{Modify, openapi::{Content, OpenApi, Ref, RefOr, ResponseBuilder}};
pub struct ErrorAddon;
impl Modify for ErrorAddon {
fn modify(&self, utoipa: &mut OpenApi) {
let response = ResponseBuilder::new()
.description("An error occurred")
.content(
"application/json",
Content::new(Some(RefOr::Ref(Ref::from_schema_name("Error")))),
)
.build();
for path in utoipa.paths.paths.values_mut() {
if let Some(route) = path.get.as_mut() {
route
.responses
.responses
.insert("default".to_string(), RefOr::T(response.clone()));
};
if let Some(route) = path.delete.as_mut() {
route
.responses
.responses
.insert("default".to_string(), RefOr::T(response.clone()));
};
if let Some(route) = path.patch.as_mut() {
route
.responses
.responses
.insert("default".to_string(), RefOr::T(response.clone()));
};
if let Some(route) = path.post.as_mut() {
route
.responses
.responses
.insert("default".to_string(), RefOr::T(response.clone()));
};
if let Some(route) = path.put.as_mut() {
route
.responses
.responses
.insert("default".to_string(), RefOr::T(response.clone()));
};
}
}
}
+6 -5
View File
@@ -1,10 +1,11 @@
[package]
name = "revolt-crond"
version = "0.8.9"
version = "0.9.3"
license = "AGPL-3.0-or-later"
authors = ["Paul Makles <me@insrt.uk>"]
edition = "2021"
description = "Revolt Daemon Service: Timed data clean up tasks"
publish = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -16,7 +17,7 @@ log = "0.4"
tokio = { version = "1" }
# Core
revolt-database = { version = "0.8.9", path = "../../core/database" }
revolt-result = { version = "0.8.9", path = "../../core/result" }
revolt-config = { version = "0.8.9", path = "../../core/config" }
revolt-files = { version = "0.8.9", path = "../../core/files" }
revolt-database = { version = "0.9.3", path = "../../core/database" }
revolt-result = { version = "0.9.3", path = "../../core/result" }
revolt-config = { version = "0.9.3", path = "../../core/config" }
revolt-files = { version = "0.9.3", path = "../../core/files" }
+8 -7
View File
@@ -1,20 +1,21 @@
[package]
name = "revolt-pushd"
version = "0.8.9"
version = "0.9.3"
edition = "2021"
license = "AGPL-3.0-or-later"
publish = false
[dependencies]
revolt-result = { version = "0.8.9", path = "../../core/result" }
revolt-config = { version = "0.8.9", path = "../../core/config", features = [
revolt-result = { version = "0.9.3", path = "../../core/result" }
revolt-config = { version = "0.9.3", path = "../../core/config", features = [
"report-macros",
"anyhow"
] }
revolt-database = { version = "0.8.9", path = "../../core/database" }
revolt-models = { version = "0.8.9", path = "../../core/models", features = [
revolt-database = { version = "0.9.3", path = "../../core/database" }
revolt-models = { version = "0.9.3", path = "../../core/models", features = [
"validator",
] }
revolt-presence = { version = "0.8.9", path = "../../core/presence", features = [
revolt-presence = { version = "0.9.3", path = "../../core/presence", features = [
"redis-is-patched",
] }
@@ -38,5 +39,5 @@ pretty_env_logger = "0.4.0"
serde_json = "1"
revolt_optional_struct = "0.2.0"
serde = { version = "1", features = ["derive"] }
iso8601-timestamp = { version = "0.4.0", features = ["serde", "bson"] }
iso8601-timestamp = { version = "0.2.10", features = ["serde", "bson"] }
base64 = "0.22.1"
+5 -4
View File
@@ -1,8 +1,9 @@
[package]
name = "revolt-voice-ingress"
version = "0.7.1"
version = "0.9.3"
license = "AGPL-3.0-or-later"
edition = "2021"
publish = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -40,9 +41,9 @@ revolt-database = { path = "../../core/database", features = ["voice"] }
revolt-permissions = { path = "../../core/permissions" }
# Voice
livekit-api = "=0.4.4"
livekit-protocol = "=0.4.0"
livekit-runtime = { version = "=0.3.1", features = ["tokio"] }
livekit-api = "0.4.4"
livekit-protocol = "0.4.0"
livekit-runtime = { version = "0.3.1", features = ["tokio"] }
# RabbitMQ
amqprs = { version = "1.7.0" }
+11 -11
View File
@@ -1,9 +1,10 @@
[package]
name = "revolt-delta"
version = "0.8.9"
version = "0.9.3"
license = "AGPL-3.0-or-later"
authors = ["Paul Makles <paulmakles@gmail.com>"]
edition = "2018"
publish = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -35,7 +36,7 @@ nanoid = "0.4.0"
serde_json = "1.0.57"
serde = { version = "1.0.115", features = ["derive"] }
validator = { version = "0.16", features = ["derive"] }
iso8601-timestamp = { version = "0.4.0", features = [] }
iso8601-timestamp = { version = "0.2.11", features = [] }
# async
futures = "0.3.8"
@@ -55,13 +56,13 @@ lettre = "0.10.0-alpha.4"
rocket = { version = "0.5.1", default-features = false, features = ["json"] }
rocket_cors = { git = "https://github.com/lawliet89/rocket_cors", rev = "072d90359b23e9b291df6b672c07c93de9c46011" }
rocket_empty = { version = "0.1.1" }
rocket_empty = { version = "0.1.1", features = ["schema"] }
rocket_authifier = { version = "1.0.15" }
rocket_prometheus = "0.10.0-rc.3"
# spec generation
utoipa = { version = "5.4.0", features = ["rocket_extras"] }
utoipa-scalar = { version = "0.3.0", features = ["rocket"] }
schemars = "0.8.8"
revolt_rocket_okapi = { version = "0.10.0", features = ["swagger"] }
# rabbit
amqprs = { version = "1.7.0" }
@@ -72,22 +73,21 @@ revolt-config = { path = "../core/config" }
revolt-database = { path = "../core/database", features = [
"rocket-impl",
"redis-is-patched",
"utoipa",
"voice",
] }
revolt-models = { path = "../core/models", features = [
"utoipa",
"schemas",
"validator",
"rocket",
] }
revolt-presence = { path = "../core/presence" }
revolt-result = { path = "../core/result", features = ["rocket", "utoipa"] }
revolt-permissions = { path = "../core/permissions", features = ["utoipa"] }
revolt-result = { path = "../core/result", features = ["rocket", "okapi"] }
revolt-permissions = { path = "../core/permissions", features = ["schemas"] }
revolt-ratelimits = { path = "../core/ratelimits", features = ["rocket"] }
# voice
livekit-api = "=0.4.4"
livekit-protocol = "=0.4.0"
livekit-api = "0.4.4"
livekit-protocol = "0.4.0"
[build-dependencies]
vergen = "7.5.0"
@@ -47,6 +47,7 @@
],
"roles": {
"__ID:5__": {
"_id": "__ID:5__",
"name": "Moderator",
"permissions": {
"a": 545270216,
@@ -55,6 +56,7 @@
"rank": 1
},
"__ID:6__": {
"_id": "__ID:6__",
"name": "Owner",
"permissions": {
"a": 0,
@@ -63,6 +65,7 @@
"rank": 0
},
"__ID:7__": {
"_id": "__ID:7__",
"name": "Lower Rank 1",
"permissions": {
"a": 0,
@@ -71,6 +74,7 @@
"rank": 2
},
"__ID:8__": {
"_id": "__ID:8__",
"name": "Lower Rank 2",
"permissions": {
"a": 0,
+28 -4
View File
@@ -1,7 +1,7 @@
#[macro_use]
extern crate rocket;
#[macro_use]
extern crate utoipa;
extern crate revolt_rocket_okapi;
#[macro_use]
extern crate serde_json;
@@ -15,8 +15,6 @@ use revolt_ratelimits::rocket as ratelimiter;
use rocket::{Build, Rocket};
use rocket_cors::{AllowedOrigins, CorsOptions};
use rocket_prometheus::PrometheusMetrics;
use utoipa::OpenApi;
use utoipa_scalar::{Scalar, Servable};
use std::net::Ipv4Addr;
use std::str::FromStr;
@@ -77,6 +75,31 @@ pub async fn web() -> Rocket<Build> {
.to_cors()
.expect("Failed to create CORS.");
// Configure Swagger
let swagger = revolt_rocket_okapi::swagger_ui::make_swagger_ui(
&revolt_rocket_okapi::swagger_ui::SwaggerUIConfig {
url: "/openapi.json".to_owned(),
..Default::default()
},
)
.into();
let swagger_0_8 = revolt_rocket_okapi::swagger_ui::make_swagger_ui(
&revolt_rocket_okapi::swagger_ui::SwaggerUIConfig {
url: "/0.8/openapi.json".to_owned(),
..Default::default()
},
)
.into();
let swagger_0_8 = revolt_rocket_okapi::swagger_ui::make_swagger_ui(
&revolt_rocket_okapi::swagger_ui::SwaggerUIConfig {
url: "/0.8/openapi.json".to_owned(),
..Default::default()
},
)
.into();
// Voice handler
let voice_client = VoiceClient::new(config.api.livekit.nodes.clone());
// Configure Rabbit
@@ -120,7 +143,8 @@ pub async fn web() -> Rocket<Build> {
.mount("/metrics", prometheus)
.mount("/", rocket_cors::catch_all_options_routes())
.mount("/", ratelimiter::routes())
.mount("/", Scalar::with_url("/scalar", routes::ApiDoc::openapi()))
.mount("/swagger/", swagger)
.mount("/0.8/swagger/", swagger_0_8)
.manage(authifier)
.manage(db)
.manage(amqp)
+2 -8
View File
@@ -5,16 +5,10 @@ use rocket::serde::json::Json;
use rocket::State;
use validator::Validate;
/// Create Bot
/// # Create Bot
///
/// Create a new Revolt bot.
#[utoipa::path(
tag = "Bots",
security(("Session-Token" = [])),
responses(
(status = 200, body = v0::BotWithUserResponse),
),
)]
#[openapi(tag = "Bots")]
#[post("/create", data = "<info>")]
pub async fn create_bot(
db: &State<Database>,
+2 -8
View File
@@ -3,16 +3,10 @@ use revolt_result::{create_error, Result};
use rocket::State;
use rocket_empty::EmptyResponse;
/// Delete Bot
/// # Delete Bot
///
/// Delete a bot by its id.
#[utoipa::path(
tag = "Bots",
security(("Session-Token" = [])),
responses(
(status = 204),
),
)]
#[openapi(tag = "Bots")]
#[delete("/<target>")]
pub async fn delete_bot(
db: &State<Database>,
+11 -10
View File
@@ -6,16 +6,10 @@ use rocket::State;
use rocket::serde::json::Json;
use validator::Validate;
/// Edit Bot
/// # Edit Bot
///
/// Edit bot details by its id.
#[utoipa::path(
tag = "Bots",
security(("Session-Token" = [])),
responses(
(status = 200, body = v0::BotWithUserResponse),
),
)]
#[openapi(tag = "Bots")]
#[patch("/<target>", data = "<data>")]
pub async fn edit_bot(
db: &State<Database>,
@@ -66,8 +60,15 @@ pub async fn edit_bot(
..Default::default()
};
bot.update(db, partial, remove.into_iter().map(|v| v.into()).collect())
.await?;
bot.update(
db,
partial,
remove
.into_iter()
.map(|v| v.into())
.collect(),
)
.await?;
Ok(Json(v0::BotWithUserResponse {
bot: bot.into(),
+2 -8
View File
@@ -3,16 +3,10 @@ use revolt_models::v0::FetchBotResponse;
use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
/// Fetch Bot
/// # Fetch Bot
///
/// Fetch details of a bot you own by its id.
#[utoipa::path(
tag = "Bots",
security(("Session-Token" = [])),
responses(
(status = 200, body = FetchBotResponse),
),
)]
#[openapi(tag = "Bots")]
#[get("/<bot>")]
pub async fn fetch_bot(
db: &State<Database>,
+2 -8
View File
@@ -5,16 +5,10 @@ use revolt_result::Result;
use rocket::serde::json::Json;
use rocket::State;
/// Fetch Owned Bots
/// # Fetch Owned Bots
///
/// Fetch all of the bots that you have control over.
#[utoipa::path(
tag = "Bots",
security(("Session-Token" = [])),
responses(
(status = 200, body = OwnedBotsResponse),
),
)]
#[openapi(tag = "Bots")]
#[get("/@me")]
pub async fn fetch_owned_bots(db: &State<Database>, user: User) -> Result<Json<OwnedBotsResponse>> {
let mut bots = db.fetch_bots_by_user(&user.id).await?;
+2 -8
View File
@@ -5,16 +5,10 @@ use revolt_result::{create_error, Result};
use rocket::serde::json::Json;
use rocket::State;
/// Fetch Public Bot
/// # Fetch Public Bot
///
/// Fetch details of a public (or owned) bot by its id.
#[utoipa::path(
tag = "Bots",
security(("Session-Token" = []), ()),
responses(
(status = 200, body = PublicBot),
),
)]
#[openapi(tag = "Bots")]
#[get("/<target>/invite")]
pub async fn fetch_public_bot(
db: &State<Database>,
+2 -8
View File
@@ -11,16 +11,10 @@ use rocket::State;
use rocket::serde::json::Json;
use rocket_empty::EmptyResponse;
/// Invite Bot
/// # Invite Bot
///
/// Invite a bot to a server or group by its id.`
#[utoipa::path(
tag = "Bots",
security(("Session-Token" = [])),
responses(
(status = 200, body = v0::BotWithUserResponse),
),
)]
#[openapi(tag = "Bots")]
#[post("/<target>/invite", data = "<dest>")]
pub async fn invite_bot(
db: &State<Database>,
+3 -16
View File
@@ -1,3 +1,4 @@
use revolt_rocket_okapi::revolt_okapi::openapi3::OpenApi;
use rocket::Route;
mod create;
@@ -8,22 +9,8 @@ mod fetch_owned;
mod fetch_public;
mod invite;
#[derive(OpenApi)]
#[openapi(
paths(
create::create_bot,
invite::invite_bot,
fetch_public::fetch_public_bot,
fetch::fetch_bot,
fetch_owned::fetch_owned_bots,
edit::edit_bot,
delete::delete_bot,
)
)]
pub struct ApiDoc;
pub fn routes() -> Vec<Route> {
routes![
pub fn routes() -> (Vec<Route>, OpenApi) {
openapi_get_routes_spec![
create::create_bot,
invite::invite_bot,
fetch_public::fetch_public_bot,
@@ -7,20 +7,10 @@ use revolt_result::{create_error, Result};
use rocket::State;
use rocket_empty::EmptyResponse;
/// Acknowledge Message
/// # Acknowledge Message
///
/// Lets the server and all other clients know that we've seen this message id in this channel.
#[utoipa::path(
tag = "Messaging",
security(("Session-Token" = [])),
params(
("target" = Reference, Path),
("message" = Reference, Path),
),
responses(
(status = 204)
)
)]
#[openapi(tag = "Messaging")]
#[put("/<target>/ack/<message>")]
pub async fn ack(
db: &State<Database>,
@@ -9,20 +9,10 @@ use revolt_result::{create_error, Result};
use rocket::State;
use rocket_empty::EmptyResponse;
/// Close Channel
/// # Close Channel
///
/// Deletes a server channel, leaves a group or closes a group.
#[utoipa::path(
tag = "Channel Information",
security(("Session-Token" = []), ("Bot-Token" = [])),
params(
("target" = Reference, Path),
v0::OptionsChannelDelete,
),
responses(
(status = 204),
),
)]
#[openapi(tag = "Channel Information")]
#[delete("/<target>?<options..>")]
pub async fn delete(
db: &State<Database>,
@@ -9,19 +9,10 @@ use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
use validator::Validate;
/// Edit Channel
/// # Edit Channel
///
/// Edit a channel object by its id.
#[utoipa::path(
tag = "Channel Information",
security(("Session-Token" = []), ("Bot-Token" = [])),
params(
("target" = Reference, Path),
),
responses(
(status = 200, body = v0::Channel),
),
)]
#[openapi(tag = "Channel Information")]
#[patch("/<target>", data = "<data>")]
pub async fn edit(
db: &State<Database>,
@@ -8,19 +8,10 @@ use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
use revolt_result::Result;
use rocket::{serde::json::Json, State};
/// Fetch Channel
/// # Fetch Channel
///
/// Fetch channel by its id.
#[utoipa::path(
tag = "Channel Information",
security(("Session-Token" = []), ("Bot-Token" = [])),
params(
("target" = Reference, Path),
),
responses(
(status = 200, body = v0::Channel),
),
)]
#[openapi(tag = "Channel Information")]
#[get("/<target>")]
pub async fn fetch(
db: &State<Database>,
@@ -8,20 +8,10 @@ use revolt_result::{create_error, Result};
use rocket::State;
use rocket_empty::EmptyResponse;
/// Add Member to Group
/// # Add Member to Group
///
/// Adds another user to the group.
#[utoipa::path(
tag = "Groups",
security(("Session-Token" = [])),
params(
("group_id" = Reference, Path),
("member_id" = Reference, Path),
),
responses(
(status = 204),
),
)]
#[openapi(tag = "Groups")]
#[put("/<group_id>/recipients/<member_id>")]
pub async fn add_member(
db: &State<Database>,
@@ -6,16 +6,10 @@ use rocket::serde::json::Json;
use rocket::State;
use validator::Validate;
/// Create Group
/// # Create Group
///
/// Create a new group channel.
#[utoipa::path(
tag = "Groups",
security(("Session-Token" = [])),
responses(
(status = 200, body = v0::Channel),
),
)]
#[openapi(tag = "Groups")]
#[post("/create", data = "<data>")]
pub async fn create_group(
db: &State<Database>,
@@ -9,20 +9,10 @@ use revolt_result::{create_error, Result};
use rocket::State;
use rocket_empty::EmptyResponse;
/// Remove Member from Group
/// # Remove Member from Group
///
/// Removes a user from the group.
#[utoipa::path(
tag = "Groups",
security(("Session-Token" = [])),
params(
("target" = Reference, Path),
("member" = Reference, Path),
),
responses(
(status = 204),
),
)]
#[openapi(tag = "Groups")]
#[delete("/<target>/recipients/<member>")]
pub async fn remove_member(
db: &State<Database>,
@@ -8,21 +8,12 @@ use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
/// Create Invite
/// # Create Invite
///
/// Creates an invite to this channel.
///
/// Channel must be a `TextChannel`.
#[utoipa::path(
tag = "Channel Invites",
security(("Session-Token" = [])),
params(
("target" = Reference, Path),
),
responses(
(status = 200, body = v0::Invite),
),
)]
#[openapi(tag = "Channel Invites")]
#[post("/<target>/invites")]
pub async fn create_invite(
db: &State<Database>,
@@ -7,21 +7,12 @@ use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
/// Fetch Group Members
/// # Fetch Group Members
///
/// Retrieves all users who are part of this group.
///
/// This may not return full user information if users are not friends but have mutual connections.
#[utoipa::path(
tag = "Groups",
security(("Session-Token" = []), ("Bot-Token" = [])),
params(
("target" = Reference, Path),
),
responses(
(status = 200, body = Vec<v0::User>),
),
)]
#[openapi(tag = "Groups")]
#[get("/<target>/members")]
pub async fn fetch_members(
db: &State<Database>,
@@ -10,23 +10,14 @@ use rocket::{serde::json::Json, State};
use rocket_empty::EmptyResponse;
use validator::Validate;
/// Bulk Delete Messages
/// # Bulk Delete Messages
///
/// Delete multiple messages you've sent or one you have permission to delete.
///
/// This will always require `ManageMessages` permission regardless of whether you own the message or not.
///
/// Messages must have been sent within the past 1 week.
#[utoipa::path(
tag = "Messaging",
security(("Session-Token" = []), ("Bot-Token" = [])),
params(
("target" = Reference, Path),
),
responses(
(status = 204),
),
)]
#[openapi(tag = "Messaging")]
#[delete("/<target>/messages/bulk", data = "<options>", rank = 1)]
pub async fn bulk_delete_messages(
db: &State<Database>,
@@ -7,22 +7,12 @@ use revolt_result::Result;
use rocket::State;
use rocket_empty::EmptyResponse;
/// Remove All Reactions from Message
/// # Remove All Reactions from Message
///
/// Remove your own, someone else's or all of a given reaction.
///
/// Requires `ManageMessages` permission.
#[utoipa::path(
tag = "Interactions",
security(("Session-Token" = []), ("Bot-Token" = [])),
params(
("target" = Reference, Path),
("msg" = Reference, Path),
),
responses(
(status = 204),
),
)]
#[openapi(tag = "Interactions")]
#[delete("/<target>/messages/<msg>/reactions")]
pub async fn clear_reactions(
db: &State<Database>,
@@ -47,7 +37,7 @@ pub async fn clear_reactions(
reactions: Some(Default::default()),
..Default::default()
},
vec![],
vec![]
)
.await
.map(|_| EmptyResponse)
@@ -7,20 +7,10 @@ use revolt_result::Result;
use rocket::State;
use rocket_empty::EmptyResponse;
/// Delete Message
/// # Delete Message
///
/// Delete a message you've sent or one you have permission to delete.
#[utoipa::path(
tag = "Messaging",
security(("Session-Token" = []), ("Bot-Token" = [])),
params(
("target" = Reference, Path),
("msg" = Reference, Path),
),
responses(
(status = 204),
),
)]
#[openapi(tag = "Messaging")]
#[delete("/<target>/messages/<msg>", rank = 2)]
pub async fn delete(
db: &State<Database>,
@@ -10,20 +10,10 @@ use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
use validator::Validate;
/// Edit Message
/// # Edit Message
///
/// Edits a message that you've previously sent.
#[utoipa::path(
tag = "Messaging",
security(("Session-Token" = []), ("Bot-Token" = [])),
params(
("target" = Reference, Path),
("msg" = Reference, Path),
),
responses(
(status = 200, body = v0::Message),
),
)]
#[openapi(tag = "Messaging")]
#[patch("/<target>/messages/<msg>", data = "<edit>")]
pub async fn edit(
db: &State<Database>,
@@ -7,20 +7,10 @@ use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
/// Fetch Message
/// # Fetch Message
///
/// Retrieves a message by its id.
#[utoipa::path(
tag = "Messaging",
security(("Session-Token" = []), ("Bot-Token" = [])),
params(
("target" = Reference, Path),
("msg" = Reference, Path),
),
responses(
(status = 200, body = v0::Message),
),
)]
#[openapi(tag = "Messaging")]
#[get("/<target>/messages/<msg>")]
pub async fn fetch(
db: &State<Database>,
@@ -8,20 +8,10 @@ use revolt_result::{create_error, Result};
use rocket::State;
use rocket_empty::EmptyResponse;
/// Pins a message
/// # Pins a message
///
/// Pins a message by its id.
#[utoipa::path(
tag = "Messaging",
security(("Session-Token" = []), ("Bot-Token" = [])),
params(
("target" = Reference, Path),
("msg" = Reference, Path),
),
responses(
(status = 204),
),
)]
#[openapi(tag = "Messaging")]
#[post("/<target>/messages/<msg>/pin")]
pub async fn message_pin(
db: &State<Database>,
@@ -8,20 +8,10 @@ use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
use validator::Validate;
/// Fetch Messages
/// # Fetch Messages
///
/// Fetch multiple messages.
#[utoipa::path(
tag = "Messaging",
security(("Session-Token" = []), ("Bot-Token" = [])),
params(
("target" = Reference, Path),
v0::OptionsQueryMessages,
),
responses(
(status = 200, body = v0::BulkMessageResponse),
),
)]
#[openapi(tag = "Messaging")]
#[get("/<target>/messages?<options..>")]
pub async fn query(
db: &State<Database>,
@@ -7,21 +7,10 @@ use revolt_result::Result;
use rocket::State;
use rocket_empty::EmptyResponse;
/// Add Reaction to Message
/// # Add Reaction to Message
///
/// React to a given message.
#[utoipa::path(
tag = "Interactions",
security(("Session-Token" = []), ("Bot-Token" = [])),
params(
("target" = Reference, Path),
("msg" = Reference, Path),
("emoji" = Reference, Path),
),
responses(
(status = 204),
),
)]
#[openapi(tag = "Interactions")]
#[put("/<target>/messages/<msg>/reactions/<emoji>")]
pub async fn react_message(
db: &State<Database>,
@@ -8,19 +8,10 @@ use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
use validator::Validate;
/// Search for Messages
/// # Search for Messages
///
/// This route searches for messages within the given parameters.
#[utoipa::path(
tag = "Messaging",
security(("Session-Token" = [])),
params(
("target" = Reference, Path),
),
responses(
(status = 200, body = v0::BulkMessageResponse),
),
)]
#[openapi(tag = "Messaging")]
#[post("/<target>/search", data = "<options>")]
pub async fn search(
db: &State<Database>,
@@ -12,20 +12,10 @@ use rocket::serde::json::Json;
use rocket::State;
use validator::Validate;
/// Send Message
/// # Send Message
///
/// Sends a message to the given channel.
#[utoipa::path(
tag = "Messaging",
security(("Session-Token" = []), ("Bot-Token" = [])),
params(IdempotencyKey),
params(
("target" = Reference, Path),
),
responses(
(status = 200, body = v0::Message),
),
)]
#[openapi(tag = "Messaging")]
#[post("/<target>/messages", data = "<data>")]
pub async fn message_send(
db: &State<Database>,
@@ -161,29 +151,20 @@ mod test {
name: "Hidden Channel".to_string(),
description: None,
nsfw: Some(false),
voice: None
voice: None,
},
true,
)
.await
.expect("Failed to make new channel");
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)
let role = Role::create(&harness.db, &server, "Show Hidden Channel".to_string())
.await
.expect("Failed to create the role");
let mut overrides = HashMap::new();
overrides.insert(
role_id.clone(),
role.id.clone(),
OverrideField {
a: (ChannelPermission::ViewChannel) as i64,
d: 0,
@@ -291,7 +272,7 @@ mod test {
"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 {
id: None,
joined_at: None,
@@ -300,7 +281,7 @@ mod test {
timeout: None,
roles: Some(second_member_roles),
can_publish: None,
can_receive: None
can_receive: None,
};
second_member
.update(&harness.db, partial, vec![])
@@ -506,7 +487,7 @@ mod test {
let (_, _, other_user) = harness.new_user().await;
let (server, _) = harness.new_server(&user).await;
let channel = harness.new_channel(&server).await;
let (role_id, _role) = harness
let role = harness
.new_role(
&server,
1,
@@ -528,7 +509,7 @@ mod test {
Some(&harness.amqp),
channel.clone(),
v0::DataMessageSend {
content: Some(format!("Mentioning @everyone and role <%{}>", &role_id)),
content: Some(format!("Mentioning @everyone and role <%{}>", &role.id)),
nonce: None,
attachments: None,
replies: None,
@@ -573,7 +554,7 @@ mod test {
Some(&harness.amqp),
channel.clone(),
v0::DataMessageSend {
content: Some(format!("Mentioning `@everyone` and role `<%{}>`", &role_id)),
content: Some(format!("Mentioning `@everyone` and role `<%{}>`", &role.id)),
nonce: None,
attachments: None,
replies: None,
@@ -615,7 +596,7 @@ mod test {
"Role mentions detected when inside codeblock"
);
other_member.roles.push(role_id.clone());
other_member.roles.push(role.id.clone());
harness
.db
.update_member(
@@ -625,10 +606,10 @@ mod test {
id: None,
joined_at: None,
nickname: None,
roles: Some(vec![role_id.clone()]),
roles: Some(vec![role.id.clone()]),
timeout: None,
can_publish: None,
can_receive: None
can_receive: None,
},
vec![],
)
@@ -642,7 +623,7 @@ mod test {
Some(&harness.amqp),
channel.clone(),
v0::DataMessageSend {
content: Some(format!("Mentioning @everyone and role <%{}>", &role_id)),
content: Some(format!("Mentioning @everyone and role <%{}>", &role.id)),
nonce: None,
attachments: None,
replies: None,
@@ -1,27 +1,14 @@
use revolt_database::{
util::{permissions::DatabasePermissionQuery, reference::Reference},
Channel, Database, FieldsMessage, PartialMessage, SystemMessage, User, AMQP,
};
use revolt_database::{util::{permissions::DatabasePermissionQuery, reference::Reference}, Channel, Database, FieldsMessage, PartialMessage, SystemMessage, User, AMQP};
use revolt_models::v0::MessageAuthor;
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
use revolt_result::{create_error, Result};
use rocket::State;
use rocket_empty::EmptyResponse;
/// Unpins a message
/// # Unpins a message
///
/// Unpins a message by its id.
#[utoipa::path(
tag = "Messaging",
security(("Session-Token" = []), ("Bot-Token" = [])),
params(
("target" = Reference, Path),
("msg" = Reference, Path),
),
responses(
(status = 204),
),
)]
#[openapi(tag = "Messaging")]
#[delete("/<target>/messages/<msg>/pin")]
pub async fn message_unpin(
db: &State<Database>,
@@ -8,24 +8,12 @@ use revolt_result::Result;
use rocket::State;
use rocket_empty::EmptyResponse;
/// Remove Reaction(s) to Message
/// # Remove Reaction(s) to Message
///
/// Remove your own, someone else's or all of a given reaction.
///
/// Requires `ManageMessages` if changing others' reactions.
#[utoipa::path(
tag = "Interactions",
security(("Session-Token" = []), ("Bot-Token" = [])),
params(
("target" = Reference, Path),
("msg" = Reference, Path),
("emoji" = Reference, Path),
v0::OptionsUnreact,
),
responses(
(status = 204),
),
)]
#[openapi(tag = "Interactions")]
#[delete("/<target>/messages/<msg>/reactions/<emoji>?<options..>")]
pub async fn unreact_message(
db: &State<Database>,
+3 -36
View File
@@ -1,3 +1,4 @@
use revolt_rocket_okapi::revolt_okapi::openapi3::OpenApi;
use rocket::Route;
mod channel_ack;
@@ -28,42 +29,8 @@ mod voice_stop_ring;
mod webhook_create;
mod webhook_fetch_all;
#[derive(OpenApi)]
#[openapi(
paths(
channel_ack::ack,
channel_fetch::fetch,
members_fetch::fetch_members,
channel_delete::delete,
channel_edit::edit,
invite_create::create_invite,
message_send::message_send,
message_query::query,
message_search::search,
message_pin::message_pin,
message_fetch::fetch,
message_edit::edit,
message_bulk_delete::bulk_delete_messages,
message_delete::delete,
message_unpin::message_unpin,
group_create::create_group,
group_add_member::add_member,
group_remove_member::remove_member,
voice_join::call,
voice_stop_ring::stop_ring,
permissions_set::set_role_permissions,
permissions_set_default::set_default_channel_permissions,
message_react::react_message,
message_unreact::unreact_message,
message_clear_reactions::clear_reactions,
webhook_create::create_webhook,
webhook_fetch_all::fetch_webhooks,
)
)]
pub struct ApiDoc;
pub fn routes() -> Vec<Route> {
routes![
pub fn routes() -> (Vec<Route>, OpenApi) {
openapi_get_routes_spec![
channel_ack::ack,
channel_fetch::fetch,
members_fetch::fetch_members,
@@ -6,22 +6,12 @@ use revolt_permissions::{calculate_channel_permissions, ChannelPermission, Overr
use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
/// Set Role Permission
/// # Set Role Permission
///
/// Sets permissions for the specified role in this channel.
///
/// Channel must be a `TextChannel`.
#[utoipa::path(
tag = "Channel Permissions",
security(("Session-Token" = []), ("Bot-Token" = [])),
params(
("target" = Reference, Path),
("role_id" = Reference, Path),
),
responses(
(status = 200, body = v0::Channel),
),
)]
#[openapi(tag = "Channel Permissions")]
#[put("/<target>/permissions/<role_id>", data = "<data>", rank = 2)]
pub async fn set_role_permissions(
db: &State<Database>,
@@ -6,21 +6,12 @@ use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
/// Set Default Permission
/// # Set Default Permission
///
/// Sets permissions for the default role in this channel.
///
/// Channel must be a `Group` or `TextChannel`.
#[utoipa::path(
tag = "Channel Permissions",
security(("Session-Token" = []), ("Bot-Token" = [])),
params(
("target" = Reference, Path),
),
responses(
(status = 200, body = v0::Channel),
),
)]
#[openapi(tag = "Channel Permissions")]
#[put("/<target>/permissions/default", data = "<data>", rank = 1)]
pub async fn set_default_channel_permissions(
db: &State<Database>,
+2 -11
View File
@@ -13,19 +13,10 @@ use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
/// Join Call
/// # Join Call
///
/// Asks the voice server for a token to join the call.
#[utoipa::path(
tag = "Voice",
security(("Session-Token" = []), ("Bot-Token" = [])),
params(
("target" = Reference, Path),
),
responses(
(status = 200, body = v0::DataJoinCall),
),
)]
#[openapi(tag = "Voice")]
#[post("/<target>/join_call", data = "<data>")]
pub async fn call(
db: &State<Database>,
@@ -8,23 +8,12 @@ use revolt_result::{create_error, Result, ToRevoltError};
use rocket::State;
use rocket_empty::EmptyResponse;
/// Stop Ring
///
/// # Stop Ring
/// Stops ringing a specific user in a dm call.
/// You must be in the call to use this endpoint, returns NotConnected otherwise.
/// Only valid in DM/Group channels, will return NoEffect in servers.
/// Returns NotFound if the user is not in the dm/group channel
#[utoipa::path(
tag = "Voice",
security(("Session-Token" = []), ("Bot-Token" = [])),
params(
("target" = Reference, Path),
("target_user." = Reference, Path),
),
responses(
(status = 204),
),
)]
#[openapi(tag = "Voice")]
#[put("/<target>/end_ring/<target_user>")]
pub async fn stop_ring(
db: &State<Database>,

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