Merge remote-tracking branch 'origin/main' into feat/audit-log

Signed-off-by: Zomatree <me@zomatree.live>
This commit is contained in:
Zomatree
2026-06-24 14:02:15 +01:00
35 changed files with 703 additions and 1150 deletions

1725
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -23,11 +23,11 @@ async-trait = "0.1.89"
tokio = "1.49.0" tokio = "1.49.0"
async-channel = "2.3.1" async-channel = "2.3.1"
futures = "0.3.32" futures = "0.3.32"
async-std = "1.8.0"
async-tungstenite = "0.17.0" async-tungstenite = "0.17.0"
futures-locks = "0.7.1" futures-locks = "0.7.1"
async-lock = "2.8.0" async-lock = "2.8.0"
async-recursion = "1.0.4" async-recursion = "1.0.4"
tokio-util = { version = "0.7.18" }
# Error Handling # Error Handling
anyhow = "1.0.100" anyhow = "1.0.100"
@@ -89,7 +89,7 @@ log = "0.4.29"
pretty_env_logger = "0.4.0" pretty_env_logger = "0.4.0"
# Redis # Redis
redis-kiss = "0.1.4" redis-kiss = { version = "0.1.4", default-features = false }
fred = "8.0.1" fred = "8.0.1"
# Serialisation # Serialisation
@@ -160,8 +160,8 @@ opentelemetry-appender-tracing = "0.31.1"
lapin = "4.7.1" lapin = "4.7.1"
# Voice # Voice
livekit-api = "0.4.4" livekit-api = "=0.4.23"
livekit-protocol = "0.7.4" livekit-protocol = "=0.7.7"
livekit-runtime = "0.4.0" livekit-runtime = "0.4.0"
# Other Utilities # Other Utilities

View File

@@ -14,7 +14,7 @@ sentry = { workspace = true }
lru = { workspace = true } lru = { workspace = true }
ulid = { workspace = true } ulid = { workspace = true }
once_cell = { workspace = true } once_cell = { workspace = true }
redis-kiss = { workspace = true } redis-kiss = { workspace = true, default-features = false, features = ["tokio-runtime"] }
lru_time_cache = { workspace = true } lru_time_cache = { workspace = true }
async-channel = { workspace = true } async-channel = { workspace = true }
@@ -30,8 +30,9 @@ serde = { workspace = true }
# async # async
futures = { workspace = true } futures = { workspace = true }
async-tungstenite = { workspace = true, features = ["async-std-runtime"] } async-tungstenite = { workspace = true, features = ["tokio-runtime"] }
async-std = { workspace = true } tokio = { workspace = true }
tokio-util = { workspace = true, features = ["compat"] }
# core # core
revolt-result = { workspace = true } revolt-result = { workspace = true }

View File

@@ -2,7 +2,7 @@ use std::{
collections::{HashMap, HashSet}, num::NonZeroUsize, sync::Arc, time::Duration collections::{HashMap, HashSet}, num::NonZeroUsize, sync::Arc, time::Duration
}; };
use async_std::sync::{Mutex, RwLock}; use tokio::sync::{Mutex, RwLock};
use lru::LruCache; use lru::LruCache;
use lru_time_cache::{LruCache as LruTimeCache, TimedEntry}; use lru_time_cache::{LruCache as LruTimeCache, TimedEntry};
use revolt_database::{Channel, Member, Server, User}; use revolt_database::{Channel, Member, Server, User};

View File

