1use matrix_sdk::{Client, ROOM_VERSION_RULES_FALLBACK, RoomState, room::RoomMemberRole};
16use ruma::{Int, OwnedRoomId, events::room::member::MembershipState};
17use tracing::info;
18
19use crate::spaces::{Error, SpaceRoom};
20
21#[derive(Debug, Clone)]
24pub struct LeaveSpaceRoom {
25 pub space_room: SpaceRoom,
27 pub is_last_owner: bool,
30 pub are_creators_privileged: bool,
32}
33
34pub struct LeaveSpaceHandle {
42 client: Client,
43 rooms: Vec<LeaveSpaceRoom>,
44}
45
46impl LeaveSpaceHandle {
47 pub(crate) async fn new(client: Client, room_ids: Vec<OwnedRoomId>) -> Self {
48 let mut rooms = Vec::new();
49
50 for room_id in &room_ids {
51 let Some(room) = client.get_room(room_id) else {
52 continue;
53 };
54
55 if room.state() != RoomState::Joined {
56 continue;
57 }
58
59 if !room.are_members_synced() {
60 info!("Syncing members for room {} to check if can leave", room.room_id());
61 _ = room.sync_members().await.ok();
62 }
63
64 let mut privileged_creator_ids = Vec::new();
65 let mut are_creators_privileged = false;
66 if let Some(mut create) = room.create_content() {
67 let rules = create.room_version.rules().unwrap_or(ROOM_VERSION_RULES_FALLBACK);
68 if rules.authorization.explicitly_privilege_room_creators {
69 are_creators_privileged = true;
70 privileged_creator_ids.push(create.creator);
71 privileged_creator_ids.append(&mut create.additional_creators);
72 }
73 }
74
75 let owner_ids = room
76 .users_with_power_levels()
77 .await
78 .into_iter()
79 .filter(|(_, power_level)| {
80 let Some(power_level) = Int::new(*power_level) else {
81 return false;
82 };
83
84 if are_creators_privileged {
85 power_level >= ruma::int!(150)
86 } else {
87 RoomMemberRole::suggested_role_for_power_level(power_level.into())
88 == RoomMemberRole::Administrator
89 }
90 })
91 .map(|p: (ruma::OwnedUserId, i64)| p.0)
92 .chain(privileged_creator_ids);
93
94 let mut joined_owner_ids = Vec::new();
95 for owner_id in owner_ids {
96 if let Ok(Some(member)) = room.get_member_no_sync(&owner_id).await
97 && *member.membership() == MembershipState::Join
98 {
99 joined_owner_ids.push(owner_id);
100 }
101 }
102 let is_last_owner = joined_owner_ids == [room.own_user_id()];
103
104 rooms.push(LeaveSpaceRoom {
105 space_room: SpaceRoom::new_from_known(&room, 0).await,
106 is_last_owner,
107 are_creators_privileged,
108 });
109 }
110
111 Self { client, rooms }
112 }
113
114 pub fn rooms(&self) -> &Vec<LeaveSpaceRoom> {
117 &self.rooms
118 }
119
120 pub async fn leave(&self, filter: impl FnMut(&LeaveSpaceRoom) -> bool) -> Result<(), Error> {
122 for room in self.rooms.clone().into_iter().filter(filter) {
123 if let Some(room) = self.client.get_room(&room.space_room.room_id) {
124 room.leave().await.map_err(Error::LeaveSpace)?;
125 } else {
126 return Err(Error::RoomNotFound(room.space_room.room_id));
127 }
128 }
129
130 Ok(())
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use matrix_sdk::test_utils::mocks::MatrixMockServer;
137 use matrix_sdk_test::{
138 InvitedRoomBuilder, JoinedRoomBuilder, LeftRoomBuilder, async_test,
139 event_factory::EventFactory,
140 };
141 use ruma::{RoomVersionId, owned_user_id, room_id};
142
143 use crate::spaces::SpaceService;
144
145 #[async_test]
146 async fn test_leaving() {
147 let server = MatrixMockServer::new().await;
148 let client = server.client_builder().build().await;
149 let user_id = client.user_id().unwrap();
150 let space_service = SpaceService::new(client.clone()).await;
151 let factory = EventFactory::new().sender(user_id);
152
153 server.mock_room_state_encryption().plain().mount().await;
154
155 let parent_space_id = room_id!("!parent_space:example.org");
158 let child_space_id_1 = room_id!("!child_space_1:example.org");
159 let child_space_id_2 = room_id!("!child_space_2:example.org");
160 let child_space_v12_id_1 = room_id!("!child_space_v12_1:example.org");
161 let child_space_v12_id_2 = room_id!("!child_space_v12_2:example.org");
162 let left_room_id = room_id!("!left_room:example.org");
163 let invited_room_id = room_id!("!invited_room:example.org");
164
165 let some_non_admin_id = owned_user_id!("@some_non_admin:a.b");
166
167 server
168 .sync_room(
169 &client,
170 JoinedRoomBuilder::new(child_space_id_1)
171 .add_state_event(factory.create(user_id, RoomVersionId::V1).with_space_type())
172 .add_state_event(
173 factory
174 .space_parent(parent_space_id.to_owned(), child_space_id_1.to_owned()),
175 )
176 .add_state_event(
177 factory.power_levels().set_users([
178 (user_id.to_owned(), 100),
179 (some_non_admin_id.clone(), 50),
180 ]),
181 )
182 .add_state_event(factory.member(user_id))
183 .add_state_event(factory.member(&some_non_admin_id))
184 .set_joined_members_count(2),
185 )
186 .await;
187
188 let other_admin_user_id = owned_user_id!("@some_other_admin:a.b");
189
190 server
191 .sync_room(
192 &client,
193 JoinedRoomBuilder::new(child_space_id_2)
194 .add_state_event(factory.create(user_id, RoomVersionId::V1).with_space_type())
195 .add_state_event(
196 factory
197 .space_parent(parent_space_id.to_owned(), child_space_id_2.to_owned()),
198 )
199 .add_state_event(
200 factory.power_levels().set_users([(other_admin_user_id.clone(), 100)]),
201 )
202 .add_state_event(factory.member(user_id))
203 .add_state_event(factory.member(&some_non_admin_id))
204 .add_state_event(factory.member(&other_admin_user_id))
205 .set_joined_members_count(3),
206 )
207 .await;
208
209 server
211 .sync_room(
212 &client,
213 JoinedRoomBuilder::new(child_space_v12_id_1)
214 .add_state_event(factory.create(user_id, RoomVersionId::V12).with_space_type())
215 .add_state_event(
216 factory.space_parent(
217 parent_space_id.to_owned(),
218 child_space_v12_id_1.to_owned(),
219 ),
220 )
221 .add_state_event(factory.member(user_id))
222 .add_state_event(factory.member(&some_non_admin_id))
223 .add_state_event(factory.member(&other_admin_user_id))
224 .add_state_event(factory.power_levels().set_users([
225 (some_non_admin_id.clone(), 50),
226 (other_admin_user_id.clone(), 100),
227 ]))
228 .set_joined_members_count(3),
229 )
230 .await;
231
232 let other_owner_user_id = owned_user_id!("@some_other_owner:a.b");
233 server
234 .sync_room(
235 &client,
236 JoinedRoomBuilder::new(child_space_v12_id_2)
237 .add_state_event(factory.create(user_id, RoomVersionId::V12).with_space_type())
238 .add_state_event(
239 factory.space_parent(
240 parent_space_id.to_owned(),
241 child_space_v12_id_2.to_owned(),
242 ),
243 )
244 .add_state_event(factory.member(user_id))
245 .add_state_event(factory.member(&some_non_admin_id))
246 .add_state_event(factory.member(&other_admin_user_id))
247 .add_state_event(factory.member(&other_owner_user_id))
248 .add_state_event(
249 factory.power_levels().set_users([(other_owner_user_id.clone(), 150)]),
250 )
251 .set_joined_members_count(4),
252 )
253 .await;
254
255 server.sync_room(&client, LeftRoomBuilder::new(invited_room_id)).await;
256 server.sync_room(&client, InvitedRoomBuilder::new(invited_room_id)).await;
257
258 server
259 .sync_room(
260 &client,
261 JoinedRoomBuilder::new(parent_space_id)
262 .add_state_event(factory.create(user_id, RoomVersionId::V1).with_space_type())
263 .add_state_event(
264 factory
265 .space_child(parent_space_id.to_owned(), child_space_id_1.to_owned()),
266 )
267 .add_state_event(
268 factory
269 .space_child(parent_space_id.to_owned(), child_space_id_2.to_owned()),
270 )
271 .add_state_event(
272 factory.space_child(
273 parent_space_id.to_owned(),
274 child_space_v12_id_1.to_owned(),
275 ),
276 )
277 .add_state_event(
278 factory.space_child(
279 parent_space_id.to_owned(),
280 child_space_v12_id_2.to_owned(),
281 ),
282 )
283 .add_state_event(
284 factory.space_child(parent_space_id.to_owned(), left_room_id.to_owned()),
285 )
286 .add_state_event(
287 factory.space_child(parent_space_id.to_owned(), invited_room_id.to_owned()),
288 ),
289 )
290 .await;
291
292 server.mock_room_leave().ok(room_id!("!does_not_matter:a:b")).mount().await;
293
294 assert!(!space_service.top_level_joined_spaces().await.is_empty());
295
296 let handle = space_service.leave_space(parent_space_id).await.unwrap();
297
298 let rooms = handle.rooms();
299
300 let child_room_1 = &rooms[0];
301 assert!(child_room_1.is_last_owner);
302 assert_eq!(child_room_1.space_room.num_joined_members, 2);
303 assert!(!child_room_1.are_creators_privileged);
304
305 let child_room_2 = &rooms[1];
306 assert!(!child_room_2.is_last_owner);
307 assert_eq!(child_room_2.space_room.num_joined_members, 3);
308 assert!(!child_room_2.are_creators_privileged);
309
310 let child_room_3 = &rooms[2];
311 assert!(child_room_3.is_last_owner);
312 assert_eq!(child_room_3.space_room.num_joined_members, 3);
313 assert!(child_room_3.are_creators_privileged);
314
315 let child_room_4 = &rooms[3];
316 assert!(!child_room_4.is_last_owner);
317 assert_eq!(child_room_4.space_room.num_joined_members, 4);
318 assert!(child_room_4.are_creators_privileged);
319
320 let room_ids = rooms.iter().map(|r| r.space_room.room_id.clone()).collect::<Vec<_>>();
321 assert_eq!(
322 room_ids,
323 vec![
324 child_space_id_1,
325 child_space_id_2,
326 child_space_v12_id_1,
327 child_space_v12_id_2,
328 parent_space_id
329 ]
330 );
331
332 handle.leave(|room| room_ids.contains(&room.space_room.room_id)).await.unwrap();
333
334 assert!(space_service.top_level_joined_spaces().await.is_empty());
335 }
336}