Compare commits

...
54 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
Paul Makles 229d4e2e1d fix: correct ordering for creating ownership change event 2022-07-15 21:46:41 +01:00
Paul Makles 7bdd2d69d6 feat: add message reactions + interactions object 2022-07-15 21:24:49 +01:00
Paul Makles ab2af9b5e5 feat: add group ownership transfer 2022-07-15 17:58:08 +01:00
Paul Makles 5aebda2d16 chore: restrict emojis to lowercase 2022-07-15 17:57:53 +01:00
Paul Makles a7273d0556 feat: allow servers to set discoverable value 2022-07-15 17:57:30 +01:00
Paul Makles 72aed64e5b chore: allow banning users not in server 2022-07-15 16:47:27 +01:00
Paul Makles 00ad4d54e7 feat: add nsfw flag to emotes 2022-07-15 16:34:08 +01:00
Paul Makles cba8c4ef3b fix: add case-insensitive flag to colour regex 2022-07-15 16:13:04 +01:00
Paul Makles b6e395fe0d fix: use default for roles key 2022-07-15 16:02:58 +01:00
Paul Makles 8d07457c58 chore: make joined_at migration shorter 2022-07-15 16:02:46 +01:00
Paul Makles f1171e5358 feat: restrict role colours to regex (increase length limit) 2022-07-15 15:36:00 +01:00
Paul Makles d96c9f62c4 chore: detach emojis on delete 2022-07-15 14:01:59 +01:00
Paul Makles 4f73e43a03 feat: restrict permissions for users in timeout 2022-07-15 13:55:55 +01:00
Paul Makles 741b8ee8fd feat: add role colours to masquerades
feat: add `joined_at` property to members
feat: add `timeout` property to members (non-functional)
2022-07-15 13:42:01 +01:00
110 changed files with 4040 additions and 825 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
+316 -175
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,22 +2104,11 @@ 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.12.0" version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1"
[[package]] [[package]]
name = "opaque-debug" name = "opaque-debug"
@@ -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=c2acaf6b31213e3969f8b48f9deed3daa125d394#c2acaf6b31213e3969f8b48f9deed3daa125d394"
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 = "1.0.6-patch.2" 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.4" 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,18 +2873,18 @@ dependencies = [
"mongodb", "mongodb",
"nanoid", "nanoid",
"num_enum", "num_enum",
"okapi", "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",
@@ -2812,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"
@@ -2879,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",
@@ -2888,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"
@@ -2900,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",
@@ -2921,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]]
@@ -2951,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=c2acaf6b31213e3969f8b48f9deed3daa125d394#c2acaf6b31213e3969f8b48f9deed3daa125d394"
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"
@@ -3066,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"
@@ -3114,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]]
@@ -3258,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",
] ]
@@ -3283,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]]
@@ -3300,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",
@@ -3346,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]]
@@ -3524,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",
@@ -3542,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"
@@ -3573,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]]
@@ -3613,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"
@@ -3688,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]]
@@ -3824,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]]
@@ -3954,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]]
@@ -4165,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",
] ]
@@ -4176,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]]
@@ -4201,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"
@@ -4256,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",
] ]
@@ -4290,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 = "1.0.6-patch.2" 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.4" 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 = "67a7fa8100c7db8411fb8fb149441418b55c19ef" } 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 { .. } => {
@@ -16,20 +16,21 @@ use validator::Validate;
pub struct DataEditChannel { pub struct DataEditChannel {
/// Channel name /// Channel name
#[validate(length(min = 1, max = 32))] #[validate(length(min = 1, max = 32))]
#[serde(skip_serializing_if = "Option::is_none")]
name: Option<String>, name: Option<String>,
/// Channel description /// Channel description
#[validate(length(min = 0, max = 1024))] #[validate(length(min = 0, max = 1024))]
#[serde(skip_serializing_if = "Option::is_none")]
description: Option<String>, description: Option<String>,
/// Group owner
owner: Option<String>,
/// Icon /// Icon
/// ///
/// Provide an Autumn attachment Id. /// Provide an Autumn attachment Id.
#[validate(length(min = 1, max = 128))] #[validate(length(min = 1, max = 128))]
icon: Option<String>, icon: Option<String>,
/// Whether this channel is age-restricted /// Whether this channel is age-restricted
#[serde(skip_serializing_if = "Option::is_none")]
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>>,
} }
@@ -59,12 +60,48 @@ pub async fn req(
&& data.description.is_none() && data.description.is_none()
&& data.icon.is_none() && data.icon.is_none()
&& data.nsfw.is_none() && data.nsfw.is_none()
&& data.owner.is_none()
&& data.remove.is_none() && data.remove.is_none()
{ {
return Ok(Json(channel)); return Ok(Json(channel));
} }
let mut partial: PartialChannel = Default::default(); let mut partial: PartialChannel = Default::default();
// Transfer group ownership
if let Some(new_owner) = data.owner {
if let Channel::Group {
owner, recipients, ..
} = &mut channel
{
// Make sure we are the owner of this group
if owner != &user.id {
return Err(Error::NotOwner);
}
// Ensure user is part of group
if !recipients.contains(&new_owner) {
return Err(Error::NotInGroup);
}
// Transfer ownership
partial.owner = Some(new_owner.to_string());
let old_owner = std::mem::replace(owner, new_owner.to_string());
// Notify clients
SystemMessage::ChannelOwnershipChanged {
from: old_owner,
to: new_owner,
}
} else {
return Err(Error::InvalidOperation);
}
.into_message(channel.id().to_string())
.create(db, &channel, None)
.await
.ok();
}
match &mut channel { match &mut channel {
Channel::Group { Channel::Group {
id, id,
@@ -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)
} }
@@ -0,0 +1,34 @@
use revolt_quark::{
models::{message::PartialMessage, User},
perms, Db, EmptyResponse, Permission, Ref, Result,
};
/// # Remove All Reactions from Message
///
/// Remove your own, someone else's or all of a given reaction.
///
/// Requires `ManageMessages` permission.
#[openapi(tag = "Interactions")]
#[delete("/<target>/messages/<msg>/reactions")]
pub async fn clear_reactions(db: &Db, user: User, target: Ref, msg: Ref) -> Result<EmptyResponse> {
let channel = target.as_channel(db).await?;
perms(&user)
.channel(&channel)
.throw_permission_and_view_channel(db, Permission::ManageMessages)
.await?;
// Fetch relevant message
let mut message = msg.as_message_in(db, channel.id()).await?;
// Clear reactions
message
.update(
db,
PartialMessage {
reactions: Some(Default::default()),
..Default::default()
},
)
.await
.map(|_| EmptyResponse)
}
@@ -0,0 +1,29 @@
use revolt_quark::{models::User, perms, Db, EmptyResponse, Permission, Ref, Result};
/// # Add Reaction to Message
///
/// React to a given message.
#[openapi(tag = "Interactions")]
#[put("/<target>/messages/<msg>/reactions/<emoji>")]
pub async fn react_message(
db: &Db,
user: User,
target: Ref,
msg: Ref,
emoji: Ref,
) -> Result<EmptyResponse> {
let channel = target.as_channel(db).await?;
perms(&user)
.channel(&channel)
.throw_permission_and_view_channel(db, Permission::React)
.await?;
// Fetch relevant message
let message = msg.as_message_in(db, channel.id()).await?;
// Add the reaction
message
.add_reaction(db, &user, &emoji.id)
.await
.map(|_| EmptyResponse)
}
@@ -2,7 +2,7 @@ use std::collections::HashSet;
use revolt_quark::{ use revolt_quark::{
models::{ models::{
message::{Masquerade, Reply, SendableEmbed}, message::{Interactions, Masquerade, Reply, SendableEmbed},
Message, User, Message, User,
}, },
perms, perms,
@@ -40,6 +40,8 @@ pub struct DataMessageSend {
/// Masquerade to apply to this message /// Masquerade to apply to this message
#[validate] #[validate]
masquerade: Option<Masquerade>, masquerade: Option<Masquerade>,
/// Information about how this message should be interacted with
interactions: Option<Interactions>,
} }
lazy_static! { lazy_static! {
@@ -63,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()))
@@ -80,12 +86,29 @@ 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(),
channel: channel.id().to_string(), channel: channel.id().to_string(),
author: user.id.clone(), author: user.id.clone(),
masquerade: data.masquerade, masquerade: data.masquerade,
interactions: data.interactions.unwrap_or_default(),
..Default::default() ..Default::default()
}; };
@@ -100,13 +123,22 @@ pub async fn message_send(
} }
// 2. Verify permissions for masquerade. // 2. Verify permissions for masquerade.
if message.masquerade.is_some() { if let Some(masq) = &message.masquerade {
permissions permissions
.throw_permission(db, Permission::Masquerade) .throw_permission(db, Permission::Masquerade)
.await?; .await?;
if masq.colour.is_some() {
permissions
.throw_permission(db, Permission::ManageRole)
.await?;
}
} }
// 3. Verify replies are valid. // 3. Ensure interactions information is correct
message.interactions.validate(db, &mut permissions).await?;
// 4. Verify replies are valid.
let mut replies = HashSet::new(); let mut replies = HashSet::new();
if let Some(entries) = data.replies { if let Some(entries) = data.replies {
if entries.len() > 5 { if entries.len() > 5 {
@@ -139,7 +171,7 @@ pub async fn message_send(
.replace(replies.into_iter().collect::<Vec<String>>()); .replace(replies.into_iter().collect::<Vec<String>>());
} }
// 4. Process included embeds. // 5. Process included embeds.
let mut embeds = vec![]; let mut embeds = vec![];
if let Some(sendable_embeds) = data.embeds { if let Some(sendable_embeds) = data.embeds {
for sendable_embed in sendable_embeds { for sendable_embed in sendable_embeds {
@@ -151,7 +183,7 @@ pub async fn message_send(
message.embeds.replace(embeds); message.embeds.replace(embeds);
} }
// 5. Add attachments to message. // 6. Add attachments to message.
let mut attachments = vec![]; let mut attachments = vec![];
if let Some(ids) = &data.attachments { if let Some(ids) = &data.attachments {
if !ids.is_empty() { if !ids.is_empty() {
@@ -177,10 +209,10 @@ pub async fn message_send(
message.attachments.replace(attachments); message.attachments.replace(attachments);
} }
// 6. Set content // 7. Set content
message.content = data.content; message.content = data.content;
// 7. Pass-through nonce value for clients // 8. Pass-through nonce value for clients
message.nonce = Some(idempotency.into_key()); message.nonce = Some(idempotency.into_key());
message.create(db, &channel, Some(&user)).await?; message.create(db, &channel, Some(&user)).await?;
@@ -0,0 +1,58 @@
use revolt_quark::{models::User, perms, Db, EmptyResponse, Permission, Ref, Result};
use serde::{Deserialize, Serialize};
/// # Query Parameters
#[derive(Serialize, Deserialize, JsonSchema, FromForm)]
pub struct OptionsUnreact {
/// Remove a specific user's reaction
user_id: Option<String>,
/// Remove all reactions
remove_all: Option<bool>,
}
/// # Remove Reaction(s) to Message
///
/// Remove your own, someone else's or all of a given reaction.
///
/// Requires `ManageMessages` if changing others' reactions.
#[openapi(tag = "Interactions")]
#[delete("/<target>/messages/<msg>/reactions/<emoji>?<options..>")]
pub async fn unreact_message(
db: &Db,
user: User,
target: Ref,
msg: Ref,
emoji: Ref,
options: OptionsUnreact,
) -> Result<EmptyResponse> {
let channel = target.as_channel(db).await?;
let mut permissions = perms(&user).channel(&channel);
permissions
.throw_permission_and_view_channel(db, Permission::React)
.await?;
// Check if we need to escalate permissions
let remove_all = options.remove_all.unwrap_or_default();
if options.user_id.is_some() || remove_all {
permissions
.throw_permission(db, Permission::ManageMessages)
.await?;
}
// Fetch relevant message
let message = msg.as_message_in(db, channel.id()).await?;
// Check if we should wipe all of this reaction
if remove_all {
return message
.clear_reaction(db, &emoji.id)
.await
.map(|_| EmptyResponse);
}
// Remove the reaction
message
.remove_reaction(db, options.user_id.as_ref().unwrap_or(&user.id), &emoji.id)
.await
.map(|_| EmptyResponse)
}
+7 -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;
@@ -11,13 +11,16 @@ mod group_remove_member;
mod invite_create; mod invite_create;
mod members_fetch; mod members_fetch;
mod message_bulk_delete; mod message_bulk_delete;
mod message_clear_reactions;
mod message_delete; mod message_delete;
mod message_edit; mod message_edit;
mod message_fetch; mod message_fetch;
mod message_query; mod message_query;
mod message_query_stale; mod message_query_stale;
mod message_react;
mod message_search; mod message_search;
mod message_send; mod message_send;
mod message_unreact;
mod permissions_set; mod permissions_set;
mod permissions_set_default; mod permissions_set_default;
mod voice_join; mod voice_join;
@@ -44,5 +47,8 @@ pub fn routes() -> (Vec<Route>, OpenApi) {
voice_join::req, voice_join::req,
permissions_set::req, permissions_set::req,
permissions_set_default::req, permissions_set_default::req,
message_react::react_message,
message_unreact::unreact_message,
message_clear_reactions::clear_reactions
] ]
} }
@@ -14,7 +14,11 @@ pub struct DataCreateEmoji {
/// Server name /// Server name
#[validate(length(min = 1, max = 32), regex = "RE_EMOJI")] #[validate(length(min = 1, max = 32), regex = "RE_EMOJI")]
name: String, name: String,
/// Parent information
parent: EmojiParent, parent: EmojiParent,
/// Whether the emoji is mature
#[serde(default)]
nsfw: bool,
} }
/// # Create New Emoji /// # Create New Emoji
@@ -51,10 +55,11 @@ 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);
} }
} }
EmojiParent::Detached => return Err(Error::InvalidOperation),
}; };
// Find the relevant attachment // Find the relevant attachment
@@ -67,6 +72,7 @@ pub async fn create_emoji(
creator_id: user.id, creator_id: user.id,
name: data.name, name: data.name,
animated: "image/gif" == &attachment.content_type, animated: "image/gif" == &attachment.content_type,
nsfw: data.nsfw,
}; };
// Save emoji // Save emoji
@@ -28,6 +28,7 @@ pub async fn delete_emoji(db: &Db, user: User, id: Ref) -> Result<EmptyResponse>
.throw_permission(db, Permission::ManageCustomisation) .throw_permission(db, Permission::ManageCustomisation)
.await?; .await?;
} }
EmojiParent::Detached => return Ok(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 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;
+6 -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(
@@ -80,6 +80,7 @@ fn custom_openapi_spec() -> OpenApi {
"Channel Invites", "Channel Invites",
"Channel Permissions", "Channel Permissions",
"Messaging", "Messaging",
"Interactions",
"Groups", "Groups",
"Voice" "Voice"
] ]
+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(),
},
})) }))
} }
+22 -8
View File
@@ -1,5 +1,5 @@
use revolt_quark::{ use revolt_quark::{
models::{ServerBan, User}, models::{server_member::MemberCompositeKey, ServerBan, User},
perms, Db, Error, Permission, Ref, Result, perms, Db, Error, Permission, Ref, Result,
}; };
@@ -47,13 +47,27 @@ pub async fn req(
.throw_permission(db, Permission::BanMembers) .throw_permission(db, Permission::BanMembers)
.await?; .await?;
let member = target.as_member(db, &server.id).await?; // If member exists, check privileges against them
if let Ok(member) = target.as_member(db, &server.id).await {
if member.get_ranking(permissions.server.get().unwrap())
<= permissions.get_member_rank().unwrap_or(i64::MIN)
{
return Err(Error::NotElevated);
}
if member.get_ranking(permissions.server.get().unwrap()) server.ban_member(db, member, data.reason).await.map(Json)
<= permissions.get_member_rank().unwrap_or(i64::MIN) } else {
{ let server_id = server.id.to_string();
return Err(Error::NotElevated); server
.ban_user(
db,
MemberCompositeKey {
server: server_id,
user: target.id,
},
data.reason,
)
.await
.map(Json)
} }
server.ban_member(db, member, data.reason).await.map(Json)
} }
+16 -8
View File
@@ -5,7 +5,7 @@ use revolt_quark::{
server_member::{FieldsMember, PartialMember}, server_member::{FieldsMember, PartialMember},
File, Member, User, File, Member, User,
}, },
perms, Db, Error, Permission, Ref, Result, perms, Db, Error, Permission, Ref, Result, Timestamp,
}; };
use rocket::serde::json::Json; use rocket::serde::json::Json;
@@ -22,6 +22,8 @@ pub struct DataMemberEdit {
avatar: Option<String>, avatar: Option<String>,
/// Array of role ids /// Array of role ids
roles: Option<Vec<String>>, roles: Option<Vec<String>>,
/// Timestamp this member is timed out until
timeout: Option<Timestamp>,
/// Fields to remove from channel object /// Fields to remove from channel object
#[validate(length(min = 1))] #[validate(length(min = 1))]
remove: Option<Vec<FieldsMember>>, remove: Option<Vec<FieldsMember>>,
@@ -89,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?;
} }
@@ -105,13 +117,7 @@ pub async fn req(
// Check permissions against roles in diff // Check permissions against roles in diff
if let Some(roles) = &data.roles { if let Some(roles) = &data.roles {
let fallback = vec![]; let current_roles = member.roles.iter().collect::<HashSet<&String>>();
let current_roles = member
.roles
.as_ref()
.unwrap_or(&fallback)
.iter()
.collect::<HashSet<&String>>();
let new_roles = roles.iter().collect::<HashSet<&String>>(); let new_roles = roles.iter().collect::<HashSet<&String>>();
let added_roles: Vec<&&String> = new_roles.difference(&current_roles).collect(); let added_roles: Vec<&&String> = new_roles.difference(&current_roles).collect();
@@ -132,12 +138,14 @@ pub async fn req(
nickname, nickname,
avatar, avatar,
roles, roles,
timeout,
remove, remove,
} = data; } = data;
let mut partial = PartialMember { let mut partial = PartialMember {
nickname, nickname,
roles, roles,
timeout,
..Default::default() ..Default::default()
}; };
@@ -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;
@@ -3,7 +3,9 @@ use revolt_quark::{
server::{FieldsRole, PartialRole, Role}, server::{FieldsRole, PartialRole, Role},
User, User,
}, },
perms, Db, Error, Permission, Ref, Result, perms,
util::regex::RE_COLOUR,
Db, Error, Permission, Ref, Result,
}; };
use rocket::serde::json::Json; use rocket::serde::json::Json;
@@ -17,7 +19,7 @@ pub struct DataEditRole {
#[validate(length(min = 1, max = 32))] #[validate(length(min = 1, max = 32))]
name: Option<String>, name: Option<String>,
/// Role colour /// Role colour
#[validate(length(min = 1, max = 32))] #[validate(length(min = 1, max = 128), regex = "RE_COLOUR")]
colour: Option<String>, colour: Option<String>,
/// Whether this role should be displayed separately /// Whether this role should be displayed separately
hoist: Option<bool>, hoist: Option<bool>,
@@ -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)
+34 -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},
@@ -33,6 +35,8 @@ pub struct DataEditServer {
// Whether this server is age-restricted // Whether this server is age-restricted
// nsfw: Option<bool>, // nsfw: Option<bool>,
/// Whether this server is public and should show up on [Revolt Discover](https://rvlt.gg)
discoverable: Option<bool>,
/// Whether analytics should be collected for this server /// Whether analytics should be collected for this server
/// ///
/// Must be enabled in order to show up on [Revolt Discover](https://rvlt.gg). /// Must be enabled in order to show up on [Revolt Discover](https://rvlt.gg).
@@ -102,6 +106,7 @@ pub async fn req(
categories, categories,
system_messages, system_messages,
// nsfw, // nsfw,
discoverable,
analytics, analytics,
remove, remove,
} = data; } = data;
@@ -112,6 +117,7 @@ pub async fn req(
categories, categories,
system_messages, system_messages,
// nsfw, // nsfw,
discoverable,
analytics, analytics,
..Default::default() ..Default::default()
}; };
@@ -131,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,
+1 -1
View File
@@ -11,4 +11,4 @@ pub static RE_USERNAME: Lazy<Regex> =
/// Regex for valid emoji names /// Regex for valid emoji names
/// ///
/// Alphanumeric and underscores /// Alphanumeric and underscores
pub static RE_EMOJI: Lazy<Regex> = Lazy::new(|| Regex::new(r"^[a-zA-Z0-9_]+$").unwrap()); pub static RE_EMOJI: Lazy<Regex> = Lazy::new(|| Regex::new(r"^[a-z0-9_]+$").unwrap());
+13 -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,11 +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"
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 }
@@ -80,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 = "67a7fa8100c7db8411fb8fb149441418b55c19ef", features = [ "async-std-runtime" ] } authifier = { version = "1.0.7", features = [ "async-std-runtime" ] }
# Sentry # Sentry
sentry = "0.25.0" sentry = "0.25.0"
File diff suppressed because it is too large Load Diff
+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")),
} ),
} }
} }
} }
+38
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};
@@ -78,6 +79,29 @@ pub enum EventV1 {
/// Delete message /// Delete message
MessageDelete { id: String, channel: String }, MessageDelete { id: String, channel: String },
/// New reaction to a message
MessageReact {
id: String,
channel_id: String,
user_id: String,
emoji_id: String,
},
/// Remove user's reaction from message
MessageUnreact {
id: String,
channel_id: String,
user_id: String,
emoji_id: String,
},
/// Remove a reaction from message
MessageRemoveReaction {
id: String,
channel_id: String,
emoji_id: String,
},
/// Bulk delete messages /// Bulk delete messages
BulkMessageDelete { channel: String, ids: Vec<String> }, BulkMessageDelete { channel: String, ids: Vec<String> },
@@ -172,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),
} }
+11 -13
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);
@@ -111,6 +107,7 @@ impl State {
// Fetch all memberships with their corresponding servers. // Fetch all memberships with their corresponding servers.
let members: Vec<Member> = db.fetch_all_memberships(&user.id).await?; let members: Vec<Member> = db.fetch_all_memberships(&user.id).await?;
let server_ids: Vec<String> = members.iter().map(|x| x.id.server.clone()).collect(); let server_ids: Vec<String> = members.iter().map(|x| x.id.server.clone()).collect();
let servers = db.fetch_servers(&server_ids).await?; let servers = db.fetch_servers(&server_ids).await?;
@@ -484,10 +481,8 @@ impl State {
if data.rank.is_some() || data.permissions.is_some() { if data.rank.is_some() || data.permissions.is_some() {
if let Some(member) = self.cache.members.get(id) { if let Some(member) = self.cache.members.get(id) {
if let Some(roles) = &member.roles { if member.roles.contains(role_id) {
if roles.contains(role_id) { queue_server = Some(id.clone());
queue_server = Some(id.clone());
}
} }
} }
} }
@@ -498,10 +493,8 @@ impl State {
} }
if let Some(member) = self.cache.members.get(id) { if let Some(member) = self.cache.members.get(id) {
if let Some(roles) = &member.roles { if member.roles.contains(role_id) {
if roles.contains(role_id) { queue_server = Some(id.clone());
queue_server = Some(id.clone());
}
} }
} }
} }
@@ -566,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)
}
} }
@@ -64,4 +64,22 @@ impl AbstractMessage for DummyDb {
) -> Result<Vec<Message>> { ) -> Result<Vec<Message>> {
Ok(vec![self.fetch_message(channel).await.unwrap()]) Ok(vec![self.fetch_message(channel).await.unwrap()])
} }
/// Add a new reaction to a message
async fn add_reaction(&self, id: &str, emoji: &str, user: &str) -> Result<()> {
info!("Add to {id} with {emoji} and {user}");
Ok(())
}
/// Remove a reaction from a message
async fn remove_reaction(&self, id: &str, emoji: &str, user: &str) -> Result<()> {
info!("Remove {emoji} from {id} for {user}");
Ok(())
}
/// Remove reaction from a message
async fn clear_reaction(&self, id: &str, emoji: &str) -> Result<()> {
info!("Clear {emoji} on {id}");
Ok(())
}
} }
+4 -3
View File
@@ -14,6 +14,7 @@ impl AbstractEmoji for DummyDb {
parent: EmojiParent::Server { id: id.into() }, parent: EmojiParent::Server { id: id.into() },
creator_id: id.into(), creator_id: id.into(),
animated: false, animated: false,
nsfw: false,
}) })
} }
@@ -33,9 +34,9 @@ impl AbstractEmoji for DummyDb {
Ok(()) Ok(())
} }
/// Delete an emoji by its id /// Detach an emoji by its id
async fn delete_emoji(&self, emoji: &Emoji) -> Result<()> { async fn detach_emoji(&self, emoji: &Emoji) -> Result<()> {
info!("Delete {emoji:?}"); info!("Detach {emoji:?}");
Ok(()) Ok(())
} }
} }
@@ -3,6 +3,8 @@ use crate::{AbstractServerMember, Result};
use super::super::DummyDb; use super::super::DummyDb;
use iso8601_timestamp::Timestamp;
#[async_trait] #[async_trait]
impl AbstractServerMember for DummyDb { impl AbstractServerMember for DummyDb {
async fn fetch_member(&self, server: &str, user: &str) -> Result<Member> { async fn fetch_member(&self, server: &str, user: &str) -> Result<Member> {
@@ -11,9 +13,11 @@ impl AbstractServerMember for DummyDb {
server: server.into(), server: server.into(),
user: user.into(), user: user.into(),
}, },
joined_at: Timestamp::now_utc(),
nickname: None, nickname: None,
avatar: None, avatar: None,
roles: None, roles: vec![],
timeout: None,
}) })
} }
@@ -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 {
@@ -305,6 +306,15 @@ impl Channel {
vec![], vec![],
) )
.await?; .await?;
SystemMessage::ChannelOwnershipChanged {
from: owner.to_string(),
to: new_owner.into(),
}
.into_message(id.to_string())
.create(db, self, None)
.await
.ok();
} else { } else {
db.delete_channel(self).await?; db.delete_channel(self).await?;
return Ok(()); return Ok(());
@@ -320,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(())
} }
@@ -8,17 +8,19 @@ use crate::{
events::client::EventV1, events::client::EventV1,
models::{ models::{
message::{ message::{
AppendMessage, BulkMessageResponse, PartialMessage, SendableEmbed, SystemMessage, AppendMessage, BulkMessageResponse, Interactions, PartialMessage, SendableEmbed,
SystemMessage,
}, },
Channel, 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 {
@@ -191,6 +193,84 @@ impl Message {
Err(Error::PayloadTooLarge) Err(Error::PayloadTooLarge)
} }
} }
/// Add a reaction to a message
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
if !self.interactions.can_use(emoji) {
return Err(Error::InvalidOperation);
}
// Check if the emoji is usable by us
if !Emoji::can_use(db, emoji).await? {
return Err(Error::InvalidOperation);
}
// Send reaction event
EventV1::MessageReact {
id: self.id.to_string(),
channel_id: self.channel.to_string(),
user_id: user.id.to_string(),
emoji_id: emoji.to_string(),
}
.p(self.channel.to_string())
.await;
// Add emoji
db.add_reaction(&self.id, emoji, &user.id).await
}
/// Remove a reaction from a message
pub async fn remove_reaction(&self, db: &Database, user: &str, emoji: &str) -> Result<()> {
// Check if it actually exists
let empty = if let Some(users) = self.reactions.get(emoji) {
if !users.contains(user) {
return Err(Error::NotFound);
}
users.len() == 1
} else {
return Err(Error::NotFound);
};
// Send reaction event
EventV1::MessageUnreact {
id: self.id.to_string(),
channel_id: self.channel.to_string(),
user_id: user.to_string(),
emoji_id: emoji.to_string(),
}
.p(self.channel.to_string())
.await;
if empty {
// If empty, remove the reaction entirely
db.clear_reaction(&self.id, emoji).await
} else {
// Otherwise only remove that one reaction
db.remove_reaction(&self.id, emoji, user).await
}
}
/// Remove a reaction from a message
pub async fn clear_reaction(&self, db: &Database, emoji: &str) -> Result<()> {
// Send reaction event
EventV1::MessageRemoveReaction {
id: self.id.to_string(),
channel_id: self.channel.to_string(),
emoji_id: emoji.to_string(),
}
.p(self.channel.to_string())
.await;
// Write to database
db.clear_reaction(&self.id, emoji).await
}
} }
pub trait IntoUsers { pub trait IntoUsers {
@@ -262,6 +342,9 @@ impl From<SystemMessage> for String {
"Channel description changed.".to_string() "Channel description changed.".to_string()
} }
SystemMessage::ChannelIconChanged { .. } => "Channel icon changed.".to_string(), SystemMessage::ChannelIconChanged { .. } => "Channel icon changed.".to_string(),
SystemMessage::ChannelOwnershipChanged { .. } => {
"Channel ownership changed.".to_string()
}
} }
} }
} }
@@ -300,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, .. } => {
@@ -321,3 +404,46 @@ impl BulkMessageResponse {
} }
} }
} }
impl Interactions {
/// Validate interactions info is correct
pub async fn validate(
&self,
db: &Database,
permissions: &mut PermissionCalculator<'_>,
) -> Result<()> {
if let Some(reactions) = &self.reactions {
permissions.throw_permission(db, Permission::React).await?;
if reactions.len() > 20 {
return Err(Error::InvalidOperation);
}
for reaction in reactions {
if !Emoji::can_use(db, reaction).await? {
return Err(Error::InvalidOperation);
}
}
}
Ok(())
}
/// Check if we can use a given emoji to react
pub fn can_use(&self, emoji: &str) -> bool {
if self.restrict_reactions {
if let Some(reactions) = &self.reactions {
reactions.contains(emoji)
} else {
false
}
} else {
true
}
}
/// Check if default initialisation of fields
pub fn is_default(&self) -> bool {
!self.restrict_reactions && self.reactions.is_none()
}
}
+24 -2
View File
@@ -1,14 +1,27 @@
use std::{collections::HashSet, str::FromStr};
use ulid::Ulid;
use crate::{ use crate::{
events::client::EventV1, events::client::EventV1,
models::{emoji::EmojiParent, Emoji}, models::{emoji::EmojiParent, Emoji},
Database, Result, Database, Result,
}; };
lazy_static! {
/// Permissible emojis
static ref PERMISSIBLE_EMOJIS: HashSet<String> = include_str!(crate::asset!("emojis.txt"))
.split('\n')
.map(|x| x.into())
.collect();
}
impl Emoji { impl Emoji {
/// Get parent id /// Get parent id
fn parent(&self) -> &str { fn parent(&self) -> &str {
match &self.parent { match &self.parent {
EmojiParent::Server { id } => id, EmojiParent::Server { id } => id,
EmojiParent::Detached => "",
} }
} }
@@ -30,7 +43,16 @@ impl Emoji {
.p(self.parent().to_string()) .p(self.parent().to_string())
.await; .await;
db.mark_attachment_as_deleted(&self.id).await?; db.detach_emoji(&self).await
db.delete_emoji(&self).await }
/// Check whether we can use a given emoji
pub async fn can_use(db: &Database, emoji: &str) -> Result<bool> {
if Ulid::from_str(emoji).is_ok() {
db.fetch_emoji(emoji).await?;
Ok(true)
} else {
Ok(PERMISSIBLE_EMOJIS.contains(emoji))
}
} }
} }
+44 -28
View File
@@ -1,3 +1,6 @@
use std::collections::HashSet;
use iso8601_timestamp::Timestamp;
use ulid::Ulid; use ulid::Ulid;
use crate::{ use crate::{
@@ -188,7 +191,11 @@ impl Server {
server: self.id.clone(), server: self.id.clone(),
user: user.id.clone(), user: user.id.clone(),
}, },
..Default::default() joined_at: Timestamp::now_utc(),
nickname: None,
avatar: None,
roles: vec![],
timeout: None,
}; };
db.insert_member(&member).await?; db.insert_member(&member).await?;
@@ -249,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?;
@@ -259,25 +267,39 @@ 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(())
} }
/// Create ban
pub async fn ban_user(
self,
db: &Database,
id: MemberCompositeKey,
reason: Option<String>,
) -> Result<ServerBan> {
let ban = ServerBan { id, reason };
db.insert_ban(&ban).await?;
Ok(ban)
}
/// Ban a member from a server /// Ban a member from a server
pub async fn ban_member( pub async fn ban_member(
self, self,
@@ -285,37 +307,31 @@ impl Server {
member: Member, member: Member,
reason: Option<String>, reason: Option<String>,
) -> Result<ServerBan> { ) -> Result<ServerBan> {
let ban = ServerBan { self.remove_member(db, member.clone(), RemovalIntention::Ban, false)
id: member.id.clone(),
reason,
};
self.remove_member(db, member, RemovalIntention::Ban)
.await?; .await?;
db.insert_ban(&ban).await?; self.ban_user(db, member.id, reason).await
Ok(ban)
} }
} }
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
@@ -1,3 +1,5 @@
use iso8601_timestamp::Timestamp;
use crate::{ use crate::{
events::client::EventV1, events::client::EventV1,
models::{ models::{
@@ -36,19 +38,24 @@ impl Member {
/// Get this user's current ranking /// Get this user's current ranking
pub fn get_ranking(&self, server: &Server) -> i64 { pub fn get_ranking(&self, server: &Server) -> i64 {
if let Some(roles) = &self.roles { let mut value = i64::MAX;
let mut value = i64::MAX; for role in &self.roles {
for role in roles { if let Some(role) = server.roles.get(role) {
if let Some(role) = server.roles.get(role) { if role.rank < value {
if role.rank < value { value = role.rank;
value = role.rank;
}
} }
} }
}
value value
}
/// Check whether this member is in timeout
pub fn in_timeout(&self) -> bool {
if let Some(timeout) = self.timeout {
*timeout > *Timestamp::now_utc()
} else { } else {
i64::MAX false
} }
} }
@@ -56,7 +63,8 @@ impl Member {
match field { match field {
FieldsMember::Avatar => self.avatar = None, FieldsMember::Avatar => self.avatar = None,
FieldsMember::Nickname => self.nickname = None, FieldsMember::Nickname => self.nickname = None,
FieldsMember::Roles => self.roles = None, FieldsMember::Roles => self.roles.clear(),
FieldsMember::Timeout => self.timeout = None,
} }
} }
} }
@@ -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 {
@@ -1,6 +1,6 @@
use std::time::Duration; use std::time::Duration;
use bson::Bson; use bson::{Bson, DateTime};
use futures::StreamExt; use futures::StreamExt;
use mongodb::{ use mongodb::{
bson::{doc, from_bson, from_document, to_document, Document}, bson::{doc, from_bson, from_document, to_document, Document},
@@ -16,7 +16,7 @@ struct MigrationInfo {
revision: i32, revision: i32,
} }
pub const LATEST_REVISION: i32 = 17; pub const LATEST_REVISION: i32 = 18;
pub async fn migrate_database(db: &MongoDb) { pub async fn migrate_database(db: &MongoDb) {
let migrations = db.col::<Document>("migrations"); let migrations = db.col::<Document>("migrations");
@@ -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();
@@ -644,6 +644,23 @@ pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 {
.expect("Failed to create emoji parent index."); .expect("Failed to create emoji parent index.");
} }
if revision <= 17 {
info!("Running migration [revision 17 / 15-07-2022]: Initialise `joined_at` property on server members.");
db.col::<Document>("server_members")
.update_many(
doc! {},
doc! {
"$set": {
"joined_at": DateTime::now().to_rfc3339_string()
}
},
None,
)
.await
.expect("Failed to update server members.");
}
// Need to migrate fields on attachments, change `user_id`, `object_id`, etc to `parent`. // Need to migrate fields on attachments, change `user_id`, `object_id`, etc to `parent`.
// Reminder to update LATEST_REVISION when adding new migrations. // Reminder to update LATEST_REVISION when adding new migrations.
@@ -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 {
@@ -279,4 +279,70 @@ impl AbstractMessage for MongoDb {
) )
.await .await
} }
/// Add a new reaction to a message
async fn add_reaction(&self, id: &str, emoji: &str, user: &str) -> Result<()> {
self.col::<Document>(COL)
.update_one(
doc! {
"_id": id
},
doc! {
"$addToSet": {
format!("reactions.{emoji}"): user
}
},
None,
)
.await
.map(|_| ())
.map_err(|_| Error::DatabaseError {
operation: "update_one",
with: "message",
})
}
/// Remove a reaction from a message
async fn remove_reaction(&self, id: &str, emoji: &str, user: &str) -> Result<()> {
self.col::<Document>(COL)
.update_one(
doc! {
"_id": id
},
doc! {
"$pull": {
format!("reactions.{emoji}"): user
}
},
None,
)
.await
.map(|_| ())
.map_err(|_| Error::DatabaseError {
operation: "update_one",
with: "message",
})
}
/// Remove reaction from a message
async fn clear_reaction(&self, id: &str, emoji: &str) -> Result<()> {
self.col::<Document>(COL)
.update_one(
doc! {
"_id": id
},
doc! {
"$unset": {
format!("reactions.{emoji}"): 1
}
},
None,
)
.await
.map(|_| ())
.map_err(|_| Error::DatabaseError {
operation: "update_one",
with: "message",
})
}
} }
+24 -3
View File
@@ -1,5 +1,7 @@
use bson::Document;
use crate::models::Emoji; use crate::models::Emoji;
use crate::{AbstractEmoji, Result}; use crate::{AbstractEmoji, Error, Result};
use super::super::MongoDb; use super::super::MongoDb;
@@ -42,7 +44,26 @@ impl AbstractEmoji for MongoDb {
} }
/// Delete an emoji by its id /// Delete an emoji by its id
async fn delete_emoji(&self, emoji: &Emoji) -> Result<()> { async fn detach_emoji(&self, emoji: &Emoji) -> Result<()> {
self.delete_one_by_id(COL, &emoji.id).await.map(|_| ()) self.col::<Document>(COL)
.update_one(
doc! {
"_id": &emoji.id
},
doc! {
"$set": {
"parent": {
"type": "Detached"
}
}
},
None,
)
.await
.map(|_| ())
.map_err(|_| Error::DatabaseError {
operation: "update_one",
with: "emojis",
})
} }
} }
@@ -129,6 +129,7 @@ impl IntoDocumentPath for FieldsMember {
FieldsMember::Avatar => "avatar", FieldsMember::Avatar => "avatar",
FieldsMember::Nickname => "nickname", FieldsMember::Nickname => "nickname",
FieldsMember::Roles => "roles", FieldsMember::Roles => "roles",
FieldsMember::Timeout => "timeout",
}) })
} }
} }
+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,
+38 -4
View File
@@ -1,8 +1,10 @@
use crate::util::regex::RE_COLOUR;
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;
@@ -11,6 +13,11 @@ use crate::{
types::january::Embed, types::january::Embed,
}; };
/// Utility function to check if a boolean value is false
pub fn if_false(t: &bool) -> bool {
!t
}
/// # Reply /// # Reply
/// ///
/// Representation of a message reply before it is sent. /// Representation of a message reply before it is sent.
@@ -33,7 +40,7 @@ pub struct SendableEmbed {
#[validate(length(min = 1, max = 2000))] #[validate(length(min = 1, max = 2000))]
pub description: Option<String>, pub description: Option<String>,
pub media: Option<String>, pub media: Option<String>,
#[validate(length(min = 1, max = 64))] #[validate(length(min = 1, max = 128), regex = "RE_COLOUR")]
pub colour: Option<String>, pub colour: Option<String>,
} }
@@ -61,6 +68,8 @@ pub enum SystemMessage {
ChannelDescriptionChanged { by: String }, ChannelDescriptionChanged { by: String },
#[serde(rename = "channel_icon_changed")] #[serde(rename = "channel_icon_changed")]
ChannelIconChanged { by: String }, ChannelIconChanged { by: String },
#[serde(rename = "channel_ownership_changed")]
ChannelOwnershipChanged { from: String, to: String },
} }
/// Name and / or avatar override information /// Name and / or avatar override information
@@ -72,8 +81,27 @@ 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
///
/// Must have `ManageRole` permission to use
#[serde(skip_serializing_if = "Option::is_none")]
#[validate(length(min = 1, max = 128), regex = "RE_COLOUR")]
pub colour: Option<String>,
}
/// Information to guide interactions on this message
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, Validate, Default)]
pub struct Interactions {
/// Reactions which should always appear and be distinct
#[serde(skip_serializing_if = "Option::is_none", default)]
pub reactions: Option<IndexSet<String>>,
/// 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)]
pub restrict_reactions: bool,
} }
/// Representation of a Message on Revolt /// Representation of a Message on Revolt
@@ -115,6 +143,12 @@ pub struct Message {
/// Array of message ids this message is replying to /// Array of message ids this message is replying to
#[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
#[serde(skip_serializing_if = "IndexMap::is_empty", default)]
pub reactions: IndexMap<String, IndexSet<String>>,
/// Information about how this message should be interacted with
#[serde(skip_serializing_if = "Interactions::is_default", default)]
pub interactions: Interactions,
/// Name and / or avatar overrides for this message /// Name and / or avatar overrides for this message
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub masquerade: Option<Masquerade>, pub masquerade: Option<Masquerade>,
+10
View File
@@ -1,10 +1,16 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// Utility function to check if a boolean value is false
pub fn if_false(t: &bool) -> bool {
!t
}
/// Information about what owns this emoji /// Information about what owns this emoji
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)] #[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)]
#[serde(tag = "type")] #[serde(tag = "type")]
pub enum EmojiParent { pub enum EmojiParent {
Server { id: String }, Server { id: String },
Detached,
} }
/// Representation of an Emoji on Revolt /// Representation of an Emoji on Revolt
@@ -20,5 +26,9 @@ pub struct Emoji {
/// Emoji name /// Emoji name
pub name: String, pub name: String,
/// Whether the emoji is animated /// Whether the emoji is animated
#[serde(skip_serializing_if = "if_false", default)]
pub animated: bool, pub animated: bool,
/// Whether the emoji is marked as nsfw
#[serde(skip_serializing_if = "if_false", default)]
pub nsfw: bool,
} }
+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,
} }
@@ -1,3 +1,4 @@
use iso8601_timestamp::Timestamp;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::models::attachment::File; use crate::models::attachment::File;
@@ -12,7 +13,7 @@ pub struct MemberCompositeKey {
} }
/// Representation of a member of a server on Revolt /// Representation of a member of a server on Revolt
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, OptionalStruct, Default)] #[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, OptionalStruct)]
#[optional_derive(Serialize, Deserialize, JsonSchema, Debug, Default, Clone)] #[optional_derive(Serialize, Deserialize, JsonSchema, Debug, Default, Clone)]
#[optional_name = "PartialMember"] #[optional_name = "PartialMember"]
#[opt_skip_serializing_none] #[opt_skip_serializing_none]
@@ -22,6 +23,9 @@ pub struct Member {
#[serde(rename = "_id")] #[serde(rename = "_id")]
pub id: MemberCompositeKey, pub id: MemberCompositeKey,
/// Time at which this user joined the server
pub joined_at: Timestamp,
/// Member's nickname /// Member's nickname
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub nickname: Option<String>, pub nickname: Option<String>,
@@ -30,16 +34,20 @@ pub struct Member {
pub avatar: Option<File>, pub avatar: Option<File>,
/// Member's roles /// Member's roles
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub roles: Vec<String>,
/// Timestamp this member is timed out until
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub roles: Option<Vec<String>>, pub timeout: Option<Timestamp>,
} }
/// 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,
Roles, Roles,
Timeout,
} }
/// Member removal intention /// Member removal intention
+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,
+18 -3
View File
@@ -45,8 +45,23 @@ impl Override {
impl PermissionValue { impl PermissionValue {
/// Apply a given override to this value /// Apply a given override to this value
pub fn apply(&mut self, v: Override) { pub fn apply(&mut self, v: Override) {
self.0 |= v.allow; self.allow(v.allow);
self.0 &= !v.deny; self.revoke(v.deny);
}
/// Allow given permissions
pub fn allow(&mut self, v: u64) {
self.0 |= v;
}
/// Revoke given permissions
pub fn revoke(&mut self, v: u64) {
self.0 &= !v;
}
/// Restrict to given permissions
pub fn restrict(&mut self, v: u64) {
self.0 &= v;
} }
} }
@@ -82,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
@@ -65,8 +65,8 @@ pub enum Permission {
UploadFiles = 1 << 27, UploadFiles = 1 << 27,
/// Masquerade messages using custom nickname and avatar /// Masquerade messages using custom nickname and avatar
Masquerade = 1 << 28, Masquerade = 1 << 28,
/// React to messages with emojis
// % 1 bits reserved React = 1 << 29,
// * Voice permissions // * Voice permissions
/// Connect to a voice channel /// Connect to a voice channel
@@ -98,6 +98,7 @@ impl_op_ex!(+ |a: &Permission, b: &Permission| -> u64 { *a as u64 | *b as u64 })
impl_op_ex_commutative!(+ |a: &u64, b: &Permission| -> u64 { *a | *b as u64 }); impl_op_ex_commutative!(+ |a: &u64, b: &Permission| -> u64 { *a | *b as u64 });
lazy_static! { lazy_static! {
pub static ref ALLOW_IN_TIMEOUT: u64 = Permission::ViewChannel + Permission::ReadMessageHistory;
pub static ref DEFAULT_PERMISSION_VIEW_ONLY: u64 = pub static ref DEFAULT_PERMISSION_VIEW_ONLY: u64 =
Permission::ViewChannel + Permission::ReadMessageHistory; Permission::ViewChannel + Permission::ReadMessageHistory;
pub static ref DEFAULT_PERMISSION: u64 = *DEFAULT_PERMISSION_VIEW_ONLY pub static ref DEFAULT_PERMISSION: u64 = *DEFAULT_PERMISSION_VIEW_ONLY
@@ -108,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! {
+31 -24
View File
@@ -2,7 +2,7 @@ use std::collections::HashSet;
use crate::{ use crate::{
models::Channel, permissions::PermissionCalculator, Override, Permission, PermissionValue, models::Channel, permissions::PermissionCalculator, Override, Permission, PermissionValue,
Permissions, Perms, Result, DEFAULT_PERMISSION_DIRECT_MESSAGE, Permissions, Perms, Result, ALLOW_IN_TIMEOUT, DEFAULT_PERMISSION_DIRECT_MESSAGE,
DEFAULT_PERMISSION_SAVED_MESSAGES, DEFAULT_PERMISSION_VIEW_ONLY, DEFAULT_PERMISSION_SAVED_MESSAGES, DEFAULT_PERMISSION_VIEW_ONLY,
}; };
@@ -55,11 +55,7 @@ async fn calculate_server_permission(
let mut permissions: PermissionValue = server.default_permissions.into(); let mut permissions: PermissionValue = server.default_permissions.into();
// 4. Resolve each role in order. // 4. Resolve each role in order.
let member_roles: HashSet<&String> = if let Some(roles) = member.roles.as_ref() { let member_roles: HashSet<&String> = member.roles.iter().collect();
roles.iter().collect()
} else {
HashSet::new()
};
if !member_roles.is_empty() { if !member_roles.is_empty() {
let mut roles = server let mut roles = server
@@ -80,6 +76,11 @@ async fn calculate_server_permission(
} }
} }
// 5. Revoke permissions if member is timed out.
if member.in_timeout() {
permissions.restrict(*ALLOW_IN_TIMEOUT);
}
Ok(permissions) Ok(permissions)
} }
@@ -118,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 {
@@ -188,11 +194,7 @@ async fn calculate_channel_permission(
} }
// 4. Resolve each role in order. // 4. Resolve each role in order.
let member_roles: HashSet<&String> = if let Some(roles) = member.roles.as_ref() { let member_roles: HashSet<&String> = member.roles.iter().collect();
roles.iter().collect()
} else {
HashSet::new()
};
if !member_roles.is_empty() { if !member_roles.is_empty() {
let mut roles = role_permissions let mut roles = role_permissions
@@ -214,6 +216,11 @@ async fn calculate_channel_permission(
} }
} }
// 5. Revoke permissions if member is timed out.
if member.in_timeout() {
permissions.restrict(*ALLOW_IN_TIMEOUT);
}
permissions permissions
} else { } else {
(Permission::GrantAllSafe as u64).into() (Permission::GrantAllSafe as u64).into()
+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>;
} }
@@ -42,4 +42,13 @@ pub trait AbstractMessage: Sync + Send {
after: Option<String>, after: Option<String>,
sort: MessageSort, sort: MessageSort,
) -> Result<Vec<Message>>; ) -> Result<Vec<Message>>;
/// Add a new reaction to a message
async fn add_reaction(&self, id: &str, emoji: &str, user: &str) -> Result<()>;
/// Remove a reaction from a message
async fn remove_reaction(&self, id: &str, emoji: &str, user: &str) -> Result<()>;
/// Remove reaction from a message
async fn clear_reaction(&self, id: &str, emoji: &str) -> Result<()>;
} }
+2 -2
View File
@@ -15,6 +15,6 @@ pub trait AbstractEmoji: Sync + Send {
/// Insert emoji into database. /// Insert emoji into database.
async fn insert_emoji(&self, emoji: &Emoji) -> Result<()>; async fn insert_emoji(&self, emoji: &Emoji) -> Result<()>;
/// Delete an emoji by its id /// Detach an emoji by its id
async fn delete_emoji(&self, emoji: &Emoji) -> Result<()>; async fn detach_emoji(&self, emoji: &Emoji) -> Result<()>;
} }
+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
} }

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