diff --git a/src/admin/room/commands.rs b/src/admin/room/commands.rs index 81f36f15..5b08ff2a 100644 --- a/src/admin/room/commands.rs +++ b/src/admin/room/commands.rs @@ -1,6 +1,6 @@ use conduwuit::{Err, Result}; use futures::StreamExt; -use ruma::OwnedRoomId; +use ruma::{OwnedRoomId, OwnedRoomOrAliasId}; use crate::{PAGE_SIZE, admin_command, get_room_info}; @@ -66,3 +66,20 @@ pub(super) async fn exists(&self, room_id: OwnedRoomId) -> Result { self.write_str(&format!("{result}")).await } + +#[admin_command] +pub(super) async fn purge_sync_tokens(&self, room: OwnedRoomOrAliasId) -> Result { + // Resolve the room ID from the room or alias ID + let room_id = self.services.rooms.alias.resolve(&room).await?; + + // Delete all tokens for this room using the service method + let deleted_count = match self.services.rooms.user.delete_room_tokens(&room_id).await { + | Ok(count) => count, + | Err(_) => return Err!("Failed to delete sync tokens for room {}", room_id), + }; + + self.write_str(&format!( + "Successfully deleted {deleted_count} sync tokens for room {room_id}" + )) + .await +} diff --git a/src/admin/room/mod.rs b/src/admin/room/mod.rs index 26d2c2d8..0eac2224 100644 --- a/src/admin/room/mod.rs +++ b/src/admin/room/mod.rs @@ -6,7 +6,7 @@ mod moderation; use clap::Subcommand; use conduwuit::Result; -use ruma::OwnedRoomId; +use ruma::{OwnedRoomId, OwnedRoomOrAliasId}; use self::{ alias::RoomAliasCommand, directory::RoomDirectoryCommand, info::RoomInfoCommand, @@ -56,4 +56,11 @@ pub(super) enum RoomCommand { Exists { room_id: OwnedRoomId, }, + + /// - Delete all sync tokens for a room + PurgeSyncTokens { + /// Room ID or alias to purge sync tokens for + #[arg(value_parser)] + room: OwnedRoomOrAliasId, + }, } diff --git a/src/service/rooms/user/mod.rs b/src/service/rooms/user/mod.rs index bd76f1f4..cc72ac97 100644 --- a/src/service/rooms/user/mod.rs +++ b/src/service/rooms/user/mod.rs @@ -127,3 +127,40 @@ pub async fn get_token_shortstatehash( .await .deserialized() } + +/// Delete all sync tokens associated with a room +/// +/// This helps clean up the database as these tokens are never otherwise removed +#[implement(Service)] +pub async fn delete_room_tokens(&self, room_id: &RoomId) -> Result { + use futures::TryStreamExt; + + let shortroomid = self.services.short.get_shortroomid(room_id).await?; + + // Create a prefix to search by - all entries for this room will start with its + // short ID + let prefix = &[shortroomid]; + + // Get all keys with this room prefix + let mut count = 0; + + // Collect all keys into a Vec first, then delete them + let keys = self + .db + .roomsynctoken_shortstatehash + .keys_prefix_raw(prefix) + .map_ok(|key| { + // Clone the key since we can't store references in the Vec + Vec::from(key) + }) + .try_collect::>() + .await?; + + // Delete each key individually + for key in &keys { + self.db.roomsynctoken_shortstatehash.del(key); + count += 1; + } + + Ok(count) +}