@@ -1,6 +1,6 @@
use std::env; use std::env;
use async_std::net::TcpListener; use tokio::net::TcpListener;
use revolt_presence::clear_region; use revolt_presence::clear_region;
#[macro_use] #[macro_use]
@@ -12,7 +12,7 @@ pub mod events;
mod database; mod database;
mod websocket; mod websocket;
#[async_std::main] #[tokio::main]
async fn main() { async fn main() {
// Configure requirements for Bonfire. // Configure requirements for Bonfire.
revolt_config::configure!(events); revolt_config::configure!(events);
@@ -33,7 +33,7 @@ async fn main() {
// Start accepting new connections and spawn a client for each connection. // Start accepting new connections and spawn a client for each connection.
while let Ok((stream, addr)) = listener.accept().await { while let Ok((stream, addr)) = listener.accept().await {
async_std::task::spawn(async move { tokio::task::spawn(async move {
info!("User connected from {addr:?}"); info!("User connected from {addr:?}");
websocket::client(database::get_db(), stream, addr).await; websocket::client(database::get_db(), stream, addr).await;
info!("User disconnected from {addr:?}"); info!("User disconnected from {addr:?}");

View File

@@ -21,11 +21,12 @@ use revolt_database::{
}; };
use revolt_presence::{create_session, delete_session}; use revolt_presence::{create_session, delete_session};
use async_std::{ use tokio::{
net::TcpStream, net::TcpStream,
sync::{Mutex, RwLock}, sync::{Mutex, RwLock},
task::spawn, task::spawn,
}; };
use tokio_util::compat::{TokioAsyncReadCompatExt, Compat};
use revolt_result::create_error; use revolt_result::create_error;
use sentry::Level; use sentry::Level;
@@ -33,8 +34,8 @@ use crate::config::{ProtocolConfiguration, WebsocketHandshakeCallback};
use crate::events::state::{State, SubscriptionStateChange}; use crate::events::state::{State, SubscriptionStateChange};
use revolt_models::v0; use revolt_models::v0;
type WsReader = SplitStream<WebSocketStream<TcpStream>>; type WsReader = SplitStream<WebSocketStream<Compat<TcpStream>>>;
type WsWriter = SplitSink<WebSocketStream<TcpStream>, async_tungstenite::tungstenite::Message>; type WsWriter = SplitSink<WebSocketStream<Compat<TcpStream>>, async_tungstenite::tungstenite::Message>;
/// Start a new WebSocket client worker given access to the database, /// Start a new WebSocket client worker given access to the database,
/// the relevant TCP stream and the remote address of the client. /// the relevant TCP stream and the remote address of the client.
@@ -44,7 +45,7 @@ pub async fn client(db: &'static Database, stream: TcpStream, addr: SocketAddr)
// e.g. wss://example.com?format=json&version=1 // e.g. wss://example.com?format=json&version=1
let (sender, receiver) = oneshot::channel(); let (sender, receiver) = oneshot::channel();
let Ok(ws) = async_tungstenite::accept_hdr_async_with_config( let Ok(ws) = async_tungstenite::accept_hdr_async_with_config(
stream, stream.compat(),
WebsocketHandshakeCallback::from(sender), WebsocketHandshakeCallback::from(sender),
None, None,
) )

View File

@@ -13,7 +13,7 @@ repository = "https://github.com/stoatchat/stoatchat"
anyhow = ["dep:sentry-anyhow"] anyhow = ["dep:sentry-anyhow"]
report-macros = ["revolt-result"] report-macros = ["revolt-result"]
sentry = ["dep:sentry"] sentry = ["dep:sentry"]
test = ["async-std"] test = ["tokio"]
default = ["sentry"] default = ["sentry"]
[dependencies] [dependencies]
@@ -26,7 +26,7 @@ serde = { workspace = true }
# Async # Async
futures-locks = { workspace = true } futures-locks = { workspace = true }
async-std = { workspace = true, features = ["attributes"], optional = true } tokio = { workspace = true, optional = true }
# Logging # Logging
log = { workspace = true } log = { workspace = true }

View File

@@ -593,7 +593,7 @@ macro_rules! configure {
mod tests { mod tests {
use crate::init; use crate::init;
#[async_std::test] #[tokio::test]
async fn it_works() { async fn it_works() {
init().await; init().await;
} }

View File

@@ -15,7 +15,7 @@ mongodb = ["dep:mongodb", "bson"]
# ... Other # ... Other
tasks = ["isahc", "linkify", "url-escape"] tasks = ["isahc", "linkify", "url-escape"]
async-std-runtime = ["async-std"] tokio-runtime = ["tokio"]
rocket-impl = [ rocket-impl = [
"rocket", "rocket",
"schemars", "schemars",
@@ -27,7 +27,7 @@ redis-is-patched = ["revolt-presence/redis-is-patched"]
voice = ["livekit-api", "livekit-protocol", "livekit-runtime"] voice = ["livekit-api", "livekit-protocol", "livekit-runtime"]
# Default Features # Default Features
default = ["mongodb", "async-std-runtime", "tasks"] default = ["mongodb", "tokio-runtime", "tasks"]
[dependencies] [dependencies]
# Core # Core
@@ -64,7 +64,7 @@ serde = { workspace = true }
iso8601-timestamp = { workspace = true, features = ["serde", "bson"] } iso8601-timestamp = { workspace = true, features = ["serde", "bson"] }
# Events # Events
redis-kiss = { workspace = true } redis-kiss = { workspace = true, default-features = false, features = ["tokio-runtime"] }
# Database # Database
bson = { workspace = true, optional = true } bson = { workspace = true, optional = true }
@@ -81,7 +81,7 @@ async-trait = { workspace = true }
async-recursion = { workspace = true } async-recursion = { workspace = true }
# Async # Async
async-std = { workspace = true, features = ["attributes"], optional = true } tokio = { workspace = true, optional = true }
# Axum Impl # Axum Impl
axum = { workspace = true, optional = true } axum = { workspace = true, optional = true }

View File

@@ -25,8 +25,8 @@ pub use mongodb;
#[macro_use] #[macro_use]
extern crate bson; extern crate bson;
#[cfg(not(feature = "async-std-runtime"))] #[cfg(not(feature = "tokio-runtime"))]
compile_error!("async-std-runtime feature must be enabled."); compile_error!("tokio-runtime feature must be enabled.");
#[macro_export] #[macro_export]
#[cfg(debug_assertions)] #[cfg(debug_assertions)]

View File

@@ -11,7 +11,7 @@ auto_derived!(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#[async_std::test] #[tokio::test]
async fn migrate() { async fn migrate() {
database_test!(|db| async move { database_test!(|db| async move {
// Initialise the database // Initialise the database

View File

@@ -452,7 +452,7 @@ pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 {
warn!("This is a destructive operation and will wipe existing permission data (excl. defaults for SendMessage)."); warn!("This is a destructive operation and will wipe existing permission data (excl. defaults for SendMessage).");
warn!("Taking a backup is advised."); warn!("Taking a backup is advised.");
warn!("Continuing in 10 seconds..."); warn!("Continuing in 10 seconds...");
async_std::task::sleep(Duration::from_secs(10)).await; tokio::time::sleep(Duration::from_secs(10)).await;
let servers = db.col::<Document>("servers"); let servers = db.col::<Document>("servers");
let mut cursor = servers.find(doc! {}).await.unwrap(); let mut cursor = servers.find(doc! {}).await.unwrap();

View File

@@ -164,7 +164,7 @@ impl Bot {
mod tests { mod tests {
use crate::{Bot, FieldsBot, PartialBot, User}; use crate::{Bot, FieldsBot, PartialBot, User};
#[async_std::test] #[tokio::test]
async fn crud() { async fn crud() {
database_test!(|db| async move { database_test!(|db| async move {
let owner = User::create(&db, "Owner".to_string(), None, None) let owner = User::create(&db, "Owner".to_string(), None, None)

View File

@@ -128,7 +128,7 @@ impl Webhook {
mod tests { mod tests {
use crate::{FieldsWebhook, PartialWebhook, Webhook}; use crate::{FieldsWebhook, PartialWebhook, Webhook};
#[async_std::test] #[tokio::test]
async fn crud() { async fn crud() {
database_test!(|db| async move { database_test!(|db| async move {
let webhook_id = "webhook"; let webhook_id = "webhook";

View File

@@ -898,7 +898,7 @@ mod tests {
use crate::{fixture, util::permissions::DatabasePermissionQuery}; use crate::{fixture, util::permissions::DatabasePermissionQuery};
#[async_std::test] #[tokio::test]
async fn permissions_group_channel() { async fn permissions_group_channel() {
database_test!(|db| async move { database_test!(|db| async move {
fixture!(db, "group_with_members", fixture!(db, "group_with_members",
@@ -924,7 +924,7 @@ mod tests {
}); });
} }
#[async_std::test] #[tokio::test]
async fn permissions_text_channel() { async fn permissions_text_channel() {
database_test!(|db| async move { database_test!(|db| async move {
fixture!(db, "server_with_roles", fixture!(db, "server_with_roles",

View File

@@ -340,7 +340,7 @@ mod tests {
use crate::{Member, PartialMember, RemovalIntention, Server, User}; use crate::{Member, PartialMember, RemovalIntention, Server, User};
#[async_std::test] #[tokio::test]
async fn muted_member_rejoin() { async fn muted_member_rejoin() {
database_test!(|db| async move { database_test!(|db| async move {
match db { match db {

View File

@@ -464,7 +464,7 @@ mod tests {
use crate::{fixture, util::permissions::DatabasePermissionQuery}; use crate::{fixture, util::permissions::DatabasePermissionQuery};
#[async_std::test] #[tokio::test]
async fn permissions() { async fn permissions() {
database_test!(|db| async move { database_test!(|db| async move {
fixture!(db, "server_with_roles", fixture!(db, "server_with_roles",

View File

@@ -926,7 +926,7 @@ mod tests {
assert!(User::validate_username(username_https).is_err()); assert!(User::validate_username(username_https).is_err());
} }
#[async_std::test] #[tokio::test]
async fn username_sanitisation_clean() { async fn username_sanitisation_clean() {
let username_clean = "Test"; let username_clean = "Test";
@@ -936,7 +936,7 @@ mod tests {
assert_eq!(username_clean, username_clean_sanitised.unwrap()); assert_eq!(username_clean, username_clean_sanitised.unwrap());
} }
#[async_std::test] #[tokio::test]
async fn username_sanitisation_homoglyphs() { async fn username_sanitisation_homoglyphs() {
let username_homoglyphs = "𝔽𝕌Ňℕy"; let username_homoglyphs = "𝔽𝕌Ňℕy";
@@ -947,7 +947,7 @@ mod tests {
assert_eq!("funny", username_homoglyphs_sanitised); assert_eq!("funny", username_homoglyphs_sanitised);
} }
#[async_std::test] #[tokio::test]
async fn username_sanitisation_padding() { async fn username_sanitisation_padding() {
let username_padding = "a"; let username_padding = "a";
@@ -956,7 +956,7 @@ mod tests {
assert_eq!("a_", username); assert_eq!("a_", username);
} }
#[async_std::test] #[tokio::test]
async fn create_user() { async fn create_user() {
use revolt_result::Result; use revolt_result::Result;

View File

@@ -305,6 +305,6 @@ pub async fn worker(db: Database, amqp: AMQP) {
} }
// Sleep for an arbitrary amount of time. // Sleep for an arbitrary amount of time.
async_std::task::sleep(Duration::from_secs(1)).await; tokio::time::sleep(Duration::from_secs(1)).await;
} }
} }

View File

@@ -82,6 +82,6 @@ pub async fn worker(db: Database) {
} }
// Sleep for an arbitrary amount of time. // Sleep for an arbitrary amount of time.
async_std::task::sleep(Duration::from_secs(1)).await; tokio::time::sleep(Duration::from_secs(1)).await;
} }
} }

View File

@@ -2,7 +2,7 @@
use crate::{Database, AMQP}; use crate::{Database, AMQP};
use async_std::task; use tokio::task;
use std::time::Instant; use std::time::Instant;
const WORKER_COUNT: usize = 5; const WORKER_COUNT: usize = 5;

View File

@@ -7,11 +7,11 @@ use revolt_config::config;
use revolt_result::Result; use revolt_result::Result;
use async_lock::Semaphore; use async_lock::Semaphore;
use async_std::task::spawn;
use deadqueue::limited::Queue; use deadqueue::limited::Queue;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use revolt_models::v0::Embed; use revolt_models::v0::Embed;
use std::{collections::HashSet, sync::Arc}; use std::{collections::HashSet, sync::Arc};
use tokio::task::spawn;
use isahc::prelude::*; use isahc::prelude::*;
@@ -159,6 +159,7 @@ pub async fn generate(
.await .await
.into_iter() .into_iter()
.flatten() .flatten()
.flatten()
.collect::<Vec<Embed>>(); .collect::<Vec<Embed>>();
// Prevent database update when no embeds are found. // Prevent database update when no embeds are found.

View File

@@ -5,7 +5,7 @@ use revolt_result::{create_error, Result};
#[cfg(feature = "rocket-impl")] #[cfg(feature = "rocket-impl")]
use revolt_result::Error; use revolt_result::Error;
use async_std::sync::Mutex; use tokio::sync::Mutex;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};

View File

@@ -18,7 +18,7 @@ try-from-primitive = ["dep:num_enum"]
[dev-dependencies] [dev-dependencies]
# Async # Async
async-std = { workspace = true, features = ["attributes"] } tokio = { workspace = true }
[dependencies] [dependencies]
# Core # Core

View File

@@ -4,7 +4,7 @@ use crate::{
DEFAULT_PERMISSION_SERVER, DEFAULT_PERMISSION_VIEW_ONLY, DEFAULT_PERMISSION_SERVER, DEFAULT_PERMISSION_VIEW_ONLY,
}; };
#[async_std::test] #[tokio::test]
async fn validate_user_permissions() { async fn validate_user_permissions() {
/// Scenario in which we are friends with a user /// Scenario in which we are friends with a user
/// and we have a DM channel open with them /// and we have a DM channel open with them
@@ -102,7 +102,7 @@ async fn validate_user_permissions() {
} }
} }
#[async_std::test] #[tokio::test]
async fn validate_group_permissions() { async fn validate_group_permissions() {
/// Scenario in which we are in a group channel with only talking permission /// Scenario in which we are in a group channel with only talking permission
struct Scenario {} struct Scenario {}
@@ -202,7 +202,7 @@ async fn validate_group_permissions() {
} }
} }
#[async_std::test] #[tokio::test]
async fn validate_server_permissions() { async fn validate_server_permissions() {
/// Scenario in which we are in a server channel where: /// Scenario in which we are in a server channel where:
/// - the server grants reading history and sending messages by default /// - the server grants reading history and sending messages by default
@@ -314,7 +314,7 @@ async fn validate_server_permissions() {
} }
} }
#[async_std::test] #[tokio::test]
async fn validate_timed_out_member() { async fn validate_timed_out_member() {
/// Scenario in which we are in a server that we have been timed out from /// Scenario in which we are in a server that we have been timed out from
struct Scenario {} struct Scenario {}

View File

@@ -14,7 +14,7 @@ redis-is-patched = []
[dev-dependencies] [dev-dependencies]
# Async # Async
async-std = { workspace = true, features = ["attributes"] } tokio = { workspace = true }
# Config for loading Redis URI # Config for loading Redis URI
revolt-config = { workspace = true } revolt-config = { workspace = true }
@@ -26,4 +26,4 @@ rand = { workspace = true }
once_cell = { workspace = true } once_cell = { workspace = true }
# Redis # Redis
redis-kiss = { workspace = true } redis-kiss = { workspace = true, default-features = false, features = ["tokio-runtime"] }

View File

@@ -195,7 +195,7 @@ mod tests {
use crate::{clear_region, create_session, delete_session, filter_online, is_online}; use crate::{clear_region, create_session, delete_session, filter_online, is_online};
use rand::Rng; use rand::Rng;
#[async_std::test] #[tokio::test]
async fn it_works() { async fn it_works() {
revolt_config::config().await; revolt_config::config().await;

View File

@@ -18,7 +18,7 @@ tokio = { workspace = true }
futures = { workspace = true } futures = { workspace = true }
# Redis # Redis
redis-kiss = { workspace = true } redis-kiss = { workspace = true, default-features = false, features = ["tokio-runtime"] }
# RabbitMQ # RabbitMQ
lapin = { workspace = true } lapin = { workspace = true }

View File

@@ -20,7 +20,7 @@ fcm_v1 = { workspace = true }
web-push = { workspace = true } web-push = { workspace = true }
isahc = { workspace = true, features = ["json"], optional = true } isahc = { workspace = true, features = ["json"], optional = true }
revolt_a2 = { workspace = true, features = ["ring"] } revolt_a2 = { workspace = true, features = ["ring"] }
redis-kiss = { workspace = true } redis-kiss = { workspace = true, default-features = false, features = ["tokio-runtime"] }
tokio = { workspace = true } tokio = { workspace = true }
async-trait = { workspace = true } async-trait = { workspace = true }
ulid = { workspace = true } ulid = { workspace = true }

View File

@@ -13,7 +13,7 @@ log = { workspace = true }
sentry = { workspace = true } sentry = { workspace = true }
lru = { workspace = true } lru = { workspace = true }
ulid = { workspace = true } ulid = { workspace = true }
redis-kiss = { workspace = true } redis-kiss = { workspace = true, default-features = false, features = ["tokio-runtime"] }
chrono = { workspace = true } chrono = { workspace = true }
# Serde # Serde
@@ -27,11 +27,6 @@ rocket_empty = { workspace = true }
# Async # Async
futures = { workspace = true } futures = { workspace = true }
async-std = { workspace = true, features = [
"tokio1",
"tokio02",
"attributes",
] }
# Core # Core
revolt-result = { workspace = true, features = ["rocket"] } revolt-result = { workspace = true, features = ["rocket"] }

View File

@@ -11,7 +11,7 @@ publish = false
[dependencies] [dependencies]
# Test # Test
rand = { workspace = true } rand = { workspace = true }
redis-kiss = { workspace = true } redis-kiss = { workspace = true, default-features = false, features = ["tokio-runtime"] }
# Utility # Utility
lru = { workspace = true } lru = { workspace = true }
@@ -42,11 +42,7 @@ futures = { workspace = true }
chrono = { workspace = true } chrono = { workspace = true }
async-channel = { workspace = true } async-channel = { workspace = true }
reqwest = { workspace = true, features = ["json"] } reqwest = { workspace = true, features = ["json"] }
async-std = { workspace = true, features = [ tokio = { workspace = true }
"tokio1",
"tokio02",
"attributes",
] }
# internal util # internal util
lettre = { workspace = true } lettre = { workspace = true }

View File

@@ -2,7 +2,7 @@
//! POST /account/create //! POST /account/create
use std::time::Duration; use std::time::Duration;
use async_std::task::sleep; use tokio::time::sleep;
use revolt_config::config; use revolt_config::config;
use revolt_database::{ use revolt_database::{
util::{ util::{

View File

@@ -2,7 +2,7 @@
//! POST /account/reverify //! POST /account/reverify
use std::time::Duration; use std::time::Duration;
use async_std::task::sleep; use tokio::time::sleep;
use rocket::{serde::json::Json, State}; use rocket::{serde::json::Json, State};
use rocket_empty::EmptyResponse; use rocket_empty::EmptyResponse;
use revolt_result::Result; use revolt_result::Result;

View File

@@ -2,7 +2,7 @@
//! POST /account/reset_password //! POST /account/reset_password
use std::time::Duration; use std::time::Duration;
use async_std::task::sleep; use tokio::time::sleep;
use rocket::serde::json::Json; use rocket::serde::json::Json;
use rocket::State; use rocket::State;
use rocket_empty::EmptyResponse; use rocket_empty::EmptyResponse;

View File

@@ -3,7 +3,7 @@
use std::ops::Add; use std::ops::Add;
use std::time::Duration; use std::time::Duration;
use async_std::task::sleep; use tokio::time::sleep;
use iso8601_timestamp::Timestamp; use iso8601_timestamp::Timestamp;
use revolt_database::{ use revolt_database::{
util::{email::normalise_email, password::assert_safe}, util::{email::normalise_email, password::assert_safe},