Compare commits

..
40 Commits
Author SHA1 Message Date
Paul Makles 0da4baf777 chore: bump version and clean up repo 2023-01-29 14:16:18 +00:00
Paul Makles c03fcda85b refactor: switch from "rauth" to Authifier crate 2023-01-29 14:14:35 +00:00
Paul Makles 0afbcc065f refactor: clippy warning for empty list 2023-01-27 16:18:00 +00:00
Paul Makles a406e5504d chore: add support for Streamable in media struct 2023-01-27 16:17:47 +00:00
Zomatree e8c233f60b feat: concurrently query January for embeds (#221) 2023-01-27 16:06:27 +00:00
insertish 72595f30d4 ci: synced local '.github/workflows/triage_pr.yml' with remote 'workflows/triage_pr.yml'
[skip ci]
2023-01-24 19:43:40 +00:00
insertish 39e345af74 ci: synced local '.github/workflows/triage_issue.yml' with remote 'workflows/triage_issue.yml'
[skip ci]
2023-01-24 19:43:40 +00:00
Paul Makles e0b918771d chore: ensure restrict_reactions is used in conjunction with reactions list 2023-01-20 18:06:44 +00:00
Paul Makles 7a6bd70dcd feat: add route to fetch user flags
chore: bump rauth to change disabled account behaviour
2023-01-20 17:47:26 +00:00
Paul Makles c0ef3d295a fix: fallback if vergen cannot generate data 2023-01-20 17:14:25 +00:00
Paul Makles f5c95f9d12 chore: bump rauth for lockout reset on pwd reset 2023-01-20 15:57:58 +00:00
Paul Makles fed1da72fd feat: support rauth events
feat: add platform user wipe event
2023-01-20 15:43:45 +00:00
Paul Makles cf9c0bc5e1 chore: bump rauth 2023-01-20 14:00:19 +00:00
Paul Makles cc2054838a chore: swap okapi for revolt_okapi 2023-01-20 14:00:19 +00:00
Paul MaklesandTheBobBobs d9d2735efa fix(ratelimiter): re-calculate reset time
closes #204

Co-authored-by: TheBobBobs <84781603+TheBobBobs@users.noreply.github.com>
2023-01-18 18:26:00 +00:00
Ryan 94b0472934 feat: pass server flags with invite data (#203) 2023-01-18 18:17:59 +00:00
Sophie L 4a7917e9ea feat: enable reactions by default in servers/DMs 2023-01-18 18:17:37 +00:00
Lea ce6b454874 fix: raise masquerade avatar URL length limit 2023-01-18 18:17:05 +00:00
ToastXC fc32996b51 chore: remove clippy warnings (#213) 2023-01-18 18:16:27 +00:00
Paul Makles 32d3683013 fix(ci): re-order docker-compose arguments
chore(ci): prevent running gen on PR
2023-01-14 15:53:09 +00:00
Paul Makles 0cebb69e02 feat: automate generating OpenAPI specification 2023-01-14 15:42:30 +00:00
Paul Makles 117958d8cd feat: include build information in root api call 2023-01-14 15:42:06 +00:00
Paul Makles c5b823ad13 chore: clippy warnings 2023-01-03 21:06:43 +00:00
Paul Makles 27b4699299 fix: unify foreign user fetch into one method 2023-01-03 21:04:04 +00:00
Paul Makles ee1c4cc2d5 chore: bump quark
feat: update email templates to include recipient
2022-11-19 16:52:51 +00:00
Paul Makles 0e0daf86f7 feat: update emails to not look garbage 2022-10-23 21:23:29 +01:00
Paul Makles 572e08c733 chore: update rauth 2022-10-23 21:08:08 +01:00
Paul Makles 9011939344 chore: remove .env file 2022-10-23 20:55:45 +01:00
Paul Makles f4dcd091cc feat: add 'Focus' presence status 2022-09-18 12:12:31 +01:00
Paul Makles 86ad72b426 fix: strip unknown channels from categories (fixes #201) 2022-09-12 21:39:29 +01:00
Paul Makles 044d82d566 fix: use correct validation for masq role colour 2022-09-03 10:06:49 +01:00
Paul Makles deffe663cb feat: add ability to leave groups / servers silently 2022-09-02 15:32:11 +01:00
Paul Makles e7f6433d77 fix: actually transfer group ownership 2022-09-01 09:45:17 +01:00
Paul Makles 345e4d5f13 fix: validate categories 2022-09-01 09:41:00 +01:00
Sophie L 81fc3e7760 fix: set the cap to 100 2022-09-01 09:01:43 +01:00
Paul Makles ca8d2e6118 fix: reaction ordering by using indexmap 2022-08-14 11:47:30 +02:00
Paul Makles c1227e8e33 fix: check React permission on interactions 2022-08-14 11:31:36 +02:00
Paul Makles 0e0c6f2c3a fix: validate number of reactions on message when adding more 2022-08-13 20:26:09 +02:00
Paul Makles fa82be74b5 fix: adjust permission checks for member_edit 2022-07-26 14:06:53 +01:00
Paul Makles 8491ced13d fix: ensure list matches 2022-07-18 12:46:33 +01:00
88 changed files with 1480 additions and 743 deletions
-79
View File
@@ -1,79 +0,0 @@
# MongoDB URI
MONGODB=mongodb://localhost
# URL to where the Revolt app is publicly accessible
REVOLT_APP_URL=http://local.revolt.chat:5000
# URL to where the API is publicly accessible
REVOLT_PUBLIC_URL=http://local.revolt.chat:8000
VITE_API_URL=http://local.revolt.chat:8000
# URL to where the WebSocket server is publicly accessible
REVOLT_EXTERNAL_WS_URL=ws://local.revolt.chat:9000
# URL to where Autumn is publicly available
AUTUMN_PUBLIC_URL=http://local.revolt.chat:3000
# URL to where January is publicly available
JANUARY_PUBLIC_URL=http://local.revolt.chat:7000
# URL to where Vortex is publicly available
# VOSO_PUBLIC_URL=https://voso.revolt.chat
##
## hCaptcha Settings
##
# If you are sure that you don't want to use hCaptcha, set to 1.
REVOLT_UNSAFE_NO_CAPTCHA=1
# hCaptcha API key
# REVOLT_HCAPTCHA_KEY=0x0000000000000000000000000000000000000000
# hCaptcha site key
# REVOLT_HCAPTCHA_SITEKEY=10000000-ffff-ffff-ffff-000000000001
##
## Email Settings
##
# If you are sure that you don't want to use email verification, set to 1.
REVOLT_UNSAFE_NO_EMAIL=1
# SMTP host
# REVOLT_SMTP_HOST=smtp.example.com
# SMTP username
# REVOLT_SMTP_USERNAME=noreply@example.com
# SMTP password
# REVOLT_SMTP_PASSWORD=CHANGEME
# SMTP From header
# REVOLT_SMTP_FROM=Revolt <noreply@example.com>
##
## Application Settings
##
# Whether to only allow users to sign up if they have an invite code
REVOLT_INVITE_ONLY=0
# Maximum number of people that can be in a group chat
REVOLT_MAX_GROUP_SIZE=150
# VAPID keys for push notifications
# Generate using this guide: https://gitlab.insrt.uk/revolt/delta/-/wikis/vapid
# --> Please replace these keys before going into production! <--
REVOLT_VAPID_PRIVATE_KEY=LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUJSUWpyTWxLRnBiVWhsUHpUbERvcEliYk1yeVNrNXpKYzVYVzIxSjJDS3hvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFWnkrQkg2TGJQZ2hEa3pEempXOG0rUXVPM3pCajRXT1phdkR6ZU00c0pqbmFwd1psTFE0WAp1ZDh2TzVodU94QWhMQlU3WWRldVovWHlBdFpWZmNyQi9BPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo=
REVOLT_VAPID_PUBLIC_KEY=BGcvgR-i2z4IQ5Mw841vJvkLjt8wY-FjmWrw83jOLCY52qcGZS0OF7nfLzuYbjsQISwVO2HXrmf18gLWVX3Kwfw=
##
## Vortex configuration
##
# VOSO_MANAGE_TOKEN=CHANGEME
-2
View File
@@ -1,2 +0,0 @@
ko_fi: insertish
custom: https://insrt.uk/donate
+49 -7
View File
@@ -1,10 +1,10 @@
name: Rust build and test name: Rust build, test, and generate specification
on: on:
push: push:
branches: [ master ] branches: [master]
pull_request: pull_request:
branches: [ master ] branches: [master]
env: env:
CARGO_TERM_COLOR: always CARGO_TERM_COLOR: always
@@ -18,16 +18,58 @@ jobs:
- name: Install latest nightly - name: Install latest nightly
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
with: with:
toolchain: nightly toolchain: nightly
override: true override: true
components: rustfmt, clippy components: rustfmt, clippy
- name: Run cargo build - name: Run cargo build
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
command: build command: build
- name: Run cargo test - name: Run cargo test
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
command: test command: test
- name: Copy .env.example
if: github.event_name != 'pull_request'
run: |
cp .env.example .env
- name: Run services in background
if: github.event_name != 'pull_request'
run: |
docker-compose -f docker-compose.db.yml up -d
- name: Start API in background
if: github.event_name != 'pull_request'
run: |
cargo run --bin revolt-delta &
- name: Wait for API to go up
if: github.event_name != 'pull_request'
uses: nev7n/wait_for_response@v1
with:
url: "http://localhost:8000/"
- name: Checkout API repository
if: github.event_name != 'pull_request'
uses: actions/checkout@v3
with:
repository: revoltchat/api
path: api
- name: Download OpenAPI specification
if: github.event_name != 'pull_request'
run: curl http://localhost:8000/openapi.json -o api/OpenAPI.json
- name: Commit changes
if: github.event_name != 'pull_request'
uses: EndBug/add-and-commit@v4
with:
cwd: "api"
add: "*.json"
message: "chore: generate OpenAPI specification"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+16 -11
View File
@@ -15,22 +15,27 @@ jobs:
gh api graphql -f query=' gh api graphql -f query='
query { query {
organization(login: "revoltchat"){ organization(login: "revoltchat"){
projectNext(number: 3) { projectV2(number: 3) {
id id
fields(first:20) { fields(first:20) {
nodes { nodes {
id ... on ProjectV2SingleSelectField {
name id
settings name
options {
id
name
}
}
} }
} }
} }
} }
}' > project_data.json }' > project_data.json
echo 'PROJECT_ID='$(jq '.data.organization.projectNext.id' project_data.json) >> $GITHUB_ENV echo 'PROJECT_ID='$(jq '.data.organization.projectV2.id' project_data.json) >> $GITHUB_ENV
echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV
echo 'TODO_OPTION_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") |.settings | fromjson.options[] | select(.name=="Todo") |.id' project_data.json) >> $GITHUB_ENV echo 'TODO_OPTION_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") | .options[] | select(.name=="Todo") |.id' project_data.json) >> $GITHUB_ENV
- name: Add issue to project - name: Add issue to project
env: env:
@@ -39,11 +44,11 @@ jobs:
run: | run: |
item_id="$( gh api graphql -f query=' item_id="$( gh api graphql -f query='
mutation($project:ID!, $issue:ID!) { mutation($project:ID!, $issue:ID!) {
addProjectNextItem(input: {projectId: $project, contentId: $issue}) { addProjectV2ItemById(input: {projectId: $project, contentId: $issue}) {
projectNextItem { item {
id id
} }
} }
}' -f project=$PROJECT_ID -f issue=$ISSUE_ID --jq '.data.addProjectNextItem.projectNextItem.id')" }' -f project=$PROJECT_ID -f issue=$ISSUE_ID --jq '.data.addProjectV2ItemById.item.id')"
echo 'ITEM_ID='$item_id >> $GITHUB_ENV echo 'ITEM_ID='$item_id >> $GITHUB_ENV
+22 -15
View File
@@ -15,22 +15,27 @@ jobs:
gh api graphql -f query=' gh api graphql -f query='
query { query {
organization(login: "revoltchat"){ organization(login: "revoltchat"){
projectNext(number: 3) { projectV2(number: 3) {
id id
fields(first:20) { fields(first:20) {
nodes { nodes {
id ... on ProjectV2SingleSelectField {
name id
settings name
options {
id
name
}
}
} }
} }
} }
} }
}' > project_data.json }' > project_data.json
echo 'PROJECT_ID='$(jq '.data.organization.projectNext.id' project_data.json) >> $GITHUB_ENV echo 'PROJECT_ID='$(jq '.data.organization.projectV2.id' project_data.json) >> $GITHUB_ENV
echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV
echo 'INCOMING_OPTION_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") |.settings | fromjson.options[] | select(.name=="Incoming PRs") |.id' project_data.json) >> $GITHUB_ENV echo 'INCOMING_OPTION_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") | .options[] | select(.name=="Incoming PRs") |.id' project_data.json) >> $GITHUB_ENV
- name: Add PR to project - name: Add PR to project
env: env:
@@ -39,13 +44,13 @@ jobs:
run: | run: |
item_id="$( gh api graphql -f query=' item_id="$( gh api graphql -f query='
mutation($project:ID!, $pr:ID!) { mutation($project:ID!, $pr:ID!) {
addProjectNextItem(input: {projectId: $project, contentId: $pr}) { addProjectV2ItemById(input: {projectId: $project, contentId: $pr}) {
projectNextItem { item {
id id
} }
} }
}' -f project=$PROJECT_ID -f pr=$PR_ID --jq '.data.addProjectNextItem.projectNextItem.id')" }' -f project=$PROJECT_ID -f pr=$PR_ID --jq '.data.addProjectV2ItemById.item.id')"
echo 'ITEM_ID='$item_id >> $GITHUB_ENV echo 'ITEM_ID='$item_id >> $GITHUB_ENV
- name: Set fields - name: Set fields
@@ -59,14 +64,16 @@ jobs:
$status_field: ID! $status_field: ID!
$status_value: String! $status_value: String!
) { ) {
set_status: updateProjectNextItemField(input: { set_status: updateProjectV2ItemFieldValue(input: {
projectId: $project projectId: $project
itemId: $item itemId: $item
fieldId: $status_field fieldId: $status_field
value: $status_value value: {
singleSelectOptionId: $status_value
}
}) { }) {
projectNextItem { projectV2Item {
id id
} }
} }
}' -f project=$PROJECT_ID -f item=$ITEM_ID -f status_field=$STATUS_FIELD_ID -f status_value=${{ env.INCOMING_OPTION_ID }} --silent }' -f project=$PROJECT_ID -f item=$ITEM_ID -f status_field=$STATUS_FIELD_ID -f status_value=${{ env.INCOMING_OPTION_ID }} --silent
+3
View File
@@ -1,3 +1,6 @@
Rocket.toml Rocket.toml
target target
.data .data
.env
.vercel
Generated
+313 -173
View File
@@ -81,6 +81,12 @@ dependencies = [
"winapi 0.3.9", "winapi 0.3.9",
] ]
[[package]]
name = "anyhow"
version = "1.0.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61"
[[package]] [[package]]
name = "arrayref" name = "arrayref"
version = "0.3.6" version = "0.3.6"
@@ -100,7 +106,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5"
dependencies = [ dependencies = [
"quote 1.0.18", "quote 1.0.18",
"syn 1.0.96", "syn 1.0.107",
] ]
[[package]] [[package]]
@@ -166,11 +172,12 @@ dependencies = [
[[package]] [[package]]
name = "async-lock" name = "async-lock"
version = "2.5.0" version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e97a171d191782fba31bb902b14ad94e24a68145032b7eedf871ab0bc0d077b6" checksum = "c8101efe8695a6c17e02911402145357e718ac92d3ff88ae8419e84b1707b685"
dependencies = [ dependencies = [
"event-listener", "event-listener",
"futures-lite",
] ]
[[package]] [[package]]
@@ -198,7 +205,7 @@ checksum = "2cda8f4bcc10624c4e85bc66b3f452cca98cfa5ca002dc83a16aad2367641bea"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote 1.0.18", "quote 1.0.18",
"syn 1.0.96", "syn 1.0.107",
] ]
[[package]] [[package]]
@@ -263,7 +270,7 @@ checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote 1.0.18", "quote 1.0.18",
"syn 1.0.96", "syn 1.0.107",
] ]
[[package]] [[package]]
@@ -280,7 +287,7 @@ checksum = "96cf8829f67d2eab0b2dfa42c5d0ef737e0724e4a82b01b3e292456202b19716"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote 1.0.18", "quote 1.0.18",
"syn 1.0.96", "syn 1.0.107",
] ]
[[package]] [[package]]
@@ -323,6 +330,40 @@ dependencies = [
"winapi 0.3.9", "winapi 0.3.9",
] ]
[[package]]
name = "authifier"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a84411393a4326726ead660991ce8047f1865fa89900fb6c18e1e6c513cf1f9b"
dependencies = [
"async-std",
"async-trait",
"base32",
"bson",
"chrono",
"futures",
"handlebars",
"iso8601-timestamp",
"lazy_static",
"lettre",
"log",
"mongodb",
"nanoid",
"rand 0.8.5",
"regex",
"reqwest",
"revolt_okapi",
"revolt_rocket_okapi",
"rocket",
"rust-argon2",
"schemars",
"serde",
"serde_json",
"totp-lite",
"ulid 0.5.0",
"validator 0.15.0",
]
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "0.1.8" version = "0.1.8"
@@ -517,6 +558,9 @@ name = "cc"
version = "1.0.73" version = "1.0.73"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
dependencies = [
"jobserver",
]
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
@@ -606,7 +650,7 @@ dependencies = [
"rand 0.8.5", "rand 0.8.5",
"sha2", "sha2",
"subtle", "subtle",
"time 0.3.9", "time 0.3.17",
"version_check", "version_check",
] ]
@@ -672,7 +716,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f877be4f7c9f246b183111634f75baa039715e3f46ce860677d3b19a69fb229c" checksum = "f877be4f7c9f246b183111634f75baa039715e3f46ce860677d3b19a69fb229c"
dependencies = [ dependencies = [
"quote 1.0.18", "quote 1.0.18",
"syn 1.0.96", "syn 1.0.107",
] ]
[[package]] [[package]]
@@ -705,7 +749,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote 1.0.18", "quote 1.0.18",
"strsim", "strsim",
"syn 1.0.96", "syn 1.0.107",
] ]
[[package]] [[package]]
@@ -716,7 +760,7 @@ checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835"
dependencies = [ dependencies = [
"darling_core", "darling_core",
"quote 1.0.18", "quote 1.0.18",
"syn 1.0.96", "syn 1.0.107",
] ]
[[package]] [[package]]
@@ -765,7 +809,7 @@ checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote 1.0.18", "quote 1.0.18",
"syn 1.0.96", "syn 1.0.107",
] ]
[[package]] [[package]]
@@ -798,7 +842,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"proc-macro2-diagnostics", "proc-macro2-diagnostics",
"quote 1.0.18", "quote 1.0.18",
"syn 1.0.96", "syn 1.0.107",
] ]
[[package]] [[package]]
@@ -872,7 +916,27 @@ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
"quote 1.0.18", "quote 1.0.18",
"syn 1.0.96", "syn 1.0.107",
]
[[package]]
name = "enum-iterator"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91a4ec26efacf4aeff80887a175a419493cb6f8b5480d26387eb0bd038976187"
dependencies = [
"enum-iterator-derive",
]
[[package]]
name = "enum-iterator-derive"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "828de45d0ca18782232dfb8f3ea9cc428e8ced380eb26a520baaacfc70de39ce"
dependencies = [
"proc-macro2",
"quote 1.0.18",
"syn 1.0.107",
] ]
[[package]] [[package]]
@@ -1056,7 +1120,7 @@ checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote 1.0.18", "quote 1.0.18",
"syn 1.0.96", "syn 1.0.107",
] ]
[[package]] [[package]]
@@ -1138,6 +1202,18 @@ dependencies = [
"wasi 0.10.0+wasi-snapshot-preview1", "wasi 0.10.0+wasi-snapshot-preview1",
] ]
[[package]]
name = "getset"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e45727250e75cc04ff2846a66397da8ef2b3db8e40e0cef4df67950a07621eb9"
dependencies = [
"proc-macro-error",
"proc-macro2",
"quote 1.0.18",
"syn 1.0.107",
]
[[package]] [[package]]
name = "ghash" name = "ghash"
version = "0.4.4" version = "0.4.4"
@@ -1154,6 +1230,19 @@ version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4"
[[package]]
name = "git2"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2994bee4a3a6a51eb90c218523be382fd7ea09b16380b9312e9dbe955ff7c7d1"
dependencies = [
"bitflags",
"libc",
"libgit2-sys",
"log",
"url",
]
[[package]] [[package]]
name = "glob" name = "glob"
version = "0.3.0" version = "0.3.0"
@@ -1454,12 +1543,12 @@ checksum = "90f97a5f38dd3ccfbe7aa80f4a0c00930f21b922c74195be0201c51028f22dcf"
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "1.8.2" version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6012d540c5baa3589337a98ce73408de9b5a25ec9fc2c6fd6be8f0d39e0ca5a" checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
dependencies = [ dependencies = [
"autocfg 1.1.0", "autocfg 1.1.0",
"hashbrown 0.11.2", "hashbrown 0.12.1",
"serde", "serde",
] ]
@@ -1514,7 +1603,7 @@ dependencies = [
"generic-array 0.14.5", "generic-array 0.14.5",
"schemars", "schemars",
"serde", "serde",
"time 0.3.9", "time 0.3.17",
] ]
[[package]] [[package]]
@@ -1529,6 +1618,15 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d"
[[package]]
name = "jobserver"
version = "0.1.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.57" version = "0.3.57"
@@ -1590,6 +1688,30 @@ version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
[[package]]
name = "libgit2-sys"
version = "0.14.1+1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a07fb2692bc3593bda59de45a502bb3071659f2c515e28c71e728306b038e17"
dependencies = [
"cc",
"libc",
"libz-sys",
"pkg-config",
]
[[package]]
name = "libz-sys"
version = "1.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]] [[package]]
name = "linked-hash-map" name = "linked-hash-map"
version = "0.5.4" version = "0.5.4"
@@ -1914,6 +2036,15 @@ dependencies = [
"minimal-lexical", "minimal-lexical",
] ]
[[package]]
name = "ntapi"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc51db7b362b205941f71232e56c625156eb9a929f8cf74a428fd5bc094a4afc"
dependencies = [
"winapi 0.3.9",
]
[[package]] [[package]]
name = "num-integer" name = "num-integer"
version = "0.1.45" version = "0.1.45"
@@ -1961,16 +2092,7 @@ dependencies = [
"proc-macro-crate", "proc-macro-crate",
"proc-macro2", "proc-macro2",
"quote 1.0.18", "quote 1.0.18",
"syn 1.0.96", "syn 1.0.107",
]
[[package]]
name = "num_threads"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
dependencies = [
"libc",
] ]
[[package]] [[package]]
@@ -1982,17 +2104,6 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "okapi"
version = "0.7.0-rc.1"
source = "git+https://github.com/insertish/okapi?rev=a1048d0c8cd771e424ec97d33d825c32e06aa120#a1048d0c8cd771e424ec97d33d825c32e06aa120"
dependencies = [
"log",
"schemars",
"serde",
"serde_json",
]
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.13.0" version = "1.13.0"
@@ -2034,7 +2145,7 @@ checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote 1.0.18", "quote 1.0.18",
"syn 1.0.96", "syn 1.0.107",
] ]
[[package]] [[package]]
@@ -2139,7 +2250,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"proc-macro2-diagnostics", "proc-macro2-diagnostics",
"quote 1.0.18", "quote 1.0.18",
"syn 1.0.96", "syn 1.0.107",
] ]
[[package]] [[package]]
@@ -2177,7 +2288,7 @@ dependencies = [
"pest_meta", "pest_meta",
"proc-macro2", "proc-macro2",
"quote 1.0.18", "quote 1.0.18",
"syn 1.0.96", "syn 1.0.107",
] ]
[[package]] [[package]]
@@ -2208,7 +2319,7 @@ checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote 1.0.18", "quote 1.0.18",
"syn 1.0.96", "syn 1.0.107",
] ]
[[package]] [[package]]
@@ -2295,7 +2406,7 @@ dependencies = [
"proc-macro-error-attr", "proc-macro-error-attr",
"proc-macro2", "proc-macro2",
"quote 1.0.18", "quote 1.0.18",
"syn 1.0.96", "syn 1.0.107",
"version_check", "version_check",
] ]
@@ -2312,9 +2423,9 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.39" version = "1.0.50"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@@ -2327,7 +2438,7 @@ checksum = "4bf29726d67464d49fa6224a1d07936a8c08bb3fba727c7493f6cf1616fdaada"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote 1.0.18", "quote 1.0.18",
"syn 1.0.96", "syn 1.0.107",
"version_check", "version_check",
"yansi", "yansi",
] ]
@@ -2502,39 +2613,6 @@ dependencies = [
"rand_core 0.3.1", "rand_core 0.3.1",
] ]
[[package]]
name = "rauth"
version = "1.0.0"
source = "git+https://github.com/insertish/rauth?rev=9c3f9bb58a549a51546bd748274130ac4a667cb4#9c3f9bb58a549a51546bd748274130ac4a667cb4"
dependencies = [
"async-std",
"async-trait",
"base32",
"bson",
"chrono",
"futures",
"handlebars",
"iso8601-timestamp",
"lazy_static",
"lettre",
"log",
"mongodb",
"nanoid",
"okapi",
"rand 0.8.5",
"regex",
"reqwest",
"rocket",
"rocket_okapi",
"rust-argon2",
"schemars",
"serde",
"serde_json",
"totp-lite",
"ulid 0.5.0",
"validator 0.15.0",
]
[[package]] [[package]]
name = "rdrand" name = "rdrand"
version = "0.4.0" version = "0.4.0"
@@ -2628,7 +2706,7 @@ checksum = "a043824e29c94169374ac5183ac0ed43f5724dc4556b19568007486bd840fa1f"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote 1.0.18", "quote 1.0.18",
"syn 1.0.96", "syn 1.0.107",
] ]
[[package]] [[package]]
@@ -2714,7 +2792,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-bonfire" name = "revolt-bonfire"
version = "0.5.5" version = "0.5.7"
dependencies = [ dependencies = [
"async-std", "async-std",
"async-tungstenite", "async-tungstenite",
@@ -2730,7 +2808,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-delta" name = "revolt-delta"
version = "0.5.5" version = "0.5.7"
dependencies = [ dependencies = [
"async-channel", "async-channel",
"async-std", "async-std",
@@ -2755,25 +2833,28 @@ dependencies = [
"regex", "regex",
"reqwest", "reqwest",
"revolt-quark", "revolt-quark",
"revolt_rocket_okapi",
"rocket", "rocket",
"rocket_authifier",
"rocket_empty", "rocket_empty",
"rocket_okapi",
"rocket_rauth",
"schemars", "schemars",
"serde", "serde",
"serde_json", "serde_json",
"ulid 0.4.1", "ulid 0.4.1",
"url", "url",
"validator 0.14.0", "validator 0.14.0",
"vergen",
] ]
[[package]] [[package]]
name = "revolt-quark" name = "revolt-quark"
version = "0.1.0" version = "0.5.7"
dependencies = [ dependencies = [
"async-lock",
"async-recursion", "async-recursion",
"async-std", "async-std",
"async-trait", "async-trait",
"authifier",
"base64 0.13.0", "base64 0.13.0",
"bincode", "bincode",
"bitfield", "bitfield",
@@ -2783,6 +2864,7 @@ dependencies = [
"dotenv", "dotenv",
"futures", "futures",
"impl_ops", "impl_ops",
"indexmap",
"iso8601-timestamp", "iso8601-timestamp",
"lazy_static", "lazy_static",
"linkify 0.8.1", "linkify 0.8.1",
@@ -2791,19 +2873,18 @@ dependencies = [
"mongodb", "mongodb",
"nanoid", "nanoid",
"num_enum", "num_enum",
"okapi",
"once_cell", "once_cell",
"optional_struct", "optional_struct",
"pretty_env_logger", "pretty_env_logger",
"rauth",
"redis-kiss", "redis-kiss",
"regex", "regex",
"reqwest", "reqwest",
"revolt_okapi",
"revolt_rocket_okapi",
"rocket", "rocket",
"rocket_cors", "rocket_cors",
"rocket_empty", "rocket_empty",
"rocket_http", "rocket_http",
"rocket_okapi",
"schemars", "schemars",
"sentry", "sentry",
"serde", "serde",
@@ -2813,6 +2894,47 @@ dependencies = [
"web-push", "web-push",
] ]
[[package]]
name = "revolt_okapi"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23bfdf7ae769c3042fe727f6e5c17363b02a64b4b33ad60c3e5f73b26df7835b"
dependencies = [
"log",
"schemars",
"serde",
"serde_json",
]
[[package]]
name = "revolt_rocket_okapi"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "275e1e9bd3343f75225cafa64f4bfb939c8b21c5f861141180fc0e24769ff6cf"
dependencies = [
"either",
"log",
"revolt_okapi",
"revolt_rocket_okapi_codegen",
"rocket",
"schemars",
"serde",
"serde_json",
]
[[package]]
name = "revolt_rocket_okapi_codegen"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc6620569d8ac8f0a1690fcca13f488503807a60e96ebf729749b59aca1dbef9"
dependencies = [
"darling",
"proc-macro2",
"quote 1.0.18",
"rocket_http",
"syn 1.0.107",
]
[[package]] [[package]]
name = "ring" name = "ring"
version = "0.16.20" version = "0.16.20"
@@ -2880,7 +3002,7 @@ dependencies = [
"serde_json", "serde_json",
"state", "state",
"tempfile", "tempfile",
"time 0.3.9", "time 0.3.17",
"tokio 1.18.2", "tokio 1.18.2",
"tokio-stream", "tokio-stream",
"tokio-util 0.7.2", "tokio-util 0.7.2",
@@ -2889,6 +3011,22 @@ dependencies = [
"yansi", "yansi",
] ]
[[package]]
name = "rocket_authifier"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4554a81937341d8c3cd3e43431ac2d23624444dc3a88e7ffd3ff66593779293"
dependencies = [
"authifier",
"iso8601-timestamp",
"revolt_okapi",
"revolt_rocket_okapi",
"rocket",
"rocket_empty",
"schemars",
"serde",
]
[[package]] [[package]]
name = "rocket_codegen" name = "rocket_codegen"
version = "0.5.0-rc.2" version = "0.5.0-rc.2"
@@ -2901,15 +3039,16 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote 1.0.18", "quote 1.0.18",
"rocket_http", "rocket_http",
"syn 1.0.96", "syn 1.0.107",
"unicode-xid 0.2.3", "unicode-xid 0.2.3",
] ]
[[package]] [[package]]
name = "rocket_cors" name = "rocket_cors"
version = "0.5.2" version = "0.6.0-alpha1"
source = "git+https://github.com/lawliet89/rocket_cors?rev=5843861a88958c16bfaa0b40f0d8910772bcd2f6#5843861a88958c16bfaa0b40f0d8910772bcd2f6" source = "git+https://github.com/lawliet89/rocket_cors?rev=c17e8145baa4790319fdb6a473e465b960f55e7c#c17e8145baa4790319fdb6a473e465b960f55e7c"
dependencies = [ dependencies = [
"http",
"log", "log",
"regex", "regex",
"rocket", "rocket",
@@ -2922,12 +3061,13 @@ dependencies = [
[[package]] [[package]]
name = "rocket_empty" name = "rocket_empty"
version = "0.1.0" version = "0.1.1"
source = "git+https://github.com/insertish/rocket_empty?branch=master#9d8234fc3bb215ea241b787301e8cff68ae53652" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c0922e47f981204fee38578a8efcf47a5c1a4ac0eb7f59e6bdfa3e61c8e3d69"
dependencies = [ dependencies = [
"okapi", "revolt_okapi",
"revolt_rocket_okapi",
"rocket", "rocket",
"rocket_okapi",
] ]
[[package]] [[package]]
@@ -2952,52 +3092,11 @@ dependencies = [
"smallvec", "smallvec",
"stable-pattern", "stable-pattern",
"state", "state",
"time 0.3.9", "time 0.3.17",
"tokio 1.18.2", "tokio 1.18.2",
"uncased", "uncased",
] ]
[[package]]
name = "rocket_okapi"
version = "0.8.0-rc.1"
source = "git+https://github.com/insertish/okapi?rev=a1048d0c8cd771e424ec97d33d825c32e06aa120#a1048d0c8cd771e424ec97d33d825c32e06aa120"
dependencies = [
"either",
"log",
"okapi",
"rocket",
"rocket_okapi_codegen",
"schemars",
"serde",
"serde_json",
]
[[package]]
name = "rocket_okapi_codegen"
version = "0.8.0-rc.1"
source = "git+https://github.com/insertish/okapi?rev=a1048d0c8cd771e424ec97d33d825c32e06aa120#a1048d0c8cd771e424ec97d33d825c32e06aa120"
dependencies = [
"darling",
"proc-macro2",
"quote 1.0.18",
"rocket_http",
"syn 1.0.96",
]
[[package]]
name = "rocket_rauth"
version = "1.0.0"
source = "git+https://github.com/insertish/rauth?rev=9c3f9bb58a549a51546bd748274130ac4a667cb4#9c3f9bb58a549a51546bd748274130ac4a667cb4"
dependencies = [
"okapi",
"rauth",
"rocket",
"rocket_empty",
"rocket_okapi",
"schemars",
"serde",
]
[[package]] [[package]]
name = "rust-argon2" name = "rust-argon2"
version = "1.0.0" version = "1.0.0"
@@ -3067,9 +3166,9 @@ dependencies = [
[[package]] [[package]]
name = "rustversion" name = "rustversion"
version = "1.0.6" version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70"
[[package]] [[package]]
name = "ryu" name = "ryu"
@@ -3115,7 +3214,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote 1.0.18", "quote 1.0.18",
"serde_derive_internals", "serde_derive_internals",
"syn 1.0.96", "syn 1.0.107",
] ]
[[package]] [[package]]
@@ -3259,16 +3358,16 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"thiserror", "thiserror",
"time 0.3.9", "time 0.3.17",
"url", "url",
"uuid", "uuid",
] ]
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.137" version = "1.0.152"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
@@ -3284,13 +3383,13 @@ dependencies = [
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.137" version = "1.0.152"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote 1.0.18", "quote 1.0.18",
"syn 1.0.96", "syn 1.0.107",
] ]
[[package]] [[package]]
@@ -3301,14 +3400,14 @@ checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote 1.0.18", "quote 1.0.18",
"syn 1.0.96", "syn 1.0.107",
] ]
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.81" version = "1.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883"
dependencies = [ dependencies = [
"indexmap", "indexmap",
"itoa 1.0.2", "itoa 1.0.2",
@@ -3347,7 +3446,7 @@ dependencies = [
"darling", "darling",
"proc-macro2", "proc-macro2",
"quote 1.0.18", "quote 1.0.18",
"syn 1.0.96", "syn 1.0.107",
] ]
[[package]] [[package]]
@@ -3525,9 +3624,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.96" version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf" checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote 1.0.18", "quote 1.0.18",
@@ -3543,6 +3642,20 @@ dependencies = [
"unicode-xid 0.0.4", "unicode-xid 0.0.4",
] ]
[[package]]
name = "sysinfo"
version = "0.27.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c215311383d25d03753375c53ab9fad8fc0cf46953c409211e065edeabf577ee"
dependencies = [
"cfg-if 1.0.0",
"core-foundation-sys",
"libc",
"ntapi",
"once_cell",
"winapi 0.3.9",
]
[[package]] [[package]]
name = "take_mut" name = "take_mut"
version = "0.2.2" version = "0.2.2"
@@ -3574,22 +3687,22 @@ dependencies = [
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.31" version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "1.0.31" version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote 1.0.18", "quote 1.0.18",
"syn 1.0.96", "syn 1.0.107",
] ]
[[package]] [[package]]
@@ -3614,21 +3727,30 @@ dependencies = [
[[package]] [[package]]
name = "time" name = "time"
version = "0.3.9" version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd" checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376"
dependencies = [ dependencies = [
"itoa 1.0.2", "itoa 1.0.2",
"libc", "serde",
"num_threads", "time-core",
"time-macros", "time-macros",
] ]
[[package]] [[package]]
name = "time-macros" name = "time-core"
version = "0.2.4" version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
[[package]]
name = "time-macros"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2"
dependencies = [
"time-core",
]
[[package]] [[package]]
name = "tinyvec" name = "tinyvec"
@@ -3689,7 +3811,7 @@ checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote 1.0.18", "quote 1.0.18",
"syn 1.0.96", "syn 1.0.107",
] ]
[[package]] [[package]]
@@ -3825,7 +3947,7 @@ checksum = "cc6b8ad3567499f98a1db7a752b07a7c8c7c7c34c332ec00effb2b0027974b7c"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote 1.0.18", "quote 1.0.18",
"syn 1.0.96", "syn 1.0.107",
] ]
[[package]] [[package]]
@@ -3955,7 +4077,7 @@ checksum = "89851716b67b937e393b3daa8423e67ddfc4bbbf1654bcf05488e95e0828db0c"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote 1.0.18", "quote 1.0.18",
"syn 1.0.96", "syn 1.0.107",
] ]
[[package]] [[package]]
@@ -4166,7 +4288,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote 1.0.18", "quote 1.0.18",
"regex", "regex",
"syn 1.0.96", "syn 1.0.107",
"validator_types", "validator_types",
] ]
@@ -4177,7 +4299,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ded9d97e1d42327632f5f3bae6403c04886e2de3036261ef42deebd931a6a291" checksum = "ded9d97e1d42327632f5f3bae6403c04886e2de3036261ef42deebd931a6a291"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"syn 1.0.96", "syn 1.0.107",
] ]
[[package]] [[package]]
@@ -4202,6 +4324,24 @@ version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "vergen"
version = "7.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "571b69f690c855821462709b6f41d42ceccc316fbd17b60bd06d06928cfe6a99"
dependencies = [
"anyhow",
"cfg-if 1.0.0",
"enum-iterator",
"getset",
"git2",
"rustc_version 0.4.0",
"rustversion",
"sysinfo",
"thiserror",
"time 0.3.17",
]
[[package]] [[package]]
name = "version_check" name = "version_check"
version = "0.9.4" version = "0.9.4"
@@ -4257,7 +4397,7 @@ dependencies = [
"log", "log",
"proc-macro2", "proc-macro2",
"quote 1.0.18", "quote 1.0.18",
"syn 1.0.96", "syn 1.0.107",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@@ -4291,7 +4431,7 @@ checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote 1.0.18", "quote 1.0.18",
"syn 1.0.96", "syn 1.0.107",
"wasm-bindgen-backend", "wasm-bindgen-backend",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
-7
View File
@@ -1,7 +0,0 @@
#!/bin/bash
# Build base image
docker build -t revolt.chat/base:latest -f Dockerfile .
# Build crates
docker build -t revolt.chat/delta:latest -f crates/delta/Dockerfile .
docker build -t revolt.chat/bonfire:latest -f crates/bonfire/Dockerfile .
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-bonfire" name = "revolt-bonfire"
version = "0.5.5" version = "0.5.7"
license = "AGPL-3.0-or-later" license = "AGPL-3.0-or-later"
edition = "2021" edition = "2021"
+7 -5
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-delta" name = "revolt-delta"
version = "0.5.5" version = "0.5.7"
license = "AGPL-3.0-or-later" license = "AGPL-3.0-or-later"
authors = ["Paul Makles <paulmakles@gmail.com>"] authors = ["Paul Makles <paulmakles@gmail.com>"]
edition = "2018" edition = "2018"
@@ -51,13 +51,15 @@ mobc-redis = { version = "0.7.0", default-features = false, features = ["async-s
# web # web
rocket = { version = "0.5.0-rc.2", default-features = false, features = ["json"] } rocket = { version = "0.5.0-rc.2", default-features = false, features = ["json"] }
rocket_empty = { git = "https://github.com/insertish/rocket_empty", branch = "master" } rocket_empty = { version = "0.1.1", features = ["schema"] }
rocket_rauth = { git = "https://github.com/insertish/rauth", rev = "9c3f9bb58a549a51546bd748274130ac4a667cb4" } rocket_authifier = { version = "1.0.7" }
# spec generation # spec generation
schemars = "0.8.8" schemars = "0.8.8"
# rocket_okapi = "0.8.0-rc.1" revolt_rocket_okapi = { version = "0.9.1", features = [ "swagger" ] }
rocket_okapi = { git = "https://github.com/insertish/okapi", rev = "a1048d0c8cd771e424ec97d33d825c32e06aa120", features = [ "swagger" ] }
# quark # quark
revolt-quark = { path = "../quark" } revolt-quark = { path = "../quark" }
[build-dependencies]
vergen = "7.5.0"
+16
View File
@@ -0,0 +1,16 @@
use vergen::{vergen, Config};
use std::process::Command;
fn main() {
if let Ok(output) = Command::new("git")
.args(["config", "--get", "remote.origin.url"])
.output()
{
if let Ok(git_origin) = String::from_utf8(output.stdout) {
println!("cargo:rustc-env=GIT_ORIGIN_URL={}", git_origin);
}
}
vergen(Config::default()).ok();
}
+29 -7
View File
@@ -1,7 +1,7 @@
#[macro_use] #[macro_use]
extern crate rocket; extern crate rocket;
#[macro_use] #[macro_use]
extern crate rocket_okapi; extern crate revolt_rocket_okapi;
#[macro_use] #[macro_use]
extern crate serde_json; extern crate serde_json;
#[macro_use] #[macro_use]
@@ -10,7 +10,9 @@ extern crate lazy_static;
pub mod routes; pub mod routes;
pub mod util; pub mod util;
use revolt_quark::rauth::RAuth; use async_std::channel::unbounded;
use revolt_quark::authifier::{Authifier, AuthifierEvent};
use revolt_quark::events::client::EventV1;
use revolt_quark::DatabaseInfo; use revolt_quark::DatabaseInfo;
#[launch] #[launch]
@@ -25,13 +27,33 @@ async fn rocket() -> _ {
let db = DatabaseInfo::Auto.connect().await.unwrap(); let db = DatabaseInfo::Auto.connect().await.unwrap();
db.migrate_database().await.unwrap(); db.migrate_database().await.unwrap();
// Setup rAuth // Setup Authifier event channel
let rauth = RAuth { let (sender, receiver) = unbounded();
// Setup Authifier
let authifier = Authifier {
database: db.clone().into(), database: db.clone().into(),
config: revolt_quark::util::rauth::config(), config: revolt_quark::util::authifier::config(),
event_channel: Some(sender),
}; };
// Launch background task workers. // Launch a listener for Authifier events
async_std::task::spawn(async move {
while let Ok(event) = receiver.recv().await {
match &event {
AuthifierEvent::CreateSession { .. } | AuthifierEvent::CreateAccount { .. } => {
EventV1::Auth(event).global().await
}
AuthifierEvent::DeleteSession { user_id, .. }
| AuthifierEvent::DeleteAllSessions { user_id, .. } => {
let id = user_id.to_string();
EventV1::Auth(event).private(id).await
}
}
}
});
// Launch background task workers
async_std::task::spawn(revolt_quark::tasks::start_workers(db.clone())); async_std::task::spawn(revolt_quark::tasks::start_workers(db.clone()));
// Configure CORS // Configure CORS
@@ -43,7 +65,7 @@ async fn rocket() -> _ {
.mount("/", revolt_quark::web::cors::catch_all_options_routes()) .mount("/", revolt_quark::web::cors::catch_all_options_routes())
.mount("/", revolt_quark::web::ratelimiter::routes()) .mount("/", revolt_quark::web::ratelimiter::routes())
.mount("/swagger/", revolt_quark::web::swagger::routes()) .mount("/swagger/", revolt_quark::web::swagger::routes())
.manage(rauth) .manage(authifier)
.manage(db) .manage(db)
.manage(cors.clone()) .manage(cors.clone())
.attach(revolt_quark::web::ratelimiter::RatelimitFairing) .attach(revolt_quark::web::ratelimiter::RatelimitFairing)
+1 -1
View File
@@ -1,5 +1,5 @@
use revolt_rocket_okapi::revolt_okapi::openapi3::OpenApi;
use rocket::Route; use rocket::Route;
use rocket_okapi::okapi::openapi3::OpenApi;
mod create; mod create;
mod delete; mod delete;
@@ -2,13 +2,27 @@ use revolt_quark::{
models::{channel::PartialChannel, Channel, User}, models::{channel::PartialChannel, Channel, User},
perms, Db, EmptyResponse, Error, Permission, Ref, Result, perms, Db, EmptyResponse, Error, Permission, Ref, Result,
}; };
use serde::{Deserialize, Serialize};
use validator::Validate;
/// # Query Parameters
#[derive(Validate, Serialize, Deserialize, JsonSchema, FromForm)]
pub struct OptionsChannelDelete {
/// Whether to not send a leave message
leave_silently: Option<bool>,
}
/// # Close Channel /// # Close Channel
/// ///
/// Deletes a server channel, leaves a group or closes a group. /// Deletes a server channel, leaves a group or closes a group.
#[openapi(tag = "Channel Information")] #[openapi(tag = "Channel Information")]
#[delete("/<target>")] #[delete("/<target>?<options..>")]
pub async fn req(db: &Db, user: User, target: Ref) -> Result<EmptyResponse> { pub async fn req(
db: &Db,
user: User,
target: Ref,
options: OptionsChannelDelete,
) -> Result<EmptyResponse> {
let mut channel = target.as_channel(db).await?; let mut channel = target.as_channel(db).await?;
let mut perms = perms(&user).channel(&channel); let mut perms = perms(&user).channel(&channel);
perms.throw_permission(db, Permission::ViewChannel).await?; perms.throw_permission(db, Permission::ViewChannel).await?;
@@ -27,7 +41,12 @@ pub async fn req(db: &Db, user: User, target: Ref) -> Result<EmptyResponse> {
.await .await
.map(|_| EmptyResponse), .map(|_| EmptyResponse),
Channel::Group { .. } => channel Channel::Group { .. } => channel
.remove_user_from_group(db, &user.id, None) .remove_user_from_group(
db,
&user.id,
None,
options.leave_silently.unwrap_or_default(),
)
.await .await
.map(|_| EmptyResponse), .map(|_| EmptyResponse),
Channel::TextChannel { .. } | Channel::VoiceChannel { .. } => { Channel::TextChannel { .. } | Channel::VoiceChannel { .. } => {
@@ -29,6 +29,8 @@ pub struct DataEditChannel {
icon: Option<String>, icon: Option<String>,
/// Whether this channel is age-restricted /// Whether this channel is age-restricted
nsfw: Option<bool>, nsfw: Option<bool>,
/// Whether this channel is archived
archived: Option<bool>,
#[validate(length(min = 1))] #[validate(length(min = 1))]
remove: Option<Vec<FieldsChannel>>, remove: Option<Vec<FieldsChannel>>,
} }
@@ -83,6 +85,7 @@ pub async fn req(
} }
// Transfer ownership // Transfer ownership
partial.owner = Some(new_owner.to_string());
let old_owner = std::mem::replace(owner, new_owner.to_string()); let old_owner = std::mem::replace(owner, new_owner.to_string());
// Notify clients // Notify clients
@@ -33,7 +33,7 @@ pub async fn req(db: &Db, user: User, target: Ref, member: Ref) -> Result<EmptyR
} }
channel channel
.remove_user_from_group(db, &member.id, Some(&user.id)) .remove_user_from_group(db, &member.id, Some(&user.id), false)
.await .await
.map(|_| EmptyResponse) .map(|_| EmptyResponse)
} }
@@ -65,16 +65,20 @@ pub async fn message_send(
data.validate() data.validate()
.map_err(|error| Error::FailedValidation { error })?; .map_err(|error| Error::FailedValidation { error })?;
// Validate Message is within reasonable length limits
Message::validate_sum(&data.content, &data.embeds)?; Message::validate_sum(&data.content, &data.embeds)?;
// Ensure the request is unique
idempotency.consume_nonce(data.nonce).await?; idempotency.consume_nonce(data.nonce).await?;
// Ensure we have permissions to send a message
let channel = target.as_channel(db).await?; let channel = target.as_channel(db).await?;
let mut permissions = perms(&user).channel(&channel); let mut permissions = perms(&user).channel(&channel);
permissions permissions
.throw_permission_and_view_channel(db, Permission::SendMessage) .throw_permission_and_view_channel(db, Permission::SendMessage)
.await?; .await?;
// Check the message is not empty
if (data.content.as_ref().map_or(true, |v| v.is_empty())) if (data.content.as_ref().map_or(true, |v| v.is_empty()))
&& (data.attachments.as_ref().map_or(true, |v| v.is_empty())) && (data.attachments.as_ref().map_or(true, |v| v.is_empty()))
&& (data.embeds.as_ref().map_or(true, |v| v.is_empty())) && (data.embeds.as_ref().map_or(true, |v| v.is_empty()))
@@ -82,6 +86,22 @@ pub async fn message_send(
return Err(Error::EmptyMessage); return Err(Error::EmptyMessage);
} }
// Ensure restrict_reactions is not specified without reactions list
if let Some(interactions) = &data.interactions {
if interactions.restrict_reactions {
let disallowed = if let Some(list) = &interactions.reactions {
list.is_empty()
} else {
true
};
if disallowed {
return Err(Error::InvalidProperty);
}
}
}
// Start constructing the message
let message_id = Ulid::new().to_string(); let message_id = Ulid::new().to_string();
let mut message = Message { let mut message = Message {
id: message_id.clone(), id: message_id.clone(),
@@ -116,7 +136,7 @@ pub async fn message_send(
} }
// 3. Ensure interactions information is correct // 3. Ensure interactions information is correct
message.interactions.validate(db).await?; message.interactions.validate(db, &mut permissions).await?;
// 4. Verify replies are valid. // 4. Verify replies are valid.
let mut replies = HashSet::new(); let mut replies = HashSet::new();
+1 -1
View File
@@ -1,5 +1,5 @@
use revolt_rocket_okapi::revolt_okapi::openapi3::OpenApi;
use rocket::Route; use rocket::Route;
use rocket_okapi::okapi::openapi3::OpenApi;
mod channel_ack; mod channel_ack;
mod channel_delete; mod channel_delete;
@@ -55,7 +55,7 @@ pub async fn create_emoji(
// Check that there are no more than 100 emoji // Check that there are no more than 100 emoji
// ! FIXME: hardcoded upper limit // ! FIXME: hardcoded upper limit
let emojis = db.fetch_emoji_by_parent_id(&server.id).await?; let emojis = db.fetch_emoji_by_parent_id(&server.id).await?;
if emojis.len() > 100 { if emojis.len() > 99 {
return Err(Error::TooManyEmoji); return Err(Error::TooManyEmoji);
} }
} }
+1 -1
View File
@@ -1,5 +1,5 @@
use revolt_rocket_okapi::revolt_okapi::openapi3::OpenApi;
use rocket::Route; use rocket::Route;
use rocket_okapi::okapi::openapi3::OpenApi;
mod emoji_create; mod emoji_create;
mod emoji_delete; mod emoji_delete;
@@ -23,6 +23,9 @@ pub enum InviteResponse {
/// Attachment for server banner /// Attachment for server banner
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
server_banner: Option<File>, server_banner: Option<File>,
/// Enum of server flags
#[serde(skip_serializing_if = "Option::is_none")]
server_flags: Option<i32>,
/// Id of server channel /// Id of server channel
channel_id: String, channel_id: String,
/// Name of server channel /// Name of server channel
@@ -94,6 +97,7 @@ pub async fn req(db: &Db, target: Ref) -> Result<Json<InviteResponse>> {
server_name: server.name, server_name: server.name,
server_icon: server.icon, server_icon: server.icon,
server_banner: server.banner, server_banner: server.banner,
server_flags: server.flags,
channel_id: id, channel_id: id,
channel_name: name, channel_name: name,
channel_description: description, channel_description: description,
+1 -1
View File
@@ -1,5 +1,5 @@
use revolt_rocket_okapi::revolt_okapi::openapi3::OpenApi;
use rocket::Route; use rocket::Route;
use rocket_okapi::okapi::openapi3::OpenApi;
mod invite_delete; mod invite_delete;
mod invite_fetch; mod invite_fetch;
+5 -5
View File
@@ -1,7 +1,7 @@
use revolt_rocket_okapi::{revolt_okapi::openapi3::OpenApi, settings::OpenApiSettings};
pub use rocket::http::Status; pub use rocket::http::Status;
pub use rocket::response::Redirect; pub use rocket::response::Redirect;
use rocket::{Build, Rocket}; use rocket::{Build, Rocket};
use rocket_okapi::{okapi::openapi3::OpenApi, settings::OpenApiSettings};
mod bots; mod bots;
mod channels; mod channels;
@@ -27,9 +27,9 @@ pub fn mount(mut rocket: Rocket<Build>) -> Rocket<Build> {
"/servers" => servers::routes(), "/servers" => servers::routes(),
"/invites" => invites::routes(), "/invites" => invites::routes(),
"/custom" => customisation::routes(), "/custom" => customisation::routes(),
"/auth/account" => rocket_rauth::routes::account::routes(), "/auth/account" => rocket_authifier::routes::account::routes(),
"/auth/session" => rocket_rauth::routes::session::routes(), "/auth/session" => rocket_authifier::routes::session::routes(),
"/auth/mfa" => rocket_rauth::routes::mfa::routes(), "/auth/mfa" => rocket_authifier::routes::mfa::routes(),
"/onboard" => onboard::routes(), "/onboard" => onboard::routes(),
"/push" => push::routes(), "/push" => push::routes(),
"/sync" => sync::routes(), "/sync" => sync::routes(),
@@ -39,7 +39,7 @@ pub fn mount(mut rocket: Rocket<Build>) -> Rocket<Build> {
} }
fn custom_openapi_spec() -> OpenApi { fn custom_openapi_spec() -> OpenApi {
use rocket_okapi::okapi::openapi3::*; use revolt_rocket_okapi::revolt_okapi::openapi3::*;
let mut extensions = schemars::Map::new(); let mut extensions = schemars::Map::new();
extensions.insert( extensions.insert(
+3 -1
View File
@@ -1,5 +1,7 @@
use crate::util::regex::RE_USERNAME; use crate::util::regex::RE_USERNAME;
use revolt_quark::{models::User, rauth::models::Session, Database, EmptyResponse, Error, Result}; use revolt_quark::{
authifier::models::Session, models::User, Database, EmptyResponse, Error, Result,
};
use rocket::{serde::json::Json, State}; use rocket::{serde::json::Json, State};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
+1 -1
View File
@@ -1,4 +1,4 @@
use revolt_quark::{models::User, rauth::models::Session}; use revolt_quark::{authifier::models::Session, models::User};
use rocket::serde::json::Json; use rocket::serde::json::Json;
use serde::Serialize; use serde::Serialize;
+1 -1
View File
@@ -1,5 +1,5 @@
use revolt_rocket_okapi::revolt_okapi::openapi3::OpenApi;
use rocket::Route; use rocket::Route;
use rocket_okapi::okapi::openapi3::OpenApi;
mod complete; mod complete;
mod hello; mod hello;
+1 -1
View File
@@ -1,5 +1,5 @@
use revolt_rocket_okapi::revolt_okapi::openapi3::OpenApi;
use rocket::Route; use rocket::Route;
use rocket_okapi::okapi::openapi3::OpenApi;
mod subscribe; mod subscribe;
mod unsubscribe; mod unsubscribe;
+4 -4
View File
@@ -1,7 +1,7 @@
use revolt_quark::{ use revolt_quark::{
rauth::{ authifier::{
models::{Session, WebPushSubscription}, models::{Session, WebPushSubscription},
RAuth, Authifier,
}, },
EmptyResponse, Error, Result, EmptyResponse, Error, Result,
}; };
@@ -16,13 +16,13 @@ use rocket::{serde::json::Json, State};
#[openapi(tag = "Web Push")] #[openapi(tag = "Web Push")]
#[post("/subscribe", data = "<data>")] #[post("/subscribe", data = "<data>")]
pub async fn req( pub async fn req(
rauth: &State<RAuth>, authifier: &State<Authifier>,
mut session: Session, mut session: Session,
data: Json<WebPushSubscription>, data: Json<WebPushSubscription>,
) -> Result<EmptyResponse> { ) -> Result<EmptyResponse> {
session.subscription = Some(data.into_inner()); session.subscription = Some(data.into_inner());
session session
.save(&rauth) .save(authifier)
.await .await
.map(|_| EmptyResponse) .map(|_| EmptyResponse)
.map_err(|_| Error::DatabaseError { .map_err(|_| Error::DatabaseError {
+3 -3
View File
@@ -1,5 +1,5 @@
use revolt_quark::{ use revolt_quark::{
rauth::{models::Session, RAuth}, authifier::{models::Session, Authifier},
EmptyResponse, Error, Result, EmptyResponse, Error, Result,
}; };
@@ -10,10 +10,10 @@ use rocket::State;
/// Remove the Web Push subscription associated with the current session. /// Remove the Web Push subscription associated with the current session.
#[openapi(tag = "Web Push")] #[openapi(tag = "Web Push")]
#[post("/unsubscribe")] #[post("/unsubscribe")]
pub async fn req(rauth: &State<RAuth>, mut session: Session) -> Result<EmptyResponse> { pub async fn req(authifier: &State<Authifier>, mut session: Session) -> Result<EmptyResponse> {
session.subscription = None; session.subscription = None;
session session
.save(&rauth) .save(authifier)
.await .await
.map(|_| EmptyResponse) .map(|_| EmptyResponse)
.map_err(|_| Error::DatabaseError { .map_err(|_| Error::DatabaseError {
+25
View File
@@ -54,6 +54,21 @@ pub struct RevoltFeatures {
pub voso: VoiceFeature, pub voso: VoiceFeature,
} }
/// # Build Information
#[derive(Serialize, JsonSchema, Debug)]
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 /// # Server Configuration
#[derive(Serialize, JsonSchema, Debug)] #[derive(Serialize, JsonSchema, Debug)]
pub struct RevoltConfig { pub struct RevoltConfig {
@@ -67,6 +82,8 @@ pub struct RevoltConfig {
pub app: String, pub app: String,
/// Web Push VAPID public key /// Web Push VAPID public key
pub vapid: String, pub vapid: String,
/// Build information
pub build: BuildInformation,
} }
/// # Query Node /// # Query Node
@@ -101,6 +118,14 @@ pub async fn root() -> Result<Json<RevoltConfig>> {
ws: EXTERNAL_WS_URL.to_string(), ws: EXTERNAL_WS_URL.to_string(),
app: APP_URL.to_string(), app: APP_URL.to_string(),
vapid: VAPID_PUBLIC_KEY.to_string(), vapid: VAPID_PUBLIC_KEY.to_string(),
build: BuildInformation {
commit_sha: env!("VERGEN_GIT_SHA", "<failed to generate>").to_string(),
commit_timestamp: env!("VERGEN_GIT_COMMIT_TIMESTAMP", "<failed to generate>")
.to_string(),
semver: env!("VERGEN_GIT_SEMVER", "<failed to generate>").to_string(),
origin_url: env!("GIT_ORIGIN_URL", "<failed to generate>").to_string(),
timestamp: env!("VERGEN_BUILD_TIMESTAMP", "<failed to generate>").to_string(),
},
})) }))
} }
@@ -91,6 +91,16 @@ pub async fn req(
required.push(Permission::AssignRoles); required.push(Permission::AssignRoles);
} }
if data.timeout.is_some()
|| data
.remove
.as_ref()
.map(|x| x.contains(&FieldsMember::Timeout))
.unwrap_or_default()
{
required.push(Permission::TimeoutMembers);
}
for permission in required { for permission in required {
permissions.throw_permission(db, permission).await?; permissions.throw_permission(db, permission).await?;
} }
@@ -1,8 +1,6 @@
use revolt_quark::{ use revolt_quark::{
models::{Member, User}, models::{Member, User},
perms, perms, Db, Ref, Result,
presence::presence_filter_online,
Db, Ref, Result,
}; };
use rocket::serde::json::Json; use rocket::serde::json::Json;
@@ -47,16 +45,7 @@ pub async fn req(
user_ids.push(member.id.user.clone()); user_ids.push(member.id.user.clone());
} }
let online_ids = presence_filter_online(&user_ids).await; let mut users = User::fetch_foreign_users(db, &user_ids).await?;
let mut users = db
.fetch_users(&user_ids)
.await?
.into_iter()
.map(|mut user| {
user.online = Some(online_ids.contains(&user.id));
user.foreign()
})
.collect::<Vec<User>>();
// Ensure the lists match up exactly. // Ensure the lists match up exactly.
members.sort_by(|a, b| a.id.user.cmp(&b.id.user)); members.sort_by(|a, b| a.id.user.cmp(&b.id.user));
@@ -34,7 +34,7 @@ pub async fn req(db: &Db, user: User, target: Ref, member: Ref) -> Result<EmptyR
} }
server server
.remove_member(db, member, RemovalIntention::Kick) .remove_member(db, member, RemovalIntention::Kick, false)
.await .await
.map(|_| EmptyResponse) .map(|_| EmptyResponse)
} }
+1 -1
View File
@@ -1,5 +1,5 @@
use revolt_rocket_okapi::revolt_okapi::openapi3::OpenApi;
use rocket::Route; use rocket::Route;
use rocket_okapi::okapi::openapi3::OpenApi;
mod ban_create; mod ban_create;
mod ban_list; mod ban_list;
@@ -2,13 +2,27 @@ use revolt_quark::{
models::{server_member::RemovalIntention, User}, models::{server_member::RemovalIntention, User},
Db, EmptyResponse, Ref, Result, Db, EmptyResponse, Ref, Result,
}; };
use serde::{Deserialize, Serialize};
use validator::Validate;
/// # Query Parameters
#[derive(Validate, Serialize, Deserialize, JsonSchema, FromForm)]
pub struct OptionsServerDelete {
/// Whether to not send a leave message
leave_silently: Option<bool>,
}
/// # Delete / Leave Server /// # Delete / Leave Server
/// ///
/// Deletes a server if owner otherwise leaves. /// Deletes a server if owner otherwise leaves.
#[openapi(tag = "Server Information")] #[openapi(tag = "Server Information")]
#[delete("/<target>")] #[delete("/<target>?<options..>")]
pub async fn req(db: &Db, user: User, target: Ref) -> Result<EmptyResponse> { pub async fn req(
db: &Db,
user: User,
target: Ref,
options: OptionsServerDelete,
) -> Result<EmptyResponse> {
let server = target.as_server(db).await?; let server = target.as_server(db).await?;
let member = db.fetch_member(&target.id, &user.id).await?; let member = db.fetch_member(&target.id, &user.id).await?;
@@ -16,7 +30,12 @@ pub async fn req(db: &Db, user: User, target: Ref) -> Result<EmptyResponse> {
server.delete(db).await server.delete(db).await
} else { } else {
server server
.remove_member(db, member, RemovalIntention::Leave) .remove_member(
db,
member,
RemovalIntention::Leave,
options.leave_silently.unwrap_or_default(),
)
.await .await
} }
.map(|_| EmptyResponse) .map(|_| EmptyResponse)
+30 -13
View File
@@ -1,3 +1,5 @@
use std::collections::HashSet;
use revolt_quark::{ use revolt_quark::{
models::{ models::{
server::{Category, FieldsServer, PartialServer, SystemMessageChannels}, server::{Category, FieldsServer, PartialServer, SystemMessageChannels},
@@ -135,29 +137,44 @@ pub async fn req(
} }
} }
// 2. Apply new icon // 2. Validate changes
if let Some(system_messages) = &partial.system_messages {
for id in system_messages.clone().into_channel_ids() {
if !server.channels.contains(&id) {
return Err(Error::NotFound);
}
}
}
if let Some(categories) = &mut partial.categories {
let mut channel_ids = HashSet::new();
for category in categories {
for channel in &category.channels {
if channel_ids.contains(channel) {
return Err(Error::InvalidOperation);
}
channel_ids.insert(channel.to_string());
}
category
.channels
.retain(|item| server.channels.contains(item));
}
}
// 3. Apply new icon
if let Some(icon) = icon { if let Some(icon) = icon {
partial.icon = Some(File::use_server_icon(db, &icon, &server.id).await?); partial.icon = Some(File::use_server_icon(db, &icon, &server.id).await?);
server.icon = partial.icon.clone(); server.icon = partial.icon.clone();
} }
// 3. Apply new banner // 4. Apply new banner
if let Some(banner) = banner { if let Some(banner) = banner {
partial.banner = Some(File::use_banner(db, &banner, &server.id).await?); partial.banner = Some(File::use_banner(db, &banner, &server.id).await?);
server.banner = partial.banner.clone(); server.banner = partial.banner.clone();
} }
// 4. Validate changes
if let Some(system_messages) = &partial.system_messages {
let channels = system_messages.clone().into_channel_ids();
if !db
.check_channels_exist(&channels.into_iter().collect())
.await?
{
return Err(Error::NotFound);
}
}
server server
.update(db, partial, remove.unwrap_or_default()) .update(db, partial, remove.unwrap_or_default())
.await?; .await?;
+1 -1
View File
@@ -1,5 +1,5 @@
use revolt_rocket_okapi::revolt_okapi::openapi3::OpenApi;
use rocket::Route; use rocket::Route;
use rocket_okapi::okapi::openapi3::OpenApi;
mod get_settings; mod get_settings;
mod get_unreads; mod get_unreads;
@@ -1,5 +1,5 @@
use crate::util::regex::RE_USERNAME; use crate::util::regex::RE_USERNAME;
use revolt_quark::{models::User, rauth::models::Account, Database, Error, Result}; use revolt_quark::{authifier::models::Account, models::User, Database, Error, Result};
use rocket::{serde::json::Json, State}; use rocket::{serde::json::Json, State};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use validator::Validate; use validator::Validate;
@@ -0,0 +1,26 @@
use revolt_quark::{Database, Ref, Result};
use rocket::{serde::json::Json, State};
use serde::Serialize;
/// # Flag Response
#[derive(Serialize, JsonSchema)]
pub struct FlagResponse {
/// Flags
flags: i32,
}
/// # Fetch User Flags
///
/// Retrieve a user's flags.
#[openapi(tag = "User Information")]
#[get("/<target>/flags")]
pub async fn fetch_user_flags(db: &State<Database>, target: Ref) -> Result<Json<FlagResponse>> {
let flags = if let Ok(target) = target.as_user(db).await {
target.flags.unwrap_or_default()
} else {
0
};
Ok(Json(FlagResponse { flags }))
}
@@ -1,7 +1,7 @@
use revolt_rocket_okapi::revolt_okapi::openapi3::{self, MediaType, RefOr};
use rocket::http::ContentType; use rocket::http::ContentType;
use rocket::response::{self, Responder}; use rocket::response::{self, Responder};
use rocket::{Request, Response}; use rocket::{Request, Response};
use rocket_okapi::okapi::openapi3::{self, MediaType, RefOr};
use schemars::schema::{InstanceType, SchemaObject, SingleOrVec}; use schemars::schema::{InstanceType, SchemaObject, SingleOrVec};
pub struct CachedFile((ContentType, Vec<u8>)); pub struct CachedFile((ContentType, Vec<u8>));
@@ -16,10 +16,10 @@ impl<'r> Responder<'r, 'static> for CachedFile {
} }
} }
impl rocket_okapi::response::OpenApiResponderInner for CachedFile { impl revolt_rocket_okapi::response::OpenApiResponderInner for CachedFile {
fn responses( fn responses(
_gen: &mut rocket_okapi::gen::OpenApiGenerator, _gen: &mut revolt_rocket_okapi::gen::OpenApiGenerator,
) -> std::result::Result<openapi3::Responses, rocket_okapi::OpenApiError> { ) -> std::result::Result<openapi3::Responses, revolt_rocket_okapi::OpenApiError> {
let mut responses = schemars::Map::new(); let mut responses = schemars::Map::new();
let mut content = schemars::Map::new(); let mut content = schemars::Map::new();
+3 -1
View File
@@ -1,5 +1,5 @@
use revolt_rocket_okapi::revolt_okapi::openapi3::OpenApi;
use rocket::Route; use rocket::Route;
use rocket_okapi::okapi::openapi3::OpenApi;
mod add_friend; mod add_friend;
mod block_user; mod block_user;
@@ -9,6 +9,7 @@ mod fetch_dms;
mod fetch_profile; mod fetch_profile;
mod fetch_self; mod fetch_self;
mod fetch_user; mod fetch_user;
mod fetch_user_flags;
mod find_mutual; mod find_mutual;
mod get_default_avatar; mod get_default_avatar;
mod open_dm; mod open_dm;
@@ -21,6 +22,7 @@ pub fn routes() -> (Vec<Route>, OpenApi) {
// User Information // User Information
fetch_self::req, fetch_self::req,
fetch_user::req, fetch_user::req,
fetch_user_flags::fetch_user_flags,
edit_user::req, edit_user::req,
change_username::req, change_username::req,
get_default_avatar::req, get_default_avatar::req,
+12 -12
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-quark" name = "revolt-quark"
version = "0.1.0" version = "0.5.7"
license = "AGPL-3.0-or-later" license = "AGPL-3.0-or-later"
edition = "2021" edition = "2021"
@@ -16,9 +16,9 @@ rocket_impl = [
"lru", "lru",
"dashmap", "dashmap",
"rauth/database-mongodb", "authifier/database-mongodb",
"rauth/rocket_impl", "authifier/rocket_impl",
"rauth/okapi_impl" "authifier/okapi_impl"
] ]
test = [ "async-std", "mongo", "mongodb/async-std-runtime", "rocket_impl" ] test = [ "async-std", "mongo", "mongodb/async-std-runtime", "rocket_impl" ]
@@ -38,10 +38,8 @@ bson = { version = "2.1.0", features = ["chrono-0_4"] }
# Spec Generation # Spec Generation
schemars = "0.8.8" schemars = "0.8.8"
okapi = { git = "https://github.com/insertish/okapi", rev = "a1048d0c8cd771e424ec97d33d825c32e06aa120" } revolt_okapi = "0.9.1"
rocket_okapi = { git = "https://github.com/insertish/okapi", rev = "a1048d0c8cd771e424ec97d33d825c32e06aa120" } revolt_rocket_okapi = { version = "0.9.1", features = [ "swagger" ] }
# okapi = "0.7.0-rc.1"
# rocket_okapi = "0.8.0-rc.1"
# Databases # Databases
redis-kiss = { version = "0.1.3" } redis-kiss = { version = "0.1.3" }
@@ -64,12 +62,14 @@ regex = "1.5.5"
nanoid = "0.4.0" nanoid = "0.4.0"
linkify = "0.8.1" linkify = "0.8.1"
dotenv = "0.15.0" dotenv = "0.15.0"
indexmap = "1.9.1"
impl_ops = "0.1.1" impl_ops = "0.1.1"
num_enum = "0.5.6" num_enum = "0.5.6"
reqwest = "0.11.10" reqwest = "0.11.10"
bitfield = "0.13.2" bitfield = "0.13.2"
once_cell = "1.13.0" once_cell = "1.13.0"
lazy_static = "1.4.0" lazy_static = "1.4.0"
async-lock = "2.6.0"
lru = { version = "0.7.6", optional = true } lru = { version = "0.7.6", optional = true }
dashmap = { version = "5.2.0", optional = true } dashmap = { version = "5.2.0", optional = true }
@@ -81,11 +81,11 @@ web-push = "0.7.2"
# Implementations # Implementations
rocket_http = { optional = true, version = "0.5.0-rc.2" } rocket_http = { optional = true, version = "0.5.0-rc.2" }
rocket = { optional = true, version = "0.5.0-rc.2", default-features = false, features = ["json"] } rocket = { optional = true, version = "0.5.0-rc.2", default-features = false, features = ["json"] }
rocket_empty = { optional = true, git = "https://github.com/insertish/rocket_empty", branch = "master" } rocket_empty = { version = "0.1.1", optional = true, features = [ "schema" ] }
rocket_cors = { optional = true, git = "https://github.com/lawliet89/rocket_cors", rev = "5843861a88958c16bfaa0b40f0d8910772bcd2f6" } rocket_cors = { optional = true, git = "https://github.com/lawliet89/rocket_cors", rev = "c17e8145baa4790319fdb6a473e465b960f55e7c" }
# rAuth # Authifier
rauth = { git = "https://github.com/insertish/rauth", rev = "9c3f9bb58a549a51546bd748274130ac4a667cb4", features = [ "async-std-runtime" ] } authifier = { version = "1.0.7", features = [ "async-std-runtime" ] }
# Sentry # Sentry
sentry = "0.25.0" sentry = "0.25.0"
+138
View File
@@ -0,0 +1,138 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<!-- Compiled with Bootstrap Email version: 1.3.1 --><meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="x-apple-disable-message-reformatting">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="format-detection" content="telephone=no, date=no, address=no, email=no">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<style type="text/css">
body,table,td{font-family:Helvetica,Arial,sans-serif !important}.ExternalClass{width:100%}.ExternalClass,.ExternalClass p,.ExternalClass span,.ExternalClass font,.ExternalClass td,.ExternalClass div{line-height:150%}a{text-decoration:none}*{color:inherit}a[x-apple-data-detectors],u+#body a,#MessageViewBody a{color:inherit;text-decoration:none;font-size:inherit;font-family:inherit;font-weight:inherit;line-height:inherit}img{-ms-interpolation-mode:bicubic}table:not([class^=s-]){font-family:Helvetica,Arial,sans-serif;mso-table-lspace:0pt;mso-table-rspace:0pt;border-spacing:0px;border-collapse:collapse}table:not([class^=s-]) td{border-spacing:0px;border-collapse:collapse}@media screen and (max-width: 600px){.w-full,.w-full>tbody>tr>td{width:100% !important}.w-24,.w-24>tbody>tr>td{width:96px !important}.p-lg-10:not(table),.p-lg-10:not(.btn)>tbody>tr>td,.p-lg-10.btn td a{padding:0 !important}.p-3:not(table),.p-3:not(.btn)>tbody>tr>td,.p-3.btn td a{padding:12px !important}.p-6:not(table),.p-6:not(.btn)>tbody>tr>td,.p-6.btn td a{padding:24px !important}*[class*=s-lg-]>tbody>tr>td{font-size:0 !important;line-height:0 !important;height:0 !important}.s-4>tbody>tr>td{font-size:16px !important;line-height:16px !important;height:16px !important}.s-6>tbody>tr>td{font-size:24px !important;line-height:24px !important;height:24px !important}.s-10>tbody>tr>td{font-size:40px !important;line-height:40px !important;height:40px !important}}
</style>
</head>
<body class="bg-light" style="outline: 0; width: 100%; min-width: 100%; height: 100%; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; font-family: Helvetica, Arial, sans-serif; line-height: 24px; font-weight: normal; font-size: 16px; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; color: #000000; margin: 0; padding: 0; border-width: 0;" bgcolor="#f7fafc">
<table class="bg-light body" valign="top" role="presentation" border="0" cellpadding="0" cellspacing="0" style="outline: 0; width: 100%; min-width: 100%; height: 100%; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; font-family: Helvetica, Arial, sans-serif; line-height: 24px; font-weight: normal; font-size: 16px; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; color: #000000; margin: 0; padding: 0; border-width: 0;" bgcolor="#f7fafc">
<tbody>
<tr>
<td valign="top" style="line-height: 24px; font-size: 16px; margin: 0;" align="left" bgcolor="#f7fafc">
<table class="container" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;">
<tbody>
<tr>
<td align="center" style="line-height: 24px; font-size: 16px; margin: 0; padding: 0 16px;">
<!--[if (gte mso 9)|(IE)]>
<table align="center" role="presentation">
<tbody>
<tr>
<td width="600">
<![endif]-->
<table align="center" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%; max-width: 600px; margin: 0 auto;">
<tbody>
<tr>
<td style="line-height: 24px; font-size: 16px; margin: 0;" align="left">
<table class="s-10 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<tbody>
<tr>
<td style="line-height: 40px; font-size: 40px; width: 100%; height: 40px; margin: 0;" align="left" width="100%" height="40">
&#160;
</td>
</tr>
</tbody>
</table>
<table class="ax-center" role="presentation" align="center" border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto;">
<tbody>
<tr>
<td style="line-height: 24px; font-size: 16px; margin: 0;" align="left">
<img alt="Revolt Logo" class="w-24" src="https://app.revolt.chat/assets/logo_round.png" style="height: auto; line-height: 100%; outline: none; text-decoration: none; display: block; width: 96px; border-style: none; border-width: 0;" width="96">
</td>
</tr>
</tbody>
</table>
<table class="s-10 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<tbody>
<tr>
<td style="line-height: 40px; font-size: 40px; width: 100%; height: 40px; margin: 0;" align="left" width="100%" height="40">
&#160;
</td>
</tr>
</tbody>
</table>
<table class="card p-6 p-lg-10 space-y-4" role="presentation" border="0" cellpadding="0" cellspacing="0" style="border-radius: 6px; border-collapse: separate !important; width: 100%; overflow: hidden; border: 1px solid #e2e8f0;" bgcolor="#ffffff">
<tbody>
<tr>
<td style="line-height: 24px; font-size: 16px; width: 100%; margin: 0; padding: 40px;" align="left" bgcolor="#ffffff">
<h1 class="h3 fw-700" style="padding-top: 0; padding-bottom: 0; font-weight: 700 !important; vertical-align: baseline; font-size: 28px; line-height: 33.6px; margin: 0;" align="left">Account Deletion</h1>
<table class="s-4 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<tbody>
<tr>
<td style="line-height: 16px; font-size: 16px; width: 100%; height: 16px; margin: 0;" align="left" width="100%" height="16">
&#160;
</td>
</tr>
</tbody>
</table>
<p class="" style="line-height: 24px; font-size: 16px; width: 100%; margin: 0;" align="left">You requested to have your account deleted, if you did not perform this action please take measures to secure your account immediately.</p>
<table class="s-4 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<tbody>
<tr>
<td style="line-height: 16px; font-size: 16px; width: 100%; height: 16px; margin: 0;" align="left" width="100%" height="16">
&#160;
</td>
</tr>
</tbody>
</table>
<table class="btn btn-primary p-3 fw-700" role="presentation" border="0" cellpadding="0" cellspacing="0" style="border-radius: 6px; border-collapse: separate !important; font-weight: 700 !important;">
<tbody>
<tr>
<td style="line-height: 24px; font-size: 16px; border-radius: 6px; font-weight: 700 !important; margin: 0;" align="center" bgcolor="#0d6efd">
<a href="{{url}}" style="color: #ffffff; font-size: 16px; font-family: Helvetica, Arial, sans-serif; text-decoration: none; border-radius: 6px; line-height: 20px; display: block; font-weight: 700 !important; white-space: nowrap; background-color: #0d6efd; padding: 12px; border: 1px solid #0d6efd;">Confirm</a>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<table class="s-6 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<tbody>
<tr>
<td style="line-height: 24px; font-size: 24px; width: 100%; height: 24px; margin: 0;" align="left" width="100%" height="24">
&#160;
</td>
</tr>
</tbody>
</table>
<div class="text-muted text-center" style="color: #718096;" align="center">
This email is intended for {{email}}<br>
Sent from Revolt<br>
Made in the EU
</div>
<table class="s-6 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<tbody>
<tr>
<td style="line-height: 24px; font-size: 24px; width: 100%; height: 24px; margin: 0;" align="left" width="100%" height="24">
&#160;
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<!--[if (gte mso 9)|(IE)]>
</td>
</tr>
</tbody>
</table>
<![endif]-->
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</body>
</html>
@@ -0,0 +1,30 @@
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style>
/* Add custom classes and styles that you want inlined here */
</style>
</head>
<body class="bg-light">
<div class="container">
<img
alt="Revolt Logo"
class="ax-center my-10 w-24"
src="https://app.revolt.chat/assets/logo_round.png"
/>
<div class="card p-6 p-lg-10 space-y-4">
<h1 class="h3 fw-700">Account Deletion</h1>
<p>
You requested to have your account deleted, if you did not perform
this action please take measures to secure your account immediately.
</p>
<a class="btn btn-primary p-3 fw-700" href="{{url}}">Confirm</a>
</div>
<div class="text-muted text-center my-6">
This email is intended for {{email}}<br />
Sent from Revolt<br />
Made in the EU
</div>
</div>
</body>
</html>
+4 -2
View File
@@ -1,5 +1,7 @@
You requested to have your account deleted, if you did not perform this action please take measures to secure your account immediately. You requested to have your account deleted, if you did not perform this action please take measures to secure your account immediately.
Confirm here: {{url}} Please navigate to: {{url}}
Sent by Revolt. This email is intended for {{email}}
Sent by Revolt
Made in the EU
+138 -63
View File
@@ -1,63 +1,138 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-GB"> <html>
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <!-- Compiled with Bootstrap Email version: 1.3.1 --><meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Reset your password.</title> <meta name="x-apple-disable-message-reformatting">
<meta name="viewport" content="width=device-width, initial-scale=1.0"/> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="format-detection" content="telephone=no, date=no, address=no, email=no">
<style type="text/css"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
a[x-apple-data-detectors] {color: inherit !important;} <style type="text/css">
</style> body,table,td{font-family:Helvetica,Arial,sans-serif !important}.ExternalClass{width:100%}.ExternalClass,.ExternalClass p,.ExternalClass span,.ExternalClass font,.ExternalClass td,.ExternalClass div{line-height:150%}a{text-decoration:none}*{color:inherit}a[x-apple-data-detectors],u+#body a,#MessageViewBody a{color:inherit;text-decoration:none;font-size:inherit;font-family:inherit;font-weight:inherit;line-height:inherit}img{-ms-interpolation-mode:bicubic}table:not([class^=s-]){font-family:Helvetica,Arial,sans-serif;mso-table-lspace:0pt;mso-table-rspace:0pt;border-spacing:0px;border-collapse:collapse}table:not([class^=s-]) td{border-spacing:0px;border-collapse:collapse}@media screen and (max-width: 600px){.w-full,.w-full>tbody>tr>td{width:100% !important}.w-24,.w-24>tbody>tr>td{width:96px !important}.p-lg-10:not(table),.p-lg-10:not(.btn)>tbody>tr>td,.p-lg-10.btn td a{padding:0 !important}.p-3:not(table),.p-3:not(.btn)>tbody>tr>td,.p-3.btn td a{padding:12px !important}.p-6:not(table),.p-6:not(.btn)>tbody>tr>td,.p-6.btn td a{padding:24px !important}*[class*=s-lg-]>tbody>tr>td{font-size:0 !important;line-height:0 !important;height:0 !important}.s-4>tbody>tr>td{font-size:16px !important;line-height:16px !important;height:16px !important}.s-6>tbody>tr>td{font-size:24px !important;line-height:24px !important;height:24px !important}.s-10>tbody>tr>td{font-size:40px !important;line-height:40px !important;height:40px !important}}
</style>
</head> </head>
<body style="margin: 0; padding: 0;"> <body class="bg-light" style="outline: 0; width: 100%; min-width: 100%; height: 100%; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; font-family: Helvetica, Arial, sans-serif; line-height: 24px; font-weight: normal; font-size: 16px; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; color: #000000; margin: 0; padding: 0; border-width: 0;" bgcolor="#f7fafc">
<table border="0" cellpadding="0" cellspacing="0" width="100%"> <table class="bg-light body" valign="top" role="presentation" border="0" cellpadding="0" cellspacing="0" style="outline: 0; width: 100%; min-width: 100%; height: 100%; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; font-family: Helvetica, Arial, sans-serif; line-height: 24px; font-weight: normal; font-size: 16px; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; color: #000000; margin: 0; padding: 0; border-width: 0;" bgcolor="#f7fafc">
<tr> <tbody>
<td style="padding: 20px 0 30px 0;"> <tr>
<td valign="top" style="line-height: 24px; font-size: 16px; margin: 0;" align="left" bgcolor="#f7fafc">
<table align="center" border="0" cellpadding="0" cellspacing="0" width="600" style="border-collapse: collapse; border: 1px solid #cccccc;"> <table class="container" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;">
<tr> <tbody>
<td align="center" bgcolor="#ff4654"> <tr>
<img src="https://revolt.chat/header.png" alt="Revolt logo" width="600" height="168" style="display: block;" /> <td align="center" style="line-height: 24px; font-size: 16px; margin: 0; padding: 0 16px;">
</td> <!--[if (gte mso 9)|(IE)]>
</tr> <table align="center" role="presentation">
<tr> <tbody>
<td bgcolor="#ffffff" style="padding: 40px 30px 40px 30px;"> <tr>
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;"> <td width="600">
<tr> <![endif]-->
<td style="color: #153643; font-family: Arial, sans-serif;"> <table align="center" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%; max-width: 600px; margin: 0 auto;">
<h1 style="font-size: 24px; margin: 0;">Reset your password!</h1> <tbody>
</td> <tr>
</tr> <td style="line-height: 24px; font-size: 16px; margin: 0;" align="left">
<tr> <table class="s-10 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<td style="color: #153643; font-family: Arial, sans-serif; font-size: 16px; line-height: 24px; padding: 20px 0 0 0;"> <tbody>
<p> <tr>
You requested a password reset, if you didn't perform this action you can safely ignore this email. <td style="line-height: 40px; font-size: 40px; width: 100%; height: 40px; margin: 0;" align="left" width="100%" height="40">
</p> &#160;
<p style="margin: 0;"> </td>
Reset your password by navigating to <a href="{{url}}">{{url}}</a>. </tr>
</p> </tbody>
</td> </table>
</tr> <table class="ax-center" role="presentation" align="center" border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto;">
</table> <tbody>
</td> <tr>
</tr> <td style="line-height: 24px; font-size: 16px; margin: 0;" align="left">
<tr> <img alt="Revolt Logo" class="w-24" src="https://app.revolt.chat/assets/logo_round.png" style="height: auto; line-height: 100%; outline: none; text-decoration: none; display: block; width: 96px; border-style: none; border-width: 0;" width="96">
<td bgcolor="#ff4654" style="padding: 30px 30px;"> </td>
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;"> </tr>
<tr> </tbody>
<td style="color: #ffffff; font-family: Arial, sans-serif; font-size: 14px;"> </table>
<p style="margin: 0;">Sent by Revolt. &middot; Website: <a style="color: white;" href="https://revolt.chat">https://revolt.chat</a></p> <table class="s-10 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<p>Revolt is a user-first chat platform built with modern web technologies.</p> <tbody>
</td> <tr>
</tr> <td style="line-height: 40px; font-size: 40px; width: 100%; height: 40px; margin: 0;" align="left" width="100%" height="40">
</table> &#160;
</td> </td>
</tr> </tr>
</table> </tbody>
</table>
</td> <table class="card p-6 p-lg-10 space-y-4" role="presentation" border="0" cellpadding="0" cellspacing="0" style="border-radius: 6px; border-collapse: separate !important; width: 100%; overflow: hidden; border: 1px solid #e2e8f0;" bgcolor="#ffffff">
</tr> <tbody>
</table> <tr>
</body> <td style="line-height: 24px; font-size: 16px; width: 100%; margin: 0; padding: 40px;" align="left" bgcolor="#ffffff">
</html> <h1 class="h3 fw-700" style="padding-top: 0; padding-bottom: 0; font-weight: 700 !important; vertical-align: baseline; font-size: 28px; line-height: 33.6px; margin: 0;" align="left">Password Reset</h1>
<table class="s-4 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<tbody>
<tr>
<td style="line-height: 16px; font-size: 16px; width: 100%; height: 16px; margin: 0;" align="left" width="100%" height="16">
&#160;
</td>
</tr>
</tbody>
</table>
<p class="" style="line-height: 24px; font-size: 16px; width: 100%; margin: 0;" align="left">You requested a password reset, click below to continue.</p>
<table class="s-4 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<tbody>
<tr>
<td style="line-height: 16px; font-size: 16px; width: 100%; height: 16px; margin: 0;" align="left" width="100%" height="16">
&#160;
</td>
</tr>
</tbody>
</table>
<table class="btn btn-primary p-3 fw-700" role="presentation" border="0" cellpadding="0" cellspacing="0" style="border-radius: 6px; border-collapse: separate !important; font-weight: 700 !important;">
<tbody>
<tr>
<td style="line-height: 24px; font-size: 16px; border-radius: 6px; font-weight: 700 !important; margin: 0;" align="center" bgcolor="#0d6efd">
<a href="{{url}}" style="color: #ffffff; font-size: 16px; font-family: Helvetica, Arial, sans-serif; text-decoration: none; border-radius: 6px; line-height: 20px; display: block; font-weight: 700 !important; white-space: nowrap; background-color: #0d6efd; padding: 12px; border: 1px solid #0d6efd;">Reset</a>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<table class="s-6 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<tbody>
<tr>
<td style="line-height: 24px; font-size: 24px; width: 100%; height: 24px; margin: 0;" align="left" width="100%" height="24">
&#160;
</td>
</tr>
</tbody>
</table>
<div class="text-muted text-center" style="color: #718096;" align="center">
This email is intended for {{email}}<br>
Sent from Revolt<br>
Made in the EU
</div>
<table class="s-6 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<tbody>
<tr>
<td style="line-height: 24px; font-size: 24px; width: 100%; height: 24px; margin: 0;" align="left" width="100%" height="24">
&#160;
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<!--[if (gte mso 9)|(IE)]>
</td>
</tr>
</tbody>
</table>
<![endif]-->
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</body>
</html>
@@ -0,0 +1,26 @@
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style>
/* Add custom classes and styles that you want inlined here */
</style>
</head>
<body class="bg-light">
<div class="container">
<img
class="ax-center my-10 w-24"
src="https://app.revolt.chat/assets/logo_round.png"
/>
<div class="card p-6 p-lg-10 space-y-4">
<h1 class="h3 fw-700">Password Reset</h1>
<p>You requested a password reset, click below to continue.</p>
<a class="btn btn-primary p-3 fw-700" href="{{url}}">Reset</a>
</div>
<div class="text-muted text-center my-6">
This email is intended for {{email}}<br />
Sent from Revolt<br />
Made in the EU
</div>
</div>
</body>
</html>
+4 -2
View File
@@ -1,5 +1,7 @@
You requested a password reset, if you did not perform this action you can safely ignore this email. You requested a password reset, if you did not perform this action you can safely ignore this email.
Reset your password here: {{url}} Please navigate to: {{url}}
Sent by Revolt. This email is intended for {{email}}
Sent by Revolt
Made in the EU
@@ -1,14 +0,0 @@
<h2>Reset your password.</h2>
<p>
You requested a password reset, if you did not perform this action you can safely ignore this email.
</p>
<p>
Reset your password here: <a href="{{url}}">{{url}}</a>
</p>
<br/>
<p>
Sent by Revolt. · Website: <a href="https://revolt.chat">https://revolt.chat</a>
</p>
<p>
Revolt is a user-first chat platform built with modern web technologies.
</p>
+138 -63
View File
@@ -1,63 +1,138 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-GB"> <html>
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <!-- Compiled with Bootstrap Email version: 1.3.1 --><meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Verify your account.</title> <meta name="x-apple-disable-message-reformatting">
<meta name="viewport" content="width=device-width, initial-scale=1.0"/> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="format-detection" content="telephone=no, date=no, address=no, email=no">
<style type="text/css"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
a[x-apple-data-detectors] {color: inherit !important;} <style type="text/css">
</style> body,table,td{font-family:Helvetica,Arial,sans-serif !important}.ExternalClass{width:100%}.ExternalClass,.ExternalClass p,.ExternalClass span,.ExternalClass font,.ExternalClass td,.ExternalClass div{line-height:150%}a{text-decoration:none}*{color:inherit}a[x-apple-data-detectors],u+#body a,#MessageViewBody a{color:inherit;text-decoration:none;font-size:inherit;font-family:inherit;font-weight:inherit;line-height:inherit}img{-ms-interpolation-mode:bicubic}table:not([class^=s-]){font-family:Helvetica,Arial,sans-serif;mso-table-lspace:0pt;mso-table-rspace:0pt;border-spacing:0px;border-collapse:collapse}table:not([class^=s-]) td{border-spacing:0px;border-collapse:collapse}@media screen and (max-width: 600px){.w-full,.w-full>tbody>tr>td{width:100% !important}.w-24,.w-24>tbody>tr>td{width:96px !important}.p-lg-10:not(table),.p-lg-10:not(.btn)>tbody>tr>td,.p-lg-10.btn td a{padding:0 !important}.p-3:not(table),.p-3:not(.btn)>tbody>tr>td,.p-3.btn td a{padding:12px !important}.p-6:not(table),.p-6:not(.btn)>tbody>tr>td,.p-6.btn td a{padding:24px !important}*[class*=s-lg-]>tbody>tr>td{font-size:0 !important;line-height:0 !important;height:0 !important}.s-4>tbody>tr>td{font-size:16px !important;line-height:16px !important;height:16px !important}.s-6>tbody>tr>td{font-size:24px !important;line-height:24px !important;height:24px !important}.s-10>tbody>tr>td{font-size:40px !important;line-height:40px !important;height:40px !important}}
</style>
</head> </head>
<body style="margin: 0; padding: 0;"> <body class="bg-light" style="outline: 0; width: 100%; min-width: 100%; height: 100%; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; font-family: Helvetica, Arial, sans-serif; line-height: 24px; font-weight: normal; font-size: 16px; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; color: #000000; margin: 0; padding: 0; border-width: 0;" bgcolor="#f7fafc">
<table border="0" cellpadding="0" cellspacing="0" width="100%"> <table class="bg-light body" valign="top" role="presentation" border="0" cellpadding="0" cellspacing="0" style="outline: 0; width: 100%; min-width: 100%; height: 100%; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; font-family: Helvetica, Arial, sans-serif; line-height: 24px; font-weight: normal; font-size: 16px; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; color: #000000; margin: 0; padding: 0; border-width: 0;" bgcolor="#f7fafc">
<tr> <tbody>
<td style="padding: 20px 0 30px 0;"> <tr>
<td valign="top" style="line-height: 24px; font-size: 16px; margin: 0;" align="left" bgcolor="#f7fafc">
<table align="center" border="0" cellpadding="0" cellspacing="0" width="600" style="border-collapse: collapse; border: 1px solid #cccccc;"> <table class="container" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;">
<tr> <tbody>
<td align="center" bgcolor="#ff4654"> <tr>
<img src="https://revolt.chat/header.png" alt="Revolt logo" width="600" height="168" style="display: block;" /> <td align="center" style="line-height: 24px; font-size: 16px; margin: 0; padding: 0 16px;">
</td> <!--[if (gte mso 9)|(IE)]>
</tr> <table align="center" role="presentation">
<tr> <tbody>
<td bgcolor="#ffffff" style="padding: 40px 30px 40px 30px;"> <tr>
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;"> <td width="600">
<tr> <![endif]-->
<td style="color: #153643; font-family: Arial, sans-serif;"> <table align="center" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%; max-width: 600px; margin: 0 auto;">
<h1 style="font-size: 24px; margin: 0;">You're almost there!</h1> <tbody>
</td> <tr>
</tr> <td style="line-height: 24px; font-size: 16px; margin: 0;" align="left">
<tr> <table class="s-10 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<td style="color: #153643; font-family: Arial, sans-serif; font-size: 16px; line-height: 24px; padding: 20px 0 0 0;"> <tbody>
<p> <tr>
Verify your account to be able to log into the platform. If you didn't perform this action you can safely ignore this email. <td style="line-height: 40px; font-size: 40px; width: 100%; height: 40px; margin: 0;" align="left" width="100%" height="40">
</p> &#160;
<p style="margin: 0;"> </td>
Please verify your account by navigating to <a href="{{url}}">{{url}}</a>. </tr>
</p> </tbody>
</td> </table>
</tr> <table class="ax-center" role="presentation" align="center" border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto;">
</table> <tbody>
</td> <tr>
</tr> <td style="line-height: 24px; font-size: 16px; margin: 0;" align="left">
<tr> <img alt="Revolt Logo" class="w-24" src="https://app.revolt.chat/assets/logo_round.png" style="height: auto; line-height: 100%; outline: none; text-decoration: none; display: block; width: 96px; border-style: none; border-width: 0;" width="96">
<td bgcolor="#ff4654" style="padding: 30px 30px;"> </td>
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;"> </tr>
<tr> </tbody>
<td style="color: #ffffff; font-family: Arial, sans-serif; font-size: 14px;"> </table>
<p style="margin: 0;">Sent by Revolt. &middot; Website: <a style="color: white;" href="https://revolt.chat">https://revolt.chat</a></p> <table class="s-10 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<p>Revolt is a user-first chat platform built with modern web technologies.</p> <tbody>
</td> <tr>
</tr> <td style="line-height: 40px; font-size: 40px; width: 100%; height: 40px; margin: 0;" align="left" width="100%" height="40">
</table> &#160;
</td> </td>
</tr> </tr>
</table> </tbody>
</table>
</td> <table class="card p-6 p-lg-10 space-y-4" role="presentation" border="0" cellpadding="0" cellspacing="0" style="border-radius: 6px; border-collapse: separate !important; width: 100%; overflow: hidden; border: 1px solid #e2e8f0;" bgcolor="#ffffff">
</tr> <tbody>
</table> <tr>
</body> <td style="line-height: 24px; font-size: 16px; width: 100%; margin: 0; padding: 40px;" align="left" bgcolor="#ffffff">
</html> <h1 class="h3 fw-700" style="padding-top: 0; padding-bottom: 0; font-weight: 700 !important; vertical-align: baseline; font-size: 28px; line-height: 33.6px; margin: 0;" align="left">Almost there!</h1>
<table class="s-4 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<tbody>
<tr>
<td style="line-height: 16px; font-size: 16px; width: 100%; height: 16px; margin: 0;" align="left" width="100%" height="16">
&#160;
</td>
</tr>
</tbody>
</table>
<p class="" style="line-height: 24px; font-size: 16px; width: 100%; margin: 0;" align="left">To complete your sign up, we just need to verify your email.</p>
<table class="s-4 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<tbody>
<tr>
<td style="line-height: 16px; font-size: 16px; width: 100%; height: 16px; margin: 0;" align="left" width="100%" height="16">
&#160;
</td>
</tr>
</tbody>
</table>
<table class="btn btn-primary p-3 fw-700" role="presentation" border="0" cellpadding="0" cellspacing="0" style="border-radius: 6px; border-collapse: separate !important; font-weight: 700 !important;">
<tbody>
<tr>
<td style="line-height: 24px; font-size: 16px; border-radius: 6px; font-weight: 700 !important; margin: 0;" align="center" bgcolor="#0d6efd">
<a href="{{url}}" style="color: #ffffff; font-size: 16px; font-family: Helvetica, Arial, sans-serif; text-decoration: none; border-radius: 6px; line-height: 20px; display: block; font-weight: 700 !important; white-space: nowrap; background-color: #0d6efd; padding: 12px; border: 1px solid #0d6efd;">Confirm</a>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<table class="s-6 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<tbody>
<tr>
<td style="line-height: 24px; font-size: 24px; width: 100%; height: 24px; margin: 0;" align="left" width="100%" height="24">
&#160;
</td>
</tr>
</tbody>
</table>
<div class="text-muted text-center" style="color: #718096;" align="center">
This email is intended for {{email}}<br>
Sent from Revolt<br>
Made in the EU
</div>
<table class="s-6 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<tbody>
<tr>
<td style="line-height: 24px; font-size: 24px; width: 100%; height: 24px; margin: 0;" align="left" width="100%" height="24">
&#160;
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<!--[if (gte mso 9)|(IE)]>
</td>
</tr>
</tbody>
</table>
<![endif]-->
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</body>
</html>
@@ -0,0 +1,27 @@
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style>
/* Add custom classes and styles that you want inlined here */
</style>
</head>
<body class="bg-light">
<div class="container">
<img
alt="Revolt Logo"
class="ax-center my-10 w-24"
src="https://app.revolt.chat/assets/logo_round.png"
/>
<div class="card p-6 p-lg-10 space-y-4">
<h1 class="h3 fw-700">Almost there!</h1>
<p>To complete your sign up, we just need to verify your email.</p>
<a class="btn btn-primary p-3 fw-700" href="{{url}}">Confirm</a>
</div>
<div class="text-muted text-center my-6">
This email is intended for {{email}}<br />
Sent from Revolt<br />
Made in the EU
</div>
</div>
</body>
</html>
+6 -4
View File
@@ -1,6 +1,8 @@
You're almost there! Almost there!
If you did not perform this action you can safely ignore this email. To complete your sign up, we just need to verify your email.
Please verify your account here: {{url}} Please navigate to: {{url}}
Sent by Revolt. This email is intended for {{email}}
Sent by Revolt
Made in the EU
@@ -1,15 +0,0 @@
<h2>You're almost there!</h2>
<p>
Verify your account to be able to log into the platform.<br/>
If you did not perform this action you can safely ignore this email.
</p>
<p>
Please verify your account here: <a href="{{url}}">{{url}}</a>
</p>
<br/>
<p>
Sent by Revolt. · Website: <a href="https://revolt.chat">https://revolt.chat</a>
</p>
<p>
Revolt is a user-first chat platform built with modern web technologies.
</p>
+5 -5
View File
@@ -61,13 +61,13 @@ impl Deref for Database {
} }
} }
impl From<Database> for rauth::Database { impl From<Database> for authifier::Database {
fn from(val: Database) -> Self { fn from(val: Database) -> Self {
match val { match val {
Database::Dummy(_) => rauth::Database::default(), Database::Dummy(_) => authifier::Database::default(),
Database::MongoDb(MongoDb(client)) => { Database::MongoDb(MongoDb(client)) => authifier::Database::MongoDb(
rauth::Database::MongoDb(rauth::database::MongoDb(client.database("revolt"))) authifier::database::MongoDb(client.database("revolt")),
} ),
} }
} }
} }
+15
View File
@@ -1,3 +1,4 @@
use authifier::AuthifierEvent;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::models::channel::{FieldsChannel, PartialChannel}; use crate::models::channel::{FieldsChannel, PartialChannel};
@@ -195,9 +196,23 @@ pub enum EventV1 {
/// Settings updated remotely /// Settings updated remotely
UserSettingsUpdate { id: String, update: UserSettings }, UserSettingsUpdate { id: String, update: UserSettings },
/// User has been platform banned or deleted their account
///
/// Clients should remove the following associated data:
/// - Messages
/// - DM Channels
/// - Relationships
/// - Server Memberships
///
/// User flags are specified to explain why a wipe is occurring though not all reasons will necessarily ever appear.
UserPlatformWipe { user_id: String, flags: i32 },
/// New emoji /// New emoji
EmojiCreate(Emoji), EmojiCreate(Emoji),
/// Delete emoji /// Delete emoji
EmojiDelete { id: String }, EmojiDelete { id: String },
/// Auth events
Auth(AuthifierEvent),
} }
+6 -5
View File
@@ -23,11 +23,7 @@ impl Cache {
pub async fn can_view_channel(&self, db: &Database, channel: &Channel) -> bool { pub async fn can_view_channel(&self, db: &Database, channel: &Channel) -> bool {
match &channel { match &channel {
Channel::TextChannel { server, .. } | Channel::VoiceChannel { server, .. } => { Channel::TextChannel { server, .. } | Channel::VoiceChannel { server, .. } => {
let member = self let member = self.members.values().find(|x| &x.id.server == server);
.members
.iter()
.map(|(_, x)| x)
.find(|x| &x.id.server == server);
let server = self.servers.get(server); let server = self.servers.get(server);
let mut perms = perms(self.users.get(&self.user_id).unwrap()).channel(channel); let mut perms = perms(self.users.get(&self.user_id).unwrap()).channel(channel);
@@ -563,4 +559,9 @@ impl EventV1 {
pub async fn private(self, id: String) { pub async fn private(self, id: String) {
self.p(format!("{}!", id)).await; self.p(format!("{}!", id)).await;
} }
/// Publish internal global event
pub async fn global(self) {
self.p("global".to_string()).await;
}
} }
@@ -1,5 +1,3 @@
use std::collections::HashSet;
use crate::models::channel::{Channel, FieldsChannel, PartialChannel}; use crate::models::channel::{Channel, FieldsChannel, PartialChannel};
use crate::{AbstractAttachment, AbstractChannel, Error, OverrideField, Result}; use crate::{AbstractAttachment, AbstractChannel, Error, OverrideField, Result};
@@ -83,8 +81,4 @@ impl AbstractChannel for DummyDb {
info!("Updating permissions for role {role} in {channel} with {permissions:?}"); info!("Updating permissions for role {role} in {channel} with {permissions:?}");
Ok(()) Ok(())
} }
async fn check_channels_exist(&self, _channels: &HashSet<String>) -> Result<bool> {
Ok(true)
}
} }
@@ -286,6 +286,7 @@ impl Channel {
db: &Database, db: &Database,
user: &str, user: &str,
by: Option<&str>, by: Option<&str>,
silent: bool,
) -> Result<()> { ) -> Result<()> {
match &self { match &self {
Channel::Group { Channel::Group {
@@ -329,20 +330,22 @@ impl Channel {
.p(id.to_string()) .p(id.to_string())
.await; .await;
if let Some(by) = by { if !silent {
SystemMessage::UserRemove { if let Some(by) = by {
id: user.to_string(), SystemMessage::UserRemove {
by: by.to_string(), id: user.to_string(),
} by: by.to_string(),
} else { }
SystemMessage::UserLeft { } else {
id: user.to_string(), SystemMessage::UserLeft {
id: user.to_string(),
}
} }
.into_message(id.to_string())
.create(db, self, None)
.await
.ok();
} }
.into_message(id.to_string())
.create(db, self, None)
.await
.ok();
Ok(()) Ok(())
} }
@@ -13,13 +13,14 @@ use crate::{
}, },
Channel, Emoji, Message, User, Channel, Emoji, Message, User,
}, },
permissions::PermissionCalculator,
presence::presence_filter_online, presence::presence_filter_online,
tasks::ack::AckEvent, tasks::ack::AckEvent,
types::{ types::{
january::{Embed, Text}, january::{Embed, Text},
push::PushNotification, push::PushNotification,
}, },
Database, Error, Result, Database, Error, Permission, Result,
}; };
impl Message { impl Message {
@@ -195,6 +196,11 @@ impl Message {
/// Add a reaction to a message /// Add a reaction to a message
pub async fn add_reaction(&self, db: &Database, user: &User, emoji: &str) -> Result<()> { pub async fn add_reaction(&self, db: &Database, user: &User, emoji: &str) -> Result<()> {
// Check how many reactions are already on the message
if self.reactions.len() >= 20 {
return Err(Error::InvalidOperation);
}
// Check if the emoji is whitelisted // Check if the emoji is whitelisted
if !self.interactions.can_use(emoji) { if !self.interactions.can_use(emoji) {
return Err(Error::InvalidOperation); return Err(Error::InvalidOperation);
@@ -377,7 +383,7 @@ impl BulkMessageResponse {
) -> Result<BulkMessageResponse> { ) -> Result<BulkMessageResponse> {
if let Some(true) = include_users { if let Some(true) = include_users {
let user_ids = messages.get_user_ids(); let user_ids = messages.get_user_ids();
let users = db.fetch_users(&user_ids).await?; let users = User::fetch_foreign_users(db, &user_ids).await?;
Ok(match channel { Ok(match channel {
Channel::TextChannel { server, .. } | Channel::VoiceChannel { server, .. } => { Channel::TextChannel { server, .. } | Channel::VoiceChannel { server, .. } => {
@@ -401,8 +407,14 @@ impl BulkMessageResponse {
impl Interactions { impl Interactions {
/// Validate interactions info is correct /// Validate interactions info is correct
pub async fn validate(&self, db: &Database) -> Result<()> { pub async fn validate(
&self,
db: &Database,
permissions: &mut PermissionCalculator<'_>,
) -> Result<()> {
if let Some(reactions) = &self.reactions { if let Some(reactions) = &self.reactions {
permissions.throw_permission(db, Permission::React).await?;
if reactions.len() > 20 { if reactions.len() > 20 {
return Err(Error::InvalidOperation); return Err(Error::InvalidOperation);
} }
+25 -20
View File
@@ -1,3 +1,5 @@
use std::collections::HashSet;
use iso8601_timestamp::Timestamp; use iso8601_timestamp::Timestamp;
use ulid::Ulid; use ulid::Ulid;
@@ -254,6 +256,7 @@ impl Server {
db: &Database, db: &Database,
member: Member, member: Member,
intention: RemovalIntention, intention: RemovalIntention,
silent: bool,
) -> Result<()> { ) -> Result<()> {
db.delete_member(&member.id).await?; db.delete_member(&member.id).await?;
@@ -264,20 +267,22 @@ impl Server {
.p(member.id.server) .p(member.id.server)
.await; .await;
if let Some(id) = self.system_messages.as_ref().and_then(|x| match intention { if !silent {
RemovalIntention::Leave => x.user_left.as_ref(), if let Some(id) = self.system_messages.as_ref().and_then(|x| match intention {
RemovalIntention::Kick => x.user_kicked.as_ref(), RemovalIntention::Leave => x.user_left.as_ref(),
RemovalIntention::Ban => x.user_banned.as_ref(), RemovalIntention::Kick => x.user_kicked.as_ref(),
}) { RemovalIntention::Ban => x.user_banned.as_ref(),
match intention { }) {
RemovalIntention::Leave => SystemMessage::UserLeft { id: member.id.user }, match intention {
RemovalIntention::Kick => SystemMessage::UserKicked { id: member.id.user }, RemovalIntention::Leave => SystemMessage::UserLeft { id: member.id.user },
RemovalIntention::Ban => SystemMessage::UserBanned { id: member.id.user }, RemovalIntention::Kick => SystemMessage::UserKicked { id: member.id.user },
RemovalIntention::Ban => SystemMessage::UserBanned { id: member.id.user },
}
.into_message(id.to_string())
.create_no_web_push(db, id, false)
.await
.ok();
} }
.into_message(id.to_string())
.create_no_web_push(db, id, false)
.await
.ok();
} }
Ok(()) Ok(())
@@ -302,7 +307,7 @@ impl Server {
member: Member, member: Member,
reason: Option<String>, reason: Option<String>,
) -> Result<ServerBan> { ) -> Result<ServerBan> {
self.remove_member(db, member.clone(), RemovalIntention::Ban) self.remove_member(db, member.clone(), RemovalIntention::Ban, false)
.await?; .await?;
self.ban_user(db, member.id, reason).await self.ban_user(db, member.id, reason).await
@@ -310,23 +315,23 @@ impl Server {
} }
impl SystemMessageChannels { impl SystemMessageChannels {
pub fn into_channel_ids(self) -> Vec<String> { pub fn into_channel_ids(self) -> HashSet<String> {
let mut ids = vec![]; let mut ids = HashSet::new();
if let Some(id) = self.user_joined { if let Some(id) = self.user_joined {
ids.push(id); ids.insert(id);
} }
if let Some(id) = self.user_left { if let Some(id) = self.user_left {
ids.push(id); ids.insert(id);
} }
if let Some(id) = self.user_kicked { if let Some(id) = self.user_kicked {
ids.push(id); ids.insert(id);
} }
if let Some(id) = self.user_banned { if let Some(id) = self.user_banned {
ids.push(id); ids.insert(id);
} }
ids ids
@@ -4,6 +4,7 @@ use crate::models::user::{
}; };
use crate::permissions::defn::UserPerms; use crate::permissions::defn::UserPerms;
use crate::permissions::r#impl::user::get_relationship; use crate::permissions::r#impl::user::get_relationship;
use crate::presence::presence_filter_online;
use crate::{perms, Database, Error, Result}; use crate::{perms, Database, Error, Result};
use futures::try_join; use futures::try_join;
@@ -67,6 +68,7 @@ impl User {
} }
/// Mutate the user object to remove redundant information /// Mutate the user object to remove redundant information
#[must_use]
pub fn foreign(mut self) -> User { pub fn foreign(mut self) -> User {
self.profile = None; self.profile = None;
self.relations = None; self.relations = None;
@@ -94,6 +96,21 @@ impl User {
self self
} }
/// Fetch foreign users by a list of IDs
pub async fn fetch_foreign_users(db: &Database, user_ids: &[String]) -> Result<Vec<User>> {
let online_ids = presence_filter_online(user_ids).await;
Ok(db
.fetch_users(user_ids)
.await?
.into_iter()
.map(|mut user| {
user.online = Some(online_ids.contains(&user.id));
user.foreign()
})
.collect::<Vec<User>>())
}
/// Mutate the user object to include relationship (if it does not already exist) /// Mutate the user object to include relationship (if it does not already exist)
#[must_use] #[must_use]
pub fn with_relationship(self, perspective: &User) -> User { pub fn with_relationship(self, perspective: &User) -> User {
@@ -215,7 +215,7 @@ pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 {
} }
if revision <= 8 { if revision <= 8 {
info!("Running migration [revision 8 / 2021-09-10]: Update to rAuth version 1."); info!("Running migration [revision 8 / 2021-09-10]: Update to Authifier version 1.");
db.db() db.db()
.run_command( .run_command(
@@ -603,20 +603,20 @@ pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 {
} }
if revision <= 15 { if revision <= 15 {
info!("Running migration [revision 15 / 04-06-2022]: Migrate rAuth to latest version."); info!("Running migration [revision 15 / 04-06-2022]: Migrate Authifier to latest version.");
let db = rauth::Database::MongoDb(rauth::database::MongoDb(db.db())); let db = authifier::Database::MongoDb(authifier::database::MongoDb(db.db()));
db.run_migration(rauth::Migration::M2022_06_03EnsureUpToSpec) db.run_migration(authifier::Migration::M2022_06_03EnsureUpToSpec)
.await .await
.unwrap(); .unwrap();
} }
if revision <= 16 { if revision <= 16 {
info!("Running migration [revision 16 / 07-07-2022]: Add `emojis` collection and rAuth migration."); info!("Running migration [revision 16 / 07-07-2022]: Add `emojis` collection and Authifier migration.");
let rauth_db = rauth::Database::MongoDb(rauth::database::MongoDb(db.db())); let authifier_db = authifier::Database::MongoDb(authifier::database::MongoDb(db.db()));
rauth_db authifier_db
.run_migration(rauth::Migration::M2022_06_09AddIndexForDeletion) .run_migration(authifier::Migration::M2022_06_09AddIndexForDeletion)
.await .await
.unwrap(); .unwrap();
@@ -1,5 +1,3 @@
use std::collections::HashSet;
use bson::{Bson, Document}; use bson::{Bson, Document};
use crate::models::channel::{Channel, FieldsChannel, PartialChannel}; use crate::models::channel::{Channel, FieldsChannel, PartialChannel};
@@ -291,25 +289,6 @@ impl AbstractChannel for MongoDb {
with: "channel", with: "channel",
}) })
} }
async fn check_channels_exist(&self, channels: &HashSet<String>) -> Result<bool> {
let count = channels.len() as u64;
self.col::<Document>(COL)
.count_documents(
doc! {
"_id": {
"$in": channels.iter().cloned().collect::<Vec<String>>()
}
},
None,
)
.await
.map(|x| x == count)
.map_err(|_| Error::DatabaseError {
operation: "count_documents",
with: "channel",
})
}
} }
impl IntoDocumentPath for FieldsChannel { impl IntoDocumentPath for FieldsChannel {
+7 -7
View File
@@ -1,7 +1,7 @@
use okapi::openapi3::{SecurityScheme, SecuritySchemeData}; use authifier::models::Session;
use rauth::models::Session; use revolt_okapi::openapi3::{SecurityScheme, SecuritySchemeData};
use rocket_okapi::gen::OpenApiGenerator; use revolt_rocket_okapi::gen::OpenApiGenerator;
use rocket_okapi::request::{OpenApiFromRequest, RequestHeaderInput}; use revolt_rocket_okapi::request::{OpenApiFromRequest, RequestHeaderInput};
use rocket::http::Status; use rocket::http::Status;
use rocket::request::{self, FromRequest, Outcome, Request}; use rocket::request::{self, FromRequest, Outcome, Request};
@@ -12,7 +12,7 @@ use crate::Database;
#[rocket::async_trait] #[rocket::async_trait]
impl<'r> FromRequest<'r> for User { impl<'r> FromRequest<'r> for User {
type Error = rauth::Error; type Error = authifier::Error;
async fn from_request(request: &'r Request<'_>) -> request::Outcome<Self, Self::Error> { async fn from_request(request: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
let user: &Option<User> = request let user: &Option<User> = request
@@ -43,7 +43,7 @@ impl<'r> FromRequest<'r> for User {
if let Some(user) = user { if let Some(user) = user {
Outcome::Success(user.clone()) Outcome::Success(user.clone())
} else { } else {
Outcome::Failure((Status::Unauthorized, rauth::Error::InvalidSession)) Outcome::Failure((Status::Unauthorized, authifier::Error::InvalidSession))
} }
} }
} }
@@ -53,7 +53,7 @@ impl<'r> OpenApiFromRequest<'r> for User {
_gen: &mut OpenApiGenerator, _gen: &mut OpenApiGenerator,
_name: String, _name: String,
_required: bool, _required: bool,
) -> rocket_okapi::Result<RequestHeaderInput> { ) -> revolt_rocket_okapi::Result<RequestHeaderInput> {
let mut requirements = schemars::Map::new(); let mut requirements = schemars::Map::new();
requirements.insert("Session Token".to_owned(), vec![]); requirements.insert("Session Token".to_owned(), vec![]);
+1 -1
View File
@@ -17,8 +17,8 @@ extern crate bitfield;
#[macro_use] #[macro_use]
extern crate bson; extern crate bson;
pub use authifier;
pub use iso8601_timestamp::Timestamp; pub use iso8601_timestamp::Timestamp;
pub use rauth;
pub use redis_kiss; pub use redis_kiss;
pub mod events; pub mod events;
+1 -1
View File
@@ -161,7 +161,7 @@ pub struct PartialChannel {
} }
/// Optional fields on channel object /// Optional fields on channel object
#[derive(Serialize, Deserialize, JsonSchema, Debug, PartialEq, Clone)] #[derive(Serialize, Deserialize, JsonSchema, Debug, PartialEq, Eq, Clone)]
pub enum FieldsChannel { pub enum FieldsChannel {
Description, Description,
Icon, Icon,
+9 -10
View File
@@ -1,11 +1,10 @@
use crate::util::regex::RE_COLOUR; use crate::util::regex::RE_COLOUR;
use std::collections::{HashMap, HashSet};
use indexmap::{IndexMap, IndexSet};
use iso8601_timestamp::Timestamp;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use validator::Validate; use validator::Validate;
use iso8601_timestamp::Timestamp;
#[cfg(feature = "rocket_impl")] #[cfg(feature = "rocket_impl")]
use rocket::FromFormField; use rocket::FromFormField;
@@ -82,15 +81,13 @@ pub struct Masquerade {
pub name: Option<String>, pub name: Option<String>,
/// Replace the avatar shown on this message (URL to image file) /// Replace the avatar shown on this message (URL to image file)
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
#[validate(length(min = 1, max = 128))] #[validate(length(min = 1, max = 256))]
pub avatar: Option<String>, pub avatar: Option<String>,
/// Replace the display role colour shown on this message /// Replace the display role colour shown on this message
/// ///
/// Must have `ManageRole` permission to use /// Must have `ManageRole` permission to use
///
/// This can be any valid CSS colour
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
#[validate(length(min = 1, max = 32))] #[validate(length(min = 1, max = 128), regex = "RE_COLOUR")]
pub colour: Option<String>, pub colour: Option<String>,
} }
@@ -99,8 +96,10 @@ pub struct Masquerade {
pub struct Interactions { pub struct Interactions {
/// Reactions which should always appear and be distinct /// Reactions which should always appear and be distinct
#[serde(skip_serializing_if = "Option::is_none", default)] #[serde(skip_serializing_if = "Option::is_none", default)]
pub reactions: Option<HashSet<String>>, pub reactions: Option<IndexSet<String>>,
/// Whether reactions should be restricted to the given list /// Whether reactions should be restricted to the given list
///
/// Can only be set to true if reactions list is of at least length 1
#[serde(skip_serializing_if = "if_false", default)] #[serde(skip_serializing_if = "if_false", default)]
pub restrict_reactions: bool, pub restrict_reactions: bool,
} }
@@ -145,8 +144,8 @@ pub struct Message {
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub replies: Option<Vec<String>>, pub replies: Option<Vec<String>>,
/// Hashmap of emoji IDs to array of user IDs /// Hashmap of emoji IDs to array of user IDs
#[serde(skip_serializing_if = "HashMap::is_empty", default)] #[serde(skip_serializing_if = "IndexMap::is_empty", default)]
pub reactions: HashMap<String, HashSet<String>>, pub reactions: IndexMap<String, IndexSet<String>>,
/// Information about how this message should be interacted with /// Information about how this message should be interacted with
#[serde(skip_serializing_if = "Interactions::is_default", default)] #[serde(skip_serializing_if = "Interactions::is_default", default)]
pub interactions: Interactions, pub interactions: Interactions,
+2 -2
View File
@@ -134,7 +134,7 @@ pub struct Server {
} }
/// Optional fields on server object /// Optional fields on server object
#[derive(Serialize, Deserialize, JsonSchema, Debug, PartialEq, Clone)] #[derive(Serialize, Deserialize, JsonSchema, Debug, PartialEq, Eq, Clone)]
pub enum FieldsServer { pub enum FieldsServer {
Description, Description,
Categories, Categories,
@@ -144,7 +144,7 @@ pub enum FieldsServer {
} }
/// Optional fields on server object /// Optional fields on server object
#[derive(Serialize, Deserialize, JsonSchema, Debug, PartialEq, Clone)] #[derive(Serialize, Deserialize, JsonSchema, Debug, PartialEq, Eq, Clone)]
pub enum FieldsRole { pub enum FieldsRole {
Colour, Colour,
} }
@@ -42,7 +42,7 @@ pub struct Member {
} }
/// Optional fields on server member object /// Optional fields on server member object
#[derive(Serialize, Deserialize, JsonSchema, Debug, PartialEq, Clone)] #[derive(Serialize, Deserialize, JsonSchema, Debug, PartialEq, Eq, Clone)]
pub enum FieldsMember { pub enum FieldsMember {
Nickname, Nickname,
Avatar, Avatar,
+1 -1
View File
@@ -56,7 +56,7 @@ pub struct Bot {
} }
/// Optional fields on bot object /// Optional fields on bot object
#[derive(Serialize, Deserialize, JsonSchema, Debug, PartialEq)] #[derive(Serialize, Deserialize, JsonSchema, Debug, PartialEq, Eq)]
pub enum FieldsBot { pub enum FieldsBot {
Token, Token,
InteractionsURL, InteractionsURL,
+11 -3
View File
@@ -10,7 +10,7 @@ pub fn if_false(t: &bool) -> bool {
} }
/// User's relationship with another user (or themselves) /// User's relationship with another user (or themselves)
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, PartialEq)] #[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, PartialEq, Eq)]
pub enum RelationshipStatus { pub enum RelationshipStatus {
None, None,
User, User,
@@ -30,11 +30,17 @@ pub struct Relationship {
} }
/// Presence status /// Presence status
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, PartialEq)] #[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, PartialEq, Eq)]
pub enum Presence { pub enum Presence {
/// User is online
Online, Online,
/// User is not currently available
Idle, Idle,
/// User is focusing / will only receive mentions
Focus,
/// User is busy / will not receive any notifications
Busy, Busy,
/// User appears to be offline
Invisible, Invisible,
} }
@@ -99,6 +105,8 @@ pub enum Flags {
Deleted = 2, Deleted = 2,
/// User was banned off the platform /// User was banned off the platform
Banned = 4, Banned = 4,
/// User was marked as spam and removed from platform
Spam = 8,
} }
/// Bot information for if the user is a bot /// Bot information for if the user is a bot
@@ -157,7 +165,7 @@ pub struct User {
} }
/// Optional fields on user object /// Optional fields on user object
#[derive(Serialize, Deserialize, JsonSchema, Debug, PartialEq, Clone)] #[derive(Serialize, Deserialize, JsonSchema, Debug, PartialEq, Eq, Clone)]
pub enum FieldsUser { pub enum FieldsUser {
Avatar, Avatar,
StatusText, StatusText,
+1 -1
View File
@@ -97,7 +97,7 @@ impl From<i64> for PermissionValue {
impl From<u64> for PermissionValue { impl From<u64> for PermissionValue {
fn from(v: u64) -> Self { fn from(v: u64) -> Self {
Self(v as u64) Self(v)
} }
} }
@@ -24,7 +24,7 @@ pub enum Permission {
/// Manage server customisation (includes emoji) /// Manage server customisation (includes emoji)
ManageCustomisation = 1 << 4, ManageCustomisation = 1 << 4,
// % 1 bits reserved // % 1 bit reserved
// * Member permissions // * Member permissions
/// Kick other members below their ranking /// Kick other members below their ranking
@@ -109,10 +109,11 @@ lazy_static! {
+ Permission::Connect + Permission::Connect
+ Permission::Speak; + Permission::Speak;
pub static ref DEFAULT_PERMISSION_SAVED_MESSAGES: u64 = Permission::GrantAllSafe as u64; pub static ref DEFAULT_PERMISSION_SAVED_MESSAGES: u64 = Permission::GrantAllSafe as u64;
pub static ref DEFAULT_PERMISSION_DIRECT_MESSAGE: u64 = pub static ref DEFAULT_PERMISSION_DIRECT_MESSAGE: u64 = *DEFAULT_PERMISSION
*DEFAULT_PERMISSION + Permission::ManageChannel; + Permission::ManageChannel
+ Permission::React;
pub static ref DEFAULT_PERMISSION_SERVER: u64 = pub static ref DEFAULT_PERMISSION_SERVER: u64 =
*DEFAULT_PERMISSION + Permission::ChangeNickname + Permission::ChangeAvatar; *DEFAULT_PERMISSION + Permission::React + Permission::ChangeNickname + Permission::ChangeAvatar;
} }
bitfield! { bitfield! {
+18 -13
View File
@@ -119,23 +119,28 @@ async fn calculate_channel_permission(
} }
} }
Channel::DirectMessage { recipients, .. } => { Channel::DirectMessage { recipients, .. } => {
// 2. Fetch user. // 2. Ensure we are a recipient.
let other_user = recipients if recipients.contains(&data.perspective.id) {
.iter() // 3. Fetch user.
.find(|x| x != &&data.perspective.id) let other_user = recipients
.unwrap(); .iter()
.find(|x| x != &&data.perspective.id)
.unwrap();
let user = db.fetch_user(other_user).await?; let user = db.fetch_user(other_user).await?;
data.user.set(user); data.user.set(user);
// 3. Calculate user permissions. // 4. Calculate user permissions.
let perms = data.calc_user(db).await; let perms = data.calc_user(db).await;
// 4. Check if the user can send messages. // 5. Check if the user can send messages.
if perms.get_send_message() { if perms.get_send_message() {
(*DEFAULT_PERMISSION_DIRECT_MESSAGE).into() (*DEFAULT_PERMISSION_DIRECT_MESSAGE).into()
} else {
(*DEFAULT_PERMISSION_VIEW_ONLY).into()
}
} else { } else {
(*DEFAULT_PERMISSION_VIEW_ONLY).into() 0_u64.into()
} }
} }
Channel::Group { Channel::Group {
+3 -3
View File
@@ -35,7 +35,7 @@ pub async fn presence_create_session(user_id: &str, flags: u8) -> (bool, u8) {
__set_key_presence_entry(&mut conn, user_id, entry).await; __set_key_presence_entry(&mut conn, user_id, entry).await;
// Add to region set in case of failure. // Add to region set in case of failure.
__add_to_set_sessions(&mut conn, &*REGION_KEY, user_id, session_id).await; __add_to_set_sessions(&mut conn, &REGION_KEY, user_id, session_id).await;
(was_empty, session_id) (was_empty, session_id)
} }
@@ -74,7 +74,7 @@ async fn presence_delete_session_internal(
// Remove from region set. // Remove from region set.
if !skip_region { if !skip_region {
__remove_from_set_sessions(&mut conn, &*REGION_KEY, user_id, session_id).await; __remove_from_set_sessions(&mut conn, &REGION_KEY, user_id, session_id).await;
} }
} }
@@ -147,7 +147,7 @@ pub async fn presence_clear_region(region_id: Option<&str>) {
// also send out any relevant events. // also send out any relevant events.
for session in sessions { for session in sessions {
let parts = session.split(':').collect::<Vec<&str>>(); let parts = session.split(':').collect::<Vec<&str>>();
if let (Some(user_id), Some(session_id)) = (parts.get(0), parts.get(1)) { if let (Some(user_id), Some(session_id)) = (parts.first(), parts.get(1)) {
if let Ok(session_id) = session_id.parse() { if let Ok(session_id) = session_id.parse() {
presence_delete_session_internal(user_id, session_id, true).await; presence_delete_session_internal(user_id, session_id, true).await;
} }
+1 -1
View File
@@ -53,5 +53,5 @@ pub async fn __get_set_sessions(conn: &mut Conn, region_id: &str) -> Vec<String>
/// Delete region session set /// Delete region session set
pub async fn __delete_set_sessions(conn: &mut Conn, region_id: &str) { pub async fn __delete_set_sessions(conn: &mut Conn, region_id: &str) {
let _: () = conn.del(region_id).await.unwrap(); conn.del::<_, ()>(region_id).await.unwrap();
} }
+26 -14
View File
@@ -1,11 +1,14 @@
use crate::util::variables::delta::{JANUARY_URL, MAX_EMBED_COUNT}; use crate::util::variables::delta::{JANUARY_URL, MAX_EMBED_COUNT, JANUARY_CONCURRENT_CONNECTIONS};
use crate::{ use crate::{
models::{message::AppendMessage, Message}, models::{message::AppendMessage, Message},
types::january::Embed, types::january::Embed,
Database, Database,
}; };
use async_lock::Semaphore;
use async_std::task::spawn;
use deadqueue::limited::Queue; use deadqueue::limited::Queue;
use std::sync::Arc;
/// Task information /// Task information
#[derive(Debug)] #[derive(Debug)]
@@ -36,21 +39,30 @@ pub async fn queue(channel: String, id: String, content: String) {
/// Start a new worker /// Start a new worker
pub async fn worker(db: Database) { pub async fn worker(db: Database) {
let semaphore = Arc::new(Semaphore::new(*JANUARY_CONCURRENT_CONNECTIONS));
loop { loop {
let task = Q.pop().await; let task = Q.pop().await;
if let Ok(embeds) = Embed::generate(task.content, &*JANUARY_URL, *MAX_EMBED_COUNT).await { let db = db.clone();
if let Err(err) = Message::append( let semaphore = semaphore.clone();
&db,
task.id, spawn(async move {
task.channel, let embeds = Embed::generate(task.content, &JANUARY_URL, *MAX_EMBED_COUNT, semaphore).await;
AppendMessage {
embeds: Some(embeds), if let Ok(embeds) = embeds {
}, if let Err(err) = Message::append(
) &db,
.await task.id,
{ task.channel,
error!("Encountered an error appending to message: {:?}", err); AppendMessage {
embeds: Some(embeds),
},
)
.await
{
error!("Encountered an error appending to message: {:?}", err);
}
} }
} });
} }
} }
+1 -1
View File
@@ -1,8 +1,8 @@
use crate::bson::doc; use crate::bson::doc;
use crate::util::variables::delta::VAPID_PRIVATE_KEY; use crate::util::variables::delta::VAPID_PRIVATE_KEY;
use authifier::Database;
use deadqueue::limited::Queue; use deadqueue::limited::Queue;
use rauth::Database;
use web_push::{ use web_push::{
ContentEncoding, SubscriptionInfo, SubscriptionKeys, VapidSignatureBuilder, WebPushClient, ContentEncoding, SubscriptionInfo, SubscriptionKeys, VapidSignatureBuilder, WebPushClient,
WebPushMessageBuilder, WebPushMessageBuilder,
@@ -1,5 +1,3 @@
use std::collections::HashSet;
use crate::models::channel::{Channel, FieldsChannel, PartialChannel}; use crate::models::channel::{Channel, FieldsChannel, PartialChannel};
use crate::{OverrideField, Result}; use crate::{OverrideField, Result};
@@ -55,7 +53,4 @@ pub trait AbstractChannel: Sync + Send {
role: &str, role: &str,
permissions: OverrideField, permissions: OverrideField,
) -> Result<()>; ) -> Result<()>;
/// Validate existence of channels
async fn check_channels_exist(&self, channels: &HashSet<String>) -> Result<bool>;
} }
+37 -16
View File
@@ -1,7 +1,10 @@
use async_lock::Semaphore;
use async_std::task::spawn;
use futures::future::join_all;
use linkify::{LinkFinder, LinkKind}; use linkify::{LinkFinder, LinkKind};
use regex::Regex; use regex::Regex;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashSet; use std::{collections::HashSet, sync::Arc};
use crate::{models::attachment::File, Error, Result}; use crate::{models::attachment::File, Error, Result};
@@ -95,6 +98,8 @@ pub enum Special {
content_type: BandcampType, content_type: BandcampType,
id: String, id: String,
}, },
/// Streamable Video
Streamable { id: String },
} }
/// Website metadata /// Website metadata
@@ -172,7 +177,12 @@ pub enum Embed {
impl Embed { impl Embed {
/// Generate embeds from given content /// Generate embeds from given content
pub async fn generate(content: String, host: &str, max_embeds: usize) -> Result<Vec<Embed>> { pub async fn generate(
content: String,
host: &str,
max_embeds: usize,
semaphore: Arc<Semaphore>,
) -> Result<Vec<Embed>> {
lazy_static! { lazy_static! {
static ref RE_CODE: Regex = Regex::new("```(?:.|\n)+?```|`(?:.|\n)+?`").unwrap(); static ref RE_CODE: Regex = Regex::new("```(?:.|\n)+?```|`(?:.|\n)+?`").unwrap();
static ref RE_IGNORED: Regex = Regex::new("(<http.+>)").unwrap(); static ref RE_IGNORED: Regex = Regex::new("(<http.+>)").unwrap();
@@ -223,26 +233,37 @@ impl Embed {
} }
// ! FIXME: batch request to january? // ! FIXME: batch request to january?
let mut embeds: Vec<Embed> = Vec::new();
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let mut tasks = Vec::new();
let url = format!("{host}/embed");
for link in links { for link in links {
let result = client let client = client.clone();
.get(&format!("{}/embed", host)) let url = url.clone();
.query(&[("url", link)]) let semaphore = semaphore.clone();
.send()
.await;
if result.is_err() { tasks.push(spawn(async move {
continue; let guard = semaphore.acquire().await;
}
let response = result.unwrap(); let response = client.get(url).query(&[("url", link)]).send().await.ok()?;
if response.status().is_success() {
let res: Embed = response.json().await.map_err(|_| Error::InvalidOperation)?; drop(guard);
embeds.push(res);
} if response.status().is_success() {
response.json::<Embed>().await.ok()
} else {
None
}
}));
} }
let embeds = join_all(tasks)
.await
.into_iter()
.flatten()
.collect::<Vec<Embed>>();
// Prevent database update when no embeds are found. // Prevent database update when no embeds are found.
if !embeds.is_empty() { if !embeds.is_empty() {
Ok(embeds) Ok(embeds)
@@ -1,9 +1,11 @@
use authifier::config::{ResolveIp, Shield};
use super::variables::delta::{ use super::variables::delta::{
APP_URL, HCAPTCHA_KEY, INVITE_ONLY, SMTP_FROM, SMTP_HOST, SMTP_PASSWORD, SMTP_USERNAME, APP_URL, AUTHIFIER_SHIELD_KEY, HCAPTCHA_KEY, INVITE_ONLY, SMTP_FROM, SMTP_HOST, SMTP_PASSWORD,
USE_EMAIL, USE_HCAPTCHA, SMTP_USERNAME, USE_EMAIL, USE_HCAPTCHA,
}; };
use crate::rauth::config::{ use crate::authifier::config::{
Captcha, Config, EmailVerificationConfig, SMTPSettings, Template, Templates, Captcha, Config, EmailVerificationConfig, SMTPSettings, Template, Templates,
}; };
@@ -26,19 +28,19 @@ pub fn config() -> Config {
title: "Verify your Revolt account.".into(), title: "Verify your Revolt account.".into(),
text: include_str!(crate::asset!("templates/verify.txt")).into(), text: include_str!(crate::asset!("templates/verify.txt")).into(),
url: format!("{}/login/verify/", *APP_URL), url: format!("{}/login/verify/", *APP_URL),
html: None, html: Some(include_str!(crate::asset!("templates/verify.html")).into()),
}, },
reset: Template { reset: Template {
title: "Reset your Revolt password.".into(), title: "Reset your Revolt password.".into(),
text: include_str!(crate::asset!("templates/reset.txt")).into(), text: include_str!(crate::asset!("templates/reset.txt")).into(),
url: format!("{}/login/reset/", *APP_URL), url: format!("{}/login/reset/", *APP_URL),
html: None, html: Some(include_str!(crate::asset!("templates/reset.html")).into()),
}, },
deletion: Template { deletion: Template {
title: "Confirm account deletion.".into(), title: "Confirm account deletion.".into(),
text: include_str!(crate::asset!("templates/deletion.txt")).into(), text: include_str!(crate::asset!("templates/deletion.txt")).into(),
url: format!("{}/delete/", *APP_URL), url: format!("{}/delete/", *APP_URL),
html: None, html: Some(include_str!(crate::asset!("templates/deletion.html")).into()),
}, },
welcome: None, welcome: None,
}, },
@@ -59,5 +61,19 @@ pub fn config() -> Config {
}; };
} }
if let Some(api_key) = &*AUTHIFIER_SHIELD_KEY {
config.shield = Shield::Enabled {
api_key: api_key.to_string(),
strict: false,
};
}
if std::env::var("TRUST_CLOUDFLARE")
.map(|x| x == "1")
.unwrap_or_default()
{
config.resolve_ip = ResolveIp::Cloudflare;
}
config config
} }
+1 -1
View File
@@ -1,7 +1,7 @@
pub mod authifier;
pub mod log; pub mod log;
pub mod manipulation; pub mod manipulation;
pub mod pfp; pub mod pfp;
pub mod rauth;
pub mod r#ref; pub mod r#ref;
pub mod regex; pub mod regex;
pub mod result; pub mod result;
+15 -12
View File
@@ -1,4 +1,5 @@
use okapi::openapi3::{self, SchemaObject}; use revolt_okapi::openapi3::SchemaObject;
use revolt_rocket_okapi::revolt_okapi::openapi3;
use rocket::{ use rocket::{
http::{ContentType, Status}, http::{ContentType, Status},
response::{self, Responder}, response::{self, Responder},
@@ -19,10 +20,10 @@ pub enum Error {
/// This error was not labeled :( /// This error was not labeled :(
LabelMe, LabelMe,
// ? Onboarding related errors. // ? Onboarding related errors
AlreadyOnboarded, AlreadyOnboarded,
// ? User related errors. // ? User related errors
UsernameTaken, UsernameTaken,
InvalidUsername, InvalidUsername,
UnknownUser, UnknownUser,
@@ -32,7 +33,7 @@ pub enum Error {
BlockedByOther, BlockedByOther,
NotFriends, NotFriends,
// ? Channel related errors. // ? Channel related errors
UnknownChannel, UnknownChannel,
UnknownAttachment, UnknownAttachment,
UnknownMessage, UnknownMessage,
@@ -49,7 +50,7 @@ pub enum Error {
AlreadyInGroup, AlreadyInGroup,
NotInGroup, NotInGroup,
// ? Server related errors. // ? Server related errors
UnknownServer, UnknownServer,
InvalidRole, InvalidRole,
Banned, Banned,
@@ -58,12 +59,12 @@ pub enum Error {
}, },
TooManyEmoji, TooManyEmoji,
// ? Bot related errors. // ? Bot related errors
ReachedMaximumBots, ReachedMaximumBots,
IsBot, IsBot,
BotIsPrivate, BotIsPrivate,
// ? Permission errors. // ? Permission errors
MissingPermission { MissingPermission {
permission: Permission, permission: Permission,
}, },
@@ -74,7 +75,7 @@ pub enum Error {
CannotGiveMissingPermissions, CannotGiveMissingPermissions,
NotOwner, NotOwner,
// ? General errors. // ? General errors
DatabaseError { DatabaseError {
operation: &'static str, operation: &'static str,
with: &'static str, with: &'static str,
@@ -82,6 +83,7 @@ pub enum Error {
InternalError, InternalError,
InvalidOperation, InvalidOperation,
InvalidCredentials, InvalidCredentials,
InvalidProperty,
InvalidSession, InvalidSession,
DuplicateNonce, DuplicateNonce,
VosoUnavailable, VosoUnavailable,
@@ -174,6 +176,7 @@ impl<'r> Responder<'r, 'static> for Error {
Error::InternalError => Status::InternalServerError, Error::InternalError => Status::InternalServerError,
Error::InvalidOperation => Status::BadRequest, Error::InvalidOperation => Status::BadRequest,
Error::InvalidCredentials => Status::Unauthorized, Error::InvalidCredentials => Status::Unauthorized,
Error::InvalidProperty => Status::BadRequest,
Error::InvalidSession => Status::Unauthorized, Error::InvalidSession => Status::Unauthorized,
Error::DuplicateNonce => Status::Conflict, Error::DuplicateNonce => Status::Conflict,
Error::VosoUnavailable => Status::BadRequest, Error::VosoUnavailable => Status::BadRequest,
@@ -194,11 +197,11 @@ impl<'r> Responder<'r, 'static> for Error {
} }
} }
impl rocket_okapi::response::OpenApiResponderInner for Error { impl revolt_rocket_okapi::response::OpenApiResponderInner for Error {
fn responses( fn responses(
gen: &mut rocket_okapi::gen::OpenApiGenerator, gen: &mut revolt_rocket_okapi::gen::OpenApiGenerator,
) -> std::result::Result<openapi3::Responses, rocket_okapi::OpenApiError> { ) -> std::result::Result<openapi3::Responses, revolt_rocket_okapi::OpenApiError> {
let mut content = okapi::Map::new(); let mut content = revolt_okapi::Map::new();
let settings = schemars::gen::SchemaSettings::default().with(|s| { let settings = schemars::gen::SchemaSettings::default().with(|s| {
s.option_nullable = true; s.option_nullable = true;
+4
View File
@@ -13,6 +13,8 @@ lazy_static! {
env::var("AUTUMN_PUBLIC_URL").unwrap_or_else(|_| "https://example.com".to_string()); env::var("AUTUMN_PUBLIC_URL").unwrap_or_else(|_| "https://example.com".to_string());
pub static ref JANUARY_URL: String = pub static ref JANUARY_URL: String =
env::var("JANUARY_PUBLIC_URL").unwrap_or_else(|_| "https://example.com".to_string()); env::var("JANUARY_PUBLIC_URL").unwrap_or_else(|_| "https://example.com".to_string());
pub static ref JANUARY_CONCURRENT_CONNECTIONS: usize =
env::var("JANUARY_CONCURRENT_CONNECTIONS").map_or(50, |v| v.parse().unwrap());
pub static ref VOSO_URL: String = pub static ref VOSO_URL: String =
env::var("VOSO_PUBLIC_URL").unwrap_or_else(|_| "https://example.com".to_string()); env::var("VOSO_PUBLIC_URL").unwrap_or_else(|_| "https://example.com".to_string());
pub static ref VOSO_WS_HOST: String = pub static ref VOSO_WS_HOST: String =
@@ -28,6 +30,8 @@ lazy_static! {
env::var("REVOLT_VAPID_PRIVATE_KEY").expect("Missing REVOLT_VAPID_PRIVATE_KEY environment variable."); env::var("REVOLT_VAPID_PRIVATE_KEY").expect("Missing REVOLT_VAPID_PRIVATE_KEY environment variable.");
pub static ref VAPID_PUBLIC_KEY: String = pub static ref VAPID_PUBLIC_KEY: String =
env::var("REVOLT_VAPID_PUBLIC_KEY").expect("Missing REVOLT_VAPID_PUBLIC_KEY environment variable."); env::var("REVOLT_VAPID_PUBLIC_KEY").expect("Missing REVOLT_VAPID_PUBLIC_KEY environment variable.");
pub static ref AUTHIFIER_SHIELD_KEY: Option<String> =
env::var("REVOLT_AUTHIFIER_SHIELD_KEY").ok();
// Application Flags // Application Flags
pub static ref INVITE_ONLY: bool = env::var("REVOLT_INVITE_ONLY").map_or(false, |v| v == "1"); pub static ref INVITE_ONLY: bool = env::var("REVOLT_INVITE_ONLY").map_or(false, |v| v == "1");
+4 -4
View File
@@ -1,11 +1,11 @@
use crate::{Error, Result}; use crate::{Error, Result};
use async_std::sync::Mutex; use async_std::sync::Mutex;
use revolt_rocket_okapi::gen::OpenApiGenerator;
use revolt_rocket_okapi::request::{OpenApiFromRequest, RequestHeaderInput};
use revolt_rocket_okapi::revolt_okapi::openapi3::{Parameter, ParameterValue};
use rocket::http::Status; use rocket::http::Status;
use rocket::request::{FromRequest, Outcome}; use rocket::request::{FromRequest, Outcome};
use rocket_okapi::gen::OpenApiGenerator;
use rocket_okapi::okapi::openapi3::{Parameter, ParameterValue};
use rocket_okapi::request::{OpenApiFromRequest, RequestHeaderInput};
use schemars::schema::{InstanceType, SchemaObject, SingleOrVec}; use schemars::schema::{InstanceType, SchemaObject, SingleOrVec};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use validator::Validate; use validator::Validate;
@@ -47,7 +47,7 @@ impl<'r> OpenApiFromRequest<'r> for IdempotencyKey {
_gen: &mut OpenApiGenerator, _gen: &mut OpenApiGenerator,
_name: String, _name: String,
_required: bool, _required: bool,
) -> rocket_okapi::Result<RequestHeaderInput> { ) -> revolt_rocket_okapi::Result<RequestHeaderInput> {
Ok(RequestHeaderInput::Parameter(Parameter { Ok(RequestHeaderInput::Parameter(Parameter {
name: "Idempotency-Key".to_string(), name: "Idempotency-Key".to_string(),
description: Some("Unique key to prevent duplicate requests".to_string()), description: Some("Unique key to prevent duplicate requests".to_string()),
+14 -9
View File
@@ -8,7 +8,7 @@ use std::hash::Hasher;
use std::ops::Add; use std::ops::Add;
use std::time::{Duration, SystemTime, UNIX_EPOCH}; use std::time::{Duration, SystemTime, UNIX_EPOCH};
use crate::rauth::models::Session; use crate::authifier::models::Session;
use rocket::fairing::{Fairing, Info, Kind}; use rocket::fairing::{Fairing, Info, Kind};
use rocket::http::uri::Origin; use rocket::http::uri::Origin;
use rocket::http::{Method, Status}; use rocket::http::{Method, Status};
@@ -16,8 +16,8 @@ use rocket::request::{FromRequest, Outcome};
use rocket::serde::json::Json; use rocket::serde::json::Json;
use rocket::{Data, Request, Response}; use rocket::{Data, Request, Response};
use rocket_okapi::gen::OpenApiGenerator; use revolt_rocket_okapi::gen::OpenApiGenerator;
use rocket_okapi::request::{OpenApiFromRequest, RequestHeaderInput}; use revolt_rocket_okapi::request::{OpenApiFromRequest, RequestHeaderInput};
use serde::Serialize; use serde::Serialize;
@@ -51,7 +51,7 @@ impl Entry {
} }
/// Deduct one unit from the bucket and save /// Deduct one unit from the bucket and save
pub fn deduct(mut self, key: u64) { pub fn deduct(mut self) {
let current_time = now().as_millis(); let current_time = now().as_millis();
if current_time > self.reset { if current_time > self.reset {
self.used = 1; self.used = 1;
@@ -59,7 +59,10 @@ impl Entry {
} else { } else {
self.used += 1; self.used += 1;
} }
}
/// Save information
pub fn save(self, key: u64) {
MAP.insert(key, self); MAP.insert(key, self);
} }
@@ -189,10 +192,12 @@ impl Ratelimiter {
let entry = Entry::from(key); let entry = Entry::from(key);
let remaining = entry.get_remaining(limit); let remaining = entry.get_remaining(limit);
let reset = entry.left_until_reset();
if remaining > 0 { if remaining > 0 {
entry.deduct(key); entry.deduct();
let reset = entry.left_until_reset();
entry.save(key);
Ok(Ratelimiter { Ok(Ratelimiter {
key, key,
limit, limit,
@@ -200,7 +205,7 @@ impl Ratelimiter {
reset, reset,
}) })
} else { } else {
Err(reset) Err(entry.left_until_reset())
} }
} }
} }
@@ -236,7 +241,7 @@ impl<'r> OpenApiFromRequest<'r> for Ratelimiter {
_gen: &mut OpenApiGenerator, _gen: &mut OpenApiGenerator,
_name: String, _name: String,
_required: bool, _required: bool,
) -> rocket_okapi::Result<RequestHeaderInput> { ) -> revolt_rocket_okapi::Result<RequestHeaderInput> {
Ok(RequestHeaderInput::None) Ok(RequestHeaderInput::None)
} }
} }
+1 -1
View File
@@ -1,7 +1,7 @@
use rocket::Route; use rocket::Route;
pub fn routes() -> Vec<Route> { pub fn routes() -> Vec<Route> {
rocket_okapi::swagger_ui::make_swagger_ui(&rocket_okapi::swagger_ui::SwaggerUIConfig { revolt_rocket_okapi::swagger_ui::make_swagger_ui(&revolt_rocket_okapi::swagger_ui::SwaggerUIConfig {
url: "../openapi.json".to_owned(), url: "../openapi.json".to_owned(),
..Default::default() ..Default::default()
}) })
+9
View File
@@ -0,0 +1,9 @@
version: "3.3"
services:
# MongoDB
database:
image: mongo
ports:
- "27017:27017"
volumes:
- ./.data/db:/data/db