From ccfb5f1a62f1be9177ac9c1c09fd18ea8e9c7f18 Mon Sep 17 00:00:00 2001 From: Zomatree Date: Wed, 5 Aug 2026 08:14:05 +0100 Subject: [PATCH 1/4] feat: add an approximate member count to servers (#884) * feat: add an approximate member count to servers Signed-off-by: Zomatree * fix: use approx member count on invites Signed-off-by: Zomatree --------- Signed-off-by: Zomatree --- crates/bonfire/src/events/impl.rs | 2 +- .../src/models/server_members/model.rs | 14 +++++-- .../core/database/src/models/servers/model.rs | 29 +++++++++++++ crates/core/database/src/util/bridge/v0.rs | 42 +++++++++---------- crates/core/models/src/v0/servers.rs | 3 ++ .../delta/src/routes/invites/invite_fetch.rs | 2 +- .../delta/src/routes/invites/invite_join.rs | 2 +- .../src/routes/servers/permissions_set.rs | 2 +- .../routes/servers/permissions_set_default.rs | 2 +- .../routes/servers/roles_edit_positions.rs | 2 +- .../delta/src/routes/servers/server_create.rs | 2 +- .../delta/src/routes/servers/server_edit.rs | 6 +-- .../delta/src/routes/servers/server_fetch.rs | 6 ++- 13 files changed, 77 insertions(+), 37 deletions(-) diff --git a/crates/bonfire/src/events/impl.rs b/crates/bonfire/src/events/impl.rs index 84a4cdc4..ff79a6cb 100644 --- a/crates/bonfire/src/events/impl.rs +++ b/crates/bonfire/src/events/impl.rs @@ -307,7 +307,7 @@ impl State { Ok(EventV1::Ready { users: if fields.users { Some(users) } else { None }, servers: if fields.servers { - Some(servers.into_iter().map(Into::into).collect()) + Some(join_all(servers.into_iter().map(|server| server.into(db))).await) } else { None }, diff --git a/crates/core/database/src/models/server_members/model.rs b/crates/core/database/src/models/server_members/model.rs index 158874e6..f452564b 100644 --- a/crates/core/database/src/models/server_members/model.rs +++ b/crates/core/database/src/models/server_members/model.rs @@ -176,7 +176,7 @@ impl Member { EventV1::ServerCreate { id: server.id.clone(), - server: server.clone().into(), + server: server.clone().into(db).await, channels: channels .clone() .into_iter() @@ -317,9 +317,15 @@ impl Member { }) { match intention { - RemovalIntention::Leave => SystemMessage::UserLeft { id: self.id.user.clone() }, - RemovalIntention::Kick => SystemMessage::UserKicked { id: self.id.user.clone() }, - RemovalIntention::Ban => SystemMessage::UserBanned { id: self.id.user.clone() }, + RemovalIntention::Leave => SystemMessage::UserLeft { + id: self.id.user.clone(), + }, + RemovalIntention::Kick => SystemMessage::UserKicked { + id: self.id.user.clone(), + }, + RemovalIntention::Ban => SystemMessage::UserBanned { + id: self.id.user.clone(), + }, } .into_message(id.to_string()) // TODO: support notifications here in the future? diff --git a/crates/core/database/src/models/servers/model.rs b/crates/core/database/src/models/servers/model.rs index 59b15000..65d37929 100644 --- a/crates/core/database/src/models/servers/model.rs +++ b/crates/core/database/src/models/servers/model.rs @@ -1,5 +1,10 @@ use std::collections::{HashMap, HashSet}; +use redis_kiss::{ + get_connection, + redis::{SetExpiry, SetOptions}, + AsyncCommands, +}; use revolt_models::v0::{self, DataCreateServerChannel}; use revolt_permissions::{OverrideField, DEFAULT_PERMISSION_SERVER}; use revolt_result::Result; @@ -322,6 +327,30 @@ impl Server { Ok(()) } + + /// Gets a approximate count of the members in this server + /// + /// this value is cached for one hour + pub async fn get_approximate_member_count(&self, db: &Database) -> usize { + let Ok(mut redis) = get_connection().await else { + return 0; + }; + let key = format!("member_count:{}", &self.id); + + if let Some(count) = redis.get::<_, Option>(&key).await.ok().flatten() { + count + } else { + let count = db.fetch_member_count(&self.id).await.unwrap_or(0); + let _ = redis + .set_options::<_, _, ()>( + &key, + count, + SetOptions::default().with_expiration(SetExpiry::EX(60 * 60)), + ) + .await; + count + } + } } impl Role { diff --git a/crates/core/database/src/util/bridge/v0.rs b/crates/core/database/src/util/bridge/v0.rs index acb0b4c5..65e90b73 100644 --- a/crates/core/database/src/util/bridge/v0.rs +++ b/crates/core/database/src/util/bridge/v0.rs @@ -751,30 +751,29 @@ impl From for RemovalIntention { } } -impl From for Server { - fn from(value: crate::Server) -> Self { +impl crate::Server { + pub async fn into(self, db: &Database) -> Server { + let approximate_member_count = self.get_approximate_member_count(db).await; + Server { - id: value.id, - owner: value.owner, - name: value.name, - description: value.description, - channels: value.channels, - categories: value + id: self.id, + owner: self.owner, + name: self.name, + description: self.description, + channels: self.channels, + categories: self .categories .map(|categories| categories.into_iter().map(|v| v.into()).collect()), - system_messages: value.system_messages.map(|v| v.into()), - roles: value - .roles - .into_iter() - .map(|(k, v)| (k, v.into())) - .collect(), - default_permissions: value.default_permissions, - icon: value.icon.map(|f| f.into()), - banner: value.banner.map(|f| f.into()), - flags: value.flags.unwrap_or_default() as u32, - nsfw: value.nsfw, - analytics: value.analytics, - discoverable: value.discoverable, + system_messages: self.system_messages.map(|v| v.into()), + roles: self.roles.into_iter().map(|(k, v)| (k, v.into())).collect(), + default_permissions: self.default_permissions, + icon: self.icon.map(|f| f.into()), + banner: self.banner.map(|f| f.into()), + flags: self.flags.unwrap_or_default() as u32, + nsfw: self.nsfw, + analytics: self.analytics, + discoverable: self.discoverable, + approximate_member_count, } } } @@ -829,6 +828,7 @@ impl From for PartialServer { nsfw: value.nsfw, analytics: value.analytics, discoverable: value.discoverable, + approximate_member_count: None, } } } diff --git a/crates/core/models/src/v0/servers.rs b/crates/core/models/src/v0/servers.rs index 2aab765e..10d2eb1d 100644 --- a/crates/core/models/src/v0/servers.rs +++ b/crates/core/models/src/v0/servers.rs @@ -78,6 +78,9 @@ auto_derived_partial!( serde(skip_serializing_if = "crate::if_false", default) )] pub discoverable: bool, + + /// Approximate amount of members in the server + pub approximate_member_count: usize, }, "PartialServer" ); diff --git a/crates/delta/src/routes/invites/invite_fetch.rs b/crates/delta/src/routes/invites/invite_fetch.rs index 7c1b7550..b45b33ab 100644 --- a/crates/delta/src/routes/invites/invite_fetch.rs +++ b/crates/delta/src/routes/invites/invite_fetch.rs @@ -28,7 +28,7 @@ pub async fn fetch(db: &State, target: Reference<'_>) -> Result Date: Fri, 7 Aug 2026 01:30:03 +0100 Subject: [PATCH 2/4] chore(main): release 0.15.0 (#874) Co-authored-by: github-actions[bot] Signed-off-by: github-actions[bot] --- .release-please-manifest.json | 2 +- CHANGELOG.md | 13 +++++++++ Cargo.lock | 36 ++++++++++++------------- Cargo.toml | 20 +++++++------- crates/bonfire/Cargo.toml | 2 +- crates/core/coalesced/Cargo.toml | 2 +- crates/core/config/Cargo.toml | 2 +- crates/core/database/Cargo.toml | 2 +- crates/core/files/Cargo.toml | 2 +- crates/core/models/Cargo.toml | 2 +- crates/core/parser/Cargo.toml | 2 +- crates/core/permissions/Cargo.toml | 2 +- crates/core/presence/Cargo.toml | 2 +- crates/core/ratelimits/Cargo.toml | 2 +- crates/core/result/Cargo.toml | 2 +- crates/daemons/crond/Cargo.toml | 2 +- crates/daemons/pushd/Cargo.toml | 2 +- crates/daemons/voice-ingress/Cargo.toml | 2 +- crates/delta/Cargo.toml | 2 +- crates/services/autumn/Cargo.toml | 2 +- crates/services/gifbox/Cargo.toml | 2 +- crates/services/january/Cargo.toml | 2 +- version.txt | 2 +- 23 files changed, 61 insertions(+), 48 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 9991f1fd..a591d301 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.14.3" + ".": "0.15.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 425202cd..701de3a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## [0.15.0](https://github.com/stoatchat/stoatchat/compare/v0.14.3...v0.15.0) (2026-08-05) + + +### Features + +* add an approximate member count to servers ([#884](https://github.com/stoatchat/stoatchat/issues/884)) ([ccfb5f1](https://github.com/stoatchat/stoatchat/commit/ccfb5f1a62f1be9177ac9c1c09fd18ea8e9c7f18)) +* add call event ([#873](https://github.com/stoatchat/stoatchat/issues/873)) ([457c770](https://github.com/stoatchat/stoatchat/commit/457c7709c75060cd8519cc45df3badcfc1b629ea)) + + +### Bug Fixes + +* rewrite youtube embedder to use youtube oembed ([#878](https://github.com/stoatchat/stoatchat/issues/878)) ([0369451](https://github.com/stoatchat/stoatchat/commit/03694512b90be90367299c3ebfb072ebbc8a681d)) + ## [0.14.3](https://github.com/stoatchat/stoatchat/compare/v0.14.2...v0.14.3) (2026-07-22) diff --git a/Cargo.lock b/Cargo.lock index 2ee99478..b59dd382 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7164,7 +7164,7 @@ dependencies = [ [[package]] name = "revolt-autumn" -version = "0.14.3" +version = "0.15.0" dependencies = [ "axum", "axum-macros", @@ -7205,7 +7205,7 @@ dependencies = [ [[package]] name = "revolt-bonfire" -version = "0.14.3" +version = "0.15.0" dependencies = [ "async-channel", "async-tungstenite", @@ -7236,7 +7236,7 @@ dependencies = [ [[package]] name = "revolt-coalesced" -version = "0.14.3" +version = "0.15.0" dependencies = [ "indexmap 2.14.0", "lru", @@ -7245,7 +7245,7 @@ dependencies = [ [[package]] name = "revolt-config" -version = "0.14.3" +version = "0.15.0" dependencies = [ "cached", "config", @@ -7261,7 +7261,7 @@ dependencies = [ [[package]] name = "revolt-crond" -version = "0.14.3" +version = "0.15.0" dependencies = [ "futures", "futures-lite", @@ -7282,7 +7282,7 @@ dependencies = [ [[package]] name = "revolt-database" -version = "0.14.3" +version = "0.15.0" dependencies = [ "async-lock 2.8.0", "async-recursion", @@ -7340,7 +7340,7 @@ dependencies = [ [[package]] name = "revolt-delta" -version = "0.14.3" +version = "0.15.0" dependencies = [ "async-channel", "bitfield", @@ -7387,7 +7387,7 @@ dependencies = [ [[package]] name = "revolt-files" -version = "0.14.3" +version = "0.15.0" dependencies = [ "aes-gcm", "anyhow", @@ -7415,7 +7415,7 @@ dependencies = [ [[package]] name = "revolt-gifbox" -version = "0.14.3" +version = "0.15.0" dependencies = [ "axum", "axum-extra", @@ -7438,7 +7438,7 @@ dependencies = [ [[package]] name = "revolt-january" -version = "0.14.3" +version = "0.15.0" dependencies = [ "async-recursion", "axum", @@ -7468,7 +7468,7 @@ dependencies = [ [[package]] name = "revolt-models" -version = "0.14.3" +version = "0.15.0" dependencies = [ "indexmap 2.14.0", "iso8601-timestamp", @@ -7488,14 +7488,14 @@ dependencies = [ [[package]] name = "revolt-parser" -version = "0.14.3" +version = "0.15.0" dependencies = [ "logos", ] [[package]] name = "revolt-permissions" -version = "0.14.3" +version = "0.15.0" dependencies = [ "async-trait", "auto_ops", @@ -7510,7 +7510,7 @@ dependencies = [ [[package]] name = "revolt-presence" -version = "0.14.3" +version = "0.15.0" dependencies = [ "log", "once_cell", @@ -7522,7 +7522,7 @@ dependencies = [ [[package]] name = "revolt-pushd" -version = "0.14.3" +version = "0.15.0" dependencies = [ "anyhow", "async-trait", @@ -7552,7 +7552,7 @@ dependencies = [ [[package]] name = "revolt-ratelimits" -version = "0.14.3" +version = "0.15.0" dependencies = [ "async-trait", "axum", @@ -7568,7 +7568,7 @@ dependencies = [ [[package]] name = "revolt-result" -version = "0.14.3" +version = "0.15.0" dependencies = [ "axum", "log", @@ -7584,7 +7584,7 @@ dependencies = [ [[package]] name = "revolt-voice-ingress" -version = "0.14.3" +version = "0.15.0" dependencies = [ "chrono", "futures", diff --git a/Cargo.toml b/Cargo.toml index 7547c893..e40db259 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -193,13 +193,13 @@ futures-lite = "2.6.1" vergen = "7.5.0" # Local packages -revolt-coalesced = { version = "0.14.3", path = "crates/core/coalesced" } -revolt-config = { version = "0.14.3", path = "crates/core/config" } -revolt-database = { version = "0.14.3", path = "crates/core/database" } -revolt-files = { version = "0.14.3", path = "crates/core/files" } -revolt-models = { version = "0.14.3", path = "crates/core/models" } -revolt-parser = { version = "0.14.3", path = "crates/core/parser" } -revolt-permissions = { version = "0.14.3", path = "crates/core/permissions" } -revolt-presence = { version = "0.14.3", path = "crates/core/presence" } -revolt-ratelimits = { version = "0.14.3", path = "crates/core/ratelimits" } -revolt-result = { version = "0.14.3", path = "crates/core/result" } +revolt-coalesced = { version = "0.15.0", path = "crates/core/coalesced" } +revolt-config = { version = "0.15.0", path = "crates/core/config" } +revolt-database = { version = "0.15.0", path = "crates/core/database" } +revolt-files = { version = "0.15.0", path = "crates/core/files" } +revolt-models = { version = "0.15.0", path = "crates/core/models" } +revolt-parser = { version = "0.15.0", path = "crates/core/parser" } +revolt-permissions = { version = "0.15.0", path = "crates/core/permissions" } +revolt-presence = { version = "0.15.0", path = "crates/core/presence" } +revolt-ratelimits = { version = "0.15.0", path = "crates/core/ratelimits" } +revolt-result = { version = "0.15.0", path = "crates/core/result" } diff --git a/crates/bonfire/Cargo.toml b/crates/bonfire/Cargo.toml index f2aed928..075575e6 100644 --- a/crates/bonfire/Cargo.toml +++ b/crates/bonfire/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "revolt-bonfire" -version = "0.14.3" +version = "0.15.0" license = "AGPL-3.0-or-later" edition = "2021" publish = false diff --git a/crates/core/coalesced/Cargo.toml b/crates/core/coalesced/Cargo.toml index 3ac531b7..5bc8f9cc 100644 --- a/crates/core/coalesced/Cargo.toml +++ b/crates/core/coalesced/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "revolt-coalesced" -version = "0.14.3" +version = "0.15.0" edition = "2021" license = "MIT" authors = ["Paul Makles ", "Zomatree "] diff --git a/crates/core/config/Cargo.toml b/crates/core/config/Cargo.toml index 1388467b..65107754 100644 --- a/crates/core/config/Cargo.toml +++ b/crates/core/config/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "revolt-config" -version = "0.14.3" +version = "0.15.0" edition = "2021" license = "MIT" authors = ["Paul Makles "] diff --git a/crates/core/database/Cargo.toml b/crates/core/database/Cargo.toml index 55368338..534bcc49 100644 --- a/crates/core/database/Cargo.toml +++ b/crates/core/database/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "revolt-database" -version = "0.14.3" +version = "0.15.0" edition = "2021" license = "AGPL-3.0-or-later" authors = ["Paul Makles "] diff --git a/crates/core/files/Cargo.toml b/crates/core/files/Cargo.toml index bb86d942..0a46d32d 100644 --- a/crates/core/files/Cargo.toml +++ b/crates/core/files/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "revolt-files" -version = "0.14.3" +version = "0.15.0" edition = "2021" license = "AGPL-3.0-or-later" authors = ["Paul Makles "] diff --git a/crates/core/models/Cargo.toml b/crates/core/models/Cargo.toml index 2742e90b..e264738c 100644 --- a/crates/core/models/Cargo.toml +++ b/crates/core/models/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "revolt-models" -version = "0.14.3" +version = "0.15.0" edition = "2021" license = "MIT" authors = ["Paul Makles "] diff --git a/crates/core/parser/Cargo.toml b/crates/core/parser/Cargo.toml index 3891cb39..4b10982f 100644 --- a/crates/core/parser/Cargo.toml +++ b/crates/core/parser/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "revolt-parser" -version = "0.14.3" +version = "0.15.0" edition = "2021" license = "MIT" authors = ["Zomatree ", "Paul Makles "] diff --git a/crates/core/permissions/Cargo.toml b/crates/core/permissions/Cargo.toml index dd8e9bc1..82416c41 100644 --- a/crates/core/permissions/Cargo.toml +++ b/crates/core/permissions/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "revolt-permissions" -version = "0.14.3" +version = "0.15.0" edition = "2021" license = "MIT" authors = ["Paul Makles "] diff --git a/crates/core/presence/Cargo.toml b/crates/core/presence/Cargo.toml index ec73dcd1..63fa9051 100644 --- a/crates/core/presence/Cargo.toml +++ b/crates/core/presence/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "revolt-presence" -version = "0.14.3" +version = "0.15.0" edition = "2021" license = "AGPL-3.0-or-later" authors = ["Paul Makles "] diff --git a/crates/core/ratelimits/Cargo.toml b/crates/core/ratelimits/Cargo.toml index 4703eba9..2c5fbbeb 100644 --- a/crates/core/ratelimits/Cargo.toml +++ b/crates/core/ratelimits/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "revolt-ratelimits" -version = "0.14.3" +version = "0.15.0" edition = "2024" license = "MIT" authors = ["Zomatree ", "Paul Makles "] diff --git a/crates/core/result/Cargo.toml b/crates/core/result/Cargo.toml index e05b8e6e..2f52e3c7 100644 --- a/crates/core/result/Cargo.toml +++ b/crates/core/result/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "revolt-result" -version = "0.14.3" +version = "0.15.0" edition = "2021" license = "MIT" authors = ["Paul Makles "] diff --git a/crates/daemons/crond/Cargo.toml b/crates/daemons/crond/Cargo.toml index dfaf67c8..e6aa0187 100644 --- a/crates/daemons/crond/Cargo.toml +++ b/crates/daemons/crond/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "revolt-crond" -version = "0.14.3" +version = "0.15.0" license = "AGPL-3.0-or-later" authors = ["Paul Makles "] edition = "2021" diff --git a/crates/daemons/pushd/Cargo.toml b/crates/daemons/pushd/Cargo.toml index f00b72b1..75b03143 100644 --- a/crates/daemons/pushd/Cargo.toml +++ b/crates/daemons/pushd/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "revolt-pushd" -version = "0.14.3" +version = "0.15.0" edition = "2021" license = "AGPL-3.0-or-later" publish = false diff --git a/crates/daemons/voice-ingress/Cargo.toml b/crates/daemons/voice-ingress/Cargo.toml index 3fec8abc..e3f2edfa 100644 --- a/crates/daemons/voice-ingress/Cargo.toml +++ b/crates/daemons/voice-ingress/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "revolt-voice-ingress" -version = "0.14.3" +version = "0.15.0" license = "AGPL-3.0-or-later" edition = "2021" publish = false diff --git a/crates/delta/Cargo.toml b/crates/delta/Cargo.toml index a2b085cf..b16a6a29 100644 --- a/crates/delta/Cargo.toml +++ b/crates/delta/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "revolt-delta" -version = "0.14.3" +version = "0.15.0" license = "AGPL-3.0-or-later" authors = ["Paul Makles "] edition = "2018" diff --git a/crates/services/autumn/Cargo.toml b/crates/services/autumn/Cargo.toml index a1ad24ba..6d8c99ea 100644 --- a/crates/services/autumn/Cargo.toml +++ b/crates/services/autumn/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "revolt-autumn" -version = "0.14.3" +version = "0.15.0" edition = "2021" license = "AGPL-3.0-or-later" publish = false diff --git a/crates/services/gifbox/Cargo.toml b/crates/services/gifbox/Cargo.toml index f2e1f94a..420e2c4a 100644 --- a/crates/services/gifbox/Cargo.toml +++ b/crates/services/gifbox/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "revolt-gifbox" -version = "0.14.3" +version = "0.15.0" edition = "2021" license = "AGPL-3.0-or-later" publish = false diff --git a/crates/services/january/Cargo.toml b/crates/services/january/Cargo.toml index 2cc6897b..90ffaf94 100644 --- a/crates/services/january/Cargo.toml +++ b/crates/services/january/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "revolt-january" -version = "0.14.3" +version = "0.15.0" edition = "2021" license = "AGPL-3.0-or-later" publish = false diff --git a/version.txt b/version.txt index ac4a7962..a5510516 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.14.3 +0.15.0 From 4feeeb11f316a8ace0f3029424585c541e3310f6 Mon Sep 17 00:00:00 2001 From: Erik LaBine Date: Fri, 7 Aug 2026 14:34:28 -0400 Subject: [PATCH 3/4] Fix: Prefer first instance of a meta property in create_website_embed (#895) fix: Give first instance of a meta tag preference when creating website embed. Signed-off-by: Assisting --- crates/services/january/src/website_embed.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/services/january/src/website_embed.rs b/crates/services/january/src/website_embed.rs index b4c51694..e1ef6bf8 100644 --- a/crates/services/january/src/website_embed.rs +++ b/crates/services/january/src/website_embed.rs @@ -25,7 +25,8 @@ pub async fn create_website_embed(original_url: &str, document: &str) -> Option< node.attr("property").or_else(|| node.attr("name")), node.attr("content"), ) { - meta.insert(property.to_string(), content.to_string()); + meta.entry(property.to_string()) + .or_insert(content.to_string()); } } From fe331f0dcb1704e0bb99a42f89bd8bfe263069d2 Mon Sep 17 00:00:00 2001 From: Tom Date: Fri, 7 Aug 2026 11:42:52 -0700 Subject: [PATCH 4/4] feat: moderation API for pulling reported images. (#880) Signed-off-by: IAmTomahawkx Release-As: 0.15.1 --- crates/core/config/Revolt.toml | 2 + crates/core/config/src/lib.rs | 1 + crates/services/autumn/src/api.rs | 67 ++++++++++++++++++++++++++++++- 3 files changed, 69 insertions(+), 1 deletion(-) diff --git a/crates/core/config/Revolt.toml b/crates/core/config/Revolt.toml index 15bc483c..6c0ec33c 100644 --- a/crates/core/config/Revolt.toml +++ b/crates/core/config/Revolt.toml @@ -70,6 +70,8 @@ trust_cloudflare = false easypwned = "" # Tenor API Key tenor_key = "" +# Admin keys for future admin api and for bonfire reported image fetching +admin_keys = [] [api.security.captcha] # hCaptcha configuration diff --git a/crates/core/config/src/lib.rs b/crates/core/config/src/lib.rs index 6eccbf59..1c354769 100644 --- a/crates/core/config/src/lib.rs +++ b/crates/core/config/src/lib.rs @@ -228,6 +228,7 @@ pub struct ApiSecurity { pub trust_cloudflare: bool, pub easypwned: String, pub tenor_key: String, + pub admin_keys: Vec, } #[derive(Deserialize, Debug, Clone)] diff --git a/crates/services/autumn/src/api.rs b/crates/services/autumn/src/api.rs index c0e45d72..e30dbe78 100644 --- a/crates/services/autumn/src/api.rs +++ b/crates/services/autumn/src/api.rs @@ -5,7 +5,7 @@ use std::{ use axum::{ extract::{DefaultBodyLimit, Path, State}, - http::{header, Method}, + http::{header, HeaderMap, Method}, response::{IntoResponse, Redirect, Response}, routing::{get, post}, Json, Router, @@ -58,6 +58,7 @@ pub async fn router() -> Router { ) .route("/:tag/:file_id", get(fetch_preview)) .route("/:tag/:file_id/:file_name", get(fetch_file)) + .route("/mod/:tag/:file_id/:file_name", get(fetch_file_mod)) .layer(cors) } @@ -503,3 +504,67 @@ async fn fetch_file( .into_response() }) } + +/// Fetch original file (Moderation) +/// +/// This is intentionally left out of the OpenApi docs. +/// It uses the config key api.security.admin_keys to determine access. This is intended for server to server communication. +async fn fetch_file_mod( + State(db): State, + headers: HeaderMap, + Path((tag, file_id, file_name)): Path<(Tag, String, String)>, +) -> Result { + let config = revolt_config::config().await; + + let token = headers + .get("X-Admin-Token") + .and_then(|value| value.to_str().ok()) + .ok_or_else(|| create_error!(NotAuthenticated))?; + + if !config + .api + .security + .admin_keys + .iter() + .any(|tok| tok == token) + { + return Err(create_error!(NotAuthenticated)); + } + + let tag: &'static str = tag.clone().into(); + let file = db.fetch_attachment(tag, &file_id).await?; + + // Ignore files that haven't been attached + if file.used_for.is_none() { + return Err(create_error!(NotFound)); + } + + // Ensure filename is correct + if file_name != file.filename { + if file_name == "original" { + let safe_filename = encode_component(&file.filename); + + return Ok( + Redirect::permanent(&format!("/{tag}/{file_id}/{}", safe_filename)).into_response(), + ); + } + + return Err(create_error!(NotFound)); + } + + let hash = file.as_hash(&db).await?; + retrieve_file_by_hash(&hash).await.map(|data| { + ( + [ + (header::CONTENT_TYPE, hash.content_type), + (header::CONTENT_DISPOSITION, "attachment".to_owned()), + ( + header::CACHE_CONTROL, + "private, max-age=300, must-revalidate".to_owned(), + ), + ], + data, + ) + .into_response() + }) +}