From 45822d9f9e658e9218c2811058617c99b5439a1c Mon Sep 17 00:00:00 2001 From: Jade Ellis Date: Wed, 21 May 2025 22:24:33 +0100 Subject: [PATCH 1/5] feat: Add admin command to delete sync tokens from a room --- src/admin/room/commands.rs | 19 +++++++++++++++++- src/admin/room/mod.rs | 9 ++++++++- src/service/rooms/user/mod.rs | 37 +++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 2 deletions(-) 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 00baf4c8..ddad81ef 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 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) +} From 087f3f3dd1ec9a6898090df5fd307006e764da1c Mon Sep 17 00:00:00 2001 From: Jade Ellis Date: Wed, 21 May 2025 22:44:15 +0100 Subject: [PATCH 2/5] feat: Add command to purge sync tokens for empty rooms --- src/admin/room/commands.rs | 166 ++++++++++++++++++++++++++++++++++ src/admin/room/mod.rs | 23 +++++ src/service/rooms/user/mod.rs | 25 +++++ 3 files changed, 214 insertions(+) diff --git a/src/admin/room/commands.rs b/src/admin/room/commands.rs index 5b08ff2a..9075389f 100644 --- a/src/admin/room/commands.rs +++ b/src/admin/room/commands.rs @@ -83,3 +83,169 @@ pub(super) async fn purge_sync_tokens(&self, room: OwnedRoomOrAliasId) -> Result )) .await } + +#[admin_command] +pub(super) async fn purge_empty_room_tokens( + &self, + yes: bool, + target_disabled: bool, + target_banned: bool, + dry_run: bool, +) -> Result { + use conduwuit::{debug, info}; + + if !yes && !dry_run { + return Err!( + "Please confirm this operation with --yes as it may delete tokens from many rooms, \ + or use --dry-run to simulate" + ); + } + + let mode = if dry_run { "Simulating" } else { "Starting" }; + + let mut total_rooms_processed = 0; + let mut empty_rooms_processed = 0; + let mut total_tokens_deleted = 0; + let mut error_count = 0; + let mut skipped_rooms = 0; + + info!("{} purge of sync tokens for rooms with no local users", mode); + + // Get all rooms in the server + let all_rooms = self + .services + .rooms + .metadata + .iter_ids() + .collect::>() + .await; + + info!("Found {} rooms total on the server", all_rooms.len()); + + // Filter rooms based on options + let mut rooms = Vec::new(); + for room_id in all_rooms { + // Filter rooms based on targeting options + let is_disabled = self.services.rooms.metadata.is_disabled(room_id).await; + let is_banned = self.services.rooms.metadata.is_banned(room_id).await; + + // If targeting specific types of rooms, only include matching rooms + if (target_disabled || target_banned) + && !((target_disabled && is_disabled) || (target_banned && is_banned)) + { + debug!("Skipping room {} as it doesn't match targeting criteria", room_id); + skipped_rooms += 1; + continue; + } + + rooms.push(room_id); + } + + // Total number of rooms we'll be checking + let total_rooms = rooms.len(); + info!( + "Processing {} rooms after filtering (skipped {} rooms)", + total_rooms, skipped_rooms + ); + + // Process each room + for room_id in rooms { + total_rooms_processed += 1; + + // Count local users in this room + let local_users_count = self + .services + .rooms + .state_cache + .local_users_in_room(room_id) + .count() + .await; + + // Only process rooms with no local users + if local_users_count == 0 { + empty_rooms_processed += 1; + + // In dry run mode, just count what would be deleted, don't actually delete + debug!( + "Room {} has no local users, {}", + room_id, + if dry_run { + "would purge sync tokens" + } else { + "purging sync tokens" + } + ); + + if dry_run { + // For dry run mode, count tokens without deleting + match self.services.rooms.user.count_room_tokens(room_id).await { + | Ok(count) => + if count > 0 { + debug!("Would delete {} sync tokens for room {}", count, room_id); + total_tokens_deleted += count; + } else { + debug!("No sync tokens found for room {}", room_id); + }, + | Err(e) => { + debug!("Error counting sync tokens for room {}: {:?}", room_id, e); + error_count += 1; + }, + } + } else { + // Real deletion mode + match self.services.rooms.user.delete_room_tokens(room_id).await { + | Ok(count) => + if count > 0 { + debug!("Deleted {} sync tokens for room {}", count, room_id); + total_tokens_deleted += count; + } else { + debug!("No sync tokens found for room {}", room_id); + }, + | Err(e) => { + debug!("Error purging sync tokens for room {}: {:?}", room_id, e); + error_count += 1; + }, + } + } + } else { + debug!("Room {} has {} local users, skipping", room_id, local_users_count); + } + + // Log progress periodically + if total_rooms_processed % 100 == 0 || total_rooms_processed == total_rooms { + info!( + "Progress: {}/{} rooms processed, {} empty rooms found, {} tokens {}", + total_rooms_processed, + total_rooms, + empty_rooms_processed, + total_tokens_deleted, + if dry_run { "would be deleted" } else { "deleted" } + ); + } + } + + let action = if dry_run { "would be deleted" } else { "deleted" }; + info!( + "Finished {}: processed {} empty rooms out of {} total, {} tokens {}, errors: {}", + if dry_run { + "purge simulation" + } else { + "purging sync tokens" + }, + empty_rooms_processed, + total_rooms, + total_tokens_deleted, + action, + error_count + ); + + let mode_msg = if dry_run { "DRY RUN: " } else { "" }; + self.write_str(&format!( + "{}Successfully processed {empty_rooms_processed} empty rooms (out of {total_rooms} \ + total rooms), {total_tokens_deleted} tokens {}. Skipped {skipped_rooms} rooms based on \ + filters. Failed for {error_count} rooms.", + mode_msg, + if dry_run { "would be deleted" } else { "deleted" } + )) + .await +} diff --git a/src/admin/room/mod.rs b/src/admin/room/mod.rs index ddad81ef..232d48f0 100644 --- a/src/admin/room/mod.rs +++ b/src/admin/room/mod.rs @@ -63,4 +63,27 @@ pub enum RoomCommand { #[arg(value_parser)] room: OwnedRoomOrAliasId, }, + + /// - Delete sync tokens for all rooms that have no local users + /// + /// By default, processes all empty rooms. You can use --target-disabled + /// and/or --target-banned to exclusively process rooms matching those + /// conditions. + PurgeEmptyRoomTokens { + /// Confirm you want to delete tokens from potentially many rooms + #[arg(long)] + yes: bool, + + /// Only purge rooms that have federation disabled + #[arg(long)] + target_disabled: bool, + + /// Only purge rooms that have been banned + #[arg(long)] + target_banned: bool, + + /// Perform a dry run without actually deleting any tokens + #[arg(long)] + dry_run: bool, + }, } diff --git a/src/service/rooms/user/mod.rs b/src/service/rooms/user/mod.rs index cc72ac97..58df427b 100644 --- a/src/service/rooms/user/mod.rs +++ b/src/service/rooms/user/mod.rs @@ -128,6 +128,31 @@ pub async fn get_token_shortstatehash( .deserialized() } +/// Count how many sync tokens exist for a room without deleting them +/// +/// This is useful for dry runs to see how many tokens would be deleted +#[implement(Service)] +pub async fn count_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]; + + // Collect all keys into a Vec and count them + let keys = self + .db + .roomsynctoken_shortstatehash + .keys_prefix_raw(prefix) + .map_ok(|_| ()) // We only need to count, not store the keys + .try_collect::>() + .await?; + + Ok(keys.len()) +} + /// Delete all sync tokens associated with a room /// /// This helps clean up the database as these tokens are never otherwise removed From 94193611d5004b40d9a6fafacdc72e2e9ed94870 Mon Sep 17 00:00:00 2001 From: Jade Ellis Date: Thu, 22 May 2025 13:49:22 +0100 Subject: [PATCH 3/5] chore: Fix more complicated clippy warnings --- src/admin/room/commands.rs | 73 ++++++++++++++++++++++------------- src/admin/room/mod.rs | 15 +++---- src/service/rooms/user/mod.rs | 6 +-- 3 files changed, 53 insertions(+), 41 deletions(-) diff --git a/src/admin/room/commands.rs b/src/admin/room/commands.rs index 9075389f..5e25ec7a 100644 --- a/src/admin/room/commands.rs +++ b/src/admin/room/commands.rs @@ -73,9 +73,8 @@ pub(super) async fn purge_sync_tokens(&self, room: OwnedRoomOrAliasId) -> Result 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), + let Ok(deleted_count) = self.services.rooms.user.delete_room_tokens(&room_id).await else { + return Err!("Failed to delete sync tokens for room {}", room_id); }; self.write_str(&format!( @@ -84,12 +83,23 @@ pub(super) async fn purge_sync_tokens(&self, room: OwnedRoomOrAliasId) -> Result .await } +/// Target options for room purging +#[derive(Default, Debug, clap::ValueEnum, Clone)] +pub(crate) enum RoomTargetOption { + #[default] + /// Target all rooms + All, + /// Target only disabled rooms + DisabledOnly, + /// Target only banned rooms + BannedOnly, +} + #[admin_command] pub(super) async fn purge_empty_room_tokens( &self, yes: bool, - target_disabled: bool, - target_banned: bool, + target_option: Option, dry_run: bool, ) -> Result { use conduwuit::{debug, info}; @@ -103,11 +113,13 @@ pub(super) async fn purge_empty_room_tokens( let mode = if dry_run { "Simulating" } else { "Starting" }; - let mut total_rooms_processed = 0; - let mut empty_rooms_processed = 0; - let mut total_tokens_deleted = 0; - let mut error_count = 0; - let mut skipped_rooms = 0; + // strictly, we should check if these reach the max value after the loop and + // warn the user that the count is too large + let mut total_rooms_processed: usize = 0; + let mut empty_rooms_processed: u32 = 0; + let mut total_tokens_deleted: usize = 0; + let mut error_count: u32 = 0; + let mut skipped_rooms: u32 = 0; info!("{} purge of sync tokens for rooms with no local users", mode); @@ -125,17 +137,24 @@ pub(super) async fn purge_empty_room_tokens( // Filter rooms based on options let mut rooms = Vec::new(); for room_id in all_rooms { - // Filter rooms based on targeting options - let is_disabled = self.services.rooms.metadata.is_disabled(room_id).await; - let is_banned = self.services.rooms.metadata.is_banned(room_id).await; - - // If targeting specific types of rooms, only include matching rooms - if (target_disabled || target_banned) - && !((target_disabled && is_disabled) || (target_banned && is_banned)) - { - debug!("Skipping room {} as it doesn't match targeting criteria", room_id); - skipped_rooms += 1; - continue; + if let Some(target) = &target_option { + match target { + | RoomTargetOption::DisabledOnly => { + if !self.services.rooms.metadata.is_disabled(room_id).await { + debug!("Skipping room {} as it's not disabled", room_id); + skipped_rooms = skipped_rooms.saturating_add(1); + continue; + } + }, + | RoomTargetOption::BannedOnly => { + if !self.services.rooms.metadata.is_banned(room_id).await { + debug!("Skipping room {} as it's not banned", room_id); + skipped_rooms = skipped_rooms.saturating_add(1); + continue; + } + }, + | RoomTargetOption::All => {}, + } } rooms.push(room_id); @@ -150,7 +169,7 @@ pub(super) async fn purge_empty_room_tokens( // Process each room for room_id in rooms { - total_rooms_processed += 1; + total_rooms_processed = total_rooms_processed.saturating_add(1); // Count local users in this room let local_users_count = self @@ -163,7 +182,7 @@ pub(super) async fn purge_empty_room_tokens( // Only process rooms with no local users if local_users_count == 0 { - empty_rooms_processed += 1; + empty_rooms_processed = empty_rooms_processed.saturating_add(1); // In dry run mode, just count what would be deleted, don't actually delete debug!( @@ -182,13 +201,13 @@ pub(super) async fn purge_empty_room_tokens( | Ok(count) => if count > 0 { debug!("Would delete {} sync tokens for room {}", count, room_id); - total_tokens_deleted += count; + total_tokens_deleted = total_tokens_deleted.saturating_add(count); } else { debug!("No sync tokens found for room {}", room_id); }, | Err(e) => { debug!("Error counting sync tokens for room {}: {:?}", room_id, e); - error_count += 1; + error_count = error_count.saturating_add(1); }, } } else { @@ -197,13 +216,13 @@ pub(super) async fn purge_empty_room_tokens( | Ok(count) => if count > 0 { debug!("Deleted {} sync tokens for room {}", count, room_id); - total_tokens_deleted += count; + total_tokens_deleted = total_tokens_deleted.saturating_add(count); } else { debug!("No sync tokens found for room {}", room_id); }, | Err(e) => { debug!("Error purging sync tokens for room {}: {:?}", room_id, e); - error_count += 1; + error_count = error_count.saturating_add(1); }, } } diff --git a/src/admin/room/mod.rs b/src/admin/room/mod.rs index 232d48f0..902330b5 100644 --- a/src/admin/room/mod.rs +++ b/src/admin/room/mod.rs @@ -5,6 +5,7 @@ mod info; mod moderation; use clap::Subcommand; +use commands::RoomTargetOption; use conduwuit::Result; use ruma::{OwnedRoomId, OwnedRoomOrAliasId}; @@ -66,21 +67,15 @@ pub enum RoomCommand { /// - Delete sync tokens for all rooms that have no local users /// - /// By default, processes all empty rooms. You can use --target-disabled - /// and/or --target-banned to exclusively process rooms matching those - /// conditions. + /// By default, processes all empty rooms. PurgeEmptyRoomTokens { /// Confirm you want to delete tokens from potentially many rooms #[arg(long)] yes: bool, - /// Only purge rooms that have federation disabled - #[arg(long)] - target_disabled: bool, - - /// Only purge rooms that have been banned - #[arg(long)] - target_banned: bool, + /// Target specific room types + #[arg(long, value_enum)] + target_option: Option, /// Perform a dry run without actually deleting any tokens #[arg(long)] diff --git a/src/service/rooms/user/mod.rs b/src/service/rooms/user/mod.rs index 58df427b..aaf735c1 100644 --- a/src/service/rooms/user/mod.rs +++ b/src/service/rooms/user/mod.rs @@ -166,9 +166,6 @@ pub async fn delete_room_tokens(&self, room_id: &RoomId) -> Result { // 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 @@ -184,8 +181,9 @@ pub async fn delete_room_tokens(&self, room_id: &RoomId) -> Result { // Delete each key individually for key in &keys { self.db.roomsynctoken_shortstatehash.del(key); - count += 1; } + let count = keys.len(); + Ok(count) } From 5c5add30eccbe9211b07ff892af43695603f5562 Mon Sep 17 00:00:00 2001 From: Jade Ellis Date: Thu, 22 May 2025 15:28:46 +0100 Subject: [PATCH 4/5] chore: cleanup --- src/admin/room/commands.rs | 155 ++++++++++++++++--------------------- src/admin/room/mod.rs | 11 +-- 2 files changed, 69 insertions(+), 97 deletions(-) diff --git a/src/admin/room/commands.rs b/src/admin/room/commands.rs index 5e25ec7a..2a4e60ac 100644 --- a/src/admin/room/commands.rs +++ b/src/admin/room/commands.rs @@ -96,32 +96,23 @@ pub(crate) enum RoomTargetOption { } #[admin_command] -pub(super) async fn purge_empty_room_tokens( +pub(super) async fn purge_all_sync_tokens( &self, - yes: bool, target_option: Option, - dry_run: bool, + execute: bool, ) -> Result { use conduwuit::{debug, info}; - if !yes && !dry_run { - return Err!( - "Please confirm this operation with --yes as it may delete tokens from many rooms, \ - or use --dry-run to simulate" - ); - } - - let mode = if dry_run { "Simulating" } else { "Starting" }; + let mode = if !execute { "Simulating" } else { "Starting" }; // strictly, we should check if these reach the max value after the loop and // warn the user that the count is too large - let mut total_rooms_processed: usize = 0; - let mut empty_rooms_processed: u32 = 0; + let mut total_rooms_checked: usize = 0; let mut total_tokens_deleted: usize = 0; let mut error_count: u32 = 0; - let mut skipped_rooms: u32 = 0; + let mut skipped_rooms: usize = 0; - info!("{} purge of sync tokens for rooms with no local users", mode); + info!("{} purge of sync tokens", mode); // Get all rooms in the server let all_rooms = self @@ -169,102 +160,86 @@ pub(super) async fn purge_empty_room_tokens( // Process each room for room_id in rooms { - total_rooms_processed = total_rooms_processed.saturating_add(1); - - // Count local users in this room - let local_users_count = self - .services - .rooms - .state_cache - .local_users_in_room(room_id) - .count() - .await; - - // Only process rooms with no local users - if local_users_count == 0 { - empty_rooms_processed = empty_rooms_processed.saturating_add(1); - - // In dry run mode, just count what would be deleted, don't actually delete - debug!( - "Room {} has no local users, {}", - room_id, - if dry_run { - "would purge sync tokens" - } else { - "purging sync tokens" - } - ); - - if dry_run { - // For dry run mode, count tokens without deleting - match self.services.rooms.user.count_room_tokens(room_id).await { - | Ok(count) => - if count > 0 { - debug!("Would delete {} sync tokens for room {}", count, room_id); - total_tokens_deleted = total_tokens_deleted.saturating_add(count); - } else { - debug!("No sync tokens found for room {}", room_id); - }, - | Err(e) => { - debug!("Error counting sync tokens for room {}: {:?}", room_id, e); - error_count = error_count.saturating_add(1); - }, - } - } else { - // Real deletion mode - match self.services.rooms.user.delete_room_tokens(room_id).await { - | Ok(count) => - if count > 0 { - debug!("Deleted {} sync tokens for room {}", count, room_id); - total_tokens_deleted = total_tokens_deleted.saturating_add(count); - } else { - debug!("No sync tokens found for room {}", room_id); - }, - | Err(e) => { - debug!("Error purging sync tokens for room {}: {:?}", room_id, e); - error_count = error_count.saturating_add(1); - }, - } - } - } else { - debug!("Room {} has {} local users, skipping", room_id, local_users_count); - } + total_rooms_checked = total_rooms_checked.saturating_add(1); // Log progress periodically - if total_rooms_processed % 100 == 0 || total_rooms_processed == total_rooms { + if total_rooms_checked % 100 == 0 || total_rooms_checked == total_rooms { info!( - "Progress: {}/{} rooms processed, {} empty rooms found, {} tokens {}", - total_rooms_processed, + "Progress: {}/{} rooms checked, {} tokens {}", + total_rooms_checked, total_rooms, - empty_rooms_processed, total_tokens_deleted, - if dry_run { "would be deleted" } else { "deleted" } + if !execute { "would be deleted" } else { "deleted" } ); } + + // In dry run mode, just count what would be deleted, don't actually delete + debug!( + "Room {} has no local users, {}", + room_id, + if !execute { + "would purge sync tokens" + } else { + "purging sync tokens" + } + ); + + if !execute { + // For dry run mode, count tokens without deleting + match self.services.rooms.user.count_room_tokens(room_id).await { + | Ok(count) => + if count > 0 { + debug!("Would delete {} sync tokens for room {}", count, room_id); + total_tokens_deleted = total_tokens_deleted.saturating_add(count); + } else { + debug!("No sync tokens found for room {}", room_id); + }, + | Err(e) => { + debug!("Error counting sync tokens for room {}: {:?}", room_id, e); + error_count = error_count.saturating_add(1); + }, + } + } else { + // Real deletion mode + match self.services.rooms.user.delete_room_tokens(room_id).await { + | Ok(count) => + if count > 0 { + debug!("Deleted {} sync tokens for room {}", count, room_id); + total_tokens_deleted = total_tokens_deleted.saturating_add(count); + } else { + debug!("No sync tokens found for room {}", room_id); + }, + | Err(e) => { + debug!("Error purging sync tokens for room {}: {:?}", room_id, e); + error_count = error_count.saturating_add(1); + }, + } + } } - let action = if dry_run { "would be deleted" } else { "deleted" }; + let action = if !execute { "would be deleted" } else { "deleted" }; info!( - "Finished {}: processed {} empty rooms out of {} total, {} tokens {}, errors: {}", - if dry_run { + "Finished {}: checked {} rooms out of {} total, {} tokens {}, errors: {}", + if !execute { "purge simulation" } else { "purging sync tokens" }, - empty_rooms_processed, + total_rooms_checked, total_rooms, total_tokens_deleted, action, error_count ); - let mode_msg = if dry_run { "DRY RUN: " } else { "" }; self.write_str(&format!( - "{}Successfully processed {empty_rooms_processed} empty rooms (out of {total_rooms} \ - total rooms), {total_tokens_deleted} tokens {}. Skipped {skipped_rooms} rooms based on \ - filters. Failed for {error_count} rooms.", - mode_msg, - if dry_run { "would be deleted" } else { "deleted" } + "Finished {}: checked {} rooms out of {} total, {} tokens {}, errors: {}", + if !execute { "simulation" } else { "purging sync tokens" }, + total_rooms_checked, + total_rooms, + total_tokens_deleted, + action, + error_count )) .await } diff --git a/src/admin/room/mod.rs b/src/admin/room/mod.rs index 902330b5..e7ec2314 100644 --- a/src/admin/room/mod.rs +++ b/src/admin/room/mod.rs @@ -68,17 +68,14 @@ pub enum RoomCommand { /// - Delete sync tokens for all rooms that have no local users /// /// By default, processes all empty rooms. - PurgeEmptyRoomTokens { - /// Confirm you want to delete tokens from potentially many rooms - #[arg(long)] - yes: bool, - + PurgeAllSyncTokens { /// Target specific room types #[arg(long, value_enum)] target_option: Option, - /// Perform a dry run without actually deleting any tokens + /// Execute token deletions. Otherwise, + /// Performs a dry run without actually deleting any tokens #[arg(long)] - dry_run: bool, + execute: bool, }, } From fad3c223fd8177ccda33fe37ec1db62f66a5d2ad Mon Sep 17 00:00:00 2001 From: Jade Ellis Date: Sat, 24 May 2025 21:48:28 +0100 Subject: [PATCH 5/5] fix: Use to_str methods on room IDs --- src/admin/room/commands.rs | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/admin/room/commands.rs b/src/admin/room/commands.rs index 2a4e60ac..c03c5101 100644 --- a/src/admin/room/commands.rs +++ b/src/admin/room/commands.rs @@ -74,11 +74,12 @@ pub(super) async fn purge_sync_tokens(&self, room: OwnedRoomOrAliasId) -> Result // Delete all tokens for this room using the service method let Ok(deleted_count) = self.services.rooms.user.delete_room_tokens(&room_id).await else { - return Err!("Failed to delete sync tokens for room {}", room_id); + return Err!("Failed to delete sync tokens for room {}", room_id.as_str()); }; self.write_str(&format!( - "Successfully deleted {deleted_count} sync tokens for room {room_id}" + "Successfully deleted {deleted_count} sync tokens for room {}", + room_id.as_str() )) .await } @@ -132,14 +133,14 @@ pub(super) async fn purge_all_sync_tokens( match target { | RoomTargetOption::DisabledOnly => { if !self.services.rooms.metadata.is_disabled(room_id).await { - debug!("Skipping room {} as it's not disabled", room_id); + debug!("Skipping room {} as it's not disabled", room_id.as_str()); skipped_rooms = skipped_rooms.saturating_add(1); continue; } }, | RoomTargetOption::BannedOnly => { if !self.services.rooms.metadata.is_banned(room_id).await { - debug!("Skipping room {} as it's not banned", room_id); + debug!("Skipping room {} as it's not banned", room_id.as_str()); skipped_rooms = skipped_rooms.saturating_add(1); continue; } @@ -175,8 +176,8 @@ pub(super) async fn purge_all_sync_tokens( // In dry run mode, just count what would be deleted, don't actually delete debug!( - "Room {} has no local users, {}", - room_id, + "Room {}: {}", + room_id.as_str(), if !execute { "would purge sync tokens" } else { @@ -189,13 +190,17 @@ pub(super) async fn purge_all_sync_tokens( match self.services.rooms.user.count_room_tokens(room_id).await { | Ok(count) => if count > 0 { - debug!("Would delete {} sync tokens for room {}", count, room_id); + debug!( + "Would delete {} sync tokens for room {}", + count, + room_id.as_str() + ); total_tokens_deleted = total_tokens_deleted.saturating_add(count); } else { - debug!("No sync tokens found for room {}", room_id); + debug!("No sync tokens found for room {}", room_id.as_str()); }, | Err(e) => { - debug!("Error counting sync tokens for room {}: {:?}", room_id, e); + debug!("Error counting sync tokens for room {}: {:?}", room_id.as_str(), e); error_count = error_count.saturating_add(1); }, } @@ -204,13 +209,13 @@ pub(super) async fn purge_all_sync_tokens( match self.services.rooms.user.delete_room_tokens(room_id).await { | Ok(count) => if count > 0 { - debug!("Deleted {} sync tokens for room {}", count, room_id); + debug!("Deleted {} sync tokens for room {}", count, room_id.as_str()); total_tokens_deleted = total_tokens_deleted.saturating_add(count); } else { - debug!("No sync tokens found for room {}", room_id); + debug!("No sync tokens found for room {}", room_id.as_str()); }, | Err(e) => { - debug!("Error purging sync tokens for room {}: {:?}", room_id, e); + debug!("Error purging sync tokens for room {}: {:?}", room_id.as_str(), e); error_count = error_count.saturating_add(1); }, }