From df2e1408a17b39e2ff27452c80e9e1ec05a2d149 Mon Sep 17 00:00:00 2001 From: Tom Date: Fri, 14 Aug 2026 14:10:05 -0700 Subject: [PATCH] fix: deleting newest message renders channels unackable until a new message is posted (#899) fix: deleting messages renders channels unackable until a new message is posted Signed-off-by: IAmTomahawkx --- .../core/database/src/models/channels/ops.rs | 21 +++++++- .../src/models/channels/ops/mongodb.rs | 49 +++++++++++++++++-- .../src/models/channels/ops/reference.rs | 46 +++++++++++++++-- .../database/src/models/messages/model.rs | 36 ++++++++++++++ 4 files changed, 144 insertions(+), 8 deletions(-) diff --git a/crates/core/database/src/models/channels/ops.rs b/crates/core/database/src/models/channels/ops.rs index ddd29b86..c33cb07e 100644 --- a/crates/core/database/src/models/channels/ops.rs +++ b/crates/core/database/src/models/channels/ops.rs @@ -1,4 +1,6 @@ -use crate::{Channel, FieldsChannel, PartialChannel, revolt_result::Result, util::ChunkedDatabaseGenerator}; +use crate::{ + revolt_result::Result, util::ChunkedDatabaseGenerator, Channel, FieldsChannel, PartialChannel, +}; use revolt_permissions::OverrideField; #[cfg(feature = "mongodb")] @@ -20,7 +22,10 @@ pub trait AbstractChannels: Sync + Send { async fn find_direct_messages(&self, user_id: &str) -> Result>; // Fetch all group dms for a user - async fn find_group_message_channels(&self, user_id: &str) -> Result>; + async fn find_group_message_channels( + &self, + user_id: &str, + ) -> Result>; // Fetch saved messages channel async fn find_saved_messages_channel(&self, user_id: &str) -> Result; @@ -55,4 +60,16 @@ pub trait AbstractChannels: Sync + Send { // Delete a channel async fn delete_channel(&self, channel_id: &Channel) -> Result<()>; + + // Fetch the last message in the channel + // Used to update the last_message_id when the current last_message_id is deleted. + async fn fetch_last_message(&self, channel_id: &str) -> Result>; + + // Update the last_message_id on the channel + // Setting this to None will remove the last_message_id + async fn update_last_messsage_id( + &self, + channel_id: &str, + message_id: Option<&str>, + ) -> Result<()>; } diff --git a/crates/core/database/src/models/channels/ops/mongodb.rs b/crates/core/database/src/models/channels/ops/mongodb.rs index 8986e58a..1837f885 100644 --- a/crates/core/database/src/models/channels/ops/mongodb.rs +++ b/crates/core/database/src/models/channels/ops/mongodb.rs @@ -1,5 +1,8 @@ use super::AbstractChannels; -use crate::{AbstractServers, Channel, FieldsChannel, IntoDocumentPath, MongoDb, PartialChannel, util::ChunkedDatabaseGenerator}; +use crate::{ + util::ChunkedDatabaseGenerator, AbstractServers, Channel, FieldsChannel, IntoDocumentPath, + MongoDb, PartialChannel, +}; use bson::{Bson, Document}; use futures::StreamExt; use mongodb::options::ReadConcern; @@ -71,7 +74,10 @@ impl AbstractChannels for MongoDb { } // Fetch all group dms for a user - async fn find_group_message_channels(&self, user_id: &str) -> Result> { + async fn find_group_message_channels( + &self, + user_id: &str, + ) -> Result> { let mut session = self .start_session() .await @@ -83,7 +89,8 @@ impl AbstractChannels for MongoDb { .await .map_err(|_| create_database_error!("start_transaction", COL))?; - let cursor = self.col(COL) + let cursor = self + .col(COL) .find(doc! { "channel_type": "Group", "recipients": user_id @@ -304,6 +311,42 @@ impl AbstractChannels for MongoDb { // Delete the channel itself query!(self, delete_one_by_id, COL, channel.id()).map(|_| ()) } + + async fn fetch_last_message(&self, channel_id: &str) -> Result> { + self.col::("messages") + .find_one(doc! {"channel": channel_id}) + .sort(doc! {"_id": -1}) + .projection(doc! {"_id": 1}) + .await + .map(|doc| doc.map(|d| d.get("_id").expect("Missing _id").to_string())) + .map_err(|_| create_database_error!("find_one", "messages")) + } + + async fn update_last_messsage_id( + &self, + channel_id: &str, + message_id: Option<&str>, + ) -> Result<()> { + if let Some(message_id) = message_id { + self.col::(COL) + .update_one( + doc! {"_id": channel_id}, + doc! {"$set": {"last_message_id": message_id}}, + ) + .await + .map(|_| ()) + .map_err(|_| create_database_error!("update_one", "channels")) + } else { + self.col::(COL) + .update_one( + doc! {"_id": channel_id}, + doc! {"$unset": {"last_message_id": ""}}, + ) + .await + .map(|_| ()) + .map_err(|_| create_database_error!("update_one", "channels")) + } + } } impl MongoDb { diff --git a/crates/core/database/src/models/channels/ops/reference.rs b/crates/core/database/src/models/channels/ops/reference.rs index 3bbd858a..515845ae 100644 --- a/crates/core/database/src/models/channels/ops/reference.rs +++ b/crates/core/database/src/models/channels/ops/reference.rs @@ -1,8 +1,8 @@ use std::collections::hash_map::Entry; use super::AbstractChannels; -use crate::ReferenceDb; use crate::util::ChunkedDatabaseGenerator; +use crate::ReferenceDb; use crate::{Channel, FieldsChannel, PartialChannel}; use revolt_permissions::OverrideField; use revolt_result::Result; @@ -53,7 +53,10 @@ impl AbstractChannels for ReferenceDb { } // Fetch all group dms for a user - async fn find_group_message_channels(&self, user_id: &str) -> Result> { + async fn find_group_message_channels( + &self, + user_id: &str, + ) -> Result> { let channels = self.channels.lock().await; let groups = channels .values() @@ -168,7 +171,7 @@ impl AbstractChannels for ReferenceDb { if let Some(Channel::Group { recipients, .. }) = channels.get_mut(&channel_id) { recipients.retain(|recipient| recipient != user_id); } - }; + } Ok(()) } @@ -182,4 +185,41 @@ impl AbstractChannels for ReferenceDb { Err(create_error!(NotFound)) } } + + async fn fetch_last_message(&self, channel_id: &str) -> Result> { + let messages = self.messages.lock().await; + let mut channel_messages: Vec<&crate::Message> = messages + .iter() + .filter(|(_, msg)| msg.channel == channel_id) + .map(|(_, msg)| msg) + .collect(); + + channel_messages.sort_unstable_by_key(|msg| &msg.id); + Ok(channel_messages.last().map(|msg| msg.id.clone()).or(None)) + } + + async fn update_last_messsage_id( + &self, + channel_id: &str, + message_id: Option<&str>, + ) -> Result<()> { + let mut channels = self.channels.lock().await; + let channel = channels + .get_mut(channel_id) + .ok_or_else(|| create_error!(NotFound))?; + + match channel { + Channel::DirectMessage { + last_message_id, .. + } + | Channel::Group { + last_message_id, .. + } + | Channel::TextChannel { + last_message_id, .. + } => *last_message_id = message_id.map(|id| id.to_string()), + _ => (), + }; + Ok(()) + } } diff --git a/crates/core/database/src/models/messages/model.rs b/crates/core/database/src/models/messages/model.rs index 04df0c73..ff51ecd3 100644 --- a/crates/core/database/src/models/messages/model.rs +++ b/crates/core/database/src/models/messages/model.rs @@ -1012,6 +1012,42 @@ impl Message { db.delete_message(&self.id).await?; + if let Ok(mut channel) = db.fetch_channel(&self.channel).await { + match &channel { + Channel::DirectMessage { + last_message_id, .. + } + | Channel::Group { + last_message_id, .. + } + | Channel::TextChannel { + last_message_id, .. + } => { + if last_message_id.is_some() && last_message_id.as_ref().unwrap() == &self.id { + let new_last_message_id = + db.fetch_last_message(channel.id()).await.unwrap(); + + db.update_last_messsage_id(channel.id(), new_last_message_id.as_deref()) + .await?; + + if new_last_message_id.is_some() { + EventV1::ChannelUpdate { + id: channel.id().to_string(), + data: revolt_models::v0::PartialChannel { + last_message_id: new_last_message_id, + ..Default::default() + }, + clear: vec![], + } + .p(channel.id().to_string()) + .await; + } + } + } + _ => (), + } + } + EventV1::MessageDelete { id: self.id.clone(), channel: self.channel.clone(),