Compare commits

...
37 Commits
Author SHA1 Message Date
izzy 717d5e9a88 chore: bump version to 0.8.4
chore: enable webhooks in test builds
2025-04-02 12:11:46 +01:00
Paul Makles 18f06467bb Merge pull request #408 from shaksternano/main
feat: add option to send message with missing replies
2025-04-02 12:05:30 +01:00
Builderb 8d88ea9963 feat: allow bots to manage emojis (#407)
* Remove bot check on emoji create

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

* Remove bot check on emoji delete

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

---------

Signed-off-by: Builderb <builderbgamer@gmail.com>
2025-04-02 12:05:04 +01:00
Paul Makles e3e1cab02e Merge pull request #399 from StupidRepo/main
fix: allow message pinning and unpinning for DM-like channels
2025-04-02 12:04:44 +01:00
Nils Ponsard 00bd56b687 feat: allow to set an icon when creating a group
Signed-off-by: Nils Ponsard <nilsponsard@gmail.com>
2025-04-02 12:03:38 +01:00
ShaksterNano a7727bba5c feat: add option to send message with missing replies
Signed-off-by: ShaksterNano <54268387+shaksternano@users.noreply.github.com>
2025-03-31 22:26:30 +01:00
Paul Makles 6b8005f530 Merge pull request #403 from phazeschift/tests
test: Add tests for invite/fetch
2025-03-31 11:42:24 +00:00
phazeschift 714fba7ea6 test: add delete server channel test
Signed-off-by: phazeschift <51278042+phazeschift@users.noreply.github.com>
2025-03-16 14:15:45 -07:00
phazeschift ac05fb385c test: add utilities for common setup
Signed-off-by: phazeschift <51278042+phazeschift@users.noreply.github.com>
2025-03-15 19:08:23 -07:00
phazeschift 537c147c4b test: Add tests for invite/fetch
Signed-off-by: phazeschift <51278042+phazeschift@users.noreply.github.com>
2025-03-14 17:17:44 -07:00
Bradlee Barnes 7bee609b84 Merge branch 'revoltchat:main' into main 2025-03-11 02:32:47 +00:00
IAmTomahawkx 859c0214ec chore: ignore python venv and dev script for user generation
Signed-off-by: IAmTomahawkx <iamtomahawkx@gmail.com>
2025-03-09 21:40:21 -07:00
Bradlee Barnes 5eea0bbc9a fix: remove SavedMessages and Group due to given permissions
Signed-off-by: Bradlee Barnes <69256931+StupidRepo@users.noreply.github.com>
2025-03-08 00:50:57 +00:00
Bradlee Barnes 15dec656a8 fix: allow message pinning and unpinning for DM-like channels
Signed-off-by: Bradlee Barnes <69256931+StupidRepo@users.noreply.github.com>
2025-03-07 22:39:04 +00:00
Paul Makles bcf6561c0c fix: include production default value for config 2025-02-25 14:24:05 +00:00
Paul Makles b177a3e201 fix: don't bump the lockfile version 2025-02-25 11:08:00 +00:00
Paul Makles d1236bf8f2 chore: bump version to 0.8.3 2025-02-24 22:18:10 +00:00
Paul Makles 01490f5723 feat: add company information to email footers 2025-02-24 22:17:48 +00:00
Paul Makles 3ceefb840b chore: clarify cargo deny / tomls 2025-02-18 11:53:21 +00:00
Paul Makles eb5f5f91cd feat: add crond container definitions 2025-02-10 20:28:48 +00:00
Paul Makles 52e1f0ddde merge: branch 'insert/feat/crond' 2025-02-10 19:54:40 +00:00
Paul Makles defc9ec79b fix: match new error type for status code 2025-02-10 19:05:21 +00:00
Paul Makles 7026961df4 chore: fix version references 2025-02-10 19:03:48 +00:00
Paul Makles 078380d305 merge: branch 'main' into insert/feat/crond 2025-02-10 19:02:19 +00:00
Paul Makles add3f40b23 fix: don't allow users to time themselves out
closes #376
2025-02-10 18:54:15 +00:00
Paul Makles 7216e9909b chore: disable LTO because it likely overloads GitHub worker 2025-02-10 18:50:19 +00:00
Paul Makles a1e6a19210 chore: bump version to 0.8.2 2025-02-10 18:37:21 +00:00
Paul Makles 5f39403ce7 fix: ensure ratelimiter adjusts for version prefix 2025-02-10 18:34:37 +00:00
Paul Makles e525ffe5e4 chore: mount API at /0.8/ as well 2025-02-10 18:19:34 +00:00
Paul Makles e3723d647e fix: change permission check for fetching channel webhooks 2025-02-10 18:04:46 +00:00
Paul Makles 5f84daa9db fix: ensure limit is always at least 1 when sent to database 2025-02-10 17:51:22 +00:00
Paul Makles e957af4ca3 docs: add some notes on overrides; fix defaults 2025-02-10 17:21:23 +00:00
Paul Makles 439bacf067 refactor: add error messages for Rabbit issues 2025-02-10 17:20:29 +00:00
Paul Makles fa55e88dd9 chore: add pushd to debug image script 2025-02-10 17:19:54 +00:00
IAmTomahawkx 71e7fe3086 chore: bump release for patch 2024-12-27 16:35:02 -08:00
Paul Makles 249749e14d feat: file deletion implementation 2024-11-27 21:54:39 +00:00
Paul Makles acc4317246 feat: init crond crate 2024-11-27 16:56:30 +00:00
81 changed files with 1710 additions and 2094 deletions
+4
View File
@@ -110,6 +110,10 @@ jobs:
"pushd": { "pushd": {
"path": "crates/daemons/pushd", "path": "crates/daemons/pushd",
"tag": "${{ github.repository_owner }}/pushd" "tag": "${{ github.repository_owner }}/pushd"
},
"crond": {
"path": "crates/daemons/crond",
"tag": "${{ github.repository_owner }}/crond"
} }
} }
export_to: output export_to: output
+3
View File
@@ -1,9 +1,12 @@
Rocket.toml Rocket.toml
Revolt.*.toml Revolt.*.toml
compose.override.yml
target target
.data .data
.env .env
.venv/
venv/
.vercel .vercel
.DS_Store .DS_Store
Generated
+30 -18
View File
@@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo. # This file is automatically @generated by Cargo.
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 4
[[package]] [[package]]
name = "addr2line" name = "addr2line"
@@ -2410,9 +2410,9 @@ dependencies = [
[[package]] [[package]]
name = "futures" name = "futures"
version = "0.3.21" version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
dependencies = [ dependencies = [
"futures-channel", "futures-channel",
"futures-core", "futures-core",
@@ -2441,9 +2441,9 @@ checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
[[package]] [[package]]
name = "futures-executor" name = "futures-executor"
version = "0.3.21" version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-task", "futures-task",
@@ -5523,7 +5523,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-autumn" name = "revolt-autumn"
version = "0.8.0" version = "0.8.4"
dependencies = [ dependencies = [
"axum", "axum",
"axum-macros", "axum-macros",
@@ -5560,7 +5560,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-bonfire" name = "revolt-bonfire"
version = "0.8.0" version = "0.8.4"
dependencies = [ dependencies = [
"async-channel 2.3.1", "async-channel 2.3.1",
"async-std", "async-std",
@@ -5590,7 +5590,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-config" name = "revolt-config"
version = "0.8.0" version = "0.8.4"
dependencies = [ dependencies = [
"async-std", "async-std",
"cached", "cached",
@@ -5604,9 +5604,21 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "revolt-crond"
version = "0.8.4"
dependencies = [
"log",
"revolt-config",
"revolt-database",
"revolt-files",
"revolt-result",
"tokio 1.40.0",
]
[[package]] [[package]]
name = "revolt-database" name = "revolt-database"
version = "0.8.0" version = "0.8.4"
dependencies = [ dependencies = [
"amqprs", "amqprs",
"async-lock 2.8.0", "async-lock 2.8.0",
@@ -5655,7 +5667,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-delta" name = "revolt-delta"
version = "0.8.0" version = "0.8.4"
dependencies = [ dependencies = [
"amqprs", "amqprs",
"async-channel 1.6.1", "async-channel 1.6.1",
@@ -5702,7 +5714,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-files" name = "revolt-files"
version = "0.8.0" version = "0.8.4"
dependencies = [ dependencies = [
"aes-gcm", "aes-gcm",
"aws-config", "aws-config",
@@ -5725,7 +5737,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-january" name = "revolt-january"
version = "0.8.0" version = "0.8.4"
dependencies = [ dependencies = [
"async-recursion", "async-recursion",
"axum", "axum",
@@ -5753,7 +5765,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-models" name = "revolt-models"
version = "0.8.0" version = "0.8.4"
dependencies = [ dependencies = [
"indexmap 1.9.3", "indexmap 1.9.3",
"iso8601-timestamp 0.2.11", "iso8601-timestamp 0.2.11",
@@ -5771,7 +5783,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-nodejs-bindings" name = "revolt-nodejs-bindings"
version = "0.8.0" version = "0.8.4"
dependencies = [ dependencies = [
"async-std", "async-std",
"neon", "neon",
@@ -5784,7 +5796,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-permissions" name = "revolt-permissions"
version = "0.8.0" version = "0.8.4"
dependencies = [ dependencies = [
"async-std", "async-std",
"async-trait", "async-trait",
@@ -5799,7 +5811,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-presence" name = "revolt-presence"
version = "0.8.0" version = "0.8.4"
dependencies = [ dependencies = [
"async-std", "async-std",
"log", "log",
@@ -5810,7 +5822,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-pushd" name = "revolt-pushd"
version = "0.8.0" version = "0.8.4"
dependencies = [ dependencies = [
"amqprs", "amqprs",
"async-trait", "async-trait",
@@ -5834,7 +5846,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-result" name = "revolt-result"
version = "0.8.0" version = "0.8.4"
dependencies = [ dependencies = [
"axum", "axum",
"revolt_okapi", "revolt_okapi",
+7 -3
View File
@@ -7,7 +7,7 @@ members = [
"crates/core/*", "crates/core/*",
"crates/services/*", "crates/services/*",
"crates/bindings/*", "crates/bindings/*",
"crates/daemons/pushd", "crates/daemons/*",
] ]
[patch.crates-io] [patch.crates-io]
@@ -16,5 +16,9 @@ redis23 = { package = "redis", version = "0.23.1", git = "https://github.com/rev
# authifier = { package = "authifier", version = "1.0.8", path = "../authifier/crates/authifier" } # authifier = { package = "authifier", version = "1.0.8", path = "../authifier/crates/authifier" }
# rocket_authifier = { package = "rocket_authifier", version = "1.0.8", path = "../authifier/crates/rocket_authifier" } # rocket_authifier = { package = "rocket_authifier", version = "1.0.8", path = "../authifier/crates/rocket_authifier" }
[profile.release] # I'm 99% sure this is overloading the GitHub worker
lto = true # 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
+1
View File
@@ -29,6 +29,7 @@ COPY crates/core/presence/Cargo.toml ./crates/core/presence/
COPY crates/core/result/Cargo.toml ./crates/core/result/ COPY crates/core/result/Cargo.toml ./crates/core/result/
COPY crates/services/autumn/Cargo.toml ./crates/services/autumn/ COPY crates/services/autumn/Cargo.toml ./crates/services/autumn/
COPY crates/services/january/Cargo.toml ./crates/services/january/ COPY crates/services/january/Cargo.toml ./crates/services/january/
COPY crates/daemons/crond/Cargo.toml ./crates/daemons/crond/
COPY crates/daemons/pushd/Cargo.toml ./crates/daemons/pushd/ COPY crates/daemons/pushd/Cargo.toml ./crates/daemons/pushd/
RUN sh /tmp/build-image-layer.sh deps RUN sh /tmp/build-image-layer.sh deps
+1
View File
@@ -25,6 +25,7 @@ COPY crates/core/presence/Cargo.toml ./crates/core/presence/
COPY crates/core/result/Cargo.toml ./crates/core/result/ COPY crates/core/result/Cargo.toml ./crates/core/result/
COPY crates/services/autumn/Cargo.toml ./crates/services/autumn/ COPY crates/services/autumn/Cargo.toml ./crates/services/autumn/
COPY crates/services/january/Cargo.toml ./crates/services/january/ COPY crates/services/january/Cargo.toml ./crates/services/january/
COPY crates/daemons/crond/Cargo.toml ./crates/daemons/crond/
COPY crates/daemons/pushd/Cargo.toml ./crates/daemons/pushd/ COPY crates/daemons/pushd/Cargo.toml ./crates/daemons/pushd/
RUN sh /tmp/build-image-layer.sh deps RUN sh /tmp/build-image-layer.sh deps
+34 -1
View File
@@ -26,6 +26,7 @@ The services and libraries that power the Revolt service.<br/>
| `services/january` | [crates/services/january](crates/services/january) | Proxy 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) | | `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) | | `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) | | `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> </div>
@@ -61,7 +62,7 @@ As a heads-up, the development environment uses the following ports:
| MinIO | 14009 | | MinIO | 14009 |
| Maildev | 14025<br>14080 | | Maildev | 14025<br>14080 |
| Revolt Web App | 14701 | | Revolt Web App | 14701 |
| RabbitMQ | 5672<br>15672 | | RabbitMQ | 5672<br>15672 |
| `crates/delta` | 14702 | | `crates/delta` | 14702 |
| `crates/bonfire` | 14703 | | `crates/bonfire` | 14703 |
| `crates/services/autumn` | 14704 | | `crates/services/autumn` | 14704 |
@@ -91,6 +92,38 @@ If you'd like to change anything, create a `Revolt.overrides.toml` file and spec
> proxy = "https://abc@your.sentry/1" > proxy = "https://abc@your.sentry/1"
> ``` > ```
> [!TIP]
> If you have port conflicts on common services, you can try the following:
>
> ```yaml
> # compose.override.yml
> services:
> redis:
> ports: !override
> - "14079:6379"
>
> database:
> ports: !override
> - "14017:27017"
>
> rabbit:
> ports: !override
> - "14072:5672"
> - "14672:15672"
> ```
>
> And corresponding Revolt configuration:
>
> ```toml
> # Revolt.overrides.toml
> [database]
> mongodb = "mongodb://127.0.0.1:14017"
> redis = "redis://127.0.0.1:14079/"
>
> [rabbit]
> port = 14072
> ```
Then continue: Then continue:
```bash ```bash
+5 -2
View File
@@ -4,10 +4,13 @@
[database] [database]
# MongoDB connection URL # MongoDB connection URL
# Defaults to the container name specified in self-hosted # Defaults to the container name specified in self-hosted
mongodb = "mongodb://127.0.0.1:14017" mongodb = "mongodb://127.0.0.1:27017"
# Redis connection URL # Redis connection URL
# Defaults to the container name specified in self-hosted # Defaults to the container name specified in self-hosted
redis = "redis://127.0.0.1:14079/" redis = "redis://127.0.0.1:6379/"
[rabbit]
host = "127.0.0.1"
[hosts] [hosts]
# Web locations of various services # Web locations of various services
+4 -4
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-nodejs-bindings" name = "revolt-nodejs-bindings"
version = "0.8.0" version = "0.8.4"
description = "Node.js bindings for the Revolt software" description = "Node.js bindings for the Revolt software"
authors = ["Paul Makles <me@insrt.uk>"] authors = ["Paul Makles <me@insrt.uk>"]
license = "MIT" license = "MIT"
@@ -20,6 +20,6 @@ serde = { version = "1", features = ["derive"] }
async-std = "1.12.0" async-std = "1.12.0"
revolt-config = { version = "0.8.0", path = "../../core/config" } revolt-config = { version = "0.8.4", path = "../../core/config" }
revolt-result = { version = "0.8.0", path = "../../core/result" } revolt-result = { version = "0.8.4", path = "../../core/result" }
revolt-database = { version = "0.8.0", path = "../../core/database" } revolt-database = { version = "0.8.4", path = "../../core/database" }
+2 -2
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-bonfire" name = "revolt-bonfire"
version = "0.8.0" version = "0.8.4"
license = "AGPL-3.0-or-later" license = "AGPL-3.0-or-later"
edition = "2021" edition = "2021"
@@ -41,7 +41,7 @@ revolt-result = { path = "../core/result" }
revolt-models = { path = "../core/models" } revolt-models = { path = "../core/models" }
revolt-config = { path = "../core/config" } revolt-config = { path = "../core/config" }
revolt-database = { path = "../core/database" } revolt-database = { path = "../core/database" }
revolt-permissions = { version = "0.8.0", path = "../core/permissions" } revolt-permissions = { version = "0.8.4", path = "../core/permissions" }
revolt-presence = { path = "../core/presence", features = ["redis-is-patched"] } revolt-presence = { path = "../core/presence", features = ["redis-is-patched"] }
# redis # redis
+2 -2
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-config" name = "revolt-config"
version = "0.8.0" version = "0.8.4"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
authors = ["Paul Makles <me@insrt.uk>"] authors = ["Paul Makles <me@insrt.uk>"]
@@ -34,4 +34,4 @@ pretty_env_logger = "0.4.0"
sentry = "0.31.5" sentry = "0.31.5"
# Core # Core
revolt-result = { version = "0.8.0", path = "../result", optional = true } revolt-result = { version = "0.8.4", path = "../result", optional = true }
+3
View File
@@ -7,3 +7,6 @@ host = "127.0.0.1"
port = 5672 port = 5672
username = "rabbituser" username = "rabbituser"
password = "rabbitpass" password = "rabbitpass"
[features]
webhooks_enabled = true
+3 -1
View File
@@ -1,3 +1,5 @@
production = false
[database] [database]
# MongoDB connection URL # MongoDB connection URL
# Defaults to the container name specified in self-hosted # Defaults to the container name specified in self-hosted
@@ -171,7 +173,6 @@ default_bucket = "revolt-uploads"
[features] [features]
# Bucket to upload to by default
# Feature gate options # Feature gate options
webhooks_enabled = false webhooks_enabled = false
@@ -258,3 +259,4 @@ api = ""
events = "" events = ""
files = "" files = ""
proxy = "" proxy = ""
crond = ""
+10 -1
View File
@@ -324,6 +324,7 @@ pub struct Sentry {
pub events: String, pub events: String,
pub files: String, pub files: String,
pub proxy: String, pub proxy: String,
pub crond: String,
} }
#[derive(Deserialize, Debug, Clone)] #[derive(Deserialize, Debug, Clone)]
@@ -336,6 +337,7 @@ pub struct Settings {
pub files: Files, pub files: Files,
pub features: Features, pub features: Features,
pub sentry: Sentry, pub sentry: Sentry,
pub production: bool,
} }
impl Settings { impl Settings {
@@ -363,7 +365,14 @@ pub async fn read() -> Config {
#[cached(time = 30)] #[cached(time = 30)]
pub async fn config() -> Settings { pub async fn config() -> Settings {
read().await.try_deserialize::<Settings>().unwrap() let mut config = read().await.try_deserialize::<Settings>().unwrap();
// auto-detect production nodes
if config.hosts.api.contains("https") && config.hosts.api.contains("revolt.chat") {
config.production = true;
}
config
} }
/// Configure logging and common Rust variables /// Configure logging and common Rust variables
+6 -6
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-database" name = "revolt-database"
version = "0.8.0" version = "0.8.4"
edition = "2021" edition = "2021"
license = "AGPL-3.0-or-later" license = "AGPL-3.0-or-later"
authors = ["Paul Makles <me@insrt.uk>"] authors = ["Paul Makles <me@insrt.uk>"]
@@ -24,15 +24,15 @@ default = ["mongodb", "async-std-runtime", "tasks"]
[dependencies] [dependencies]
# Core # Core
revolt-config = { version = "0.8.0", path = "../config", features = [ revolt-config = { version = "0.8.4", path = "../config", features = [
"report-macros", "report-macros",
] } ] }
revolt-result = { version = "0.8.0", path = "../result" } revolt-result = { version = "0.8.4", path = "../result" }
revolt-models = { version = "0.8.0", path = "../models", features = [ revolt-models = { version = "0.8.4", path = "../models", features = [
"validator", "validator",
] } ] }
revolt-presence = { version = "0.8.0", path = "../presence" } revolt-presence = { version = "0.8.4", path = "../presence" }
revolt-permissions = { version = "0.8.0", path = "../permissions", features = [ revolt-permissions = { version = "0.8.4", path = "../permissions", features = [
"serde", "serde",
"bson", "bson",
] } ] }
+44 -20
View File
@@ -120,26 +120,50 @@ impl Database {
use_tls: config.api.smtp.use_tls, use_tls: config.api.smtp.use_tls,
}, },
expiry: Default::default(), expiry: Default::default(),
templates: Templates { templates: if config.production {
verify: Template { Templates {
title: "Verify your Revolt account.".into(), verify: Template {
text: include_str!("../../templates/verify.txt").into(), title: "Verify your Revolt account.".into(),
url: format!("{}/login/verify/", config.hosts.app), text: include_str!("../../templates/verify.txt").into(),
html: Some(include_str!("../../templates/verify.html").into()), url: format!("{}/login/verify/", config.hosts.app),
}, html: Some(include_str!("../../templates/verify.html").into()),
reset: Template { },
title: "Reset your Revolt password.".into(), reset: Template {
text: include_str!("../../templates/reset.txt").into(), title: "Reset your Revolt password.".into(),
url: format!("{}/login/reset/", config.hosts.app), text: include_str!("../../templates/reset.txt").into(),
html: Some(include_str!("../../templates/reset.html").into()), url: format!("{}/login/reset/", config.hosts.app),
}, html: Some(include_str!("../../templates/reset.html").into()),
deletion: Template { },
title: "Confirm account deletion.".into(), deletion: Template {
text: include_str!("../../templates/deletion.txt").into(), title: "Confirm account deletion.".into(),
url: format!("{}/delete/", config.hosts.app), text: include_str!("../../templates/deletion.txt").into(),
html: Some(include_str!("../../templates/deletion.html").into()), url: format!("{}/delete/", config.hosts.app),
}, html: Some(include_str!("../../templates/deletion.html").into()),
welcome: None, },
welcome: None,
}
} else {
Templates {
verify: Template {
title: "Verify your account.".into(),
text: include_str!("../../templates/verify.whitelabel.txt").into(),
url: format!("{}/login/verify/", config.hosts.app),
html: None,
},
reset: 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(),
url: format!("{}/delete/", config.hosts.app),
html: None,
},
welcome: None,
}
}, },
} }
} else { } else {
@@ -269,16 +269,24 @@ impl Channel {
})); }));
} }
let id = ulid::Ulid::new().to_string();
let icon = if let Some(icon_id) = data.icon {
Some(File::use_channel_icon(db, &icon_id, &id, &owner_id).await?)
} else {
None
};
let recipients = data.users.into_iter().collect::<Vec<String>>(); let recipients = data.users.into_iter().collect::<Vec<String>>();
let channel = Channel::Group { let channel = Channel::Group {
id: ulid::Ulid::new().to_string(), id,
name: data.name, name: data.name,
owner: owner_id, owner: owner_id,
description: data.description, description: data.description,
recipients: recipients.clone(), recipients: recipients.clone(),
icon: None, icon,
last_message_id: None, last_message_id: None,
permissions: None, permissions: None,
@@ -15,4 +15,7 @@ pub trait AbstractAttachmentHashes: Sync + Send {
/// Update an attachment hash nonce value. /// Update an attachment hash nonce value.
async fn set_attachment_hash_nonce(&self, hash: &str, nonce: &str) -> Result<()>; async fn set_attachment_hash_nonce(&self, hash: &str, nonce: &str) -> Result<()>;
/// Delete attachment hash by id.
async fn delete_attachment_hash(&self, id: &str) -> Result<()>;
} }
@@ -48,4 +48,9 @@ impl AbstractAttachmentHashes for MongoDb {
.map(|_| ()) .map(|_| ())
.map_err(|_| create_database_error!("update_one", COL)) .map_err(|_| create_database_error!("update_one", COL))
} }
/// Delete attachment hash by id.
async fn delete_attachment_hash(&self, id: &str) -> Result<()> {
query!(self, delete_one_by_id, COL, id).map(|_| ())
}
} }
@@ -23,8 +23,8 @@ impl AbstractAttachmentHashes for ReferenceDb {
let hashes = self.file_hashes.lock().await; let hashes = self.file_hashes.lock().await;
hashes hashes
.values() .values()
.find(|&hash| hash.id == hash_value || hash.processed_hash == hash_value)
.cloned() .cloned()
.find(|hash| hash.id == hash_value || hash.processed_hash == hash_value)
.ok_or(create_error!(NotFound)) .ok_or(create_error!(NotFound))
} }
@@ -38,4 +38,14 @@ impl AbstractAttachmentHashes for ReferenceDb {
Err(create_error!(NotFound)) Err(create_error!(NotFound))
} }
} }
/// Delete attachment hash by id.
async fn delete_attachment_hash(&self, id: &str) -> Result<()> {
let mut file_hashes = self.file_hashes.lock().await;
if file_hashes.remove(id).is_some() {
Ok(())
} else {
Err(create_error!(NotFound))
}
}
} }
@@ -15,6 +15,15 @@ pub trait AbstractAttachments: Sync + Send {
/// Fetch an attachment by its id. /// Fetch an attachment by its id.
async fn fetch_attachment(&self, tag: &str, file_id: &str) -> Result<File>; async fn fetch_attachment(&self, tag: &str, file_id: &str) -> Result<File>;
/// Fetch all deleted attachments.
async fn fetch_deleted_attachments(&self) -> Result<Vec<File>>;
/// Fetch all dangling attachments.
async fn fetch_dangling_files(&self) -> Result<Vec<File>>;
/// Count references to a given hash.
async fn count_file_hash_references(&self, hash: &str) -> Result<usize>;
/// Find an attachment by its details and mark it as used by a given parent. /// Find an attachment by its details and mark it as used by a given parent.
async fn find_and_use_attachment( async fn find_and_use_attachment(
&self, &self,
@@ -32,4 +41,7 @@ pub trait AbstractAttachments: Sync + Send {
/// Mark multiple attachments as having been deleted. /// Mark multiple attachments as having been deleted.
async fn mark_attachments_as_deleted(&self, ids: &[String]) -> Result<()>; async fn mark_attachments_as_deleted(&self, ids: &[String]) -> Result<()>;
/// Delete the attachment entry.
async fn delete_attachment(&self, id: &str) -> Result<()>;
} }
@@ -32,6 +32,51 @@ impl AbstractAttachments for MongoDb {
.ok_or_else(|| create_error!(NotFound)) .ok_or_else(|| create_error!(NotFound))
} }
/// Fetch all deleted attachments.
async fn fetch_deleted_attachments(&self) -> Result<Vec<File>> {
query!(
self,
find,
COL,
doc! {
"deleted": true,
"reported": {
"$ne": true
}
}
)
}
/// Fetch all dangling attachments.
async fn fetch_dangling_files(&self) -> Result<Vec<File>> {
query!(
self,
find,
COL,
doc! {
"used_for.type": {
"$exists": 0
},
"deleted": {
"$ne": true
}
}
)
}
/// Count references to a given hash.
async fn count_file_hash_references(&self, hash: &str) -> Result<usize> {
query!(
self,
count_documents,
COL,
doc! {
"hash": hash
}
)
.map(|count| count as usize)
}
/// Find an attachment by its details and mark it as used by a given parent. /// Find an attachment by its details and mark it as used by a given parent.
async fn find_and_use_attachment( async fn find_and_use_attachment(
&self, &self,
@@ -114,7 +159,7 @@ impl AbstractAttachments for MongoDb {
/// Mark multiple attachments as having been deleted. /// Mark multiple attachments as having been deleted.
async fn mark_attachments_as_deleted(&self, ids: &[String]) -> Result<()> { async fn mark_attachments_as_deleted(&self, ids: &[String]) -> Result<()> {
self.col::<Document>(COL) self.col::<Document>(COL)
.update_one( .update_many(
doc! { doc! {
"_id": { "_id": {
"$in": ids "$in": ids
@@ -129,7 +174,12 @@ impl AbstractAttachments for MongoDb {
) )
.await .await
.map(|_| ()) .map(|_| ())
.map_err(|_| create_database_error!("update_one", COL)) .map_err(|_| create_database_error!("update_many", COL))
}
/// Delete the attachment entry.
async fn delete_attachment(&self, id: &str) -> Result<()> {
query!(self, delete_one_by_id, COL, id).map(|_| ())
} }
} }
@@ -33,6 +33,41 @@ impl AbstractAttachments for ReferenceDb {
} }
} }
/// Fetch all deleted attachments.
async fn fetch_deleted_attachments(&self) -> Result<Vec<File>> {
let files = self.files.lock().await;
Ok(files
.values()
.filter(|file| {
// file has been marked as deleted
file.deleted.is_some_and(|v| v)
// and it has not been reported
&& !file.reported.is_some_and(|v| v)
})
.cloned()
.collect())
}
/// Fetch all dangling attachments.
async fn fetch_dangling_files(&self) -> Result<Vec<File>> {
let files = self.files.lock().await;
Ok(files
.values()
.filter(|file| file.used_for.is_none() && !file.deleted.is_some_and(|v| v))
.cloned()
.collect())
}
/// Count references to a given hash.
async fn count_file_hash_references(&self, hash: &str) -> Result<usize> {
let files = self.files.lock().await;
Ok(files
.values()
.filter(|file| file.hash.as_ref().is_some_and(|h| h == hash))
.cloned()
.count())
}
/// Find an attachment by its details and mark it as used by a given parent. /// Find an attachment by its details and mark it as used by a given parent.
async fn find_and_use_attachment( async fn find_and_use_attachment(
&self, &self,
@@ -96,4 +131,14 @@ impl AbstractAttachments for ReferenceDb {
Ok(()) Ok(())
} }
/// Delete the attachment entry.
async fn delete_attachment(&self, id: &str) -> Result<()> {
let mut files = self.files.lock().await;
if files.remove(id).is_some() {
Ok(())
} else {
Err(create_error!(NotFound))
}
}
} }
@@ -8,7 +8,7 @@ use revolt_models::v0::{
MessageWebhook, PushNotification, ReplyIntent, SendableEmbed, Text, RE_MENTION, MessageWebhook, PushNotification, ReplyIntent, SendableEmbed, Text, RE_MENTION,
}; };
use revolt_permissions::{ChannelPermission, PermissionValue}; use revolt_permissions::{ChannelPermission, PermissionValue};
use revolt_result::Result; use revolt_result::{ErrorType, Result};
use ulid::Ulid; use ulid::Ulid;
use validator::Validate; use validator::Validate;
@@ -327,14 +327,31 @@ impl Message {
})); }));
} }
for ReplyIntent { id, mention } in entries { for ReplyIntent {
let message = db.fetch_message(&id).await?; id,
mention,
fail_if_not_exists,
} in entries
{
match db.fetch_message(&id).await {
// Referenced message exists
Ok(message) => {
if mention && allow_mentions {
mentions.insert(message.author.to_owned());
}
if mention && allow_mentions { replies.insert(message.id);
mentions.insert(message.author.to_owned()); }
// If the referenced message doesn't exist and fail_if_not_exists
// is set to false, send the message without the reply.
Err(e) => {
if !matches!(e.error_type, ErrorType::NotFound)
|| fail_if_not_exists.unwrap_or(true)
{
return Err(e);
}
}
} }
replies.insert(message.id);
} }
} }
@@ -95,7 +95,7 @@ impl AbstractMessages for MongoDb {
COL, COL,
older_message_filter, older_message_filter,
FindOptions::builder() FindOptions::builder()
.limit(limit / 2) .limit(limit / 2 + 1)
.sort(doc! { .sort(doc! {
"_id": -1_i32 "_id": -1_i32
}) })
@@ -152,7 +152,7 @@ pub static DISCRIMINATOR_SEARCH_SPACE: Lazy<HashSet<String>> = Lazy::new(|| {
.collect::<HashSet<String>>(); .collect::<HashSet<String>>();
for discrim in [ for discrim in [
123, 1234, 1111, 2222, 3333, 4444, 5555, 6666, 7777, 8888, 9999, 123, 1234, 1111, 2222, 3333, 4444, 5555, 6666, 7777, 8888, 9999, 1488,
] { ] {
set.remove(&format!("{:0>4}", discrim)); set.remove(&format!("{:0>4}", discrim));
} }
@@ -293,7 +293,14 @@ impl User {
} }
// Ensure none of the following substrings show up in the username // Ensure none of the following substrings show up in the username
const BLOCKED_SUBSTRINGS: &[&str] = &["```"]; const BLOCKED_SUBSTRINGS: &[&str] = &[
"```",
"discord.gg",
"rvlt.gg",
"guilded.gg",
"https://",
"http://",
];
for substr in BLOCKED_SUBSTRINGS { for substr in BLOCKED_SUBSTRINGS {
if username_lowercase.contains(substr) { if username_lowercase.contains(substr) {
+74 -453
View File
@@ -1,464 +1,93 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html> <html>
<head> <head>
<!-- Compiled with Bootstrap Email version: 1.3.1 --> <!-- Compiled with Bootstrap Email version: 1.5.1 --><meta http-equiv="x-ua-compatible" content="ie=edge">
<meta http-equiv="x-ua-compatible" content="ie=edge" /> <meta name="x-apple-disable-message-reformatting">
<meta name="x-apple-disable-message-reformatting" /> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="format-detection" content="telephone=no, date=no, address=no, email=no">
<meta <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
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"> <style type="text/css">
body, 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}}
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> </style>
</head> </head>
<body <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">
class="bg-light" <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">
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> <tbody>
<tr> <tr>
<td <td valign="top" style="line-height: 24px; font-size: 16px; margin: 0;" align="left" bgcolor="#f7fafc">
valign="top" <table class="container" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;">
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> <tbody>
<tr> <tr>
<td <td align="center" style="line-height: 24px; font-size: 16px; margin: 0; padding: 0 16px;">
align="center"
style="
line-height: 24px;
font-size: 16px;
margin: 0;
padding: 0 16px;
"
>
<!--[if (gte mso 9)|(IE)]> <!--[if (gte mso 9)|(IE)]>
<table align="center" role="presentation"> <table align="center" role="presentation">
<tbody> <tbody>
<tr> <tr>
<td width="600"> <td width="600">
<![endif]--> <![endif]-->
<table <table align="center" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%; max-width: 600px; margin: 0 auto;">
align="center"
role="presentation"
border="0"
cellpadding="0"
cellspacing="0"
style="width: 100%; max-width: 600px; margin: 0 auto"
>
<tbody> <tbody>
<tr> <tr>
<td <td style="line-height: 24px; font-size: 16px; margin: 0;" align="left">
style=" <table class="s-10 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
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> <tbody>
<tr> <tr>
<td <td style="line-height: 40px; font-size: 40px; width: 100%; height: 40px; margin: 0;" align="left" width="100%" height="40">
style="
line-height: 40px;
font-size: 40px;
width: 100%;
height: 40px;
margin: 0;
"
align="left"
width="100%"
height="40"
>
&#160; &#160;
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<table <table class="ax-center" role="presentation" align="center" border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto;">
class="ax-center"
role="presentation"
align="center"
border="0"
cellpadding="0"
cellspacing="0"
style="margin: 0 auto"
>
<tbody> <tbody>
<tr> <tr>
<td <td style="line-height: 24px; font-size: 16px; margin: 0;" align="left">
style=" <img alt="Revolt Logo" class="w-24" src="https://app.revolt.chat/assets/logo_round.png" style="height: auto; line-height: 100%; outline: none; text-decoration: none; display: block; width: 96px; border-style: none; border-width: 0;" width="96">
line-height: 24px;
font-size: 16px;
margin: 0;
"
align="left"
>
<img
alt="Revolt Logo"
class="w-24"
src="https://app.revolt.chat/assets/logo_round.png"
style="
height: auto;
line-height: 100%;
outline: none;
text-decoration: none;
display: block;
width: 96px;
border-style: none;
border-width: 0;
"
width="96"
/>
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<table <table class="s-10 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
class="s-10 w-full"
role="presentation"
border="0"
cellpadding="0"
cellspacing="0"
style="width: 100%"
width="100%"
>
<tbody> <tbody>
<tr> <tr>
<td <td style="line-height: 40px; font-size: 40px; width: 100%; height: 40px; margin: 0;" align="left" width="100%" height="40">
style="
line-height: 40px;
font-size: 40px;
width: 100%;
height: 40px;
margin: 0;
"
align="left"
width="100%"
height="40"
>
&#160; &#160;
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<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">
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> <tbody>
<tr> <tr>
<td <td style="line-height: 24px; font-size: 16px; width: 100%; margin: 0; padding: 40px;" align="left" bgcolor="#ffffff">
style=" <h1 class="h3 fw-700" style="padding-top: 0; padding-bottom: 0; font-weight: 700 !important; vertical-align: baseline; font-size: 28px; line-height: 33.6px; margin: 0;" align="left">Account Deletion</h1>
line-height: 24px; <table class="s-4 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
font-size: 16px;
width: 100%;
margin: 0;
padding: 40px;
"
align="left"
bgcolor="#ffffff"
>
<h1
class="h3 fw-700"
style="
padding-top: 0;
padding-bottom: 0;
font-weight: 700 !important;
vertical-align: baseline;
font-size: 28px;
line-height: 33.6px;
margin: 0;
"
align="left"
>
Account Deletion
</h1>
<table
class="s-4 w-full"
role="presentation"
border="0"
cellpadding="0"
cellspacing="0"
style="width: 100%"
width="100%"
>
<tbody> <tbody>
<tr> <tr>
<td <td style="line-height: 16px; font-size: 16px; width: 100%; height: 16px; margin: 0;" align="left" width="100%" height="16">
style="
line-height: 16px;
font-size: 16px;
width: 100%;
height: 16px;
margin: 0;
"
align="left"
width="100%"
height="16"
>
&#160; &#160;
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<p <p class="" style="line-height: 24px; font-size: 16px; width: 100%; margin: 0;" align="left">
class="" You requested to have your account deleted, if you did not perform
style=" this action please take measures to secure your account immediately.
line-height: 24px;
font-size: 16px;
width: 100%;
margin: 0;
"
align="left"
>
You requested to have your account
deleted, if you did not perform this
action please take measures to secure your
account immediately.
</p> </p>
<table <table class="s-4 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
class="s-4 w-full"
role="presentation"
border="0"
cellpadding="0"
cellspacing="0"
style="width: 100%"
width="100%"
>
<tbody> <tbody>
<tr> <tr>
<td <td style="line-height: 16px; font-size: 16px; width: 100%; height: 16px; margin: 0;" align="left" width="100%" height="16">
style="
line-height: 16px;
font-size: 16px;
width: 100%;
height: 16px;
margin: 0;
"
align="left"
width="100%"
height="16"
>
&#160; &#160;
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<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;">
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> <tbody>
<tr> <tr>
<td <td style="line-height: 24px; font-size: 16px; border-radius: 6px; font-weight: 700 !important; margin: 0;" align="center" bgcolor="#0d6efd">
style=" <a href="{{url}}" style="color: #ffffff; font-size: 16px; font-family: Helvetica, Arial, sans-serif; text-decoration: none; border-radius: 6px; line-height: 20px; display: block; font-weight: 700 !important; white-space: nowrap; background-color: #0d6efd; padding: 12px; border: 1px solid #0d6efd;">Confirm</a>
line-height: 24px;
font-size: 16px;
border-radius: 6px;
font-weight: 700 !important;
margin: 0;
"
align="center"
bgcolor="#0d6efd"
>
<a
href="{{url}}"
style="
color: #ffffff;
font-size: 16px;
font-family: Helvetica, Arial,
sans-serif;
text-decoration: none;
border-radius: 6px;
line-height: 20px;
display: block;
font-weight: 700 !important;
white-space: nowrap;
background-color: #0d6efd;
padding: 12px;
border: 1px solid #0d6efd;
"
>Confirm</a
>
</td> </td>
</tr> </tr>
</tbody> </tbody>
@@ -467,66 +96,58 @@
</tr> </tr>
</tbody> </tbody>
</table> </table>
<table <table class="s-6 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
class="s-6 w-full"
role="presentation"
border="0"
cellpadding="0"
cellspacing="0"
style="width: 100%"
width="100%"
>
<tbody> <tbody>
<tr> <tr>
<td <td style="line-height: 24px; font-size: 24px; width: 100%; height: 24px; margin: 0;" align="left" width="100%" height="24">
style="
line-height: 24px;
font-size: 24px;
width: 100%;
height: 24px;
margin: 0;
"
align="left"
width="100%"
height="24"
>
&#160; &#160;
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<div <div class="text-muted text-center" style="color: #718096;" align="center">
class="text-muted text-center" This email is intended for {{email}}<br>
style="color: #718096" Sent from Revolt<br>
align="center"
>
This email is intended for {{email}}<br />
Sent from Revolt<br />
Made in Europe Made in Europe
</div> </div>
<table <table class="s-6 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
class="s-6 w-full"
role="presentation"
border="0"
cellpadding="0"
cellspacing="0"
style="width: 100%"
width="100%"
>
<tbody> <tbody>
<tr> <tr>
<td <td style="line-height: 24px; font-size: 24px; width: 100%; height: 24px; margin: 0;" align="left" width="100%" height="24">
style=" &#160;
line-height: 24px; </td>
font-size: 24px; </tr>
width: 100%; </tbody>
height: 24px; </table>
margin: 0; <table class="s-6 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
" <tbody>
align="left" <tr>
width="100%" <td style="line-height: 24px; font-size: 24px; width: 100%; height: 24px; margin: 0;" align="left" width="100%" height="24">
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; &#160;
</td> </td>
</tr> </tr>
@@ -25,6 +25,14 @@
Sent from Revolt<br /> Sent from Revolt<br />
Made in Europe Made in Europe
</div> </div>
<div class="text-muted text-xs my-6 ax-center">
Revolt Platforms Ltd. is a company incorporated and registered under the
laws of England and Wales.<br />
Registered Company Number: 16260658<br />
Registered Office:<br />
Suite 5703 Unit 3A, 34-35 Hatton Garden,<br />
Holborn, United Kingdom, EC1N 8DX
</div>
</div> </div>
</body> </body>
</html> </html>
@@ -5,3 +5,10 @@ Please navigate to: {{url}}
This email is intended for {{email}} This email is intended for {{email}}
Sent by Revolt Sent by Revolt
Made in Europe Made in Europe
Revolt Platforms Ltd. is a company incorporated and registered under the laws of England and Wales.
Registration Number: 16260658
Registered Office:
Suite 5703 Unit 3A, 34-35 Hatton Garden,
Holborn, United Kingdom, EC1N 8DX
@@ -0,0 +1,9 @@
You requested to have your account deleted, if you did not perform this action please take measures to secure your account immediately.
Please navigate to: {{url}}
This email is intended for {{email}}
This email has no association with Revolt or Revolt Platforms Ltd.
Learn more about third party instances here:
https://developers.revolt.chat/faq.html
@@ -0,0 +1,53 @@
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style>
/* Add custom classes and styles that you want inlined here */
</style>
</head>
<body class="bg-light">
<div class="container">
<img
alt="Revolt Logo"
class="ax-center my-10 w-24"
src="https://app.revolt.chat/assets/logo_round.png"
/>
<div class="card p-6 p-lg-10 space-y-4">
<h1 class="h3 fw-700">Notice of Terms &amp; Policy Change</h1>
<p>
This email is to notify you that we're updating all of our policy
documents.
</p>
<p>
You can read our updated Terms of Service and Privacy Policy below:<br />
Effective. 24th March 2025
</p>
<a class="p-3 fw-700" href="https://revolt.chat/terms"
>Terms of Service</a
>
<a class="p-3 fw-700" href="https://revolt.chat/terms"
>Privacy Policy</a
>
<p>
Our Community Guidelines have also been updated to help us better
protect and safeguard the platform. You can check them out here:
</p>
<a class="p-3 fw-700" href="https://revolt.chat/terms"
>Community Guidelines</a
>
</div>
<div class="text-muted text-center my-6">
Sent by 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>
+72 -452
View File
@@ -1,462 +1,90 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html> <html>
<head> <head>
<!-- Compiled with Bootstrap Email version: 1.3.1 --> <!-- Compiled with Bootstrap Email version: 1.5.1 --><meta http-equiv="x-ua-compatible" content="ie=edge">
<meta http-equiv="x-ua-compatible" content="ie=edge" /> <meta name="x-apple-disable-message-reformatting">
<meta name="x-apple-disable-message-reformatting" /> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="format-detection" content="telephone=no, date=no, address=no, email=no">
<meta <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
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"> <style type="text/css">
body, 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}}
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> </style>
</head> </head>
<body <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">
class="bg-light" <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">
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> <tbody>
<tr> <tr>
<td <td valign="top" style="line-height: 24px; font-size: 16px; margin: 0;" align="left" bgcolor="#f7fafc">
valign="top" <table class="container" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;">
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> <tbody>
<tr> <tr>
<td <td align="center" style="line-height: 24px; font-size: 16px; margin: 0; padding: 0 16px;">
align="center"
style="
line-height: 24px;
font-size: 16px;
margin: 0;
padding: 0 16px;
"
>
<!--[if (gte mso 9)|(IE)]> <!--[if (gte mso 9)|(IE)]>
<table align="center" role="presentation"> <table align="center" role="presentation">
<tbody> <tbody>
<tr> <tr>
<td width="600"> <td width="600">
<![endif]--> <![endif]-->
<table <table align="center" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%; max-width: 600px; margin: 0 auto;">
align="center"
role="presentation"
border="0"
cellpadding="0"
cellspacing="0"
style="width: 100%; max-width: 600px; margin: 0 auto"
>
<tbody> <tbody>
<tr> <tr>
<td <td style="line-height: 24px; font-size: 16px; margin: 0;" align="left">
style=" <table class="s-10 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
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> <tbody>
<tr> <tr>
<td <td style="line-height: 40px; font-size: 40px; width: 100%; height: 40px; margin: 0;" align="left" width="100%" height="40">
style="
line-height: 40px;
font-size: 40px;
width: 100%;
height: 40px;
margin: 0;
"
align="left"
width="100%"
height="40"
>
&#160; &#160;
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<table <table class="ax-center" role="presentation" align="center" border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto;">
class="ax-center"
role="presentation"
align="center"
border="0"
cellpadding="0"
cellspacing="0"
style="margin: 0 auto"
>
<tbody> <tbody>
<tr> <tr>
<td <td style="line-height: 24px; font-size: 16px; margin: 0;" align="left">
style=" <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">
line-height: 24px;
font-size: 16px;
margin: 0;
"
align="left"
>
<img
alt="Revolt Logo"
class="w-24"
src="https://app.revolt.chat/assets/logo_round.png"
style="
height: auto;
line-height: 100%;
outline: none;
text-decoration: none;
display: block;
width: 96px;
border-style: none;
border-width: 0;
"
width="96"
/>
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<table <table class="s-10 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
class="s-10 w-full"
role="presentation"
border="0"
cellpadding="0"
cellspacing="0"
style="width: 100%"
width="100%"
>
<tbody> <tbody>
<tr> <tr>
<td <td style="line-height: 40px; font-size: 40px; width: 100%; height: 40px; margin: 0;" align="left" width="100%" height="40">
style="
line-height: 40px;
font-size: 40px;
width: 100%;
height: 40px;
margin: 0;
"
align="left"
width="100%"
height="40"
>
&#160; &#160;
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<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">
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> <tbody>
<tr> <tr>
<td <td style="line-height: 24px; font-size: 16px; width: 100%; margin: 0; padding: 40px;" align="left" bgcolor="#ffffff">
style=" <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>
line-height: 24px; <table class="s-4 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
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> <tbody>
<tr> <tr>
<td <td style="line-height: 16px; font-size: 16px; width: 100%; height: 16px; margin: 0;" align="left" width="100%" height="16">
style="
line-height: 16px;
font-size: 16px;
width: 100%;
height: 16px;
margin: 0;
"
align="left"
width="100%"
height="16"
>
&#160; &#160;
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<p <p class="" style="line-height: 24px; font-size: 16px; width: 100%; margin: 0;" align="left">You requested a password reset, click below to continue.</p>
class="" <table class="s-4 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
style="
line-height: 24px;
font-size: 16px;
width: 100%;
margin: 0;
"
align="left"
>
You requested a password reset, click
below to continue.
</p>
<table
class="s-4 w-full"
role="presentation"
border="0"
cellpadding="0"
cellspacing="0"
style="width: 100%"
width="100%"
>
<tbody> <tbody>
<tr> <tr>
<td <td style="line-height: 16px; font-size: 16px; width: 100%; height: 16px; margin: 0;" align="left" width="100%" height="16">
style="
line-height: 16px;
font-size: 16px;
width: 100%;
height: 16px;
margin: 0;
"
align="left"
width="100%"
height="16"
>
&#160; &#160;
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<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;">
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> <tbody>
<tr> <tr>
<td <td style="line-height: 24px; font-size: 16px; border-radius: 6px; font-weight: 700 !important; margin: 0;" align="center" bgcolor="#0d6efd">
style=" <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>
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> </td>
</tr> </tr>
</tbody> </tbody>
@@ -465,66 +93,58 @@
</tr> </tr>
</tbody> </tbody>
</table> </table>
<table <table class="s-6 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
class="s-6 w-full"
role="presentation"
border="0"
cellpadding="0"
cellspacing="0"
style="width: 100%"
width="100%"
>
<tbody> <tbody>
<tr> <tr>
<td <td style="line-height: 24px; font-size: 24px; width: 100%; height: 24px; margin: 0;" align="left" width="100%" height="24">
style="
line-height: 24px;
font-size: 24px;
width: 100%;
height: 24px;
margin: 0;
"
align="left"
width="100%"
height="24"
>
&#160; &#160;
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<div <div class="text-muted text-center" style="color: #718096;" align="center">
class="text-muted text-center" This email is intended for {{email}}<br>
style="color: #718096" Sent from Revolt<br>
align="center"
>
This email is intended for {{email}}<br />
Sent from Revolt<br />
Made in Europe Made in Europe
</div> </div>
<table <table class="s-6 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
class="s-6 w-full"
role="presentation"
border="0"
cellpadding="0"
cellspacing="0"
style="width: 100%"
width="100%"
>
<tbody> <tbody>
<tr> <tr>
<td <td style="line-height: 24px; font-size: 24px; width: 100%; height: 24px; margin: 0;" align="left" width="100%" height="24">
style=" &#160;
line-height: 24px; </td>
font-size: 24px; </tr>
width: 100%; </tbody>
height: 24px; </table>
margin: 0; <table class="s-6 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
" <tbody>
align="left" <tr>
width="100%" <td style="line-height: 24px; font-size: 24px; width: 100%; height: 24px; margin: 0;" align="left" width="100%" height="24">
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; &#160;
</td> </td>
</tr> </tr>
@@ -21,6 +21,14 @@
Sent from Revolt<br /> Sent from Revolt<br />
Made in Europe Made in Europe
</div> </div>
<div class="text-muted text-xs my-6 ax-center">
Revolt Platforms Ltd. is a company incorporated and registered under the
laws of England and Wales.<br />
Registered Company Number: 16260658<br />
Registered Office:<br />
Suite 5703 Unit 3A, 34-35 Hatton Garden,<br />
Holborn, United Kingdom, EC1N 8DX
</div>
</div> </div>
</body> </body>
</html> </html>
+7
View File
@@ -5,3 +5,10 @@ Please navigate to: {{url}}
This email is intended for {{email}} This email is intended for {{email}}
Sent by Revolt Sent by Revolt
Made in Europe Made in Europe
Revolt Platforms Ltd. is a company incorporated and registered under the laws of England and Wales.
Registration Number: 16260658
Registered Office:
Suite 5703 Unit 3A, 34-35 Hatton Garden,
Holborn, United Kingdom, EC1N 8DX
@@ -0,0 +1,9 @@
You requested a password reset, if you did not perform this action you can safely ignore this email.
Please navigate to: {{url}}
This email is intended for {{email}}
This email has no association with Revolt or Revolt Platforms Ltd.
Learn more about third party instances here:
https://developers.revolt.chat/faq.html
+82 -505
View File
@@ -1,408 +1,80 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html> <html>
<head> <head>
<!-- Compiled with Bootstrap Email version: 1.5.1 --> <!-- Compiled with Bootstrap Email version: 1.5.1 --><meta http-equiv="x-ua-compatible" content="ie=edge">
<meta http-equiv="x-ua-compatible" content="ie=edge" /> <meta name="x-apple-disable-message-reformatting">
<meta name="x-apple-disable-message-reformatting" /> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="format-detection" content="telephone=no, date=no, address=no, email=no">
<meta <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
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"> <style type="text/css">
body, 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-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}}
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-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> </style>
</head> </head>
<body <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">
class="bg-light" <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">
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> <tbody>
<tr> <tr>
<td <td valign="top" style="line-height: 24px; font-size: 16px; margin: 0;" align="left" bgcolor="#f7fafc">
valign="top" <table class="container" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;">
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> <tbody>
<tr> <tr>
<td <td align="center" style="line-height: 24px; font-size: 16px; margin: 0; padding: 0 16px;">
align="center"
style="
line-height: 24px;
font-size: 16px;
margin: 0;
padding: 0 16px;
"
>
<!--[if (gte mso 9)|(IE)]> <!--[if (gte mso 9)|(IE)]>
<table align="center" role="presentation"> <table align="center" role="presentation">
<tbody> <tbody>
<tr> <tr>
<td width="600"> <td width="600">
<![endif]--> <![endif]-->
<table <table align="center" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%; max-width: 600px; margin: 0 auto;">
align="center"
role="presentation"
border="0"
cellpadding="0"
cellspacing="0"
style="width: 100%; max-width: 600px; margin: 0 auto"
>
<tbody> <tbody>
<tr> <tr>
<td <td style="line-height: 24px; font-size: 16px; margin: 0;" align="left">
style=" <table class="s-10 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
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> <tbody>
<tr> <tr>
<td <td style="line-height: 40px; font-size: 40px; width: 100%; height: 40px; margin: 0;" align="left" width="100%" height="40">
style="
line-height: 40px;
font-size: 40px;
width: 100%;
height: 40px;
margin: 0;
"
align="left"
width="100%"
height="40"
>
&#160; &#160;
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<table <table class="ax-center" role="presentation" align="center" border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto;">
class="ax-center"
role="presentation"
align="center"
border="0"
cellpadding="0"
cellspacing="0"
style="margin: 0 auto"
>
<tbody> <tbody>
<tr> <tr>
<td <td style="line-height: 24px; font-size: 16px; margin: 0;" align="left">
style=" <img alt="Revolt Logo" class="w-24" src="https://app.revolt.chat/assets/logo_round.png" style="height: auto; line-height: 100%; outline: none; text-decoration: none; display: block; width: 96px; border-style: none; border-width: 0;" width="96">
line-height: 24px;
font-size: 16px;
margin: 0;
"
align="left"
>
<img
alt="Revolt Logo"
class="w-24"
src="https://app.revolt.chat/assets/logo_round.png"
style="
height: auto;
line-height: 100%;
outline: none;
text-decoration: none;
display: block;
width: 96px;
border-style: none;
border-width: 0;
"
width="96"
/>
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<table <table class="s-10 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
class="s-10 w-full"
role="presentation"
border="0"
cellpadding="0"
cellspacing="0"
style="width: 100%"
width="100%"
>
<tbody> <tbody>
<tr> <tr>
<td <td style="line-height: 40px; font-size: 40px; width: 100%; height: 40px; margin: 0;" align="left" width="100%" height="40">
style="
line-height: 40px;
font-size: 40px;
width: 100%;
height: 40px;
margin: 0;
"
align="left"
width="100%"
height="40"
>
&#160; &#160;
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<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">
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> <tbody>
<tr> <tr>
<td <td style="line-height: 24px; font-size: 16px; width: 100%; margin: 0; padding: 40px;" align="left" bgcolor="#ffffff">
style=" <h1 class="h3 fw-700" style="padding-top: 0; padding-bottom: 0; font-weight: 700 !important; vertical-align: baseline; font-size: 28px; line-height: 33.6px; margin: 0;" align="left">Account Suspended</h1>
line-height: 24px; <table class="s-4 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
font-size: 16px;
width: 100%;
margin: 0;
padding: 40px;
"
align="left"
bgcolor="#ffffff"
>
<h1
class="h3 fw-700"
style="
padding-top: 0;
padding-bottom: 0;
font-weight: 700 !important;
vertical-align: baseline;
font-size: 28px;
line-height: 33.6px;
margin: 0;
"
align="left"
>
Account Suspended
</h1>
<table
class="s-4 w-full"
role="presentation"
border="0"
cellpadding="0"
cellspacing="0"
style="width: 100%"
width="100%"
>
<tbody> <tbody>
<tr> <tr>
<td <td style="line-height: 16px; font-size: 16px; width: 100%; height: 16px; margin: 0;" align="left" width="100%" height="16">
style="
line-height: 16px;
font-size: 16px;
width: 100%;
height: 16px;
margin: 0;
"
align="left"
width="100%"
height="16"
>
&#160; &#160;
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<p <p class="" style="line-height: 24px; font-size: 16px; width: 100%; margin: 0;" align="left">Your account has been suspended, for one or more reasons:</p>
class="" <table class="s-4 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
style="
line-height: 24px;
font-size: 16px;
width: 100%;
margin: 0;
"
align="left"
>
Your account has been suspended, for one
or more reasons:
</p>
<table
class="s-4 w-full"
role="presentation"
border="0"
cellpadding="0"
cellspacing="0"
style="width: 100%"
width="100%"
>
<tbody> <tbody>
<tr> <tr>
<td <td style="line-height: 16px; font-size: 16px; width: 100%; height: 16px; margin: 0;" align="left" width="100%" height="16">
style="
line-height: 16px;
font-size: 16px;
width: 100%;
height: 16px;
margin: 0;
"
align="left"
width="100%"
height="16"
>
&#160; &#160;
</td> </td>
</tr> </tr>
@@ -411,193 +83,98 @@
<ul class=""> <ul class="">
{{list}} {{list}}
</ul> </ul>
<table <table class="s-4 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
class="s-4 w-full"
role="presentation"
border="0"
cellpadding="0"
cellspacing="0"
style="width: 100%"
width="100%"
>
<tbody> <tbody>
<tr> <tr>
<td <td style="line-height: 16px; font-size: 16px; width: 100%; height: 16px; margin: 0;" align="left" width="100%" height="16">
style="
line-height: 16px;
font-size: 16px;
width: 100%;
height: 16px;
margin: 0;
"
align="left"
width="100%"
height="16"
>
&#160; &#160;
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<p <p style="display: {{duration_display}}; line-height: 24px; font-size: 16px; width: 100%; margin: 0;" class="" align="left">
style="display: {{duration_display}}; line-height: 24px; font-size: 16px; width: 100%; margin: 0;" You will be able to use your account again in {{duration}} days.
class=""
align="left"
>
You will be able to use your account again
in {{duration}} days.
</p> </p>
<table <table class="s-4 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
class="s-4 w-full"
role="presentation"
border="0"
cellpadding="0"
cellspacing="0"
style="width: 100%"
width="100%"
>
<tbody> <tbody>
<tr> <tr>
<td <td style="line-height: 16px; font-size: 16px; width: 100%; height: 16px; margin: 0;" align="left" width="100%" height="16">
style="
line-height: 16px;
font-size: 16px;
width: 100%;
height: 16px;
margin: 0;
"
align="left"
width="100%"
height="16"
>
&#160; &#160;
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<p <p class="" style="line-height: 24px; font-size: 16px; width: 100%; margin: 0;" align="left">
class="" Further violations may result in a permanent ban depending on
style=" severity, please abide by the
line-height: 24px; <a href="https://revolt.chat/aup" style="color: #0d6efd;">Acceptable Usage Policy</a>.
font-size: 16px;
width: 100%;
margin: 0;
"
align="left"
>
Further violations may result in a
permanent ban depending on severity,
please abide by the
<a
href="https://revolt.chat/aup"
style="color: #0d6efd"
>Acceptable Usage Policy</a
>.
</p> </p>
<table <table class="s-4 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
class="s-4 w-full"
role="presentation"
border="0"
cellpadding="0"
cellspacing="0"
style="width: 100%"
width="100%"
>
<tbody> <tbody>
<tr> <tr>
<td <td style="line-height: 16px; font-size: 16px; width: 100%; height: 16px; margin: 0;" align="left" width="100%" height="16">
style="
line-height: 16px;
font-size: 16px;
width: 100%;
height: 16px;
margin: 0;
"
align="left"
width="100%"
height="16"
>
&#160; &#160;
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<p <p style="line-height: 24px; font-size: 16px; width: 100%; margin: 0;" align="left">Ban evasion is prohibited and will be dealt with accordingly.</p>
style="
line-height: 24px;
font-size: 16px;
width: 100%;
margin: 0;
"
align="left"
>
Ban evasion is prohibited and will be
dealt with accordingly.
</p>
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<table <table class="s-6 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
class="s-6 w-full"
role="presentation"
border="0"
cellpadding="0"
cellspacing="0"
style="width: 100%"
width="100%"
>
<tbody> <tbody>
<tr> <tr>
<td <td style="line-height: 24px; font-size: 24px; width: 100%; height: 24px; margin: 0;" align="left" width="100%" height="24">
style="
line-height: 24px;
font-size: 24px;
width: 100%;
height: 24px;
margin: 0;
"
align="left"
width="100%"
height="24"
>
&#160; &#160;
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<div <div class="text-muted text-center" style="color: #718096;" align="center">
class="text-muted text-center" This email is intended for {{email}}<br>
style="color: #718096" Sent from Revolt<br>
align="center"
>
This email is intended for {{email}}<br />
Sent from Revolt<br />
Made in Europe Made in Europe
</div> </div>
<table <table class="s-6 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
class="s-6 w-full"
role="presentation"
border="0"
cellpadding="0"
cellspacing="0"
style="width: 100%"
width="100%"
>
<tbody> <tbody>
<tr> <tr>
<td <td style="line-height: 24px; font-size: 24px; width: 100%; height: 24px; margin: 0;" align="left" width="100%" height="24">
style=" &#160;
line-height: 24px; </td>
font-size: 24px; </tr>
width: 100%; </tbody>
height: 24px; </table>
margin: 0; <table class="s-6 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
" <tbody>
align="left" <tr>
width="100%" <td style="line-height: 24px; font-size: 24px; width: 100%; height: 24px; margin: 0;" align="left" width="100%" height="24">
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; &#160;
</td> </td>
</tr> </tr>
@@ -33,6 +33,14 @@
Sent from Revolt<br /> Sent from Revolt<br />
Made in Europe Made in Europe
</div> </div>
<div class="text-muted text-xs my-6 ax-center">
Revolt Platforms Ltd. is a company incorporated and registered under the
laws of England and Wales.<br />
Registered Company Number: 16260658<br />
Registered Office:<br />
Suite 5703 Unit 3A, 34-35 Hatton Garden,<br />
Holborn, United Kingdom, EC1N 8DX
</div>
</div> </div>
</body> </body>
</html> </html>
@@ -10,3 +10,10 @@ Ban evasion is prohibited and will be dealt with accordingly.
This email is intended for {{email}} This email is intended for {{email}}
Sent by Revolt Sent by Revolt
Made in Europe Made in Europe
Revolt Platforms Ltd. is a company incorporated and registered under the laws of England and Wales.
Registration Number: 16260658
Registered Office:
Suite 5703 Unit 3A, 34-35 Hatton Garden,
Holborn, United Kingdom, EC1N 8DX
@@ -0,0 +1,8 @@
Your account has been suspended, for one or more reasons:
{{list}}
This email is intended for {{email}}
This email has no association with Revolt or Revolt Platforms Ltd.
Learn more about third party instances here:
https://developers.revolt.chat/faq.html
+72 -452
View File
@@ -1,462 +1,90 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html> <html>
<head> <head>
<!-- Compiled with Bootstrap Email version: 1.3.1 --> <!-- Compiled with Bootstrap Email version: 1.5.1 --><meta http-equiv="x-ua-compatible" content="ie=edge">
<meta http-equiv="x-ua-compatible" content="ie=edge" /> <meta name="x-apple-disable-message-reformatting">
<meta name="x-apple-disable-message-reformatting" /> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="format-detection" content="telephone=no, date=no, address=no, email=no">
<meta <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
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"> <style type="text/css">
body, 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}}
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> </style>
</head> </head>
<body <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">
class="bg-light" <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">
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> <tbody>
<tr> <tr>
<td <td valign="top" style="line-height: 24px; font-size: 16px; margin: 0;" align="left" bgcolor="#f7fafc">
valign="top" <table class="container" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;">
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> <tbody>
<tr> <tr>
<td <td align="center" style="line-height: 24px; font-size: 16px; margin: 0; padding: 0 16px;">
align="center"
style="
line-height: 24px;
font-size: 16px;
margin: 0;
padding: 0 16px;
"
>
<!--[if (gte mso 9)|(IE)]> <!--[if (gte mso 9)|(IE)]>
<table align="center" role="presentation"> <table align="center" role="presentation">
<tbody> <tbody>
<tr> <tr>
<td width="600"> <td width="600">
<![endif]--> <![endif]-->
<table <table align="center" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%; max-width: 600px; margin: 0 auto;">
align="center"
role="presentation"
border="0"
cellpadding="0"
cellspacing="0"
style="width: 100%; max-width: 600px; margin: 0 auto"
>
<tbody> <tbody>
<tr> <tr>
<td <td style="line-height: 24px; font-size: 16px; margin: 0;" align="left">
style=" <table class="s-10 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
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> <tbody>
<tr> <tr>
<td <td style="line-height: 40px; font-size: 40px; width: 100%; height: 40px; margin: 0;" align="left" width="100%" height="40">
style="
line-height: 40px;
font-size: 40px;
width: 100%;
height: 40px;
margin: 0;
"
align="left"
width="100%"
height="40"
>
&#160; &#160;
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<table <table class="ax-center" role="presentation" align="center" border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto;">
class="ax-center"
role="presentation"
align="center"
border="0"
cellpadding="0"
cellspacing="0"
style="margin: 0 auto"
>
<tbody> <tbody>
<tr> <tr>
<td <td style="line-height: 24px; font-size: 16px; margin: 0;" align="left">
style=" <img alt="Revolt Logo" class="w-24" src="https://app.revolt.chat/assets/logo_round.png" style="height: auto; line-height: 100%; outline: none; text-decoration: none; display: block; width: 96px; border-style: none; border-width: 0;" width="96">
line-height: 24px;
font-size: 16px;
margin: 0;
"
align="left"
>
<img
alt="Revolt Logo"
class="w-24"
src="https://app.revolt.chat/assets/logo_round.png"
style="
height: auto;
line-height: 100%;
outline: none;
text-decoration: none;
display: block;
width: 96px;
border-style: none;
border-width: 0;
"
width="96"
/>
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<table <table class="s-10 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
class="s-10 w-full"
role="presentation"
border="0"
cellpadding="0"
cellspacing="0"
style="width: 100%"
width="100%"
>
<tbody> <tbody>
<tr> <tr>
<td <td style="line-height: 40px; font-size: 40px; width: 100%; height: 40px; margin: 0;" align="left" width="100%" height="40">
style="
line-height: 40px;
font-size: 40px;
width: 100%;
height: 40px;
margin: 0;
"
align="left"
width="100%"
height="40"
>
&#160; &#160;
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<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">
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> <tbody>
<tr> <tr>
<td <td style="line-height: 24px; font-size: 16px; width: 100%; margin: 0; padding: 40px;" align="left" bgcolor="#ffffff">
style=" <h1 class="h3 fw-700" style="padding-top: 0; padding-bottom: 0; font-weight: 700 !important; vertical-align: baseline; font-size: 28px; line-height: 33.6px; margin: 0;" align="left">Almost there!</h1>
line-height: 24px; <table class="s-4 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
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"
>
Almost there!
</h1>
<table
class="s-4 w-full"
role="presentation"
border="0"
cellpadding="0"
cellspacing="0"
style="width: 100%"
width="100%"
>
<tbody> <tbody>
<tr> <tr>
<td <td style="line-height: 16px; font-size: 16px; width: 100%; height: 16px; margin: 0;" align="left" width="100%" height="16">
style="
line-height: 16px;
font-size: 16px;
width: 100%;
height: 16px;
margin: 0;
"
align="left"
width="100%"
height="16"
>
&#160; &#160;
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<p <p class="" style="line-height: 24px; font-size: 16px; width: 100%; margin: 0;" align="left">To complete your sign up, we just need to verify your email.</p>
class="" <table class="s-4 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
style="
line-height: 24px;
font-size: 16px;
width: 100%;
margin: 0;
"
align="left"
>
To complete your sign up, we just need to
verify your email.
</p>
<table
class="s-4 w-full"
role="presentation"
border="0"
cellpadding="0"
cellspacing="0"
style="width: 100%"
width="100%"
>
<tbody> <tbody>
<tr> <tr>
<td <td style="line-height: 16px; font-size: 16px; width: 100%; height: 16px; margin: 0;" align="left" width="100%" height="16">
style="
line-height: 16px;
font-size: 16px;
width: 100%;
height: 16px;
margin: 0;
"
align="left"
width="100%"
height="16"
>
&#160; &#160;
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<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;">
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> <tbody>
<tr> <tr>
<td <td style="line-height: 24px; font-size: 16px; border-radius: 6px; font-weight: 700 !important; margin: 0;" align="center" bgcolor="#0d6efd">
style=" <a href="{{url}}" style="color: #ffffff; font-size: 16px; font-family: Helvetica, Arial, sans-serif; text-decoration: none; border-radius: 6px; line-height: 20px; display: block; font-weight: 700 !important; white-space: nowrap; background-color: #0d6efd; padding: 12px; border: 1px solid #0d6efd;">Confirm</a>
line-height: 24px;
font-size: 16px;
border-radius: 6px;
font-weight: 700 !important;
margin: 0;
"
align="center"
bgcolor="#0d6efd"
>
<a
href="{{url}}"
style="
color: #ffffff;
font-size: 16px;
font-family: Helvetica, Arial,
sans-serif;
text-decoration: none;
border-radius: 6px;
line-height: 20px;
display: block;
font-weight: 700 !important;
white-space: nowrap;
background-color: #0d6efd;
padding: 12px;
border: 1px solid #0d6efd;
"
>Confirm</a
>
</td> </td>
</tr> </tr>
</tbody> </tbody>
@@ -465,66 +93,58 @@
</tr> </tr>
</tbody> </tbody>
</table> </table>
<table <table class="s-6 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
class="s-6 w-full"
role="presentation"
border="0"
cellpadding="0"
cellspacing="0"
style="width: 100%"
width="100%"
>
<tbody> <tbody>
<tr> <tr>
<td <td style="line-height: 24px; font-size: 24px; width: 100%; height: 24px; margin: 0;" align="left" width="100%" height="24">
style="
line-height: 24px;
font-size: 24px;
width: 100%;
height: 24px;
margin: 0;
"
align="left"
width="100%"
height="24"
>
&#160; &#160;
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<div <div class="text-muted text-center" style="color: #718096;" align="center">
class="text-muted text-center" This email is intended for {{email}}<br>
style="color: #718096" Sent from Revolt<br>
align="center"
>
This email is intended for {{email}}<br />
Sent from Revolt<br />
Made in Europe Made in Europe
</div> </div>
<table <table class="s-6 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
class="s-6 w-full"
role="presentation"
border="0"
cellpadding="0"
cellspacing="0"
style="width: 100%"
width="100%"
>
<tbody> <tbody>
<tr> <tr>
<td <td style="line-height: 24px; font-size: 24px; width: 100%; height: 24px; margin: 0;" align="left" width="100%" height="24">
style=" &#160;
line-height: 24px; </td>
font-size: 24px; </tr>
width: 100%; </tbody>
height: 24px; </table>
margin: 0; <table class="s-6 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
" <tbody>
align="left" <tr>
width="100%" <td style="line-height: 24px; font-size: 24px; width: 100%; height: 24px; margin: 0;" align="left" width="100%" height="24">
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; &#160;
</td> </td>
</tr> </tr>
@@ -22,6 +22,14 @@
Sent from Revolt<br /> Sent from Revolt<br />
Made in Europe Made in Europe
</div> </div>
<div class="text-muted text-xs my-6 ax-center">
Revolt Platforms Ltd. is a company incorporated and registered under the
laws of England and Wales.<br />
Registered Company Number: 16260658<br />
Registered Office:<br />
Suite 5703 Unit 3A, 34-35 Hatton Garden,<br />
Holborn, United Kingdom, EC1N 8DX
</div>
</div> </div>
</body> </body>
</html> </html>
@@ -6,3 +6,10 @@ Please navigate to: {{url}}
This email is intended for {{email}} This email is intended for {{email}}
Sent by Revolt Sent by Revolt
Made in Europe Made in Europe
Revolt Platforms Ltd. is a company incorporated and registered under the laws of England and Wales.
Registration Number: 16260658
Registered Office:
Suite 5703 Unit 3A, 34-35 Hatton Garden,
Holborn, United Kingdom, EC1N 8DX
@@ -0,0 +1,10 @@
Almost there!
To complete your sign up, we just need to verify your email.
Please navigate to: {{url}}
This email is intended for {{email}}
This email has no association with Revolt or Revolt Platforms Ltd.
Learn more about third party instances here:
https://developers.revolt.chat/faq.html
+3 -3
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-files" name = "revolt-files"
version = "0.8.0" version = "0.8.4"
edition = "2021" edition = "2021"
license = "AGPL-3.0-or-later" license = "AGPL-3.0-or-later"
authors = ["Paul Makles <me@insrt.uk>"] authors = ["Paul Makles <me@insrt.uk>"]
@@ -20,10 +20,10 @@ typenum = "1.17.0"
aws-config = "1.5.5" aws-config = "1.5.5"
aws-sdk-s3 = { version = "1.46.0", features = ["behavior-version-latest"] } aws-sdk-s3 = { version = "1.46.0", features = ["behavior-version-latest"] }
revolt-config = { version = "0.8.0", path = "../config", features = [ revolt-config = { version = "0.8.4", path = "../config", features = [
"report-macros", "report-macros",
] } ] }
revolt-result = { version = "0.8.0", path = "../result" } revolt-result = { version = "0.8.4", path = "../result" }
# image processing # image processing
jxl-oxide = "0.8.1" jxl-oxide = "0.8.1"
+17
View File
@@ -113,6 +113,23 @@ pub async fn upload_to_s3(bucket_id: &str, path: &str, buf: &[u8]) -> Result<Str
Ok(BASE64_STANDARD.encode(nonce)) Ok(BASE64_STANDARD.encode(nonce))
} }
/// Delete a file from S3 by path
pub async fn delete_from_s3(bucket_id: &str, path: &str) -> Result<()> {
let config = config().await;
let client = create_client(config.files.s3);
report_internal_error!(
client
.delete_object()
.bucket(bucket_id)
.key(path)
.send()
.await
)?;
Ok(())
}
/// Determine size of image at temp file /// Determine size of image at temp file
pub fn image_size(f: &NamedTempFile) -> Option<(usize, usize)> { pub fn image_size(f: &NamedTempFile) -> Option<(usize, usize)> {
if let Ok(size) = imagesize::size(f.path()) if let Ok(size) = imagesize::size(f.path())
+3 -3
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-models" name = "revolt-models"
version = "0.8.0" version = "0.8.4"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
authors = ["Paul Makles <me@insrt.uk>"] authors = ["Paul Makles <me@insrt.uk>"]
@@ -20,8 +20,8 @@ default = ["serde", "partials", "rocket"]
[dependencies] [dependencies]
# Core # Core
revolt-config = { version = "0.8.0", path = "../config" } revolt-config = { version = "0.8.4", path = "../config" }
revolt-permissions = { version = "0.8.0", path = "../permissions" } revolt-permissions = { version = "0.8.4", path = "../permissions" }
# Utility # Utility
regex = "1" regex = "1"
+5 -1
View File
@@ -239,6 +239,10 @@ auto_derived!(
pub id: String, pub id: String,
/// Whether this reply should mention the message's author /// Whether this reply should mention the message's author
pub mention: bool, pub mention: bool,
/// Whether to error if the referenced message doesn't exist.
/// Otherwise, send a message without this reply.
/// Default is true.
pub fail_if_not_exists: Option<bool>,
} }
/// Message to send /// Message to send
@@ -280,7 +284,7 @@ auto_derived!(
pub struct OptionsQueryMessages { pub struct OptionsQueryMessages {
/// Maximum number of messages to fetch /// Maximum number of messages to fetch
/// ///
/// For fetching nearby messages, this is \`(limit + 1)\`. /// For fetching nearby messages, this is \`(limit + 2)\`.
#[cfg_attr(feature = "validator", validate(range(min = 1, max = 100)))] #[cfg_attr(feature = "validator", validate(range(min = 1, max = 100)))]
pub limit: Option<i64>, pub limit: Option<i64>,
/// Message id before which messages should be fetched /// Message id before which messages should be fetched
+2 -2
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-permissions" name = "revolt-permissions"
version = "0.8.0" version = "0.8.4"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
authors = ["Paul Makles <me@insrt.uk>"] authors = ["Paul Makles <me@insrt.uk>"]
@@ -21,7 +21,7 @@ async-std = { version = "1.8.0", features = ["attributes"] }
[dependencies] [dependencies]
# Core # Core
revolt-result = { version = "0.8.0", path = "../result" } revolt-result = { version = "0.8.4", path = "../result" }
# Utility # Utility
auto_ops = "0.3.0" auto_ops = "0.3.0"
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-presence" name = "revolt-presence"
version = "0.8.0" version = "0.8.4"
edition = "2021" edition = "2021"
license = "AGPL-3.0-or-later" license = "AGPL-3.0-or-later"
authors = ["Paul Makles <me@insrt.uk>"] authors = ["Paul Makles <me@insrt.uk>"]
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-result" name = "revolt-result"
version = "0.8.0" version = "0.8.4"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
authors = ["Paul Makles <me@insrt.uk>"] authors = ["Paul Makles <me@insrt.uk>"]
+1
View File
@@ -42,6 +42,7 @@ impl IntoResponse for Error {
ErrorType::InvalidRole => StatusCode::NOT_FOUND, ErrorType::InvalidRole => StatusCode::NOT_FOUND,
ErrorType::Banned => StatusCode::FORBIDDEN, ErrorType::Banned => StatusCode::FORBIDDEN,
ErrorType::AlreadyInServer => StatusCode::CONFLICT, ErrorType::AlreadyInServer => StatusCode::CONFLICT,
ErrorType::CannotTimeoutYourself => StatusCode::BAD_REQUEST,
ErrorType::TooManyServers { .. } => StatusCode::BAD_REQUEST, ErrorType::TooManyServers { .. } => StatusCode::BAD_REQUEST,
ErrorType::TooManyEmbeds { .. } => StatusCode::BAD_REQUEST, ErrorType::TooManyEmbeds { .. } => StatusCode::BAD_REQUEST,
+1
View File
@@ -116,6 +116,7 @@ pub enum ErrorType {
max: usize, max: usize,
}, },
AlreadyInServer, AlreadyInServer,
CannotTimeoutYourself,
// ? Bot related errors // ? Bot related errors
ReachedMaximumBots, ReachedMaximumBots,
+1
View File
@@ -47,6 +47,7 @@ impl<'r> Responder<'r, 'static> for Error {
ErrorType::InvalidRole => Status::NotFound, ErrorType::InvalidRole => Status::NotFound,
ErrorType::Banned => Status::Forbidden, ErrorType::Banned => Status::Forbidden,
ErrorType::AlreadyInServer => Status::Conflict, ErrorType::AlreadyInServer => Status::Conflict,
ErrorType::CannotTimeoutYourself => Status::BadRequest,
ErrorType::TooManyServers { .. } => Status::BadRequest, ErrorType::TooManyServers { .. } => Status::BadRequest,
ErrorType::TooManyEmbeds { .. } => Status::BadRequest, ErrorType::TooManyEmbeds { .. } => Status::BadRequest,
+22
View File
@@ -0,0 +1,22 @@
[package]
name = "revolt-crond"
version = "0.8.4"
license = "AGPL-3.0-or-later"
authors = ["Paul Makles <me@insrt.uk>"]
edition = "2021"
description = "Revolt Daemon Service: Timed data clean up tasks"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
# Utility
log = "0.4"
# Async
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" }
+10
View File
@@ -0,0 +1,10 @@
# Build Stage
FROM ghcr.io/revoltchat/base:latest AS builder
FROM debian:12 AS debian
# Bundle Stage
FROM gcr.io/distroless/cc-debian12:nonroot
COPY --from=builder /home/rust/src/target/release/revolt-crond ./
USER nonroot
CMD ["./revolt-crond"]
+19
View File
@@ -0,0 +1,19 @@
use revolt_config::configure;
use revolt_database::DatabaseInfo;
use revolt_result::Result;
use tasks::{file_deletion, prune_dangling_files};
use tokio::try_join;
pub mod tasks;
#[tokio::main]
async fn main() -> Result<()> {
configure!(crond);
let db = DatabaseInfo::Auto.connect().await.expect("database");
try_join!(
file_deletion::task(db.clone()),
prune_dangling_files::task(db)
)
.map(|_| ())
}
@@ -0,0 +1,39 @@
use std::time::Duration;
use log::info;
use revolt_database::Database;
use revolt_files::delete_from_s3;
use revolt_result::Result;
use tokio::time::sleep;
pub async fn task(db: Database) -> Result<()> {
loop {
let files = db.fetch_deleted_attachments().await?;
for file in files {
let count = db
.count_file_hash_references(file.hash.as_ref().expect("no `hash` present"))
.await?;
// No other files reference this file on disk anymore
if count <= 1 {
let file_hash = db
.fetch_attachment_hash(file.hash.as_ref().expect("no `hash` present"))
.await?;
// Delete from S3
delete_from_s3(&file_hash.bucket_id, &file_hash.path).await?;
// Delete the hash
db.delete_attachment_hash(&file_hash.id).await?;
info!("Deleted file hash {}", file_hash.id);
}
// Delete the file
db.delete_attachment(&file.id).await?;
info!("Deleted file {}", file.id);
}
sleep(Duration::from_secs(60)).await;
}
}
+2
View File
@@ -0,0 +1,2 @@
pub mod file_deletion;
pub mod prune_dangling_files;
@@ -0,0 +1,36 @@
use std::time::Duration;
use revolt_database::{iso8601_timestamp::Timestamp, Database};
use revolt_result::Result;
use tokio::time::sleep;
use log::info;
pub async fn task(db: Database) -> Result<()> {
loop {
// This could just be a single database query
// ... but timestamps are inconsistently serialised
// ... sometimes they are dates/numbers, hard to query
// ... in the future, we could use Postgres instead! :D
// ...
// ... on the plus side, it's still only 2 queries
let files = db.fetch_dangling_files().await?;
let file_ids: Vec<String> = files
.into_iter()
.filter(|file| {
file.uploaded_at.is_some_and(|uploaded_at| {
Timestamp::now_utc().duration_since(uploaded_at) > Duration::from_secs(60 * 60)
})
})
.map(|file| file.id)
.collect();
if !file_ids.is_empty() {
db.mark_attachments_as_deleted(&file_ids).await?;
info!("Marked {} dangling files for deletion", file_ids.len());
}
sleep(Duration::from_secs(60)).await;
}
}
+5 -4
View File
@@ -1,12 +1,13 @@
[package] [package]
name = "revolt-pushd" name = "revolt-pushd"
version = "0.8.0" version = "0.8.4"
edition = "2021" edition = "2021"
license = "AGPL-3.0-or-later"
[dependencies] [dependencies]
revolt-config = { version = "0.8.0", path = "../../core/config" } revolt-config = { version = "0.8.4", path = "../../core/config" }
revolt-database = { version = "0.8.0", path = "../../core/database" } revolt-database = { version = "0.8.4", path = "../../core/database" }
revolt-models = { version = "0.8.0", path = "../../core/models", features = [ revolt-models = { version = "0.8.4", path = "../../core/models", features = [
"validator", "validator",
] } ] }
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-delta" name = "revolt-delta"
version = "0.8.0" version = "0.8.4"
license = "AGPL-3.0-or-later" license = "AGPL-3.0-or-later"
authors = ["Paul Makles <paulmakles@gmail.com>"] authors = ["Paul Makles <paulmakles@gmail.com>"]
edition = "2018" edition = "2018"
+16 -3
View File
@@ -75,7 +75,15 @@ pub async fn web() -> Rocket<Build> {
// Configure Swagger // Configure Swagger
let swagger = revolt_rocket_okapi::swagger_ui::make_swagger_ui( let swagger = revolt_rocket_okapi::swagger_ui::make_swagger_ui(
&revolt_rocket_okapi::swagger_ui::SwaggerUIConfig { &revolt_rocket_okapi::swagger_ui::SwaggerUIConfig {
url: "../openapi.json".to_owned(), url: "/openapi.json".to_owned(),
..Default::default()
},
)
.into();
let swagger_0_8 = revolt_rocket_okapi::swagger_ui::make_swagger_ui(
&revolt_rocket_okapi::swagger_ui::SwaggerUIConfig {
url: "/0.8/openapi.json".to_owned(),
..Default::default() ..Default::default()
}, },
) )
@@ -89,8 +97,12 @@ pub async fn web() -> Rocket<Build> {
&config.rabbit.password, &config.rabbit.password,
)) ))
.await .await
.unwrap(); .expect("Failed to connect to RabbitMQ");
let channel = connection.open_channel(None).await.unwrap();
let channel = connection
.open_channel(None)
.await
.expect("Failed to open RabbitMQ channel");
channel channel
.exchange_declare( .exchange_declare(
@@ -116,6 +128,7 @@ pub async fn web() -> Rocket<Build> {
.mount("/", rocket_cors::catch_all_options_routes()) .mount("/", rocket_cors::catch_all_options_routes())
.mount("/", util::ratelimiter::routes()) .mount("/", util::ratelimiter::routes())
.mount("/swagger/", swagger) .mount("/swagger/", swagger)
.mount("/0.8/swagger/", swagger_0_8)
.manage(authifier) .manage(authifier)
.manage(db) .manage(db)
.manage(amqp) .manage(amqp)
@@ -99,5 +99,26 @@ mod test {
// TEST: member leaves group (no delete) // TEST: member leaves group (no delete)
// TEST: no effect with saved messages // TEST: no effect with saved messages
// TEST: DM set to inactive // TEST: DM set to inactive
// TEST: server channel deleted
#[rocket::async_test]
async fn success_delete_channel() {
let mut harness = TestHarness::new().await;
let (_, session, user) = harness.new_user().await;
let (_, channels) = harness.new_server(&user).await;
let response = TestHarness::with_session(
session,
harness
.client
.delete(format!("/channels/{}", channels[0].id())),
)
.await;
assert_eq!(response.status(), Status::NoContent);
drop(response);
harness
.wait_for_event(channels[0].id(), |event| match event {
EventV1::ChannelDelete { id, .. } => id == channels[0].id(),
_ => false,
})
.await;
}
} }
@@ -1,7 +1,4 @@
use revolt_database::{ use revolt_database::{util::{permissions::DatabasePermissionQuery, reference::Reference}, Channel, Database, PartialMessage, SystemMessage, User, AMQP};
util::{permissions::DatabasePermissionQuery, reference::Reference},
Database, PartialMessage, SystemMessage, User, AMQP,
};
use revolt_models::v0::MessageAuthor; use revolt_models::v0::MessageAuthor;
use revolt_permissions::{calculate_channel_permissions, ChannelPermission}; use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
use revolt_result::{create_error, Result}; use revolt_result::{create_error, Result};
@@ -22,10 +19,12 @@ pub async fn message_pin(
) -> Result<EmptyResponse> { ) -> Result<EmptyResponse> {
let channel = target.as_channel(db).await?; let channel = target.as_channel(db).await?;
let mut query = DatabasePermissionQuery::new(db, &user).channel(&channel); if !matches!(channel, Channel::DirectMessage { .. }) {
calculate_channel_permissions(&mut query) let mut query = DatabasePermissionQuery::new(db, &user).channel(&channel);
.await calculate_channel_permissions(&mut query)
.throw_if_lacking_channel_permission(ChannelPermission::ManageMessages)?; .await
.throw_if_lacking_channel_permission(ChannelPermission::ManageMessages)?;
}
let mut message = msg.as_message_in_channel(db, channel.id()).await?; let mut message = msg.as_message_in_channel(db, channel.id()).await?;
@@ -323,4 +323,163 @@ mod test {
"Mention was scrubbed when the user can see the channel" "Mention was scrubbed when the user can see the channel"
); );
} }
#[rocket::async_test]
async fn message_reply() {
let harness = TestHarness::new().await;
let (_, _, user) = harness.new_user().await;
let (server, channels) = harness.new_server(&user).await;
let channel = harness.new_channel(&server).await;
let (_, member, message) = harness.new_message(&user, &server, channels).await;
// Send a message with a reply
// Should succeed
let message_with_reply = Message::create_from_api(
&harness.db,
Some(&harness.amqp),
channel.clone(),
v0::DataMessageSend {
content: Some("Message with reply".to_string()),
nonce: None,
attachments: None,
replies: Some(vec![v0::ReplyIntent {
id: message.id.clone(),
mention: false,
fail_if_not_exists: Some(true),
}]),
embeds: None,
masquerade: None,
interactions: None,
flags: None,
},
v0::MessageAuthor::User(&user.clone().into(&harness.db, Some(&user)).await),
Some(user.clone().into(&harness.db, Some(&user)).await),
Some(member.clone().into()),
user.limits().await,
IdempotencyKey::unchecked_from_string("1".to_string()),
false,
false,
)
.await
.expect("Failed to create message with reply");
assert!(
message_with_reply.replies.is_some(),
"Message replies is None",
);
let replies = message_with_reply.replies.unwrap();
assert!(!replies.is_empty(), "Message replies is empty",);
assert_eq!(replies[0], message.id, "Message reply ID does not match",);
// Delete the message
message
.clone()
.delete(&harness.db)
.await
.expect("Failed to delete message");
// Attempt to create messages with a reply to a deleted message
// fail_if_not_exists is set to false
// Should send the message without a reply
let message_with_missing_reply = Message::create_from_api(
&harness.db,
Some(&harness.amqp),
channel.clone(),
v0::DataMessageSend {
content: Some("Message with missing reply".to_string()),
nonce: None,
attachments: None,
replies: Some(vec![v0::ReplyIntent {
id: message.id.clone(),
mention: false,
fail_if_not_exists: Some(false),
}]),
embeds: None,
masquerade: None,
interactions: None,
flags: None,
},
v0::MessageAuthor::User(&user.clone().into(&harness.db, Some(&user)).await),
Some(user.clone().into(&harness.db, Some(&user)).await),
Some(member.clone().into()),
user.limits().await,
IdempotencyKey::unchecked_from_string("3".to_string()),
false,
false,
)
.await
.expect("Failed to create message with missing reply");
assert!(
message_with_missing_reply.replies.is_none()
|| message_with_missing_reply.replies.unwrap().is_empty(),
"Message replies exist when they shouldn't",
);
// fail_if_not_exists is set to true
// Should fail to send the message
Message::create_from_api(
&harness.db,
Some(&harness.amqp),
channel.clone(),
v0::DataMessageSend {
content: Some("Message with missing reply".to_string()),
nonce: None,
attachments: None,
replies: Some(vec![v0::ReplyIntent {
id: message.id.clone(),
mention: false,
fail_if_not_exists: Some(true),
}]),
embeds: None,
masquerade: None,
interactions: None,
flags: None,
},
v0::MessageAuthor::User(&user.clone().into(&harness.db, Some(&user)).await),
Some(user.clone().into(&harness.db, Some(&user)).await),
Some(member.clone().into()),
user.limits().await,
IdempotencyKey::unchecked_from_string("4".to_string()),
false,
false,
)
.await
.expect_err("Created message with missing reply and true fail");
// fail_if_not_exists is not set
// Should fail to send the message
Message::create_from_api(
&harness.db,
Some(&harness.amqp),
channel.clone(),
v0::DataMessageSend {
content: Some("Message with missing reply".to_string()),
nonce: None,
attachments: None,
replies: Some(vec![v0::ReplyIntent {
id: message.id.clone(),
mention: false,
fail_if_not_exists: None,
}]),
embeds: None,
masquerade: None,
interactions: None,
flags: None,
},
v0::MessageAuthor::User(&user.clone().into(&harness.db, Some(&user)).await),
Some(user.clone().into(&harness.db, Some(&user)).await),
Some(member.clone().into()),
user.limits().await,
IdempotencyKey::unchecked_from_string("4".to_string()),
false,
false,
)
.await
.expect_err("Created message with missing reply and none fail");
}
} }
@@ -1,7 +1,4 @@
use revolt_database::{ use revolt_database::{util::{permissions::DatabasePermissionQuery, reference::Reference}, Channel, Database, FieldsMessage, PartialMessage, SystemMessage, User, AMQP};
util::{permissions::DatabasePermissionQuery, reference::Reference},
Database, FieldsMessage, PartialMessage, SystemMessage, User, AMQP,
};
use revolt_models::v0::MessageAuthor; use revolt_models::v0::MessageAuthor;
use revolt_permissions::{calculate_channel_permissions, ChannelPermission}; use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
use revolt_result::{create_error, Result}; use revolt_result::{create_error, Result};
@@ -22,10 +19,12 @@ pub async fn message_unpin(
) -> Result<EmptyResponse> { ) -> Result<EmptyResponse> {
let channel = target.as_channel(db).await?; let channel = target.as_channel(db).await?;
let mut query = DatabasePermissionQuery::new(db, &user).channel(&channel); if !matches!(channel, Channel::DirectMessage { .. }) {
calculate_channel_permissions(&mut query) let mut query = DatabasePermissionQuery::new(db, &user).channel(&channel);
.await calculate_channel_permissions(&mut query)
.throw_if_lacking_channel_permission(ChannelPermission::ManageMessages)?; .await
.throw_if_lacking_channel_permission(ChannelPermission::ManageMessages)?;
}
let mut message = msg.as_message_in_channel(db, channel.id()).await?; let mut message = msg.as_message_in_channel(db, channel.id()).await?;
@@ -22,7 +22,7 @@ pub async fn fetch_webhooks(
let mut query = DatabasePermissionQuery::new(db, &user).channel(&channel); let mut query = DatabasePermissionQuery::new(db, &user).channel(&channel);
calculate_channel_permissions(&mut query) calculate_channel_permissions(&mut query)
.await .await
.throw_if_lacking_channel_permission(ChannelPermission::ViewChannel)?; .throw_if_lacking_channel_permission(ChannelPermission::ManageWebhooks)?;
Ok(Json( Ok(Json(
db.fetch_webhooks_for_channel(channel.id()) db.fetch_webhooks_for_channel(channel.id())
@@ -27,11 +27,6 @@ pub async fn create_emoji(
}) })
})?; })?;
// Bots cannot manage emojis
if user.bot.is_some() {
return Err(create_error!(IsBot));
}
// Validate we have permission to write into parent // Validate we have permission to write into parent
match &data.parent { match &data.parent {
v0::EmojiParent::Server { id } => { v0::EmojiParent::Server { id } => {
@@ -18,10 +18,6 @@ pub async fn delete_emoji(
user: User, user: User,
emoji_id: Reference, emoji_id: Reference,
) -> Result<EmptyResponse> { ) -> Result<EmptyResponse> {
// Bots cannot manage emoji
if user.bot.is_some() {
return Err(create_error!(IsBot));
}
// Fetch the emoji // Fetch the emoji
let emoji = emoji_id.as_emoji(db).await?; let emoji = emoji_id.as_emoji(db).await?;
@@ -76,3 +76,169 @@ pub async fn fetch(db: &State<Database>, target: Reference) -> Result<Json<v0::I
} }
})) }))
} }
#[cfg(test)]
mod test {
use crate::{rocket, util::test::TestHarness};
use revolt_database::{Channel, Server};
use revolt_models::v0::{
DataCreateGroup, DataCreateServerChannel, Invite, InviteResponse, LegacyServerChannelType,
};
use rocket::http::Status;
#[rocket::async_test]
async fn success_fetch_group_invite() {
let harness = TestHarness::new().await;
let (_, session, user) = harness.new_user().await;
let group = Channel::create_group(
&harness.db,
DataCreateGroup {
..Default::default()
},
user.id.clone(),
)
.await
.expect("`Channel`");
let create_response = TestHarness::with_session(
session,
harness
.client
.post(format!("/channels/{}/invites", group.id())),
)
.await;
assert_eq!(create_response.status(), Status::Ok);
let invite_from_create: Invite = create_response.into_json().await.expect("`Invite`");
let invite_code = match invite_from_create {
Invite::Group { code, .. } => code,
_ => unreachable!(),
};
let response = harness
.client
.get(format!("/invites/{}", invite_code))
.dispatch()
.await;
assert_eq!(response.status(), Status::Ok);
let invite_response: InviteResponse = response.into_json().await.expect("`FetchInvite`");
match invite_response {
InviteResponse::Group {
code,
channel_id,
user_name,
..
} => {
assert_eq!(code, invite_code);
assert_eq!(channel_id, group.id());
assert_eq!(user_name, user.username);
}
_ => unreachable!(),
}
}
#[rocket::async_test]
async fn fail_fetch_missing_invite() {
let harness = TestHarness::new().await;
let response = harness
.client
.get(format!("/invites/{}", TestHarness::rand_string()))
.dispatch()
.await;
assert_eq!(response.status(), Status::NotFound);
}
#[rocket::async_test]
async fn success_fetch_text_channel_invite() {
let harness = TestHarness::new().await;
let (_, session, user) = harness.new_user().await;
let (_, channels) = harness.new_server(&user).await;
let channel = channels.first().expect("Server Channel");
let create_response = TestHarness::with_session(
session,
harness
.client
.post(format!("/channels/{}/invites", channel.id())),
)
.await;
assert_eq!(create_response.status(), Status::Ok);
let invite_from_create: Invite = create_response.into_json().await.expect("`Invite`");
let invite_code = match invite_from_create {
Invite::Server { code, .. } => code,
_ => unreachable!(),
};
let response = harness
.client
.get(format!("/invites/{}", invite_code))
.dispatch()
.await;
assert_eq!(response.status(), Status::Ok);
let invite_response: InviteResponse = response.into_json().await.expect("`FetchInvite`");
match invite_response {
InviteResponse::Server {
code,
channel_id,
user_name,
..
} => {
assert_eq!(code, invite_code);
assert_eq!(channel_id, channel.id());
assert_eq!(user_name, user.username);
}
_ => unreachable!(),
};
}
#[rocket::async_test]
async fn success_fetch_voice_channel_invite() {
let harness = TestHarness::new().await;
let (_, session, user) = harness.new_user().await;
let (server, _) = harness.new_server(&user).await;
let server_mut: &mut Server = &mut server.clone();
let channel = Channel::create_server_channel(
&harness.db,
server_mut,
DataCreateServerChannel {
channel_type: LegacyServerChannelType::Voice,
name: "Voice Channel".to_string(),
description: None,
nsfw: Some(false),
},
true,
)
.await
.expect("Failed to make new channel");
let create_response = TestHarness::with_session(
session,
harness
.client
.post(format!("/channels/{}/invites", channel.id())),
)
.await;
assert_eq!(create_response.status(), Status::Ok);
let invite_from_create: Invite = create_response.into_json().await.expect("`Invite`");
let invite_code = match invite_from_create {
Invite::Server { code, .. } => code,
_ => unreachable!(),
};
let response = harness
.client
.get(format!("/invites/{}", invite_code))
.dispatch()
.await;
assert_eq!(response.status(), Status::Ok);
let invite_response: InviteResponse = response.into_json().await.expect("`FetchInvite`");
match invite_response {
InviteResponse::Server {
code,
channel_id,
user_name,
..
} => {
assert_eq!(code, invite_code);
assert_eq!(channel_id, channel.id());
assert_eq!(user_name, user.username);
}
_ => unreachable!(),
};
}
}
+47 -1
View File
@@ -61,6 +61,47 @@ pub fn mount(config: Settings, mut rocket: Rocket<Build>) -> Rocket<Build> {
}; };
} }
if config.features.webhooks_enabled {
mount_endpoints_and_merged_docs! {
rocket, "/0.8".to_owned(), settings,
"/" => (vec![], custom_openapi_spec()),
"" => openapi_get_routes_spec![root::root],
"/users" => users::routes(),
"/bots" => bots::routes(),
"/channels" => channels::routes(),
"/servers" => servers::routes(),
"/invites" => invites::routes(),
"/custom" => customisation::routes(),
"/safety" => safety::routes(),
"/auth/account" => rocket_authifier::routes::account::routes(),
"/auth/session" => rocket_authifier::routes::session::routes(),
"/auth/mfa" => rocket_authifier::routes::mfa::routes(),
"/onboard" => onboard::routes(),
"/push" => push::routes(),
"/sync" => sync::routes(),
"/webhooks" => webhooks::routes()
};
} else {
mount_endpoints_and_merged_docs! {
rocket, "/0.8".to_owned(), settings,
"/" => (vec![], custom_openapi_spec()),
"" => openapi_get_routes_spec![root::root],
"/users" => users::routes(),
"/bots" => bots::routes(),
"/channels" => channels::routes(),
"/servers" => servers::routes(),
"/invites" => invites::routes(),
"/custom" => customisation::routes(),
"/safety" => safety::routes(),
"/auth/account" => rocket_authifier::routes::account::routes(),
"/auth/session" => rocket_authifier::routes::session::routes(),
"/auth/mfa" => rocket_authifier::routes::mfa::routes(),
"/onboard" => onboard::routes(),
"/push" => push::routes(),
"/sync" => sync::routes()
};
}
rocket rocket
} }
@@ -190,10 +231,15 @@ fn custom_openapi_spec() -> OpenApi {
..Default::default() ..Default::default()
}, },
Server { Server {
url: "http://local.revolt.chat:8000".to_owned(), url: "http://local.revolt.chat:14702".to_owned(),
description: Some("Local Revolt Environment".to_owned()), description: Some("Local Revolt Environment".to_owned()),
..Default::default() ..Default::default()
}, },
Server {
url: "http://local.revolt.chat:14702/0.8".to_owned(),
description: Some("Local Revolt Environment (v0.8)".to_owned()),
..Default::default()
},
], ],
external_docs: Some(ExternalDocs { external_docs: Some(ExternalDocs {
url: "https://developers.revolt.chat".to_owned(), url: "https://developers.revolt.chat".to_owned(),
@@ -15,12 +15,12 @@ use validator::Validate;
/// ///
/// Edit a member by their id. /// Edit a member by their id.
#[openapi(tag = "Server Members")] #[openapi(tag = "Server Members")]
#[patch("/<server>/members/<target>", data = "<data>")] #[patch("/<server>/members/<member>", data = "<data>")]
pub async fn edit( pub async fn edit(
db: &State<Database>, db: &State<Database>,
user: User, user: User,
server: Reference, server: Reference,
target: Reference, member: Reference,
data: Json<v0::DataMemberEdit>, data: Json<v0::DataMemberEdit>,
) -> Result<Json<v0::Member>> { ) -> Result<Json<v0::Member>> {
let data = data.into_inner(); let data = data.into_inner();
@@ -30,9 +30,9 @@ pub async fn edit(
}) })
})?; })?;
// Fetch server and target member // Fetch server and member
let mut server = server.as_server(db).await?; let mut server = server.as_server(db).await?;
let mut member = target.as_member(db, &server.id).await?; let mut member = member.as_member(db, &server.id).await?;
// Fetch our currrent permissions // Fetch our currrent permissions
let mut query = DatabasePermissionQuery::new(db, &user).server(&server); let mut query = DatabasePermissionQuery::new(db, &user).server(&server);
@@ -84,6 +84,10 @@ pub async fn edit(
.map(|x| x.contains(&v0::FieldsMember::Timeout)) .map(|x| x.contains(&v0::FieldsMember::Timeout))
.unwrap_or_default() .unwrap_or_default()
{ {
if data.timeout.is_some() && member.id.user == user.id {
return Err(create_error!(CannotTimeoutYourself));
}
permissions.throw_if_lacking_channel_permission(ChannelPermission::TimeoutMembers)?; permissions.throw_if_lacking_channel_permission(ChannelPermission::TimeoutMembers)?;
} }
+18 -4
View File
@@ -94,14 +94,28 @@ pub struct Ratelimiter {
/// ///
/// Optionally, include a resource id to hash against. /// Optionally, include a resource id to hash against.
fn resolve_bucket<'r>(request: &'r rocket::Request<'_>) -> (&'r str, Option<&'r str>) { fn resolve_bucket<'r>(request: &'r rocket::Request<'_>) -> (&'r str, Option<&'r str>) {
if let Some(segment) = request.routed_segment(0) { let (segment, resource, extra) = if matches!(request.routed_segment(0), Some("0.8")) {
let resource = request.routed_segment(1); (
request.routed_segment(1),
request.routed_segment(2),
request.routed_segment(3),
)
} else {
(
request.routed_segment(0),
request.routed_segment(1),
request.routed_segment(2),
)
};
if let Some(segment) = segment {
let resource = resource;
let method = request.method(); let method = request.method();
match (segment, resource, method) { match (segment, resource, method) {
("users", target, Method::Patch) => ("user_edit", target), ("users", target, Method::Patch) => ("user_edit", target),
("users", _, _) => { ("users", _, _) => {
if let Some("default_avatar") = request.routed_segment(2) { if let Some("default_avatar") = extra {
return ("default_avatar", None); return ("default_avatar", None);
} }
@@ -110,7 +124,7 @@ fn resolve_bucket<'r>(request: &'r rocket::Request<'_>) -> (&'r str, Option<&'r
("bots", _, _) => ("bots", None), ("bots", _, _) => ("bots", None),
("channels", Some(id), _) => { ("channels", Some(id), _) => {
if request.method() == Method::Post { if request.method() == Method::Post {
if let Some("messages") = request.routed_segment(2) { if let Some("messages") = extra {
return ("messaging", Some(id)); return ("messaging", Some(id));
} }
} }
+83 -2
View File
@@ -5,9 +5,13 @@ use authifier::{
use futures::StreamExt; use futures::StreamExt;
use rand::Rng; use rand::Rng;
use redis_kiss::redis::aio::PubSub; use redis_kiss::redis::aio::PubSub;
use revolt_database::{events::client::EventV1, Database, User, AMQP}; use revolt_database::util::idempotency::IdempotencyKey;
use revolt_database::{
events::client::EventV1, Channel, Database, Member, Message, Server, User, AMQP,
};
use revolt_models::v0; use revolt_models::v0;
use rocket::local::asynchronous::Client; use rocket::http::Header;
use rocket::local::asynchronous::{Client, LocalRequest, LocalResponse};
pub struct TestHarness { pub struct TestHarness {
pub client: Client, pub client: Client,
@@ -104,6 +108,83 @@ impl TestHarness {
(account, session, user) (account, session, user)
} }
pub async fn new_server(&self, user: &User) -> (Server, Vec<Channel>) {
Server::create(
&self.db,
v0::DataCreateServer {
name: "Test Server".to_string(),
..Default::default()
},
user,
true,
)
.await
.expect("Failed to create test server")
}
pub async fn new_channel(&self, server: &Server) -> Channel {
Channel::create_server_channel(
&self.db,
&mut server.clone(),
v0::DataCreateServerChannel {
channel_type: v0::LegacyServerChannelType::Text,
name: "Test Channel".to_string(),
description: None,
nsfw: Some(false),
},
true,
)
.await
.expect("Failed to make test channel")
}
pub async fn new_message(
&self,
user: &User,
server: &Server,
channels: Vec<Channel>,
) -> (Channel, Member, Message) {
let (member, channels) = Member::create(&self.db, server, user, Some(channels))
.await
.expect("Failed to create member");
let channel = &channels[0];
let message = Message::create_from_api(
&self.db,
None,
channel.clone(),
v0::DataMessageSend {
content: Some("Test message".to_string()),
nonce: None,
attachments: None,
replies: None,
embeds: None,
masquerade: None,
interactions: None,
flags: None,
},
v0::MessageAuthor::User(&user.clone().into(&self.db, Some(user)).await),
Some(user.clone().into(&self.db, Some(user)).await),
Some(member.clone().into()),
user.limits().await,
IdempotencyKey::unchecked_from_string("0".to_string()),
false,
false,
)
.await
.expect("Failed to create message");
(channel.clone(), member, message)
}
pub async fn with_session<'c>(
session: Session,
request: LocalRequest<'c>,
) -> LocalResponse<'c> {
request
.header(Header::new("x-session-token", session.token.to_string()))
.dispatch()
.await
}
pub async fn wait_for_event<F>(&mut self, topic: &str, predicate: F) -> EventV1 pub async fn wait_for_event<F>(&mut self, topic: &str, predicate: F) -> EventV1
where where
F: Fn(&EventV1) -> bool, F: Fn(&EventV1) -> bool,
+6 -5
View File
@@ -1,7 +1,8 @@
[package] [package]
name = "revolt-autumn" name = "revolt-autumn"
version = "0.8.0" version = "0.8.4"
edition = "2021" edition = "2021"
license = "AGPL-3.0-or-later"
[dependencies] [dependencies]
# ID generation # ID generation
@@ -42,12 +43,12 @@ tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing-subscriber = { version = "0.3", features = ["env-filter"] }
# Core crates # Core crates
revolt-files = { version = "0.8.0", path = "../../core/files" } revolt-files = { version = "0.8.4", path = "../../core/files" }
revolt-config = { version = "0.8.0", path = "../../core/config" } revolt-config = { version = "0.8.4", path = "../../core/config" }
revolt-database = { version = "0.8.0", path = "../../core/database", features = [ revolt-database = { version = "0.8.4", path = "../../core/database", features = [
"axum-impl", "axum-impl",
] } ] }
revolt-result = { version = "0.8.0", path = "../../core/result", features = [ revolt-result = { version = "0.8.4", path = "../../core/result", features = [
"utoipa", "utoipa",
"axum", "axum",
] } ] }
+6 -5
View File
@@ -1,7 +1,8 @@
[package] [package]
name = "revolt-january" name = "revolt-january"
version = "0.8.0" version = "0.8.4"
edition = "2021" edition = "2021"
license = "AGPL-3.0-or-later"
[dependencies] [dependencies]
# Utility # Utility
@@ -31,13 +32,13 @@ tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing-subscriber = { version = "0.3", features = ["env-filter"] }
# Core crates # Core crates
revolt-config = { version = "0.8.0", path = "../../core/config" } revolt-config = { version = "0.8.4", path = "../../core/config" }
revolt-models = { version = "0.8.0", path = "../../core/models" } revolt-models = { version = "0.8.4", path = "../../core/models" }
revolt-result = { version = "0.8.0", path = "../../core/result", features = [ revolt-result = { version = "0.8.4", path = "../../core/result", features = [
"utoipa", "utoipa",
"axum", "axum",
] } ] }
revolt-files = { version = "0.8.0", path = "../../core/files" } revolt-files = { version = "0.8.4", path = "../../core/files" }
# Axum / web server # Axum / web server
axum = { version = "0.7.5" } axum = { version = "0.7.5" }
+2 -18
View File
@@ -1,26 +1,10 @@
let { pkgs ? import <nixpkgs> {} }:
# Pinned nixpkgs, deterministic. Last updated: 28-07-2024.
pkgs = import (fetchTarball("https://github.com/NixOS/nixpkgs/archive/9b34ca580417e1ebc56c4df57d8b387dad686665.tar.gz")) {};
# Rolling updates, not deterministic.
# pkgs = import (fetchTarball("channel:nixpkgs-unstable")) {};
in pkgs.mkShell {
name = "revoltEnv";
# LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath [
# pkgs.gcc-unwrapped
# pkgs.zlib
# pkgs.glib
# pkgs.libGL
# ];
pkgs.mkShell rec {
buildInputs = [ buildInputs = [
# Tools # Tools
pkgs.git pkgs.git
# Database
# pkgs.mongodb
# Cargo # Cargo
pkgs.cargo pkgs.cargo
pkgs.cargo-nextest pkgs.cargo-nextest
+113 -72
View File
@@ -9,6 +9,11 @@
# The values provided in this template are the default values that will be used # The values provided in this template are the default values that will be used
# when any section or field is not specified in your own configuration # when any section or field is not specified in your own configuration
# Root options
# The graph table configures how the dependency graph is constructed and thus
# which crates the checks are performed against
[graph]
# If 1 or more target triples (and optionally, target_features) are specified, # If 1 or more target triples (and optionally, target_features) are specified,
# only the specified targets will be checked when running `cargo deny check`. # only the specified targets will be checked when running `cargo deny check`.
# This means, if a particular package is only ever used as a target specific # This means, if a particular package is only ever used as a target specific
@@ -20,85 +25,83 @@
targets = [ targets = [
# The triple can be any string, but only the target triples built in to # The triple can be any string, but only the target triples built in to
# rustc (as of 1.40) can be checked against actual config expressions # rustc (as of 1.40) can be checked against actual config expressions
#{ triple = "x86_64-unknown-linux-musl" }, #"x86_64-unknown-linux-musl",
# You can also specify which target_features you promise are enabled for a # You can also specify which target_features you promise are enabled for a
# particular target. target_features are currently not validated against # particular target. target_features are currently not validated against
# the actual valid features supported by the target architecture. # the actual valid features supported by the target architecture.
#{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, #{ triple = "wasm32-unknown-unknown", features = ["atomics"] },
] ]
# When creating the dependency graph used as the source of truth when checks are
# executed, this field can be used to prune crates from the graph, removing them
# from the view of cargo-deny. This is an extremely heavy hammer, as if a crate
# is pruned from the graph, all of its dependencies will also be pruned unless
# they are connected to another crate in the graph that hasn't been pruned,
# so it should be used with care. The identifiers are [Package ID Specifications]
# (https://doc.rust-lang.org/cargo/reference/pkgid-spec.html)
#exclude = []
# If true, metadata will be collected with `--all-features`. Note that this can't
# be toggled off if true, if you want to conditionally enable `--all-features` it
# is recommended to pass `--all-features` on the cmd line instead
all-features = false
# If true, metadata will be collected with `--no-default-features`. The same
# caveat with `all-features` applies
no-default-features = false
# If set, these feature will be enabled when collecting metadata. If `--features`
# is specified on the cmd line they will take precedence over this option.
#features = []
# The output table provides options for how/if diagnostics are outputted
[output]
# When outputting inclusion graphs in diagnostics that include features, this
# option can be used to specify the depth at which feature edges will be added.
# This option is included since the graphs can be quite large and the addition
# of features from the crate(s) to all of the graph roots can be far too verbose.
# This option can be overridden via `--feature-depth` on the cmd line
feature-depth = 1
# This section is considered when running `cargo deny check advisories` # This section is considered when running `cargo deny check advisories`
# More documentation for the advisories section can be found here: # More documentation for the advisories section can be found here:
# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html # https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html
[advisories] [advisories]
# The path where the advisory database is cloned/fetched into # The path where the advisory databases are cloned/fetched into
db-path = "~/.cargo/advisory-db" #db-path = "$CARGO_HOME/advisory-dbs"
# The url(s) of the advisory databases to use # The url(s) of the advisory databases to use
db-urls = ["https://github.com/rustsec/advisory-db"] #db-urls = ["https://github.com/rustsec/advisory-db"]
# The lint level for security vulnerabilities
vulnerability = "deny"
# The lint level for unmaintained crates
unmaintained = "warn"
# The lint level for crates that have been yanked from their source registry
yanked = "warn"
# The lint level for crates with security notices. Note that as of
# 2019-12-17 there are no security notice advisories in
# https://github.com/rustsec/advisory-db
notice = "warn"
# A list of advisory IDs to ignore. Note that ignored advisories will still # A list of advisory IDs to ignore. Note that ignored advisories will still
# output a note when they are encountered. # output a note when they are encountered.
ignore = [ ignore = [
#"RUSTSEC-0000-0000", #"RUSTSEC-0000-0000",
#{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" },
#"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish
#{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" },
] ]
# Threshold for security vulnerabilities, any vulnerability with a CVSS score # If this is true, then cargo deny will use the git executable to fetch advisory database.
# lower than the range specified will be ignored. Note that ignored advisories # If this is false, then it uses a built-in git library.
# will still output a note when they are encountered. # Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support.
# * None - CVSS Score 0.0 # See Git Authentication for more information about setting up git authentication.
# * Low - CVSS Score 0.1 - 3.9 #git-fetch-with-cli = true
# * Medium - CVSS Score 4.0 - 6.9
# * High - CVSS Score 7.0 - 8.9
# * Critical - CVSS Score 9.0 - 10.0
#severity-threshold =
# This section is considered when running `cargo deny check licenses` # This section is considered when running `cargo deny check licenses`
# More documentation for the licenses section can be found here: # More documentation for the licenses section can be found here:
# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html # https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html
[licenses] [licenses]
# The lint level for crates which do not have a detectable license
unlicensed = "warn"
# List of explicitly allowed licenses # List of explicitly allowed licenses
# See https://spdx.org/licenses/ for list of possible licenses # See https://spdx.org/licenses/ for list of possible licenses
# [possible values: any SPDX 3.11 short identifier (+ optional exception)]. # [possible values: any SPDX 3.11 short identifier (+ optional exception)].
allow = [ allow = [
# any non-copyleft licenses are okay!
"MIT", "MIT",
"ISC", "ISC",
"0BSD", "0BSD",
"Zlib",
"CC0-1.0", "CC0-1.0",
"MPL-2.0",
"Apache-2.0", "Apache-2.0",
"Apache-2.0 WITH LLVM-exception",
"BSD-2-Clause", "BSD-2-Clause",
"BSD-3-Clause", "BSD-3-Clause",
#"Apache-2.0 WITH LLVM-exception", "OpenSSL",
] ]
# List of explicitly disallowed licenses
# See https://spdx.org/licenses/ for list of possible licenses
# [possible values: any SPDX 3.11 short identifier (+ optional exception)].
deny = [
#"Nokia",
]
# Lint level for licenses considered copyleft
copyleft = "warn"
# Blanket approval or denial for OSI-approved or FSF Free/Libre licenses
# * both - The license will be approved if it is both OSI-approved *AND* FSF
# * either - The license will be approved if it is either OSI-approved *OR* FSF
# * osi-only - The license will be approved if is OSI-approved *AND NOT* FSF
# * fsf-only - The license will be approved if is FSF *AND NOT* OSI-approved
# * neither - This predicate is ignored and the default lint level is used
allow-osi-fsf-free = "neither"
# Lint level used when no other predicates are matched
# 1. License isn't in the allow or deny lists
# 2. License isn't copyleft
# 3. License isn't OSI/FSF, or allow-osi-fsf-free = "neither"
default = "deny"
# The confidence threshold for detecting a license from license text. # The confidence threshold for detecting a license from license text.
# The higher the value, the more closely the license text must be to the # The higher the value, the more closely the license text must be to the
# canonical license text of a valid SPDX license file. # canonical license text of a valid SPDX license file.
@@ -108,29 +111,34 @@ confidence-threshold = 0.8
# aren't accepted for every possible crate as with the normal allow list # aren't accepted for every possible crate as with the normal allow list
exceptions = [ exceptions = [
# Each entry is the crate and version constraint, and its specific allow # Each entry is the crate and version constraint, and its specific allow
# list { allow = ["AGPL-3.0"], crate = "revolt-database" },
#{ allow = ["Zlib"], name = "adler32", version = "*" }, { allow = ["AGPL-3.0"], crate = "revolt-presence" },
{ allow = ["AGPL-3.0"], crate = "revolt-files" },
{ allow = ["AGPL-3.0"], crate = "revolt-pushd" },
{ allow = ["AGPL-3.0"], crate = "revolt-crond" },
{ allow = ["AGPL-3.0"], crate = "revolt-delta" },
{ allow = ["AGPL-3.0"], crate = "revolt-bonfire" },
{ allow = ["AGPL-3.0"], crate = "revolt-autumn" },
{ allow = ["AGPL-3.0"], crate = "revolt-january" },
] ]
# Some crates don't have (easily) machine readable licensing information, # Some crates don't have (easily) machine readable licensing information,
# adding a clarification entry for it allows you to manually specify the # adding a clarification entry for it allows you to manually specify the
# licensing information # licensing information
#[[licenses.clarify]] [[licenses.clarify]]
# The name of the crate the clarification applies to # The package spec the clarification applies to
#name = "ring" crate = "ring"
# The optional version constraint for the crate
#version = "*"
# The SPDX expression for the license requirements of the crate # The SPDX expression for the license requirements of the crate
#expression = "MIT AND ISC AND OpenSSL" expression = "MIT AND ISC AND OpenSSL"
# One or more files in the crate's source used as the "source of truth" for # One or more files in the crate's source used as the "source of truth" for
# the license expression. If the contents match, the clarification will be used # the license expression. If the contents match, the clarification will be used
# when running the license check, otherwise the clarification will be ignored # when running the license check, otherwise the clarification will be ignored
# and the crate will be checked normally, which may produce warnings or errors # and the crate will be checked normally, which may produce warnings or errors
# depending on the rest of your configuration # depending on the rest of your configuration
#license-files = [ license-files = [
# Each entry is a crate relative path, and the (opaque) hash of its contents # Each entry is a crate relative path, and the (opaque) hash of its contents
#{ path = "LICENSE", hash = 0xbd0eed23 } { path = "LICENSE", hash = 0xbd0eed23 },
#] ]
[licenses.private] [licenses.private]
# If true, ignores workspace crates that aren't published, or are only # If true, ignores workspace crates that aren't published, or are only
@@ -159,30 +167,63 @@ wildcards = "allow"
# * simplest-path - The path to the version with the fewest edges is highlighted # * simplest-path - The path to the version with the fewest edges is highlighted
# * all - Both lowest-version and simplest-path are used # * all - Both lowest-version and simplest-path are used
highlight = "all" highlight = "all"
# The default lint level for `default` features for crates that are members of
# the workspace that is being checked. This can be overridden by allowing/denying
# `default` on a crate-by-crate basis if desired.
workspace-default-features = "allow"
# The default lint level for `default` features for external crates that are not
# members of the workspace. This can be overridden by allowing/denying `default`
# on a crate-by-crate basis if desired.
external-default-features = "allow"
# List of crates that are allowed. Use with care! # List of crates that are allowed. Use with care!
allow = [ allow = [
#{ name = "ansi_term", version = "=0.11.0" }, #"ansi_term@0.11.0",
#{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is allowed" },
] ]
# List of crates to deny # List of crates to deny
deny = [ deny = [
# Each entry the name of a crate and a version range. If version is #"ansi_term@0.11.0",
# not specified, all versions will be matched. #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is banned" },
#{ name = "ansi_term", version = "=0.11.0" },
#
# Wrapper crates can optionally be specified to allow the crate when it # Wrapper crates can optionally be specified to allow the crate when it
# is a direct dependency of the otherwise banned crate # is a direct dependency of the otherwise banned crate
#{ name = "ansi_term", version = "=0.11.0", wrappers = [] }, #{ crate = "ansi_term@0.11.0", wrappers = ["this-crate-directly-depends-on-ansi_term"] },
] ]
# List of features to allow/deny
# Each entry the name of a crate and a version range. If version is
# not specified, all versions will be matched.
#[[bans.features]]
#crate = "reqwest"
# Features to not allow
#deny = ["json"]
# Features to allow
#allow = [
# "rustls",
# "__rustls",
# "__tls",
# "hyper-rustls",
# "rustls",
# "rustls-pemfile",
# "rustls-tls-webpki-roots",
# "tokio-rustls",
# "webpki-roots",
#]
# If true, the allowed features must exactly match the enabled feature set. If
# this is set there is no point setting `deny`
#exact = true
# Certain crates/versions that will be skipped when doing duplicate detection. # Certain crates/versions that will be skipped when doing duplicate detection.
skip = [ skip = [
#{ name = "ansi_term", version = "=0.11.0" }, #"ansi_term@0.11.0",
#{ crate = "ansi_term@0.11.0", reason = "you can specify a reason why it can't be updated/removed" },
] ]
# Similarly to `skip` allows you to skip certain crates during duplicate # Similarly to `skip` allows you to skip certain crates during duplicate
# detection. Unlike skip, it also includes the entire tree of transitive # detection. Unlike skip, it also includes the entire tree of transitive
# dependencies starting at the specified crate, up to a certain depth, which is # dependencies starting at the specified crate, up to a certain depth, which is
# by default infinite # by default infinite.
skip-tree = [ skip-tree = [
#{ name = "ansi_term", version = "=0.11.0", depth = 20 }, #"ansi_term@0.11.0", # will be skipped along with _all_ of its direct and transitive dependencies
#{ crate = "ansi_term@0.11.0", depth = 20 },
] ]
# This section is considered when running `cargo deny check sources`. # This section is considered when running `cargo deny check sources`.
@@ -202,9 +243,9 @@ allow-registry = ["https://github.com/rust-lang/crates.io-index"]
allow-git = [] allow-git = []
[sources.allow-org] [sources.allow-org]
# 1 or more github.com organizations to allow git sources for # github.com organizations to allow git sources for
github = ["insertish"] github = []
# 1 or more gitlab.com organizations to allow git sources for # gitlab.com organizations to allow git sources for
# gitlab = [] gitlab = []
# 1 or more bitbucket.org organizations to allow git sources for # bitbucket.org organizations to allow git sources for
# bitbucket = [""] bitbucket = []
+3
View File
@@ -34,12 +34,14 @@ deps() {
crates/core/result/src \ crates/core/result/src \
crates/services/autumn/src \ crates/services/autumn/src \
crates/services/january/src \ crates/services/january/src \
crates/daemons/crond/src \
crates/daemons/pushd/src crates/daemons/pushd/src
echo 'fn main() { panic!("stub"); }' | echo 'fn main() { panic!("stub"); }' |
tee crates/bonfire/src/main.rs | tee crates/bonfire/src/main.rs |
tee crates/delta/src/main.rs | tee crates/delta/src/main.rs |
tee crates/services/autumn/src/main.rs | tee crates/services/autumn/src/main.rs |
tee crates/services/january/src/main.rs | tee crates/services/january/src/main.rs |
tee crates/daemons/crond/src/main.rs |
tee crates/daemons/pushd/src/main.rs tee crates/daemons/pushd/src/main.rs
echo '' | echo '' |
tee crates/bindings/node/src/lib.rs | tee crates/bindings/node/src/lib.rs |
@@ -62,6 +64,7 @@ apps() {
touch -am \ touch -am \
crates/bonfire/src/main.rs \ crates/bonfire/src/main.rs \
crates/delta/src/main.rs \ crates/delta/src/main.rs \
crates/daemons/crond/src/main.rs \
crates/daemons/pushd/src/main.rs \ crates/daemons/pushd/src/main.rs \
crates/core/config/src/lib.rs \ crates/core/config/src/lib.rs \
crates/core/database/src/lib.rs \ crates/core/database/src/lib.rs \
+93
View File
@@ -0,0 +1,93 @@
# a test script that generates a ton of users for debugging use
# note that you'll need to comment out the ratelimiter in delta/src/main.rs
# and keep the number relatively low or requests will time out (the beefier the machine the more you can handle).
# this script assumes mailhog is running, and uses that to automate "emails".
# In the real world, antispam will catch this and nuke you to hell and back.
# But it works fine in a dev env!
# requires httpx
import asyncio
import os
import re
import uuid
import httpx
API_URL: str = os.getenv("API_URL") # type: ignore
MAILHOG_API: str = os.getenv("MAILHOG_API") # type: ignore
COUNT = int(os.getenv("COUNT")) # type: ignore # cbf to deal with type checking
INVITE: str = os.getenv("INVITE") # type: ignore
assert API_URL and MAILHOG_API and COUNT and INVITE
API_URL = API_URL.strip("/")
MAILHOG_API = MAILHOG_API.strip("/")
async def filter_hog(client: httpx.AsyncClient, email: str) -> str:
"""
returns the token provided by the mail server.
This script assumes the use of mailhog.
"""
resp = await client.get(MAILHOG_API + "/api/v2/search", params={"kind": "to", "query": email}, follow_redirects=True, timeout=60)
if resp.status_code != 200:
raise Exception(resp.status_code, resp.content)
data = resp.json()
if not data["items"]:
raise Exception("No message found")
message_id = data["items"][0]["ID"]
body = data["items"][0]["Content"]["Body"].replace("\r", "")
token = re.search("/login/verify(=\n/|/\n=|/=\n)(?P<token>[^\n]+)", body, re.MULTILINE)
if not token:
raise Exception("No token found")
ret = token.group("token")
await client.delete(MAILHOG_API + f"/api/v1/messages/{message_id}", timeout=60)
return ret
async def task() -> None:
_id = str(uuid.uuid4())[:4]
email = f"{_id}@example.com"
async with httpx.AsyncClient() as client:
resp = await client.post(API_URL + "/auth/account/create", json={"email": email, "password": _id*3, "invite": INVITE}, timeout=60)
if resp.status_code != 204:
raise Exception(resp.status_code, resp.content)
token = await filter_hog(client, email)
resp = await client.post(API_URL + f"/auth/account/verify/{token}", timeout=60)
if resp.status_code != 200:
raise Exception("verify", resp.status_code, resp.content)
ticket = resp.json()["ticket"]
userid = ticket["_id"]
resp = await client.post(API_URL + "/auth/session/login", json={"email": email, "password": _id*3, "friendly_name": "Not A Client"}, timeout=60)
if resp.status_code != 200:
raise Exception("session", resp.status_code, resp.content)
session = resp.json()
token = session["token"]
resp = await client.post(API_URL + "/onboard/complete", json={"username": _id}, headers={"x-session-token": token}, timeout=60) # complete onboarding to allow creating a session
if resp.status_code != 200:
raise Exception("onboard", resp.status_code, resp.content)
resp = await client.post(API_URL + f"/invites/{INVITE}", headers={"x-session-token": token}, timeout=60)
if resp.status_code != 200:
raise Exception("invite", resp.status_code, resp.content)
print(f"Created account and session for {email} with ID: {userid}")
return userid
async def main():
tasks = [asyncio.create_task(task()) for _ in range(COUNT)]
print(await asyncio.gather(*tasks))
asyncio.run(main())
+4
View File
@@ -25,6 +25,8 @@ docker build -t ghcr.io/revoltchat/server:$TAG - < crates/delta/Dockerfile
docker build -t ghcr.io/revoltchat/bonfire:$TAG - < crates/bonfire/Dockerfile docker build -t ghcr.io/revoltchat/bonfire:$TAG - < crates/bonfire/Dockerfile
docker build -t ghcr.io/revoltchat/autumn:$TAG - < crates/services/autumn/Dockerfile docker build -t ghcr.io/revoltchat/autumn:$TAG - < crates/services/autumn/Dockerfile
docker build -t ghcr.io/revoltchat/january:$TAG - < crates/services/january/Dockerfile docker build -t ghcr.io/revoltchat/january:$TAG - < crates/services/january/Dockerfile
docker build -t ghcr.io/revoltchat/crond:$TAG - < crates/daemons/crond/Dockerfile
docker build -t ghcr.io/revoltchat/pushd:$TAG - < crates/daemons/pushd/Dockerfile
if [ "$DEBUG" = "true" ]; then if [ "$DEBUG" = "true" ]; then
git restore Cargo.toml git restore Cargo.toml
@@ -34,3 +36,5 @@ docker push ghcr.io/revoltchat/server:$TAG
docker push ghcr.io/revoltchat/bonfire:$TAG docker push ghcr.io/revoltchat/bonfire:$TAG
docker push ghcr.io/revoltchat/autumn:$TAG docker push ghcr.io/revoltchat/autumn:$TAG
docker push ghcr.io/revoltchat/january:$TAG docker push ghcr.io/revoltchat/january:$TAG
docker push ghcr.io/revoltchat/crond:$TAG
docker push ghcr.io/revoltchat/pushd:$TAG