Files
continuwuity/src/admin/room/commands.rs
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

267 lines
6.9 KiB
Rust
Raw Normal View History

use conduwuit::{Err, Result};
2024-08-08 17:18:30 +00:00
use futures::StreamExt;
use ruma::{OwnedRoomId, OwnedRoomOrAliasId};
2024-04-20 19:55:14 -04:00
use crate::{PAGE_SIZE, admin_command, get_room_info};
2024-04-20 19:55:14 -04:00
2026-02-15 22:53:30 +00:00
#[allow(clippy::fn_params_excessive_bools)]
2024-07-27 00:11:41 +00:00
#[admin_command]
pub(super) async fn list_rooms(
&self,
page: Option<usize>,
exclude_disabled: bool,
exclude_banned: bool,
include_empty: bool,
no_details: bool,
) -> Result {
2024-04-20 19:55:14 -04:00
// TODO: i know there's a way to do this with clap, but i can't seem to find it
let page = page.unwrap_or(1);
2024-07-27 00:11:41 +00:00
let mut rooms = self
.services
2024-04-20 19:55:14 -04:00
.rooms
.metadata
.iter_ids()
2024-09-29 00:50:12 -04:00
.filter_map(|room_id| async move {
(!exclude_disabled || !self.services.rooms.metadata.is_disabled(room_id).await)
.then_some(room_id)
2024-09-29 00:50:12 -04:00
})
.filter_map(|room_id| async move {
(!exclude_banned || !self.services.rooms.metadata.is_banned(room_id).await)
.then_some(room_id)
2024-09-29 00:50:12 -04:00
})
2024-08-08 17:18:30 +00:00
.then(|room_id| get_room_info(self.services, room_id))
.then(|(room_id, total_members, name)| async move {
let local_members: Vec<_> = self
.services
.rooms
.state_cache
2026-02-15 22:53:30 +00:00
.active_local_users_in_room(&room_id)
.collect()
.await;
2026-02-15 22:53:30 +00:00
let local_members = local_members.len();
(room_id, total_members, local_members, name)
})
.filter_map(|(room_id, total_members, local_members, name)| async move {
(include_empty || local_members > 0).then_some((room_id, total_members, name))
})
2024-08-08 17:18:30 +00:00
.collect::<Vec<_>>()
.await;
2024-04-20 19:55:14 -04:00
rooms.sort_by_key(|r| r.1);
rooms.reverse();
let rooms = rooms
.into_iter()
2024-05-05 20:40:58 -04:00
.skip(page.saturating_sub(1).saturating_mul(PAGE_SIZE))
2024-04-20 19:55:14 -04:00
.take(PAGE_SIZE)
.collect::<Vec<_>>();
if rooms.is_empty() {
return Err!("No more rooms.");
2025-02-25 18:38:12 +00:00
}
2024-04-20 19:55:14 -04:00
let body = rooms
.iter()
.map(|(id, members, name)| {
if no_details {
format!("{id}")
} else {
format!("{id}\tMembers: {members}\tName: {name}")
}
})
.collect::<Vec<_>>()
.join("\n");
self.write_str(&format!("Rooms ({}):\n```\n{body}\n```", rooms.len(),))
.await
2024-04-20 19:55:14 -04:00
}
2024-08-08 17:18:30 +00:00
#[admin_command]
pub(super) async fn exists(&self, room_id: OwnedRoomId) -> Result {
2024-08-08 17:18:30 +00:00
let result = self.services.rooms.metadata.exists(&room_id).await;
self.write_str(&format!("{result}")).await
2024-08-08 17:18:30 +00:00
}
#[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
2025-05-22 13:49:22 +01:00
let Ok(deleted_count) = self.services.rooms.user.delete_room_tokens(&room_id).await else {
2025-05-24 21:48:28 +01:00
return Err!("Failed to delete sync tokens for room {}", room_id.as_str());
};
self.write_str(&format!(
2025-05-24 21:48:28 +01:00
"Successfully deleted {deleted_count} sync tokens for room {}",
room_id.as_str()
))
.await
}
2025-05-22 13:49:22 +01:00
/// Target options for room purging
#[derive(Default, Debug, clap::ValueEnum, Clone)]
2026-04-09 15:37:39 +01:00
pub enum RoomTargetOption {
2025-05-22 13:49:22 +01:00
#[default]
/// Target all rooms
All,
/// Target only disabled rooms
DisabledOnly,
/// Target only banned rooms
BannedOnly,
}
#[admin_command]
2025-05-22 15:28:46 +01:00
pub(super) async fn purge_all_sync_tokens(
&self,
2025-05-22 13:49:22 +01:00
target_option: Option<RoomTargetOption>,
2025-05-22 15:28:46 +01:00
execute: bool,
) -> Result {
use conduwuit::{debug, info};
2025-05-22 15:28:46 +01:00
let mode = if !execute { "Simulating" } else { "Starting" };
2025-05-22 13:49:22 +01:00
// strictly, we should check if these reach the max value after the loop and
// warn the user that the count is too large
2025-05-22 15:28:46 +01:00
let mut total_rooms_checked: usize = 0;
2025-05-22 13:49:22 +01:00
let mut total_tokens_deleted: usize = 0;
let mut error_count: u32 = 0;
2025-05-22 15:28:46 +01:00
let mut skipped_rooms: usize = 0;
2025-05-22 15:28:46 +01:00
info!("{} purge of sync tokens", mode);
// Get all rooms in the server
let all_rooms = self
.services
.rooms
.metadata
.iter_ids()
.collect::<Vec<_>>()
.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 {
2025-05-22 13:49:22 +01:00
if let Some(target) = &target_option {
match target {
| RoomTargetOption::DisabledOnly => {
if !self.services.rooms.metadata.is_disabled(room_id).await {
2025-05-24 21:48:28 +01:00
debug!("Skipping room {} as it's not disabled", room_id.as_str());
2025-05-22 13:49:22 +01:00
skipped_rooms = skipped_rooms.saturating_add(1);
continue;
}
},
| RoomTargetOption::BannedOnly => {
if !self.services.rooms.metadata.is_banned(room_id).await {
2025-05-24 21:48:28 +01:00
debug!("Skipping room {} as it's not banned", room_id.as_str());
2025-05-22 13:49:22 +01:00
skipped_rooms = skipped_rooms.saturating_add(1);
continue;
}
},
| RoomTargetOption::All => {},
}
}
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 {
2025-05-22 15:28:46 +01:00
total_rooms_checked = total_rooms_checked.saturating_add(1);
2025-05-22 15:28:46 +01:00
// Log progress periodically
2026-04-09 15:37:39 +01:00
if total_rooms_checked.is_multiple_of(100) || total_rooms_checked == total_rooms {
2025-05-22 15:28:46 +01:00
info!(
"Progress: {}/{} rooms checked, {} tokens {}",
total_rooms_checked,
total_rooms,
total_tokens_deleted,
if !execute { "would be deleted" } else { "deleted" }
);
2025-05-22 15:28:46 +01:00
}
2025-05-22 15:28:46 +01:00
// In dry run mode, just count what would be deleted, don't actually delete
debug!(
2025-05-24 21:48:28 +01:00
"Room {}: {}",
room_id.as_str(),
2025-05-22 15:28:46 +01:00
if !execute {
"would purge sync tokens"
} else {
2025-05-22 15:28:46 +01:00
"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 {
2025-05-24 21:48:28 +01:00
debug!(
"Would delete {} sync tokens for room {}",
count,
room_id.as_str()
);
2025-05-22 15:28:46 +01:00
total_tokens_deleted = total_tokens_deleted.saturating_add(count);
} else {
2025-05-24 21:48:28 +01:00
debug!("No sync tokens found for room {}", room_id.as_str());
},
2025-05-22 15:28:46 +01:00
| Err(e) => {
2025-05-24 21:48:28 +01:00
debug!("Error counting sync tokens for room {}: {:?}", room_id.as_str(), e);
2025-05-22 15:28:46 +01:00
error_count = error_count.saturating_add(1);
},
}
} else {
2025-05-22 15:28:46 +01:00
// Real deletion mode
match self.services.rooms.user.delete_room_tokens(room_id).await {
| Ok(count) =>
if count > 0 {
2025-05-24 21:48:28 +01:00
debug!("Deleted {} sync tokens for room {}", count, room_id.as_str());
2025-05-22 15:28:46 +01:00
total_tokens_deleted = total_tokens_deleted.saturating_add(count);
} else {
2025-05-24 21:48:28 +01:00
debug!("No sync tokens found for room {}", room_id.as_str());
2025-05-22 15:28:46 +01:00
},
| Err(e) => {
2025-05-24 21:48:28 +01:00
debug!("Error purging sync tokens for room {}: {:?}", room_id.as_str(), e);
2025-05-22 15:28:46 +01:00
error_count = error_count.saturating_add(1);
},
}
}
}
2025-05-22 15:28:46 +01:00
let action = if !execute { "would be deleted" } else { "deleted" };
info!(
2025-05-22 15:28:46 +01:00
"Finished {}: checked {} rooms out of {} total, {} tokens {}, errors: {}",
if !execute {
"purge simulation"
} else {
"purging sync tokens"
},
2025-05-22 15:28:46 +01:00
total_rooms_checked,
total_rooms,
total_tokens_deleted,
action,
error_count
);
self.write_str(&format!(
2025-05-22 15:28:46 +01:00
"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
}