tuwunel_admin/room/
purge_user.rs1use futures::{Stream, StreamExt, TryStreamExt};
2use regex::Regex;
3use ruma::{OwnedRoomId, RoomId};
4use tuwunel_core::{Result, utils::stream::ReadyExt};
5use tuwunel_service::Services;
6
7use crate::{Context, admin_command, get_room_info, utils::parse_user_id};
8
9#[admin_command]
10pub(super) async fn room_purge_user(
11 &self,
12 user_id: String,
13 regex: bool,
14 sole_member: bool,
15 dry_run: bool,
16) -> Result {
17 let services = self.services;
18
19 if dry_run {
20 self.write_str("Matching rooms:\n```\n").await?;
21 }
22
23 let count = if regex {
24 let pattern = &Regex::new(&user_id)?;
25 let rooms = services
26 .metadata
27 .iter_ids()
28 .map(ToOwned::to_owned)
29 .filter_map(async |room_id| {
30 (!services.admin.is_admin_room(&room_id).await
31 && room_has_matching_member(services, &room_id, pattern, sole_member).await)
32 .then_some(room_id)
33 });
34
35 purge_stream(self, rooms, dry_run).await?
36 } else {
37 let user_id = parse_user_id(services, &user_id)?;
38 let rooms = services
39 .state_cache
40 .rooms_joined(&user_id)
41 .map(ToOwned::to_owned)
42 .filter_map(async |room_id| {
43 (!services.admin.is_admin_room(&room_id).await
44 && (!sole_member || is_sole_joined_member(services, &room_id).await))
45 .then_some(room_id)
46 });
47
48 purge_stream(self, rooms, dry_run).await?
49 };
50
51 match (dry_run, count) {
52 | (true, _) => write!(self, "```\nMatched {count} rooms."),
53 | (false, 0) => write!(self, "No rooms matched."),
54 | (false, _) => write!(self, "Deleted {count} rooms from our database."),
55 }
56 .await
57}
58
59async fn room_has_matching_member(
60 services: &Services,
61 room_id: &RoomId,
62 pattern: &Regex,
63 sole_member: bool,
64) -> bool {
65 let sole_ok = !sole_member || is_sole_joined_member(services, room_id).await;
66
67 sole_ok
68 && services
69 .state_cache
70 .room_members(room_id)
71 .ready_any(|user| pattern.is_match(user.as_str()))
72 .await
73}
74
75async fn is_sole_joined_member(services: &Services, room_id: &RoomId) -> bool {
76 services
77 .state_cache
78 .room_joined_count(room_id)
79 .await
80 .is_ok_and(|count| count == 1)
81}
82
83async fn purge_stream<S>(context: &Context<'_>, rooms: S, dry_run: bool) -> Result<usize>
85where
86 S: Stream<Item = OwnedRoomId> + Send,
87{
88 let services = context.services;
89
90 rooms
91 .map(Ok)
92 .try_fold(0_usize, async |count, room_id: OwnedRoomId| {
93 if dry_run {
94 let (id, members, name) = get_room_info(services, &room_id).await;
95
96 writeln!(context, "{id}\tMembers: {members}\tName: {name}").await?;
97 } else {
98 let state_lock = services.state.mutex.lock(&room_id).await;
99
100 services
102 .delete
103 .delete_room(&room_id, false, state_lock)
104 .await?;
105 }
106
107 Ok(count.saturating_add(1))
108 })
109 .await
110}