Compare commits

..
Author SHA1 Message Date
izzy 8ea8f50fb8 chore: bump version to 0.8.5
closes #402
2025-05-10 16:51:38 +01:00
izzy 68bcfcb626 fix: ensure easypwned api is actually set before using 2025-05-10 16:43:30 +01:00
izzy 9632a0dc1b refactor: remove crate bindings/node 2025-05-10 16:40:02 +01:00
izzy a4a0934475 feat: add use_starttls config option for SMTP 2025-05-10 16:38:26 +01:00
izzy 2061794305 docs: configure reset existing email template 2025-05-10 16:36:41 +01:00
izzy 3a7ebad883 merge: branch 'izzy/patch/easypwned'
fix: configure new authifier properties
2025-05-10 16:31:37 +01:00
izzy 6daa8223b2 ci: must login to ghcr before pushing base image 2025-05-09 23:10:35 +01:00
izzy 4a58d6eae0 chore: enable lto for production builds (#341) 2025-05-09 23:04:37 +01:00
izzy a8a2e491b0 ci: reduce threads available to build 2025-05-09 22:59:51 +01:00
izzy f98f09a5ea ci: bump Docker builds to Rust 1.86.0 2025-05-09 22:57:45 +01:00
izzy 7a4e12a732 ci: include parser toml in Dockerfile 2025-05-09 22:53:22 +01:00
izzy aa8f8575bc fix: include revolt-parser in build scripts 2025-05-09 22:51:16 +01:00
izzy 70d6a5ac08 ci: for multi-arch builds, must push instead of load 2025-05-09 22:48:50 +01:00
izzy 7f85ad5e7b ci: load the built base image into Docker 2025-05-09 22:44:58 +01:00
izzy c785ae615d ci: new attempt at using hosted runner 2025-05-09 22:39:29 +01:00
Paul Makles 9c8a362e9f merge: pull request #413 from Docteh/Docteh-patch-1 2025-05-09 20:38:33 +01:00
Kyle Kienapfel e708c612cf fix: Actually create crond container in github workflow
Just one line missing to make the container creation happen

Related to eb5f5f91cd

Signed-off-by: Kyle Kienapfel <doctor.whom@gmail.com>
2025-04-29 18:19:43 -07:00
60 changed files with 569 additions and 1757 deletions
+121 -69
View File
@@ -2,24 +2,13 @@ name: Docker Test & Publish
on:
push:
# branches:
# - "main"
tags:
- "*"
paths-ignore:
- ".github/**"
- "!.github/workflows/docker.yml"
- ".vscode/**"
- "doc/**"
- ".gitignore"
- "LICENSE"
- "README"
pull_request:
branches:
- "main"
paths:
- "Dockerfile"
workflow_dispatch:
permissions:
contents: read
@@ -27,8 +16,9 @@ permissions:
jobs:
base:
name: Test base image build
runs-on: ubuntu-latest
name: Build base image
if: github.event_name == 'pull_request'
steps:
# Configure build environment
- name: Checkout
@@ -36,33 +26,21 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
# Authenticate with GHCR
- name: Login to Github Container Registry
if: ${{ github.event_name != 'pull_request' }}
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# Build the image
- name: Build base image
uses: docker/build-push-action@v4
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
push: false
platforms: linux/amd64,linux/arm64
tags: ghcr.io/${{ github.repository_owner }}/base:latest
cache-from: type=gha,scope=buildx-base-multi-arch
cache-to: type=gha,scope=buildx-base-multi-arch,mode=max
publish:
needs: [base]
runs-on: ubuntu-latest
runs-on: self-hosted
if: github.event_name != 'pull_request'
strategy:
matrix:
project: [delta, bonfire, autumn, january, pushd]
name: Build ${{ matrix.project }} image
name: Publish Docker images
steps:
# Configure build environment
- name: Checkout
@@ -77,6 +55,7 @@ jobs:
registry: docker.io
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to Github Container Registry
uses: docker/login-action@v2
with:
@@ -84,58 +63,131 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# Resolve the correct project
- uses: kanga333/variable-mapper@master
id: export
# Build the image
- name: Build base image
uses: docker/build-push-action@v4
with:
key: "${{ matrix.project }}"
map: |
{
"delta": {
"path": "crates/delta",
"tag": "${{ github.repository_owner }}/server"
},
"bonfire": {
"path": "crates/bonfire",
"tag": "${{ github.repository_owner }}/bonfire"
},
"autumn": {
"path": "crates/services/autumn",
"tag": "${{ github.repository_owner }}/autumn"
},
"january": {
"path": "crates/services/january",
"tag": "${{ github.repository_owner }}/january"
},
"pushd": {
"path": "crates/daemons/pushd",
"tag": "${{ github.repository_owner }}/pushd"
},
"crond": {
"path": "crates/daemons/crond",
"tag": "${{ github.repository_owner }}/crond"
}
}
export_to: output
context: .
push: true
platforms: linux/amd64,linux/arm64
tags: ghcr.io/${{ github.repository_owner }}/base:latest
# Configure metadata
# revoltchat/server
- name: Docker meta
id: meta
id: meta-delta
uses: docker/metadata-action@v4
with:
images: |
docker.io/${{ steps.export.outputs.tag }}
ghcr.io/${{ steps.export.outputs.tag }}
# Build crate image
docker.io/revoltchat/server
ghcr.io/revoltchat/server
- name: Publish
uses: docker/build-push-action@v4
with:
context: .
push: true
platforms: linux/amd64,linux/arm64
file: ${{ steps.export.outputs.path }}/Dockerfile
tags: ${{ steps.meta.outputs.tags }}
file: crates/delta/Dockerfile
tags: ${{ steps.meta-delta.outputs.tags }}
build-args: |
BASE_IMAGE=ghcr.io/${{ github.repository_owner }}/base:latest
labels: ${{ steps.meta.outputs.labels }}
labels: ${{ steps.meta-delta.outputs.labels }}
# revoltchat/bonfire
- name: Docker meta
id: meta-bonfire
uses: docker/metadata-action@v4
with:
images: |
docker.io/revoltchat/bonfire
ghcr.io/revoltchat/bonfire
- name: Publish
uses: docker/build-push-action@v4
with:
context: .
push: true
platforms: linux/amd64,linux/arm64
file: crates/bonfire/Dockerfile
tags: ${{ steps.meta-bonfire.outputs.tags }}
build-args: |
BASE_IMAGE=ghcr.io/${{ github.repository_owner }}/base:latest
labels: ${{ steps.meta-bonfire.outputs.labels }}
# revoltchat/autumn
- name: Docker meta
id: meta-autumn
uses: docker/metadata-action@v4
with:
images: |
docker.io/revoltchat/autumn
ghcr.io/revoltchat/autumn
- name: Publish
uses: docker/build-push-action@v4
with:
context: .
push: true
platforms: linux/amd64,linux/arm64
file: crates/services/autumn/Dockerfile
tags: ${{ steps.meta-autumn.outputs.tags }}
build-args: |
BASE_IMAGE=ghcr.io/${{ github.repository_owner }}/base:latest
labels: ${{ steps.meta-autumn.outputs.labels }}
# revoltchat/january
- name: Docker meta
id: meta-january
uses: docker/metadata-action@v4
with:
images: |
docker.io/revoltchat/january
ghcr.io/revoltchat/january
- name: Publish
uses: docker/build-push-action@v4
with:
context: .
push: true
platforms: linux/amd64,linux/arm64
file: crates/services/january/Dockerfile
tags: ${{ steps.meta-january.outputs.tags }}
build-args: |
BASE_IMAGE=ghcr.io/${{ github.repository_owner }}/base:latest
labels: ${{ steps.meta-january.outputs.labels }}
# revoltchat/crond
- name: Docker meta
id: meta-crond
uses: docker/metadata-action@v4
with:
images: |
docker.io/revoltchat/crond
ghcr.io/revoltchat/crond
- name: Publish
uses: docker/build-push-action@v4
with:
context: .
push: true
platforms: linux/amd64,linux/arm64
file: crates/daemons/crond/Dockerfile
tags: ${{ steps.meta-crond.outputs.tags }}
build-args: |
BASE_IMAGE=ghcr.io/${{ github.repository_owner }}/base:latest
labels: ${{ steps.meta-crond.outputs.labels }}
# revoltchat/pushd
- name: Docker meta
id: meta-pushd
uses: docker/metadata-action@v4
with:
images: |
docker.io/revoltchat/pushd
ghcr.io/revoltchat/pushd
- name: Publish
uses: docker/build-push-action@v4
with:
context: .
push: true
platforms: linux/amd64,linux/arm64
file: crates/daemons/pushd/Dockerfile
tags: ${{ steps.meta-pushd.outputs.tags }}
build-args: |
BASE_IMAGE=ghcr.io/${{ github.repository_owner }}/base:latest
labels: ${{ steps.meta-pushd.outputs.labels }}
Generated
+25 -134
View File
@@ -443,9 +443,9 @@ dependencies = [
[[package]]
name = "authifier"
version = "1.0.10"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edfe88c4a9fcf24b18a216b27c9766966af2a40603d24c7782237afd6ca0d5cd"
checksum = "ca26cb6b5ab776a1de5a8122fe6cc77fadd84fed30704c496f2aca451637877b"
dependencies = [
"async-std",
"async-trait",
@@ -454,7 +454,7 @@ dependencies = [
"chrono",
"futures",
"handlebars",
"iso8601-timestamp 0.1.11",
"iso8601-timestamp",
"lazy_static",
"lettre",
"log",
@@ -470,6 +470,7 @@ dependencies = [
"schemars",
"serde",
"serde_json",
"sha1",
"totp-lite",
"ulid 0.5.0",
"validator 0.15.0",
@@ -3647,17 +3648,6 @@ dependencies = [
"waker-fn",
]
[[package]]
name = "iso8601-timestamp"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b088f3296223f11d6bd70998132653b30b6f39e6ab4be055c0bda486fa8d11c7"
dependencies = [
"generic-array 0.14.7",
"serde",
"time",
]
[[package]]
name = "iso8601-timestamp"
version = "0.2.17"
@@ -3996,16 +3986,6 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "libloading"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
dependencies = [
"cfg-if",
"windows-targets 0.52.6",
]
[[package]]
name = "libm"
version = "0.2.11"
@@ -4499,43 +4479,6 @@ dependencies = [
"tempfile",
]
[[package]]
name = "neon"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d75440242411c87dc39847b0e33e961ec1f10326a9d8ecf9c1ea64a3b3c13dc"
dependencies = [
"getrandom",
"libloading",
"neon-macros",
"once_cell",
"semver",
"send_wrapper",
"smallvec",
]
[[package]]
name = "neon-macros"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6813fde79b646e47e7ad75f480aa80ef76a5d9599e2717407961531169ee38b"
dependencies = [
"quote 1.0.38",
"syn 2.0.94",
"syn-mid",
]
[[package]]
name = "neon-serde4"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8778b4afaa506e067da24566623873c38797987be593232a4f9ad82c84bb3eb3"
dependencies = [
"neon",
"num",
"serde",
]
[[package]]
name = "new_debug_unreachable"
version = "1.0.6"
@@ -4577,19 +4520,6 @@ dependencies = [
"winapi",
]
[[package]]
name = "num"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23"
dependencies = [
"num-complex",
"num-integer",
"num-iter",
"num-rational",
"num-traits",
]
[[package]]
name = "num-bigint"
version = "0.4.6"
@@ -4617,15 +4547,6 @@ dependencies = [
"zeroize",
]
[[package]]
name = "num-complex"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
dependencies = [
"num-traits",
]
[[package]]
name = "num-conv"
version = "0.1.0"
@@ -5983,7 +5904,7 @@ dependencies = [
[[package]]
name = "revolt-autumn"
version = "0.8.4"
version = "0.8.5"
dependencies = [
"axum",
"axum-macros",
@@ -6020,7 +5941,7 @@ dependencies = [
[[package]]
name = "revolt-bonfire"
version = "0.8.4"
version = "0.8.5"
dependencies = [
"async-channel 2.3.1",
"async-std",
@@ -6050,7 +5971,7 @@ dependencies = [
[[package]]
name = "revolt-config"
version = "0.8.4"
version = "0.8.5"
dependencies = [
"async-std",
"cached",
@@ -6066,7 +5987,7 @@ dependencies = [
[[package]]
name = "revolt-crond"
version = "0.8.4"
version = "0.8.5"
dependencies = [
"log",
"revolt-config",
@@ -6078,7 +5999,7 @@ dependencies = [
[[package]]
name = "revolt-database"
version = "0.8.4"
version = "0.8.5"
dependencies = [
"amqprs",
"async-lock 2.8.0",
@@ -6095,7 +6016,7 @@ dependencies = [
"futures",
"indexmap 1.9.3",
"isahc",
"iso8601-timestamp 0.2.17",
"iso8601-timestamp",
"linkify 0.8.1",
"log",
"lru 0.11.1",
@@ -6128,7 +6049,7 @@ dependencies = [
[[package]]
name = "revolt-delta"
version = "0.8.4"
version = "0.8.5"
dependencies = [
"amqprs",
"async-channel 1.9.0",
@@ -6140,7 +6061,7 @@ dependencies = [
"env_logger",
"futures",
"impl_ops",
"iso8601-timestamp 0.2.17",
"iso8601-timestamp",
"lettre",
"linkify 0.6.0",
"log",
@@ -6175,7 +6096,7 @@ dependencies = [
[[package]]
name = "revolt-files"
version = "0.8.4"
version = "0.8.5"
dependencies = [
"aes-gcm",
"aws-config",
@@ -6198,7 +6119,7 @@ dependencies = [
[[package]]
name = "revolt-january"
version = "0.8.4"
version = "0.8.5"
dependencies = [
"async-recursion",
"axum",
@@ -6226,10 +6147,10 @@ dependencies = [
[[package]]
name = "revolt-models"
version = "0.8.4"
version = "0.8.5"
dependencies = [
"indexmap 1.9.3",
"iso8601-timestamp 0.2.17",
"iso8601-timestamp",
"num_enum 0.6.1",
"once_cell",
"regex",
@@ -6243,29 +6164,16 @@ dependencies = [
"validator 0.16.1",
]
[[package]]
name = "revolt-nodejs-bindings"
version = "0.8.4"
dependencies = [
"async-std",
"neon",
"neon-serde4",
"revolt-config",
"revolt-database",
"revolt-result",
"serde",
]
[[package]]
name = "revolt-parser"
version = "0.1.0"
version = "0.1.1"
dependencies = [
"logos",
]
[[package]]
name = "revolt-permissions"
version = "0.8.4"
version = "0.8.5"
dependencies = [
"async-std",
"async-trait",
@@ -6280,7 +6188,7 @@ dependencies = [
[[package]]
name = "revolt-presence"
version = "0.8.4"
version = "0.8.5"
dependencies = [
"async-std",
"log",
@@ -6291,7 +6199,7 @@ dependencies = [
[[package]]
name = "revolt-pushd"
version = "0.8.4"
version = "0.8.5"
dependencies = [
"amqprs",
"async-trait",
@@ -6299,7 +6207,7 @@ dependencies = [
"base64 0.22.1",
"fcm_v1",
"isahc",
"iso8601-timestamp 0.2.17",
"iso8601-timestamp",
"log",
"pretty_env_logger",
"revolt-config",
@@ -6317,7 +6225,7 @@ dependencies = [
[[package]]
name = "revolt-result"
version = "0.8.4"
version = "0.8.5"
dependencies = [
"axum",
"revolt_okapi",
@@ -6517,12 +6425,12 @@ dependencies = [
[[package]]
name = "rocket_authifier"
version = "1.0.10"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2aa1bfaee106d088b78de7c3cc2a8ab2c9e05a0286b6b912f0b18ea656b329e"
checksum = "0490f4a6f0a359a9ffff9acbdf4eddf49890ec94941b8ddd88163c16f6159f5a"
dependencies = [
"authifier",
"iso8601-timestamp 0.1.11",
"iso8601-timestamp",
"revolt_okapi",
"revolt_rocket_okapi",
"rocket",
@@ -7001,12 +6909,6 @@ version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba"
[[package]]
name = "send_wrapper"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73"
[[package]]
name = "sentry"
version = "0.31.8"
@@ -7597,17 +7499,6 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "syn-mid"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5dc35bb08dd1ca3dfb09dce91fd2d13294d6711c88897d9a9d60acf39bce049"
dependencies = [
"proc-macro2",
"quote 1.0.38",
"syn 2.0.94",
]
[[package]]
name = "sync_wrapper"
version = "0.1.2"
+3 -4
View File
@@ -6,7 +6,6 @@ members = [
"crates/bonfire",
"crates/core/*",
"crates/services/*",
"crates/bindings/*",
"crates/daemons/*",
]
@@ -16,6 +15,6 @@ redis23 = { package = "redis", version = "0.23.3", git = "https://github.com/rev
# I'm 99% sure this is overloading the GitHub worker
# hence builds have been failing since, let's just
# disable it for now. In the future, we could use this
# if we were rolling our own CI.
# [profile.release]
# lto = true
# if we were rolling our own CI (that is now).
[profile.release]
lto = true
+2 -2
View File
@@ -1,5 +1,5 @@
# Build Stage
FROM --platform="${BUILDPLATFORM}" rust:1.77.2-slim-bookworm
FROM --platform="${BUILDPLATFORM}" rust:1.86.0-slim-bookworm
USER 0:0
WORKDIR /home/rust/src
@@ -17,13 +17,13 @@ RUN sh /tmp/build-image-layer.sh tools
# Build all dependencies
COPY Cargo.toml Cargo.lock ./
COPY crates/bindings/node/Cargo.toml ./crates/bindings/node/
COPY crates/bonfire/Cargo.toml ./crates/bonfire/
COPY crates/delta/Cargo.toml ./crates/delta/
COPY crates/core/config/Cargo.toml ./crates/core/config/
COPY crates/core/database/Cargo.toml ./crates/core/database/
COPY crates/core/files/Cargo.toml ./crates/core/files/
COPY crates/core/models/Cargo.toml ./crates/core/models/
COPY crates/core/parser/Cargo.toml ./crates/core/parser/
COPY crates/core/permissions/Cargo.toml ./crates/core/permissions/
COPY crates/core/presence/Cargo.toml ./crates/core/presence/
COPY crates/core/result/Cargo.toml ./crates/core/result/
+2 -2
View File
@@ -1,5 +1,5 @@
# Build Stage
FROM rust:1.77.2-slim-bookworm
FROM rust:1.86.0-slim-bookworm
USER 0:0
WORKDIR /home/rust/src
@@ -13,13 +13,13 @@ COPY scripts/build-image-layer.sh /tmp/
# Build all dependencies
COPY Cargo.toml Cargo.lock ./
COPY crates/bindings/node/Cargo.toml ./crates/bindings/node/
COPY crates/bonfire/Cargo.toml ./crates/bonfire/
COPY crates/delta/Cargo.toml ./crates/delta/
COPY crates/core/config/Cargo.toml ./crates/core/config/
COPY crates/core/database/Cargo.toml ./crates/core/database/
COPY crates/core/files/Cargo.toml ./crates/core/files/
COPY crates/core/models/Cargo.toml ./crates/core/models/
COPY crates/core/parser/Cargo.toml ./crates/core/parser/
COPY crates/core/permissions/Cargo.toml ./crates/core/permissions/
COPY crates/core/presence/Cargo.toml ./crates/core/presence/
COPY crates/core/result/Cargo.toml ./crates/core/result/
+17 -18
View File
@@ -12,32 +12,31 @@
The services and libraries that power the Revolt service.<br/>
<br/>
| Crate | Path | Description | |
| ------------------ | -------------------------------------------------- | ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `core/config` | [crates/core/config](crates/core/config) | Core: Configuration | ![Crates.io Version](https://img.shields.io/crates/v/revolt-config) ![Crates.io Version](https://img.shields.io/crates/msrv/revolt-config) ![Crates.io Version](https://img.shields.io/crates/size/revolt-config) ![Crates.io License](https://img.shields.io/crates/l/revolt-config) |
| `core/database` | [crates/core/database](crates/core/database) | Core: Database Implementation | ![Crates.io Version](https://img.shields.io/crates/v/revolt-database) ![Crates.io Version](https://img.shields.io/crates/msrv/revolt-database) ![Crates.io Version](https://img.shields.io/crates/size/revolt-database) ![Crates.io License](https://img.shields.io/crates/l/revolt-database) |
| `core/files` | [crates/core/files](crates/core/files) | Core: S3 and encryption subroutines | ![Crates.io Version](https://img.shields.io/crates/v/revolt-files) ![Crates.io Version](https://img.shields.io/crates/msrv/revolt-files) ![Crates.io Version](https://img.shields.io/crates/size/revolt-files) ![Crates.io License](https://img.shields.io/crates/l/revolt-files) |
| `core/models` | [crates/core/models](crates/core/models) | Core: API Models | ![Crates.io Version](https://img.shields.io/crates/v/revolt-models) ![Crates.io Version](https://img.shields.io/crates/msrv/revolt-models) ![Crates.io Version](https://img.shields.io/crates/size/revolt-models) ![Crates.io License](https://img.shields.io/crates/l/revolt-models) |
| `core/permissions` | [crates/core/permissions](crates/core/permissions) | Core: Permission Logic | ![Crates.io Version](https://img.shields.io/crates/v/revolt-permissions) ![Crates.io Version](https://img.shields.io/crates/msrv/revolt-permissions) ![Crates.io Version](https://img.shields.io/crates/size/revolt-permissions) ![Crates.io License](https://img.shields.io/crates/l/revolt-permissions) |
| `core/presence` | [crates/core/presence](crates/core/presence) | Core: User Presence | ![Crates.io Version](https://img.shields.io/crates/v/revolt-presence) ![Crates.io Version](https://img.shields.io/crates/msrv/revolt-presence) ![Crates.io Version](https://img.shields.io/crates/size/revolt-presence) ![Crates.io License](https://img.shields.io/crates/l/revolt-presence) |
| `core/result` | [crates/core/result](crates/core/result) | Core: Result and Error types | ![Crates.io Version](https://img.shields.io/crates/v/revolt-result) ![Crates.io Version](https://img.shields.io/crates/msrv/revolt-result) ![Crates.io Version](https://img.shields.io/crates/size/revolt-result) ![Crates.io License](https://img.shields.io/crates/l/revolt-result) |
| `delta` | [crates/delta](crates/delta) | REST API server | ![License](https://img.shields.io/badge/license-AGPL--3.0--or--later-blue) |
| `bonfire` | [crates/bonfire](crates/bonfire) | WebSocket events server | ![License](https://img.shields.io/badge/license-AGPL--3.0--or--later-blue) |
| `services/january` | [crates/services/january](crates/services/january) | Proxy server | ![License](https://img.shields.io/badge/license-AGPL--3.0--or--later-blue) |
| `services/autumn` | [crates/services/autumn](crates/services/autumn) | File server | ![License](https://img.shields.io/badge/license-AGPL--3.0--or--later-blue) |
| `bindings/node` | [crates/bindings/node](crates/bindings/node) | Node.js bindings for the Revolt software | ![License](https://img.shields.io/badge/license-AGPL--3.0--or--later-blue) |
| `daemons/crond` | [crates/daemons/crond](crates/daemons/crond) | Timed data clean up daemon server | ![License](https://img.shields.io/badge/license-AGPL--3.0--or--later-blue) |
| `daemons/pushd` | [crates/daemons/pushd](crates/daemons/pushd) | Push notification daemon server | ![License](https://img.shields.io/badge/license-AGPL--3.0--or--later-blue) |
| Crate | Path | Description | |
| ------------------ | -------------------------------------------------- | ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `core/config` | [crates/core/config](crates/core/config) | Core: Configuration | ![Crates.io Version](https://img.shields.io/crates/v/revolt-config) ![Crates.io Version](https://img.shields.io/crates/msrv/revolt-config) ![Crates.io Version](https://img.shields.io/crates/size/revolt-config) ![Crates.io License](https://img.shields.io/crates/l/revolt-config) |
| `core/database` | [crates/core/database](crates/core/database) | Core: Database Implementation | ![Crates.io Version](https://img.shields.io/crates/v/revolt-database) ![Crates.io Version](https://img.shields.io/crates/msrv/revolt-database) ![Crates.io Version](https://img.shields.io/crates/size/revolt-database) ![Crates.io License](https://img.shields.io/crates/l/revolt-database) |
| `core/files` | [crates/core/files](crates/core/files) | Core: S3 and encryption subroutines | ![Crates.io Version](https://img.shields.io/crates/v/revolt-files) ![Crates.io Version](https://img.shields.io/crates/msrv/revolt-files) ![Crates.io Version](https://img.shields.io/crates/size/revolt-files) ![Crates.io License](https://img.shields.io/crates/l/revolt-files) |
| `core/models` | [crates/core/models](crates/core/models) | Core: API Models | ![Crates.io Version](https://img.shields.io/crates/v/revolt-models) ![Crates.io Version](https://img.shields.io/crates/msrv/revolt-models) ![Crates.io Version](https://img.shields.io/crates/size/revolt-models) ![Crates.io License](https://img.shields.io/crates/l/revolt-models) |
| `core/permissions` | [crates/core/permissions](crates/core/permissions) | Core: Permission Logic | ![Crates.io Version](https://img.shields.io/crates/v/revolt-permissions) ![Crates.io Version](https://img.shields.io/crates/msrv/revolt-permissions) ![Crates.io Version](https://img.shields.io/crates/size/revolt-permissions) ![Crates.io License](https://img.shields.io/crates/l/revolt-permissions) |
| `core/presence` | [crates/core/presence](crates/core/presence) | Core: User Presence | ![Crates.io Version](https://img.shields.io/crates/v/revolt-presence) ![Crates.io Version](https://img.shields.io/crates/msrv/revolt-presence) ![Crates.io Version](https://img.shields.io/crates/size/revolt-presence) ![Crates.io License](https://img.shields.io/crates/l/revolt-presence) |
| `core/result` | [crates/core/result](crates/core/result) | Core: Result and Error types | ![Crates.io Version](https://img.shields.io/crates/v/revolt-result) ![Crates.io Version](https://img.shields.io/crates/msrv/revolt-result) ![Crates.io Version](https://img.shields.io/crates/size/revolt-result) ![Crates.io License](https://img.shields.io/crates/l/revolt-result) |
| `delta` | [crates/delta](crates/delta) | REST API server | ![License](https://img.shields.io/badge/license-AGPL--3.0--or--later-blue) |
| `bonfire` | [crates/bonfire](crates/bonfire) | WebSocket events server | ![License](https://img.shields.io/badge/license-AGPL--3.0--or--later-blue) |
| `services/january` | [crates/services/january](crates/services/january) | Proxy server | ![License](https://img.shields.io/badge/license-AGPL--3.0--or--later-blue) |
| `services/autumn` | [crates/services/autumn](crates/services/autumn) | File server | ![License](https://img.shields.io/badge/license-AGPL--3.0--or--later-blue) |
| `daemons/crond` | [crates/daemons/crond](crates/daemons/crond) | Timed data clean up daemon server | ![License](https://img.shields.io/badge/license-AGPL--3.0--or--later-blue) |
| `daemons/pushd` | [crates/daemons/pushd](crates/daemons/pushd) | Push notification daemon server | ![License](https://img.shields.io/badge/license-AGPL--3.0--or--later-blue) |
</div>
<br/>
## Minimum Supported Rust Version
Rust 1.76 or higher.
Rust 1.86.0 or higher.
> [!CAUTION]
> The events server has a significant performance regression between Rust 1.77.2 and 1.78.0 onwards, see [issue #341](https://github.com/revoltchat/backend/issues/341).
> The events server has a significant performance regression between Rust 1.77.2 and 1.78.0 onwards, see [issue #341](https://github.com/revoltchat/backend/issues/341). This is currently solved by build time options but we are looking for a proper fix.
## Development Guide
+1
View File
@@ -38,6 +38,7 @@ from_address = "development@revolt.chat"
reply_to = "support@revolt.chat"
port = 14025
use_tls = false
use_starttls = false
[files.s3]
# S3 protocol endpoint
-7
View File
@@ -1,7 +0,0 @@
target
index.node
**/node_modules
**/.DS_Store
npm-debug.log*
cargo.log
cross.log
-25
View File
@@ -1,25 +0,0 @@
[package]
name = "revolt-nodejs-bindings"
version = "0.8.4"
description = "Node.js bindings for the Revolt software"
authors = ["Paul Makles <me@insrt.uk>"]
license = "MIT"
edition = "2021"
exclude = ["index.node"]
[lib]
crate-type = ["cdylib"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
neon = "1.0.0"
neon-serde4 = "1.0.0"
serde = { version = "1", features = ["derive"] }
async-std = "1.12.0"
revolt-config = { version = "0.8.4", path = "../../core/config" }
revolt-result = { version = "0.8.4", path = "../../core/result" }
revolt-database = { version = "0.8.4", path = "../../core/database" }
-92
View File
@@ -1,92 +0,0 @@
# revolt.node
**revolt.node:** Node.js bindings for the Revolt software
This project was bootstrapped by [create-neon](https://www.npmjs.com/package/create-neon).
## Building revolt.node
Building revolt.node requires a [supported version of Node and Rust](https://github.com/neon-bindings/neon#platform-support).
To run the build, run:
```sh
$ npm run build
```
This command uses the [@neon-rs/cli](https://www.npmjs.com/package/@neon-rs/cli) utility to assemble the binary Node addon from the output of `cargo`.
## Exploring revolt.node
After building revolt.node, you can explore its exports at the Node console:
```sh
$ npm i
$ npm run build
$ node
> require('.').hello()
'hello node'
```
## Available Scripts
In the project directory, you can run:
#### `npm install`
Installs the project, including running `npm run build`.
#### `npm run build`
Builds the Node addon (`index.node`) from source, generating a release build with `cargo --release`.
Additional [`cargo build`](https://doc.rust-lang.org/cargo/commands/cargo-build.html) arguments may be passed to `npm run build` and similar commands. For example, to enable a [cargo feature](https://doc.rust-lang.org/cargo/reference/features.html):
```
npm run build -- --feature=beetle
```
#### `npm run debug`
Similar to `npm run build` but generates a debug build with `cargo`.
#### `npm run cross`
Similar to `npm run build` but uses [cross-rs](https://github.com/cross-rs/cross) to cross-compile for another platform. Use the [`CARGO_BUILD_TARGET`](https://doc.rust-lang.org/cargo/reference/config.html#buildtarget) environment variable to select the build target.
#### `npm test`
Runs the unit tests by calling `cargo test`. You can learn more about [adding tests to your Rust code](https://doc.rust-lang.org/book/ch11-01-writing-tests.html) from the [Rust book](https://doc.rust-lang.org/book/).
## Project Layout
The directory structure of this project is:
```
revolt.node/
├── Cargo.toml
├── README.md
├── src/
| └── lib.rs
├── index.node
├── package.json
└── target/
```
| Entry | Purpose |
|----------------|------------------------------------------------------------------------------------------------------------------------------------------|
| `Cargo.toml` | The Cargo [manifest file](https://doc.rust-lang.org/cargo/reference/manifest.html), which informs the `cargo` command. |
| `README.md` | This file. |
| `src/` | The directory tree containing the Rust source code for the project. |
| `lib.rs` | Entry point for the Rust source code. |
| `index.node` | The main module, a [Node addon](https://nodejs.org/api/addons.html) generated by the build and pointed to by `"main"` in `package.json`. |
| `package.json` | The npm [manifest file](https://docs.npmjs.com/cli/v7/configuring-npm/package-json), which informs the `npm` command. |
| `target/` | Binary artifacts generated by the Rust build. |
## Learn More
Learn more about:
- [Neon](https://neon-bindings.com).
- [Rust](https://www.rust-lang.org).
- [Node](https://nodejs.org).
-84
View File
@@ -1,84 +0,0 @@
import { Channel, User } from "revolt-api";
/**
* Opaque type for Revolt database
*/
export declare interface Database {}
/**
* Opaque type for Revolt database
*/
export declare interface OpaqueUser {}
/**
* Error type from Revolt backend
*/
export declare interface Err {
type: string;
location: string;
}
/**
* Initialises background tasks and logging, must be called before anything else!
* Can be called multiple times!
*/
export declare function init();
/**
* Gets a new handle to the Revolt database
* @returns {Database} Handle
*/
export declare function database(): Database;
/**
* Fetch user from database
* @param {string} userId User's ID
* @this {Database}
*/
export declare function database_fetch_user(userId: string): OpaqueUser;
/**
* Fetch user from database
* @param {string} username Username
* @param {string} discriminator Discriminator
* @this {Database}
*/
export declare function database_fetch_user_by_username(
username: string,
discriminator: string
): OpaqueUser;
/**
* Gets model data as JSON
* @this {OpaqueUser}
*/
export declare function model_data(): User;
/**
* Gets error if the model failed to fetch
* @this {OpaqueUser}
*/
export declare function model_error(): Err;
/**
* Open a direct message channel between two users
* @param {string} userA User A ID
* @param {string} userB User B ID
* @returns Existing or newly created channel
*/
export declare function proc_channels_create_dm(
userA: string,
userB: string
): Promise<Channel & { error: Err }>;
/**
* Suspend a user
* @param {string} user User
* @param {number} duration Duration (in days), set to 0 for indefinite
* @param {string} reason Pipe-separated list of reasons (e.g. reason1|reason2|reason3)
*/
export declare function proc_users_suspend(
user: OpaqueUser,
duration: number,
reason: string
): Promise<{ error: Err }>;
-36
View File
@@ -1,36 +0,0 @@
{
"name": "revolt-nodejs-bindings",
"version": "0.7.15-rev0.0.3",
"description": "Node.js bindings for the Revolt software",
"main": "index.node",
"scripts": {
"test": "cargo test",
"cargo-build": "cargo build --message-format=json > cargo.log",
"cross-build": "cross build --message-format=json > cross.log",
"postcargo-build": "neon dist < cargo.log",
"postcross-build": "neon dist -m /target < cross.log",
"debug": "npm run cargo-build --",
"build": "npm run cargo-build -- --release",
"cross": "npm run cross-build -- --release"
},
"author": "Paul Makles",
"license": "AGPL-3.0",
"devDependencies": {
"@neon-rs/cli": "0.1.73"
},
"repository": {
"type": "git",
"url": "git+https://github.com/revoltchat/backend"
},
"keywords": [
"revolt",
"chat"
],
"bugs": {
"url": "https://github.com/revoltchat/backend/issues"
},
"homepage": "https://github.com/revoltchat/backend#readme",
"dependencies": {
"revolt-api": "^0.7.15"
}
}
-117
View File
@@ -1,117 +0,0 @@
lockfileVersion: '6.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
dependencies:
revolt-api:
specifier: ^0.7.15
version: 0.7.15
devDependencies:
'@neon-rs/cli':
specifier: 0.1.73
version: 0.1.73
packages:
/@cargo-messages/android-arm-eabi@0.1.72:
resolution: {integrity: sha512-gGZxIM1mj+Y5x+ULND6ZCNr7f70OJi9wDlycSK8hGONy9wrChN6JAIHryddC5cqcwlYAoQ6IDcDFElnhAYbybA==}
cpu: [arm]
os: [android]
requiresBuild: true
dev: true
optional: true
/@cargo-messages/darwin-arm64@0.1.72:
resolution: {integrity: sha512-EAzN5MLaXPljZKZDO5qR+aBs44eSq2ZbEnS7AI/FziE3MzeXbrGOS3fLba5+7yWPFXJyZolXzePm8N1EBv8ovg==}
cpu: [arm64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/@cargo-messages/darwin-x64@0.1.72:
resolution: {integrity: sha512-RLo6j8s3nYbjdd1LDct4wamfChyRit7zokUuxtIYCu9XOlltkN5vnj1vwnrPvoqCMZ/7CbbuHFwSTn9A71de/w==}
cpu: [x64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/@cargo-messages/linux-arm-gnueabihf@0.1.72:
resolution: {integrity: sha512-tHsRshuzfjrX6SDW3jg6al8vMNLTMgczGnVYl5RuBZf/yrAUuwe30KxA9ge6w6mW6Ox797DyBchzAc9OLgTgmQ==}
cpu: [arm]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@cargo-messages/linux-x64-gnu@0.1.72:
resolution: {integrity: sha512-VGtL6CCnUbhsP4aYuBNT5kfrAL7o0qjrxw97a+ax13t+nJd26tVEEIKHMu5drvvS/Nm/hn7sLT8zMnnCv0pvHg==}
cpu: [x64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@cargo-messages/win32-arm64-msvc@0.1.72:
resolution: {integrity: sha512-V93Cgz39K+yqa3MveNbhh29pYCp8izK5uEavjPoxlNxAbsMCWH+s0verGDdUcfGxjR1H2V7oZ4FszPqR2SqMRQ==}
cpu: [arm64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@cargo-messages/win32-x64-msvc@0.1.72:
resolution: {integrity: sha512-knz3uSrO0OSbq3U5VWfCY8FB4NsM43BOWLZ7x4sfaMOC1XWv+IyvDdkLe6DhJx8KUw46KIAimYs9YROrp6l46Q==}
cpu: [x64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@neon-rs/cli@0.1.73:
resolution: {integrity: sha512-1kv8S/feB6UQWQQwsnGfkSkEBOtlFDNExnioL81E2BwvUWgjQPaseHgpi2EpWVgsPUgur5eBm4QowmlpWkD4/w==}
hasBin: true
optionalDependencies:
'@cargo-messages/android-arm-eabi': 0.1.72
'@cargo-messages/darwin-arm64': 0.1.72
'@cargo-messages/darwin-x64': 0.1.72
'@cargo-messages/linux-arm-gnueabihf': 0.1.72
'@cargo-messages/linux-x64-gnu': 0.1.72
'@cargo-messages/win32-arm64-msvc': 0.1.72
'@cargo-messages/win32-x64-msvc': 0.1.72
dev: true
/axios@0.26.1:
resolution: {integrity: sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==}
dependencies:
follow-redirects: 1.15.6
transitivePeerDependencies:
- debug
dev: false
/follow-redirects@1.15.6:
resolution: {integrity: sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==}
engines: {node: '>=4.0'}
peerDependencies:
debug: '*'
peerDependenciesMeta:
debug:
optional: true
dev: false
/lodash.defaultsdeep@4.6.1:
resolution: {integrity: sha512-3j8wdDzYuWO3lM3Reg03MuQR957t287Rpcxp1njpEa8oDrikb+FwGdW3n+FELh/A6qib6yPit0j/pv9G/yeAqA==}
dev: false
/revolt-api@0.7.15:
resolution: {integrity: sha512-rWRnjBFvoXt/RHRxHg9KuP+x1Jk61KdsOpKOczVJg5tIqGDmqp/6QZHw5rJTtG+wCH3Ah3YKwY+MM5NHfg1W3Q==}
dependencies:
axios: 0.26.1
lodash.defaultsdeep: 4.6.1
transitivePeerDependencies:
- debug
dev: false
-221
View File
@@ -1,221 +0,0 @@
#[macro_use]
extern crate serde;
use std::sync::OnceLock;
use neon::prelude::*;
use revolt_database::{Database, DatabaseInfo};
fn js_init(mut cx: FunctionContext) -> JsResult<JsUndefined> {
// static INIT: OnceLock<()> = OnceLock::new();
// if INIT.get().is_none() {
// INIT.get_or_init(|| {
// async_std::task::block_on(async {
// revolt_config::configure!(api);
// match DatabaseInfo::Auto.connect().await {
// Ok(db) => {
// let authifier_db = db.clone().to_authifier().await.database;
// revolt_database::tasks::start_workers(db, authifier_db);
// Ok(())
// }
// Err(err) => Err(err),
// }
// })
// .or_else(|err| cx.throw_error(err))
// .unwrap();
// });
// }
Ok(cx.undefined())
}
struct DatabaseBinding(Database, Channel);
impl Finalize for DatabaseBinding {}
impl DatabaseBinding {
fn take(&self) -> (Database, Channel) {
(self.0.clone(), self.1.clone())
}
}
fn js_database(mut cx: FunctionContext) -> JsResult<JsBox<DatabaseBinding>> {
let db = async_std::task::block_on(DatabaseInfo::Auto.connect())
.or_else(|err| cx.throw_error(err))?;
let channel = cx.channel();
Ok(cx.boxed(DatabaseBinding(db, channel)))
}
// Implementations for models
#[derive(Clone)]
#[allow(clippy::large_enum_variant)]
enum Model {
User(revolt_database::User),
Error(revolt_result::Error),
}
impl Model {
fn give(&self) -> Model {
self.clone()
}
}
impl Finalize for Model {}
macro_rules! shim_boxed {
($cx: ident, $name: ident, $model: ident, $( $variable: ident $type: ident $id: expr )+, $cmd: ident, $( $arg: expr, )+) => {
fn $name(mut cx: FunctionContext) -> JsResult<JsPromise> {
$(
let $variable = cx.argument::<$type>($id)?.value(&mut cx);
)+
let (db, channel) = cx.this::<JsBox<DatabaseBinding>>()?.take();
let (deferred, promise) = cx.promise();
async_std::task::spawn(async move {
let result = db.$cmd($($arg,)+).await;
deferred.settle_with(&channel, move |mut cx| {
Ok(cx.boxed(match result {
Ok(value) => Model::$model(value),
Err(error) => Model::Error(error)
}))
})
});
Ok(promise)
}
$cx.export_function(stringify!($name), $name)?;
};
}
fn js_data(mut cx: FunctionContext) -> JsResult<JsValue> {
match cx.this::<JsBox<Model>>()?.give() {
Model::Error(_) => neon_serde4::to_value(&mut cx, &None::<()>),
Model::User(user) => neon_serde4::to_value(&mut cx, &user),
}
.or_else(|e| cx.throw_error(e.to_string()))
}
fn js_error(mut cx: FunctionContext) -> JsResult<JsValue> {
let value = match cx.this::<JsBox<Model>>()?.give() {
Model::Error(err) => Some(err),
_ => None,
};
neon_serde4::to_value(&mut cx, &value).or_else(|e| cx.throw_error(e.to_string()))
}
// Basic data implementation
#[derive(Serialize, Deserialize)]
struct ResultBinding<T> {
#[serde(flatten)]
value: Option<T>,
error: Option<revolt_result::Error>,
}
macro_rules! shim {
($cx: ident, $name: ident, $( $variable: ident $type: ident $id: expr )*, $( $model: ident $modelType: ident $modelId: expr )*, | $db: ident | $closure: expr, $( $arg: expr, )+) => {
fn $name(mut cx: FunctionContext) -> JsResult<JsPromise> {
$(
let $variable = cx.argument::<$type>($id)?.value(&mut cx);
)*
$(
let mut $model = if let Model::$modelType(value) = cx.argument::<JsBox<Model>>($modelId)?.give() {
value
} else {
unreachable!()
};
)*
let (db, channel) = cx.this::<JsBox<DatabaseBinding>>()?.take();
let (deferred, promise) = cx.promise();
async_std::task::spawn(async move {
#[allow(clippy::redundant_closure_call)]
let result = (|$db: $crate::Database| $closure)(db.clone()).await;
deferred.settle_with(&channel, move |mut cx| {
neon_serde4::to_value(
&mut cx,
&match result {
Ok(value) => ResultBinding {
value: Some(value),
error: None,
},
Err(error) => ResultBinding {
value: None,
error: Some(error),
},
},
)
.or_else(|e| cx.throw_error(e.to_string()))
})
});
Ok(promise)
}
$cx.export_function(stringify!($name), $name)?;
};
}
#[neon::main]
fn main(mut cx: ModuleContext) -> NeonResult<()> {
// initialise required background stuff
cx.export_function("init", js_init)?;
// database & model stuff
cx.export_function("database", js_database)?;
cx.export_function("model_data", js_data)?;
cx.export_function("model_error", js_error)?;
shim_boxed!(
cx,
database_fetch_user,
User,
user_id JsString 0,
fetch_user,
&user_id,
);
shim_boxed!(
cx,
database_fetch_user_by_username,
User,
username JsString 0
discriminator JsString 1,
fetch_user_by_username,
&username, &discriminator,
);
// procedure calls
shim!(
cx,
proc_channels_create_dm,
user_a JsString 0
user_b JsString 1,
,
|db| async move {
let user_a = db.fetch_user(&user_a).await?;
let user_b = db.fetch_user(&user_b).await?;
revolt_database::Channel::create_dm(&db, &user_a, &user_b).await
},
&userA, &userB,
);
shim!(
cx,
proc_users_suspend,
duration JsNumber 1
reason JsString 2,
user User 0,
|db| async move {
let duration = duration as usize;
user.suspend(&db, if duration == 0 { None } else { Some(duration) }, Some(reason.split('|').map(|x| x.to_owned()).collect())).await
},
&user,
);
Ok(())
}
-53
View File
@@ -1,53 +0,0 @@
const Internal = require(".");
// playing around with class wrapper, not practical
class Model {
constructor(model) {
this.model = model;
}
data() {
return Internal.model_data.bind(this.model)();
}
error() {
return Internal.model_error.bind(this.model)();
}
}
class User extends Model {
constructor(db, user) {
super(user);
this.db = db;
}
}
class Database {
constructor() {
this.db = Internal.database();
}
async fetchUser(userId) {
return new User(
this,
await Internal.database_fetch_user.bind(this.db)(userId)
);
}
async fetchUserByUsername(username, discriminator) {
return new User(
this,
await Internal.database_fetch_user_by_username.bind(this.db)(
username,
discriminator
)
);
}
}
const db = new Database();
db.fetchUserByUsername("dos", "7624").then((user) => console.info(user.data()));
db.fetchUserByUsername("dos", "1111").then((user) => console.info(user.data()));
db.fetchUserByUsername("dos", "1111").then((user) =>
console.info(user.error())
);
-6
View File
@@ -1,6 +0,0 @@
{
"name": "bindings",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}
+3 -3
View File
@@ -1,6 +1,6 @@
[package]
name = "revolt-bonfire"
version = "0.8.4"
version = "0.8.5"
license = "AGPL-3.0-or-later"
edition = "2021"
@@ -36,12 +36,12 @@ async-std = { version = "1.8.0", features = [
] }
# core
authifier = { version = "1.0.10" }
authifier = { version = "1.0.13" }
revolt-result = { path = "../core/result" }
revolt-models = { path = "../core/models" }
revolt-config = { path = "../core/config" }
revolt-database = { path = "../core/database" }
revolt-permissions = { version = "0.8.4", path = "../core/permissions" }
revolt-permissions = { version = "0.8.5", path = "../core/permissions" }
revolt-presence = { path = "../core/presence", features = ["redis-is-patched"] }
# redis
+2 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "revolt-config"
version = "0.8.4"
version = "0.8.5"
edition = "2021"
license = "MIT"
authors = ["Paul Makles <me@insrt.uk>"]
@@ -34,4 +34,4 @@ pretty_env_logger = "0.4.0"
sentry = "0.31.5"
# Core
revolt-result = { version = "0.8.4", path = "../result", optional = true }
revolt-result = { version = "0.8.5", path = "../result", optional = true }
+2 -1
View File
@@ -54,6 +54,8 @@ authifier_shield_key = ""
voso_legacy_token = ""
# Whether services are behind the Cloudflare network
trust_cloudflare = false
# easypwned endpoint
easypwned = ""
[api.security.captcha]
# hCaptcha configuration
@@ -200,7 +202,6 @@ message_reactions = 20
server_emoji = 100
server_roles = 200
server_channels = 200
server_categories = 50
# How many hours since creation a user is considered new
new_user_hours = 72
+3 -2
View File
@@ -123,6 +123,7 @@ pub struct ApiSmtp {
pub reply_to: Option<String>,
pub port: Option<i32>,
pub use_tls: Option<bool>,
pub use_starttls: Option<bool>,
}
#[derive(Deserialize, Debug, Clone)]
@@ -168,6 +169,7 @@ pub struct ApiSecurity {
pub voso_legacy_token: String,
pub captcha: ApiSecurityCaptcha,
pub trust_cloudflare: bool,
pub easypwned: String,
}
#[derive(Deserialize, Debug, Clone)]
@@ -177,7 +179,7 @@ pub struct ApiWorkers {
#[derive(Deserialize, Debug, Clone)]
pub struct ApiUsers {
pub early_adopter_cutoff: Option<u64>
pub early_adopter_cutoff: Option<u64>,
}
#[derive(Deserialize, Debug, Clone)]
@@ -281,7 +283,6 @@ pub struct GlobalLimits {
pub server_emoji: usize,
pub server_roles: usize,
pub server_channels: usize,
pub server_categories: usize,
pub new_user_hours: usize,
+8 -8
View File
@@ -1,6 +1,6 @@
[package]
name = "revolt-database"
version = "0.8.4"
version = "0.8.5"
edition = "2021"
license = "AGPL-3.0-or-later"
authors = ["Paul Makles <me@insrt.uk>"]
@@ -24,19 +24,19 @@ default = ["mongodb", "async-std-runtime", "tasks"]
[dependencies]
# Core
revolt-config = { version = "0.8.4", path = "../config", features = [
revolt-config = { version = "0.8.5", path = "../config", features = [
"report-macros",
] }
revolt-result = { version = "0.8.4", path = "../result" }
revolt-models = { version = "0.8.4", path = "../models", features = [
revolt-result = { version = "0.8.5", path = "../result" }
revolt-models = { version = "0.8.5", path = "../models", features = [
"validator",
] }
revolt-presence = { version = "0.8.4", path = "../presence" }
revolt-permissions = { version = "0.8.4", path = "../permissions", features = [
revolt-presence = { version = "0.8.5", path = "../presence" }
revolt-permissions = { version = "0.8.5", path = "../permissions", features = [
"serde",
"bson",
] }
revolt-parser = { version = "0.1.0", path = "../parser" }
revolt-parser = { version = "0.1.1", path = "../parser" }
# Utility
log = "0.4"
@@ -97,7 +97,7 @@ web-push = "0.10.0"
revolt_a2 = { version = "0.10", default-features = false, features = ["ring"] }
# Authifier
authifier = { version = "1.0.10", features = ["rocket_impl"] }
authifier = { version = "1.0.13", features = ["rocket_impl"] }
# RabbitMQ
amqprs = { version = "1.7.0" }
+26 -1
View File
@@ -3,6 +3,7 @@ mod reference;
use authifier::config::Captcha;
use authifier::config::EmailVerificationConfig;
use authifier::config::PasswordScanning;
use authifier::config::ResolveIp;
use authifier::config::SMTPSettings;
use authifier::config::Shield;
@@ -102,6 +103,13 @@ impl Database {
let config = config().await;
let mut auth_config = authifier::Config {
password_scanning: if config.api.security.easypwned.is_empty() {
Default::default()
} else {
PasswordScanning::EasyPwned {
endpoint: config.api.security.easypwned,
}
},
email_verification: if !config.api.smtp.host.is_empty() {
EmailVerificationConfig::Enabled {
smtp: SMTPSettings {
@@ -118,6 +126,7 @@ impl Database {
),
port: config.api.smtp.port,
use_tls: config.api.smtp.use_tls,
use_starttls: config.api.smtp.use_starttls,
},
expiry: Default::default(),
templates: if config.production {
@@ -134,6 +143,15 @@ impl Database {
url: format!("{}/login/reset/", config.hosts.app),
html: Some(include_str!("../../templates/reset.html").into()),
},
reset_existing: Template {
title: "You already have a Revolt account, reset your password."
.into(),
text: include_str!("../../templates/reset-existing.txt").into(),
url: format!("{}/login/reset/", config.hosts.app),
html: Some(
include_str!("../../templates/reset-existing.html").into(),
),
},
deletion: Template {
title: "Confirm account deletion.".into(),
text: include_str!("../../templates/deletion.txt").into(),
@@ -156,9 +174,16 @@ impl Database {
url: format!("{}/login/reset/", config.hosts.app),
html: None,
},
reset_existing: Template {
title: "Reset your password.".into(),
text: include_str!("../../templates/reset.whitelabel.txt").into(),
url: format!("{}/login/reset/", config.hosts.app),
html: None,
},
deletion: Template {
title: "Confirm account deletion.".into(),
text: include_str!("../../templates/deletion.whitelabel.txt").into(),
text: include_str!("../../templates/deletion.whitelabel.txt")
.into(),
url: format!("{}/delete/", config.hosts.app),
html: None,
},
@@ -100,10 +100,6 @@ auto_derived!(
)]
role_permissions: HashMap<String, OverrideField>,
/// Category the channel is in
#[serde(skip_serializing_if = "Option::is_none")]
parent: Option<String>,
/// Whether this channel is marked as not safe for work
#[serde(skip_serializing_if = "crate::if_false", default)]
nsfw: bool,
@@ -135,10 +131,6 @@ auto_derived!(
)]
role_permissions: HashMap<String, OverrideField>,
/// Category the channel is in
#[serde(skip_serializing_if = "Option::is_none")]
parent: Option<String>,
/// Whether this channel is marked as not safe for work
#[serde(skip_serializing_if = "crate::if_false", default)]
nsfw: bool,
@@ -169,8 +161,6 @@ auto_derived!(
pub default_permissions: Option<OverrideField>,
#[serde(skip_serializing_if = "Option::is_none")]
pub last_message_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub parent: Option<String>,
}
/// Optional fields on channel object
@@ -229,7 +219,6 @@ impl Channel {
default_permissions: None,
role_permissions: HashMap::new(),
nsfw: data.nsfw.unwrap_or(false),
parent: None
},
v0::LegacyServerChannelType::Voice => Channel::VoiceChannel {
id: id.clone(),
@@ -240,7 +229,6 @@ impl Channel {
default_permissions: None,
role_permissions: HashMap::new(),
nsfw: data.nsfw.unwrap_or(false),
parent: None
},
};
@@ -449,13 +437,6 @@ impl Channel {
}
}
pub fn parent(&self) -> Option<&str> {
match self {
Channel::TextChannel { parent, .. } | Channel::VoiceChannel { parent, .. } => parent.as_deref(),
_ => None
}
}
/// Set role permission on a channel
pub async fn set_role_permission(
&mut self,
@@ -1,6 +1,6 @@
use std::collections::{HashMap, HashSet};
use revolt_models::v0::{self, DataCreateCategory, DataCreateServerChannel, DataEditCategory};
use revolt_models::v0::{self, DataCreateServerChannel};
use revolt_permissions::{OverrideField, DEFAULT_PERMISSION_SERVER};
use revolt_result::Result;
use ulid::Ulid;
@@ -26,8 +26,8 @@ auto_derived_partial!(
// TODO: investigate if this is redundant and can be removed
pub channels: Vec<String>,
/// Categories for this server
#[serde(skip_serializing_if = "HashMap::is_empty")]
pub categories: HashMap<String, Category>,
#[serde(skip_serializing_if = "Option::is_none")]
pub categories: Option<Vec<Category>>,
/// Configuration for sending system event messages
#[serde(skip_serializing_if = "Option::is_none")]
pub system_messages: Option<SystemMessageChannels>,
@@ -87,29 +87,17 @@ auto_derived_partial!(
"PartialRole"
);
auto_derived_partial!(
auto_derived!(
/// Channel category
pub struct Category {
/// Unique ID for this category
pub id: String,
/// Title for this category
pub title: String,
/// Default permissions assigned to users in this category
#[serde(skip_serializing_if = "Option::is_none")]
pub default_permissions: Option<OverrideField>,
/// Permissions assigned based on role to this category
#[serde(
default = "HashMap::<String, OverrideField>::new",
skip_serializing_if = "HashMap::<String, OverrideField>::is_empty"
)]
pub role_permissions: HashMap<String, OverrideField>,
/// Channels in this category
pub channels: Vec<String>,
},
"PartialCategory"
);
}
auto_derived!(
/// System message channel assignments
pub struct SystemMessageChannels {
/// ID of channel to send user join messages in
@@ -139,10 +127,6 @@ auto_derived!(
pub enum FieldsRole {
Colour,
}
pub enum FieldsCategory {
DefaultPermissions
}
);
#[allow(clippy::disallowed_methods)]
@@ -165,7 +149,7 @@ impl Server {
analytics: false,
banner: None,
categories: HashMap::new(),
categories: None,
discoverable: false,
flags: None,
icon: None,
@@ -237,7 +221,7 @@ impl Server {
pub fn remove_field(&mut self, field: &FieldsServer) {
match field {
FieldsServer::Description => self.description = None,
FieldsServer::Categories => self.categories.clear(),
FieldsServer::Categories => self.categories = None,
FieldsServer::SystemMessages => self.system_messages = None,
FieldsServer::Icon => self.icon = None,
FieldsServer::Banner => self.banner = None,
@@ -350,110 +334,6 @@ impl Role {
}
}
impl Category {
pub fn remove_field(&mut self, field: FieldsCategory) {
match field {
FieldsCategory::DefaultPermissions => self.default_permissions = None
};
}
pub async fn create(db: &Database, server: &mut Server, data: DataCreateCategory) -> Result<Category> {
let channels = data.channels.clone().unwrap_or_default()
.into_iter()
.filter(|c| server.channels.contains(&c))
.collect();
let category = Category {
id: Ulid::new().to_string(),
title: data.title,
channels: channels,
default_permissions: None,
role_permissions: HashMap::new()
};
server.categories.insert(category.id.clone(), category.clone());
let partial_server = PartialServer { categories: Some(server.categories.clone()), ..Default::default() };
db.update_server(&server.id, &partial_server, Vec::new()).await?;
EventV1::ServerUpdate {
id: server.id.clone(),
data: partial_server.into(),
clear: Vec::new(),
}
.p(server.id.clone())
.await;
Ok(category)
}
pub async fn delete(&self, db: &Database, server: &mut Server) -> Result<()> {
// update the parent server model with the new category
server.categories.remove(&self.id);
let partial_server = PartialServer { categories: Some(server.categories.clone()), ..Default::default() };
db.update_server(&server.id, &partial_server, Vec::new()).await?;
EventV1::ServerUpdate {
id: server.id.clone(),
data: partial_server.into(),
clear: Vec::new(),
}
.p(server.id.clone())
.await;
Ok(())
}
pub async fn update(&mut self, db: &Database, server: &mut Server, partial: PartialCategory, remove: Vec<FieldsCategory>) -> Result<()> {
for field in remove {
self.remove_field(field);
};
self.apply_options(partial);
// update the parent server model with the new category
server.categories.insert(self.id.clone(), self.clone());
let partial_server = PartialServer { categories: Some(server.categories.clone()), ..Default::default() };
db.update_server(&server.id, &partial_server, Vec::new()).await?;
EventV1::ServerUpdate {
id: server.id.clone(),
data: partial_server.into(),
clear: Vec::new(),
}
.p(server.id.clone())
.await;
Ok(())
}
pub async fn set_role_permission(&mut self, db: &Database, server: &mut Server, role_id: String, role_override: OverrideField) -> Result<()> {
self.role_permissions.insert(role_id, role_override);
// update the parent server model with the new category
server.categories.insert(self.id.clone(), self.clone());
let partial_server = PartialServer { categories: Some(server.categories.clone()), ..Default::default() };
db.update_server(&server.id, &partial_server, Vec::new()).await?;
EventV1::ServerUpdate {
id: server.id.clone(),
data: partial_server.into(),
clear: Vec::new(),
}
.p(server.id.clone())
.await;
Ok(())
}
}
impl SystemMessageChannels {
pub fn into_channel_ids(self) -> HashSet<String> {
let mut ids = HashSet::new();
+12 -34
View File
@@ -187,7 +187,6 @@ impl From<crate::Channel> for Channel {
last_message_id,
default_permissions,
role_permissions,
parent,
nsfw,
} => Channel::TextChannel {
id,
@@ -198,7 +197,6 @@ impl From<crate::Channel> for Channel {
last_message_id,
default_permissions,
role_permissions,
parent,
nsfw,
},
crate::Channel::VoiceChannel {
@@ -209,7 +207,6 @@ impl From<crate::Channel> for Channel {
icon,
default_permissions,
role_permissions,
parent,
nsfw,
} => Channel::VoiceChannel {
id,
@@ -219,7 +216,6 @@ impl From<crate::Channel> for Channel {
icon: icon.map(|file| file.into()),
default_permissions,
role_permissions,
parent,
nsfw,
},
}
@@ -271,7 +267,6 @@ impl From<Channel> for crate::Channel {
last_message_id,
default_permissions,
role_permissions,
parent,
nsfw,
} => crate::Channel::TextChannel {
id,
@@ -282,7 +277,6 @@ impl From<Channel> for crate::Channel {
last_message_id,
default_permissions,
role_permissions,
parent,
nsfw,
},
Channel::VoiceChannel {
@@ -293,7 +287,6 @@ impl From<Channel> for crate::Channel {
icon,
default_permissions,
role_permissions,
parent,
nsfw,
} => crate::Channel::VoiceChannel {
id,
@@ -303,7 +296,6 @@ impl From<Channel> for crate::Channel {
icon: icon.map(|file| file.into()),
default_permissions,
role_permissions,
parent,
nsfw,
},
}
@@ -323,7 +315,6 @@ impl From<crate::PartialChannel> for PartialChannel {
role_permissions: value.role_permissions,
default_permissions: value.default_permissions,
last_message_id: value.last_message_id,
parent: value.parent,
}
}
}
@@ -341,7 +332,6 @@ impl From<PartialChannel> for crate::PartialChannel {
role_permissions: value.role_permissions,
default_permissions: value.default_permissions,
last_message_id: value.last_message_id,
parent: value.parent,
}
}
}
@@ -739,7 +729,9 @@ impl From<crate::Server> for Server {
name: value.name,
description: value.description,
channels: value.channels,
categories: value.categories.into_iter().map(|(k, v)| (k, v.into())).collect(),
categories: value
.categories
.map(|categories| categories.into_iter().map(|v| v.into()).collect()),
system_messages: value.system_messages.map(|v| v.into()),
roles: value
.roles
@@ -765,7 +757,9 @@ impl From<Server> for crate::Server {
name: value.name,
description: value.description,
channels: value.channels,
categories: value.categories.into_iter().map(|(k, v)| (k, v.into())).collect(),
categories: value
.categories
.map(|categories| categories.into_iter().map(|v| v.into()).collect()),
system_messages: value.system_messages.map(|v| v.into()),
roles: value
.roles
@@ -791,7 +785,9 @@ impl From<crate::PartialServer> for PartialServer {
name: value.name,
description: value.description,
channels: value.channels,
categories: value.categories.map(|x| x.into_iter().map(|(k, v)| (k, v.into())).collect()),
categories: value
.categories
.map(|categories| categories.into_iter().map(|v| v.into()).collect()),
system_messages: value.system_messages.map(|v| v.into()),
roles: value
.roles
@@ -815,7 +811,9 @@ impl From<PartialServer> for crate::PartialServer {
name: value.name,
description: value.description,
channels: value.channels,
categories: value.categories.map(|x| x.into_iter().map(|(k, v)| (k, v.into())).collect()),
categories: value
.categories
.map(|categories| categories.into_iter().map(|v| v.into()).collect()),
system_messages: value.system_messages.map(|v| v.into()),
roles: value
.roles
@@ -861,8 +859,6 @@ impl From<crate::Category> for Category {
id: value.id,
title: value.title,
channels: value.channels,
role_permissions: value.role_permissions,
default_permissions: value.default_permissions,
}
}
}
@@ -873,24 +869,6 @@ impl From<Category> for crate::Category {
id: value.id,
title: value.title,
channels: value.channels,
role_permissions: value.role_permissions,
default_permissions: value.default_permissions,
}
}
}
impl From<crate::FieldsCategory> for FieldsCategory {
fn from(value: crate::FieldsCategory) -> Self {
match value {
crate::FieldsCategory::DefaultPermissions => FieldsCategory::DefaultPermissions,
}
}
}
impl From<FieldsCategory> for crate::FieldsCategory {
fn from(value: FieldsCategory) -> crate::FieldsCategory {
match value {
FieldsCategory::DefaultPermissions => crate::FieldsCategory::DefaultPermissions,
}
}
}
@@ -5,7 +5,7 @@ use revolt_permissions::{
DEFAULT_PERMISSION_DIRECT_MESSAGE,
};
use crate::{Category, Channel, Database, Member, Server, User};
use crate::{Channel, Database, Member, Server, User};
#[derive(Clone)]
pub struct BulkDatabasePermissionQuery<'a> {
@@ -197,7 +197,7 @@ async fn calculate_members_permissions<'a>(
) -> HashMap<String, PermissionValue> {
let mut resp = HashMap::new();
let (_, channel_role_permissions, channel_default_permissions, parent) = match query
let (_, channel_role_permissions, channel_default_permissions) = match query
.channel
.as_ref()
.expect("A channel must be assigned to calculate channel permissions")
@@ -207,16 +207,14 @@ async fn calculate_members_permissions<'a>(
id,
role_permissions,
default_permissions,
parent,
..
}
| Channel::VoiceChannel {
id,
role_permissions,
default_permissions,
parent,
..
} => (id, role_permissions, default_permissions, parent),
} => (id, role_permissions, default_permissions),
_ => panic!("Calculation of member permissions must be done on a server channel"),
};
@@ -270,8 +268,6 @@ async fn calculate_members_permissions<'a>(
.map(|m| (&m.id.user, m)),
);
let category = parent.map(|parent| query.server.categories.get(&parent).expect("Channel parent doesnt exist"));
for user in users {
let member = members.get(&user.id);
@@ -302,39 +298,11 @@ async fn calculate_members_permissions<'a>(
// Get the user's server permissions
let mut permission = calculate_server_permissions(&query.server, user, member);
// Apply category overrides
if let Some(category) = category {
if let Some(defaults) = category.default_permissions {
permission.apply(defaults.into());
};
// Get the applicable role overrides for the category
let mut roles = category.role_permissions
.iter()
.filter(|(id, _)| member.roles.contains(id))
.filter_map(|(id, permission)| {
query.server.roles.get(id).map(|role| {
let v: Override = (*permission).into();
(role.rank, v)
})
})
.collect::<Vec<(i64, Override)>>();
roles.sort_by(|a, b| b.0.cmp(&a.0));
let overrides = roles.into_iter().map(|(_, v)| v);
for role_override in overrides {
permission.apply(role_override)
}
};
// apply channel overrides
if let Some(defaults) = channel_default_permissions {
permission.apply(defaults.into());
}
// Get the applicable role overrides for the channel
// Get the applicable role overrides
let mut roles = channel_role_permissions
.iter()
.filter(|(id, _)| member.roles.contains(id))
+1 -52
View File
@@ -5,7 +5,7 @@ use revolt_permissions::{
RelationshipStatus, DEFAULT_PERMISSION_DIRECT_MESSAGE,
};
use crate::{Category, Channel, Database, Member, Server, User};
use crate::{Channel, Database, Member, Server, User};
/// Permissions calculator
#[derive(Clone)]
@@ -16,7 +16,6 @@ pub struct DatabasePermissionQuery<'a> {
perspective: &'a User,
user: Option<Cow<'a, User>>,
channel: Option<Cow<'a, Channel>>,
category: Option<Cow<'a, Category>>,
server: Option<Cow<'a, Server>>,
member: Option<Cow<'a, Member>>,
@@ -289,40 +288,6 @@ impl PermissionQuery for DatabasePermissionQuery<'_> {
}
}
async fn get_default_category_permissions(&mut self) -> Override {
if let Some(category) = &self.category {
category.default_permissions.unwrap_or_default().into()
} else {
Default::default()
}
}
async fn get_our_category_role_overrides(&mut self) -> Vec<Override> {
if let Some((server, category)) = self.server.as_ref().zip(self.category.as_ref()) {
let member_roles = self
.member
.as_ref()
.map(|member| member.roles.clone())
.unwrap_or_default();
let mut roles = category.role_permissions
.iter()
.filter(|(id, _)| member_roles.contains(id))
.filter_map(|(id, permission)| {
server.roles.get(id).map(|role| {
let v: Override = (*permission).into();
(role.rank, v)
})
})
.collect::<Vec<(i64, Override)>>();
roles.sort_by(|a, b| b.0.cmp(&a.0));
roles.into_iter().map(|(_, v)| v).collect()
} else {
vec![]
}
}
/// Do we own this group or saved messages channel if it is one of those?
async fn do_we_own_the_channel(&mut self) -> bool {
if let Some(channel) = &self.channel {
@@ -410,14 +375,6 @@ impl PermissionQuery for DatabasePermissionQuery<'_> {
}
}
}
async fn set_category_from_channel(&mut self) {
if let Some((server, channel)) = self.server.as_ref().zip(self.channel.as_ref()) {
let category = channel.parent().and_then(|id| server.categories.get(id)).cloned();
self.category = category.map(Cow::Owned);
}
}
}
impl<'a> DatabasePermissionQuery<'a> {
@@ -428,7 +385,6 @@ impl<'a> DatabasePermissionQuery<'a> {
perspective,
user: None,
channel: None,
category: None,
server: None,
member: None,
@@ -487,13 +443,6 @@ impl<'a> DatabasePermissionQuery<'a> {
}
}
pub fn category(self, category: &'a Category) -> DatabasePermissionQuery<'a> {
DatabasePermissionQuery {
category: Some(Cow::Borrowed(category)),
..self
}
}
/// Use member
pub fn member(self, member: &'a Member) -> DatabasePermissionQuery {
DatabasePermissionQuery {
+1 -1
View File
@@ -10,7 +10,7 @@ use schemars::{
};
use crate::{
Bot, Category, Channel, Database, Emoji, Invite, Member, Message, Server, ServerBan, User, Webhook
Bot, Channel, Database, Emoji, Invite, Member, Message, Server, ServerBan, User, Webhook,
};
/// Reference to some object in the database
@@ -0,0 +1,188 @@
<!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.5.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 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">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 tried to create a new account with or try to re-verify this email
but there is already an existing verified account under this 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>
<p class="" style="line-height: 24px; font-size: 16px; width: 100%; margin: 0;" align="left">
If you wish, you can now proceed to reset the password on it, 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 Europe
</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>
<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>
<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">
<div class="text-muted text-xs" style="color: #718096; font-size: 12px; line-height: 14.4px;">
Revolt Platforms Ltd. is a company incorporated and registered under the
laws of England and Wales.<br>
Registered Company Number: 16260658<br>
Registered Office:<br>
Suite 5703 Unit 3A, 34-35 Hatton Garden,<br>
Holborn, United Kingdom, EC1N 8DX
</div>
</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>
</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,41 @@
<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 tried to create a new account with or try to re-verify this email
but there is already an existing verified account under this email.
</p>
<p>
If you wish, you can now proceed to reset the password on it, click
below to continue.
</p>
<a class="btn btn-primary p-3 fw-700" href="{{url}}">Reset</a>
</div>
<div class="text-muted text-center my-6">
This email is intended for {{email}}<br />
Sent from Revolt<br />
Made in Europe
</div>
<div class="text-muted text-xs my-6 ax-center">
Revolt Platforms Ltd. is a company incorporated and registered under the
laws of England and Wales.<br />
Registered Company Number: 16260658<br />
Registered Office:<br />
Suite 5703 Unit 3A, 34-35 Hatton Garden,<br />
Holborn, United Kingdom, EC1N 8DX
</div>
</div>
</body>
</html>
@@ -0,0 +1,19 @@
You tried to create a new account with or try
to re-verify this email but there is already
an existing verified account under this email.
If you wish, you can now proceed to reset the
password on it, click below to continue.
Please navigate to: {{url}}
This email is intended for {{email}}
Sent by Revolt
Made in Europe
Revolt Platforms Ltd. is a company incorporated and registered under the laws of England and Wales.
Registration Number: 16260658
Registered Office:
Suite 5703 Unit 3A, 34-35 Hatton Garden,
Holborn, United Kingdom, EC1N 8DX
+3 -3
View File
@@ -1,6 +1,6 @@
[package]
name = "revolt-files"
version = "0.8.4"
version = "0.8.5"
edition = "2021"
license = "AGPL-3.0-or-later"
authors = ["Paul Makles <me@insrt.uk>"]
@@ -20,10 +20,10 @@ typenum = "1.17.0"
aws-config = "1.5.5"
aws-sdk-s3 = { version = "1.46.0", features = ["behavior-version-latest"] }
revolt-config = { version = "0.8.4", path = "../config", features = [
revolt-config = { version = "0.8.5", path = "../config", features = [
"report-macros",
] }
revolt-result = { version = "0.8.4", path = "../result" }
revolt-result = { version = "0.8.5", path = "../result" }
# image processing
jxl-oxide = "0.8.1"
+3 -3
View File
@@ -1,6 +1,6 @@
[package]
name = "revolt-models"
version = "0.8.4"
version = "0.8.5"
edition = "2021"
license = "MIT"
authors = ["Paul Makles <me@insrt.uk>"]
@@ -20,8 +20,8 @@ default = ["serde", "partials", "rocket"]
[dependencies]
# Core
revolt-config = { version = "0.8.4", path = "../config" }
revolt-permissions = { version = "0.8.4", path = "../permissions" }
revolt-config = { version = "0.8.5", path = "../config" }
revolt-permissions = { version = "0.8.5", path = "../permissions" }
# Utility
regex = "1.11"
-10
View File
@@ -101,10 +101,6 @@ auto_derived!(
)]
role_permissions: HashMap<String, OverrideField>,
/// Category the channel is in
#[serde(skip_serializing_if = "Option::is_none")]
parent: Option<String>,
/// Whether this channel is marked as not safe for work
#[cfg_attr(
feature = "serde",
@@ -142,10 +138,6 @@ auto_derived!(
)]
role_permissions: HashMap<String, OverrideField>,
/// Category the channel is in
#[serde(skip_serializing_if = "Option::is_none")]
parent: Option<String>,
/// Whether this channel is marked as not safe for work
#[cfg_attr(
feature = "serde",
@@ -178,8 +170,6 @@ auto_derived!(
pub default_permissions: Option<OverrideField>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub last_message_id: Option<String>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub parent: Option<String>
}
/// Optional fields on channel object
+7 -40
View File
@@ -28,8 +28,8 @@ auto_derived_partial!(
// TODO: investigate if this is redundant and can be removed
pub channels: Vec<String>,
/// Categories for this server
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "HashMap::is_empty"))]
pub categories: HashMap<String, Category>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub categories: Option<Vec<Category>>,
/// Configuration for sending system event messages
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub system_messages: Option<SystemMessageChannels>,
@@ -126,52 +126,15 @@ auto_derived!(
#[cfg_attr(feature = "validator", derive(Validate))]
pub struct Category {
/// Unique ID for this category
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 32)))]
pub id: String,
/// Title for this category
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 32)))]
pub title: String,
/// Default permissions assigned to users in this channel
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub default_permissions: Option<OverrideField>,
/// Permissions assigned based on role to this channel
#[cfg_attr(
feature = "serde",
serde(
default = "HashMap::<String, OverrideField>::new",
skip_serializing_if = "HashMap::<String, OverrideField>::is_empty"
)
)]
pub role_permissions: HashMap<String, OverrideField>,
/// Channels in this category
pub channels: Vec<String>,
}
#[cfg_attr(feature = "validator", derive(Validate))]
pub struct DataCreateCategory {
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 32)))]
pub title: String,
/// Channels in this category
pub channels: Option<Vec<String>>,
}
#[cfg_attr(feature = "validator", derive(Validate))]
pub struct DataEditCategory {
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 32)))]
pub title: Option<String>,
/// Channels in this category
pub channels: Option<Vec<String>>,
/// Fields to remove from category object
pub remove: Option<Vec<FieldsCategory>>,
}
pub enum FieldsCategory {
DefaultPermissions
}
pub struct DataDefaultCategoryPermissions {
pub permissions: Override
}
/// System message channel assignments
pub struct SystemMessageChannels {
/// ID of channel to send user join messages in
@@ -263,6 +226,10 @@ auto_derived!(
pub icon: Option<String>,
/// Attachment Id for banner
pub banner: Option<String>,
/// Category structure for server
#[cfg_attr(feature = "validator", validate)]
pub categories: Option<Vec<Category>>,
/// System message configuration
pub system_messages: Option<SystemMessageChannels>,
+2 -2
View File
@@ -1,7 +1,7 @@
[package]
name = "revolt-parser"
version = "0.1.0"
version = "0.1.1"
edition = "2021"
[dependencies]
logos = { version = "*" }
logos = { version = "*" }
+2 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "revolt-permissions"
version = "0.8.4"
version = "0.8.5"
edition = "2021"
license = "MIT"
authors = ["Paul Makles <me@insrt.uk>"]
@@ -21,7 +21,7 @@ async-std = { version = "1.8.0", features = ["attributes"] }
[dependencies]
# Core
revolt-result = { version = "0.8.4", path = "../result" }
revolt-result = { version = "0.8.5", path = "../result" }
# Utility
auto_ops = "0.3.0"
+1 -29
View File
@@ -68,26 +68,6 @@ pub async fn calculate_server_permissions<P: PermissionQuery>(query: &mut P) ->
permissions
}
pub async fn calculate_category_permissions<P: PermissionQuery>(query: &mut P) -> PermissionValue {
let mut permissions = calculate_server_permissions(query).await;
permissions.apply(query.get_default_category_permissions().await);
for role_override in query.get_our_category_role_overrides().await {
permissions.apply(role_override)
}
if !permissions.has_channel_permission(ChannelPermission::ViewChannel) {
permissions.revoke_all();
}
if query.are_we_timed_out().await {
permissions.restrict(*ALLOW_IN_TIMEOUT);
}
permissions
}
/// Calculate permissions against a channel
pub async fn calculate_channel_permissions<P: PermissionQuery>(query: &mut P) -> PermissionValue {
if query.are_we_privileged().await {
@@ -129,19 +109,11 @@ pub async fn calculate_channel_permissions<P: PermissionQuery>(query: &mut P) ->
}
ChannelType::ServerChannel => {
query.set_server_from_channel().await;
query.set_category_from_channel().await;
if query.are_we_server_owner().await {
ChannelPermission::GrantAllSafe.into()
} else if query.are_we_a_member().await {
let mut permissions = calculate_server_permissions(query).await;
permissions.apply(query.get_default_category_permissions().await);
for role_override in query.get_our_category_role_overrides().await {
permissions.apply(role_override)
}
permissions.apply(query.get_default_channel_permissions().await);
for role_override in query.get_our_channel_role_overrides().await {
@@ -163,4 +135,4 @@ pub async fn calculate_channel_permissions<P: PermissionQuery>(query: &mut P) ->
}
ChannelType::Unknown => 0_u64.into(),
}
}
}
@@ -96,10 +96,8 @@ pub enum ChannelPermission {
/// Mention roles
MentionRoles = 1 << 38,
MoveChannels = 1 << 39,
// * Misc. permissions
// % Bits 40 to 52: free area
// % Bits 38 to 52: free area
// % Bits 53 to 64: do not use
// * Grant all permissions
-157
View File
@@ -91,18 +91,6 @@ async fn validate_user_permissions() {
async fn set_server_from_channel(&mut self) {
unreachable!()
}
async fn get_default_category_permissions(&mut self) -> Override {
unreachable!()
}
async fn get_our_category_role_overrides(&mut self) -> Vec<Override> {
unreachable!()
}
async fn set_category_from_channel(&mut self) {
unreachable!()
}
}
}
@@ -195,18 +183,6 @@ async fn validate_group_permissions() {
async fn set_server_from_channel(&mut self) {
unreachable!()
}
async fn get_default_category_permissions(&mut self) -> Override {
unreachable!()
}
async fn get_our_category_role_overrides(&mut self) -> Vec<Override> {
unreachable!()
}
async fn set_category_from_channel(&mut self) {
unreachable!()
}
}
}
@@ -311,18 +287,6 @@ async fn validate_server_permissions() {
async fn set_server_from_channel(&mut self) {
// no-op
}
async fn get_default_category_permissions(&mut self) -> Override {
Override { allow: 0, deny: 0 }
}
async fn get_our_category_role_overrides(&mut self) -> Vec<Override> {
vec![]
}
async fn set_category_from_channel(&mut self) {
// no-op
}
}
}
@@ -409,126 +373,5 @@ async fn validate_timed_out_member() {
async fn set_server_from_channel(&mut self) {
// no-op
}
async fn get_default_category_permissions(&mut self) -> Override {
Override { allow: 0, deny: 0 }
}
async fn get_our_category_role_overrides(&mut self) -> Vec<Override> {
vec![]
}
async fn set_category_from_channel(&mut self) {
// no-op
}
}
}
#[async_std::test]
async fn validate_category_permissions() {
/// Scenario in which we are in a channel in a server and has a role
/// Server defaults let us send messages, view channels
/// Category default denies sending, allows reacting
/// Category role allows sending message
struct Scenario {}
let mut query = Scenario {};
let perms = calculate_channel_permissions(&mut query).await;
let value: u64 = perms.into();
assert_eq!(value, ChannelPermission::ViewChannel as u64 | ChannelPermission::React as u64 | ChannelPermission::SendMessage as u64);
#[async_trait]
impl PermissionQuery for Scenario {
async fn are_we_privileged(&mut self) -> bool {
false
}
async fn are_we_a_bot(&mut self) -> bool {
unreachable!()
}
async fn are_the_users_same(&mut self) -> bool {
unreachable!()
}
async fn user_relationship(&mut self) -> RelationshipStatus {
unreachable!()
}
async fn user_is_bot(&mut self) -> bool {
unreachable!()
}
async fn have_mutual_connection(&mut self) -> bool {
unreachable!()
}
async fn are_we_server_owner(&mut self) -> bool {
false
}
async fn are_we_a_member(&mut self) -> bool {
true
}
async fn get_default_server_permissions(&mut self) -> u64 {
ChannelPermission::SendMessage as u64 | ChannelPermission::ViewChannel as u64
}
async fn get_our_server_role_overrides(&mut self) -> Vec<Override> {
vec![]
}
async fn are_we_timed_out(&mut self) -> bool {
false
}
async fn get_channel_type(&mut self) -> ChannelType {
ChannelType::ServerChannel
}
async fn get_default_channel_permissions(&mut self) -> Override {
Override { allow: 0, deny: 0 }
}
async fn get_our_channel_role_overrides(&mut self) -> Vec<Override> {
vec![]
}
async fn do_we_own_the_channel(&mut self) -> bool {
unreachable!()
}
async fn are_we_part_of_the_channel(&mut self) -> bool {
unreachable!()
}
async fn set_recipient_as_user(&mut self) {
unreachable!()
}
async fn set_server_from_channel(&mut self) {
// no-op
}
async fn get_default_category_permissions(&mut self) -> Override {
Override {
allow: ChannelPermission::React as u64,
deny: ChannelPermission::SendMessage as u64
}
}
async fn get_our_category_role_overrides(&mut self) -> Vec<Override> {
vec![
Override {
allow: ChannelPermission::SendMessage as u64,
deny: 0
}
]
}
async fn set_category_from_channel(&mut self) {
// no-op
}
}
}
-9
View File
@@ -51,12 +51,6 @@ pub trait PermissionQuery {
/// Get the ordered role overrides (from lowest to highest) for this member in this channel
async fn get_our_channel_role_overrides(&mut self) -> Vec<Override>;
/// Get the default category permissions
async fn get_default_category_permissions(&mut self) -> Override;
/// Get the ordered role overrides (from lowest to highest) for this member in this category
async fn get_our_category_role_overrides(&mut self) -> Vec<Override>;
/// Do we own this group or saved messages channel if it is one of those?
async fn do_we_own_the_channel(&mut self) -> bool;
@@ -70,7 +64,4 @@ pub trait PermissionQuery {
/// Set the current server as the server owning this channel
/// (this will only ever be called for server channels, use unimplemented!() for other code paths)
async fn set_server_from_channel(&mut self);
//// SEts the current category as the category the channel is in
async fn set_category_from_channel(&mut self);
}
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "revolt-presence"
version = "0.8.4"
version = "0.8.5"
edition = "2021"
license = "AGPL-3.0-or-later"
authors = ["Paul Makles <me@insrt.uk>"]
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "revolt-result"
version = "0.8.4"
version = "0.8.5"
edition = "2021"
license = "MIT"
authors = ["Paul Makles <me@insrt.uk>"]
-2
View File
@@ -42,14 +42,12 @@ impl IntoResponse for Error {
ErrorType::Banned => StatusCode::FORBIDDEN,
ErrorType::AlreadyInServer => StatusCode::CONFLICT,
ErrorType::CannotTimeoutYourself => StatusCode::BAD_REQUEST,
ErrorType::UnknownCategory => StatusCode::NOT_FOUND,
ErrorType::TooManyServers { .. } => StatusCode::BAD_REQUEST,
ErrorType::TooManyEmbeds { .. } => StatusCode::BAD_REQUEST,
ErrorType::TooManyEmoji { .. } => StatusCode::BAD_REQUEST,
ErrorType::TooManyChannels { .. } => StatusCode::BAD_REQUEST,
ErrorType::TooManyRoles { .. } => StatusCode::BAD_REQUEST,
ErrorType::TooManyCategories { .. } => StatusCode::BAD_REQUEST,
ErrorType::ReachedMaximumBots => StatusCode::BAD_REQUEST,
ErrorType::IsBot => StatusCode::BAD_REQUEST,
-4
View File
@@ -115,10 +115,6 @@ pub enum ErrorType {
TooManyRoles {
max: usize,
},
TooManyCategories {
max: usize,
},
UnknownCategory,
AlreadyInServer,
CannotTimeoutYourself,
+1 -3
View File
@@ -49,14 +49,12 @@ impl<'r> Responder<'r, 'static> for Error {
ErrorType::Banned => Status::Forbidden,
ErrorType::AlreadyInServer => Status::Conflict,
ErrorType::CannotTimeoutYourself => Status::BadRequest,
ErrorType::UnknownCategory => Status::NotFound,
ErrorType::TooManyServers { .. } => Status::BadRequest,
ErrorType::TooManyEmbeds { .. } => Status::BadRequest,
ErrorType::TooManyEmoji { .. } => Status::BadRequest,
ErrorType::TooManyChannels { .. } => Status::BadRequest,
ErrorType::TooManyRoles { .. } => Status::BadRequest,
ErrorType::TooManyCategories { .. } => Status::BadRequest,
ErrorType::ReachedMaximumBots => Status::BadRequest,
ErrorType::IsBot => Status::BadRequest,
+5 -5
View File
@@ -1,6 +1,6 @@
[package]
name = "revolt-crond"
version = "0.8.4"
version = "0.8.5"
license = "AGPL-3.0-or-later"
authors = ["Paul Makles <me@insrt.uk>"]
edition = "2021"
@@ -16,7 +16,7 @@ log = "0.4"
tokio = { version = "1" }
# Core
revolt-database = { version = "0.8.4", path = "../../core/database" }
revolt-result = { version = "0.8.4", path = "../../core/result" }
revolt-config = { version = "0.8.4", path = "../../core/config" }
revolt-files = { version = "0.8.4", path = "../../core/files" }
revolt-database = { version = "0.8.5", path = "../../core/database" }
revolt-result = { version = "0.8.5", path = "../../core/result" }
revolt-config = { version = "0.8.5", path = "../../core/config" }
revolt-files = { version = "0.8.5", path = "../../core/files" }
+5 -5
View File
@@ -1,16 +1,16 @@
[package]
name = "revolt-pushd"
version = "0.8.4"
version = "0.8.5"
edition = "2021"
license = "AGPL-3.0-or-later"
[dependencies]
revolt-config = { version = "0.8.4", path = "../../core/config" }
revolt-database = { version = "0.8.4", path = "../../core/database" }
revolt-models = { version = "0.8.4", path = "../../core/models", features = [
revolt-config = { version = "0.8.5", path = "../../core/config" }
revolt-database = { version = "0.8.5", path = "../../core/database" }
revolt-models = { version = "0.8.5", path = "../../core/models", features = [
"validator",
] }
revolt-presence = { version = "0.8.1", path = "../../core/presence", features = [
revolt-presence = { version = "0.8.5", path = "../../core/presence", features = [
"redis-is-patched",
] }
+4 -3
View File
@@ -1,6 +1,6 @@
[package]
name = "revolt-delta"
version = "0.8.4"
version = "0.8.5"
license = "AGPL-3.0-or-later"
authors = ["Paul Makles <paulmakles@gmail.com>"]
edition = "2018"
@@ -52,10 +52,11 @@ async-std = { version = "1.8.0", features = [
lettre = "0.10.0-alpha.4"
# web
rocket = { version = "0.5.1", default-features = false, features = ["json"] }
rocket_cors = { git = "https://github.com/lawliet89/rocket_cors", rev = "072d90359b23e9b291df6b672c07c93de9c46011" }
rocket_empty = { version = "0.1.1", features = ["schema"] }
rocket_authifier = { version = "1.0.10" }
rocket_authifier = { version = "1.0.13" }
rocket_prometheus = "0.10.0-rc.3"
# spec generation
@@ -66,7 +67,7 @@ revolt_rocket_okapi = { version = "0.10.0", features = ["swagger"] }
amqprs = { version = "1.7.0" }
# core
authifier = "1.0.10"
authifier = "1.0.13"
revolt-config = { path = "../core/config" }
revolt-database = { path = "../core/database", features = [
"rocket-impl",
@@ -192,7 +192,6 @@ mod test {
d: ChannelPermission::ViewChannel as i64,
}),
last_message_id: None,
parent: None,
};
locked_channel
.update(&harness.db, partial, vec![])
@@ -1,45 +0,0 @@
use revolt_config::config;
use revolt_database::{
util::{permissions::DatabasePermissionQuery, reference::Reference}, Category, Database, Role, User
};
use revolt_models::v0;
use revolt_permissions::{calculate_server_permissions, ChannelPermission};
use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
use validator::Validate;
/// # Create a category
///
/// Creates a new server category.
#[openapi(tag = "Server Categories")]
#[post("/<target>/categories", data = "<data>")]
pub async fn create(
db: &State<Database>,
user: User,
target: Reference,
data: Json<v0::DataCreateCategory>,
) -> Result<Json<v0::Category>> {
let data = data.into_inner();
data.validate().map_err(|error| {
create_error!(FailedValidation {
error: error.to_string()
})
})?;
let mut server = target.as_server(db).await?;
let mut query = DatabasePermissionQuery::new(db, &user).server(&server);
calculate_server_permissions(&mut query)
.await
.throw_if_lacking_channel_permission(ChannelPermission::ManageChannel)?;
let config = config().await;
if server.categories.len() >= config.features.limits.global.server_categories {
return Err(create_error!(TooManyCategories {
max: config.features.limits.global.server_categories,
}));
};
let category = Category::create(db, &mut server, data).await?;
Ok(Json(category.into()))
}
@@ -1,38 +0,0 @@
use revolt_config::config;
use revolt_database::{
util::{permissions::DatabasePermissionQuery, reference::Reference}, Category, Database, PartialCategory, Role, User
};
use revolt_models::v0::{self, DataEditCategory};
use revolt_permissions::{calculate_server_permissions, ChannelPermission};
use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
use rocket_empty::EmptyResponse;
use validator::Validate;
/// # Edits a category
///
/// Edits a server category.
#[openapi(tag = "Server Categories")]
#[delete("/<server>/categories/<category>")]
pub async fn delete(
db: &State<Database>,
user: User,
server: Reference,
category: String
) -> Result<EmptyResponse> {
let mut server = server.as_server(db).await?;
let category = server.categories
.get(&category)
.ok_or(create_error!(UnknownCategory))?
.clone();
let mut query = DatabasePermissionQuery::new(db, &user).server(&server);
calculate_server_permissions(&mut query)
.await
.throw_if_lacking_channel_permission(ChannelPermission::ManageChannel)?;
category.delete(db, &mut server).await?;
Ok(EmptyResponse)
}
@@ -1,107 +0,0 @@
use revolt_config::config;
use revolt_database::{
util::{permissions::DatabasePermissionQuery, reference::Reference}, Category, Channel, Database, PartialCategory, PartialChannel, Role, User
};
use revolt_models::v0::{self, DataEditCategory};
use revolt_permissions::{calculate_server_permissions, ChannelPermission};
use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
use validator::Validate;
/// # Edits a category
///
/// Edits a server category.
#[openapi(tag = "Server Categories")]
#[patch("/<server>/categories/<category>", data = "<data>")]
pub async fn edit(
db: &State<Database>,
user: User,
server: Reference,
category: String,
data: Json<v0::DataEditCategory>,
) -> Result<Json<v0::Category>> {
let data = data.into_inner();
data.validate().map_err(|error| {
create_error!(FailedValidation {
error: error.to_string()
})
})?;
let mut server = server.as_server(db).await?;
let mut category = server.categories
.get(&category)
.ok_or(create_error!(UnknownCategory))?
.clone();
let mut query = DatabasePermissionQuery::new(db, &user).server(&server).category(&category);
let permissions = calculate_server_permissions(&mut query)
.await;
permissions.throw_if_lacking_channel_permission(ChannelPermission::ManageChannel)?;
let DataEditCategory {
title,
mut channels,
remove
} = data;
if channels.is_some() {
permissions.throw_if_lacking_channel_permission(ChannelPermission::MoveChannels)?;
}
// if any channel we are adding to this category are in another category we need to make sure we have MoveChannels in the category before moving it over.
for category in server.categories.values() {
if category.channels.iter().any(|c| channels.as_ref().is_some_and(|cs| cs.contains(c))) {
let mut category_query = query.clone().category(category);
calculate_server_permissions(&mut category_query)
.await
.throw_if_lacking_channel_permission(ChannelPermission::MoveChannels)?;
}
}
// remove the channels from any existing categories to avoid it having two
for category in server.categories.values_mut() {
category.channels.retain(|c| channels.as_ref().is_some_and(|cs| cs.contains(c)));
}
// only keep channels which exist in the server
if let Some(channels) = &mut channels {
channels.retain(|c| server.channels.contains(c));
}
// unset parent from all channels which are removed from the category
for channel_id in &category.channels {
if channels.as_ref().is_some_and(|cs| !cs.contains(channel_id)) {
db.update_channel(channel_id, &PartialChannel { parent: None, ..Default::default() }, Vec::new()).await?;
};
};
// update the category with the new values
category.update(
db,
&mut server,
PartialCategory {
title,
channels: channels.clone(),
..Default::default()
},
remove
.map(|v| v.into_iter().map(Into::into).collect())
.unwrap_or_default()
).await?;
let channels = db.fetch_channels(&channels.unwrap_or_default()).await?;
// update all channels to have the parent set
for channel in channels {
if let Channel::TextChannel { ref parent, .. } | Channel::VoiceChannel { ref parent, .. } = channel {
if parent.as_ref() != Some(&category.id) {
db.update_channel(channel.id(), &PartialChannel { parent: Some(category.id.clone()), ..Default::default() }, Vec::new()).await?;
};
};
};
Ok(Json(category.into()))
}
@@ -1,50 +0,0 @@
use revolt_database::{
util::{permissions::DatabasePermissionQuery, reference::Reference}, Database, User
};
use revolt_models::v0;
use revolt_permissions::{calculate_category_permissions, ChannelPermission, Override};
use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
/// # Set Role Permission
///
/// Sets permissions for the specified role in this channel.
///
/// Channel must be a `TextChannel` or `VoiceChannel`.
#[openapi(tag = "Channel Permissions")]
#[put("/<server>/categories/<category>/permissions/<role_id>", data = "<data>", rank = 2)]
pub async fn set_role_permissions(
db: &State<Database>,
user: User,
server: Reference,
category: String,
role_id: String,
data: Json<v0::DataSetRolePermissions>,
) -> Result<Json<v0::Category>> {
let mut server = server.as_server(db).await?;
let mut category = server.categories.get(&category).ok_or(create_error!(UnknownCategory))?.clone();
let mut query = DatabasePermissionQuery::new(db, &user).server(&server).category(&category);
let permissions = calculate_category_permissions(&mut query).await;
permissions.throw_if_lacking_channel_permission(ChannelPermission::ManagePermissions)?;
if let Some(role) = server.roles.get(&role_id) {
if role.rank <= query.get_member_rank().unwrap_or(i64::MIN) {
return Err(create_error!(NotElevated));
}
let current_value: Override = role.permissions.into();
permissions
.throw_permission_override(current_value, &data.permissions)
.await?;
category
.set_role_permission(db, &mut server, role_id, data.permissions.clone().into())
.await?;
Ok(Json(category.into()))
} else {
Err(create_error!(NotFound))
}
}
@@ -1,50 +0,0 @@
use revolt_database::{
util::{permissions::DatabasePermissionQuery, reference::Reference}, Channel, Database, PartialCategory, PartialChannel, User
};
use revolt_models::v0::{self, DataDefaultChannelPermissions};
use revolt_permissions::{calculate_category_permissions, calculate_channel_permissions, ChannelPermission};
use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
/// # Set Default Permission
///
/// Sets permissions for the default role in this channel.
///
/// Channel must be a `Group`, `TextChannel` or `VoiceChannel`.
#[openapi(tag = "Category Permissions")]
#[put("/<server>/categories/<target>/permissions/default", data = "<data>", rank = 1)]
pub async fn set_default_permissions(
db: &State<Database>,
user: User,
server: Reference,
target: String,
data: Json<v0::DataDefaultCategoryPermissions>,
) -> Result<Json<v0::Category>> {
let data = data.into_inner();
let mut server = server.as_server(db).await?;
let mut category = server.categories.get(&target).ok_or(create_error!(UnknownCategory))?.clone();
let mut query = DatabasePermissionQuery::new(db, &user).server(&server).category(&category);
let permissions = calculate_category_permissions(&mut query).await;
permissions.throw_if_lacking_channel_permission(ChannelPermission::ManagePermissions)?;
permissions
.throw_permission_override(category.default_permissions.map(|x| x.into()), &data.permissions)
.await?;
category
.update(
db,
&mut server,
PartialCategory {
default_permissions: Some(data.permissions.into()),
..Default::default()
},
vec![],
)
.await?;
Ok(Json(category.into()))
}
+1 -11
View File
@@ -23,11 +23,6 @@ mod server_create;
mod server_delete;
mod server_edit;
mod server_fetch;
mod category_create;
mod category_edit;
mod category_delete;
mod category_permissions_set_default;
mod category_permissions_set;
pub fn routes() -> (Vec<Route>, OpenApi) {
openapi_get_routes_spec![
@@ -52,11 +47,6 @@ pub fn routes() -> (Vec<Route>, OpenApi) {
roles_delete::delete,
permissions_set::set_role_permission,
permissions_set_default::set_default_permissions,
emoji_list::list_emoji,
category_create::create,
category_edit::edit,
category_delete::delete,
category_permissions_set_default::set_default_permissions,
category_permissions_set::set_role_permissions,
emoji_list::list_emoji
]
}
+27 -1
View File
@@ -1,4 +1,4 @@
use std::collections::{HashMap, HashSet};
use std::collections::HashSet;
use revolt_database::{
util::{permissions::DatabasePermissionQuery, reference::Reference},
@@ -38,6 +38,7 @@ pub async fn edit(
&& data.icon.is_none()
&& data.banner.is_none()
&& data.system_messages.is_none()
&& data.categories.is_none()
// && data.nsfw.is_none()
&& data.flags.is_none()
&& data.analytics.is_none()
@@ -62,11 +63,18 @@ pub async fn edit(
{
return Err(create_error!(NotPrivileged));
}
// Changing categories requires manage channel
if data.categories.is_some() {
permissions.throw_if_lacking_channel_permission(ChannelPermission::ManageChannel)?;
}
let v0::DataEditServer {
name,
description,
icon,
banner,
categories,
system_messages,
flags,
// nsfw,
@@ -78,6 +86,7 @@ pub async fn edit(
let mut partial = PartialServer {
name,
description,
categories: categories.map(|v| v.into_iter().map(Into::into).collect()),
system_messages: system_messages.map(Into::into),
flags,
// nsfw,
@@ -110,6 +119,23 @@ pub async fn edit(
}
}
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(create_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 {
partial.icon = Some(File::use_server_icon(db, &icon, &server.id, &user.id).await?);
+5 -5
View File
@@ -1,6 +1,6 @@
[package]
name = "revolt-autumn"
version = "0.8.4"
version = "0.8.5"
edition = "2021"
license = "AGPL-3.0-or-later"
@@ -43,12 +43,12 @@ tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
# Core crates
revolt-files = { version = "0.8.4", path = "../../core/files" }
revolt-config = { version = "0.8.4", path = "../../core/config" }
revolt-database = { version = "0.8.4", path = "../../core/database", features = [
revolt-files = { version = "0.8.5", path = "../../core/files" }
revolt-config = { version = "0.8.5", path = "../../core/config" }
revolt-database = { version = "0.8.5", path = "../../core/database", features = [
"axum-impl",
] }
revolt-result = { version = "0.8.4", path = "../../core/result", features = [
revolt-result = { version = "0.8.5", path = "../../core/result", features = [
"utoipa",
"axum",
] }
+5 -5
View File
@@ -1,6 +1,6 @@
[package]
name = "revolt-january"
version = "0.8.4"
version = "0.8.5"
edition = "2021"
license = "AGPL-3.0-or-later"
@@ -32,13 +32,13 @@ tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
# Core crates
revolt-config = { version = "0.8.4", path = "../../core/config" }
revolt-models = { version = "0.8.4", path = "../../core/models" }
revolt-result = { version = "0.8.4", path = "../../core/result", features = [
revolt-config = { version = "0.8.5", path = "../../core/config" }
revolt-models = { version = "0.8.5", path = "../../core/models" }
revolt-result = { version = "0.8.5", path = "../../core/result", features = [
"utoipa",
"axum",
] }
revolt-files = { version = "0.8.4", path = "../../core/files" }
revolt-files = { version = "0.8.5", path = "../../core/files" }
# Axum / web server
axum = { version = "0.7.5" }
+7 -6
View File
@@ -22,13 +22,13 @@ tools() {
deps() {
mkdir -p \
crates/bindings/node/src \
crates/bonfire/src \
crates/delta/src \
crates/core/config/src \
crates/core/database/src \
crates/core/files/src \
crates/core/models/src \
crates/core/parser/src \
crates/core/permissions/src \
crates/core/presence/src \
crates/core/result/src \
@@ -44,19 +44,19 @@ deps() {
tee crates/daemons/crond/src/main.rs |
tee crates/daemons/pushd/src/main.rs
echo '' |
tee crates/bindings/node/src/lib.rs |
tee crates/core/config/src/lib.rs |
tee crates/core/database/src/lib.rs |
tee crates/core/files/src/lib.rs |
tee crates/core/models/src/lib.rs |
tee crates/core/parser/src/lib.rs |
tee crates/core/permissions/src/lib.rs |
tee crates/core/presence/src/lib.rs |
tee crates/core/result/src/lib.rs
if [ -z "$TARGETARCH" ]; then
cargo build --locked --release
cargo build -j 10 --locked --release
else
cargo build --locked --release --target "${BUILD_TARGET}"
cargo build -j 10 --locked --release --target "${BUILD_TARGET}"
fi
}
@@ -69,14 +69,15 @@ apps() {
crates/core/config/src/lib.rs \
crates/core/database/src/lib.rs \
crates/core/models/src/lib.rs \
crates/core/parser/src/lib.rs \
crates/core/permissions/src/lib.rs \
crates/core/presence/src/lib.rs \
crates/core/result/src/lib.rs
if [ -z "$TARGETARCH" ]; then
cargo build --locked --release
cargo build -j 10 --locked --release
else
cargo build --locked --release --target "${BUILD_TARGET}"
cargo build -j 10 --locked --release --target "${BUILD_TARGET}"
mv target _target && mv _target/"${BUILD_TARGET}" target
fi
}