Skip to main content

tuwunel_api/client/admin/rooms/
mod.rs

1//! Synapse admin API: room endpoints.
2
3mod block;
4mod context;
5mod delete_room;
6mod delete_status;
7mod forward_extremities;
8mod hierarchy;
9mod join;
10mod list_rooms;
11mod make_room_admin;
12mod messages;
13mod purge_history;
14mod room_details;
15mod room_members;
16mod state;
17mod timestamp_to_event;
18
19use futures::{
20	FutureExt, StreamExt, TryFutureExt,
21	future::{join, join3, join5},
22};
23use ruma::{
24	RoomId, UInt,
25	events::{
26		StateEventType,
27		room::{
28			guest_access::RoomGuestAccessEventContent,
29			history_visibility::RoomHistoryVisibilityEventContent,
30		},
31	},
32	uint,
33};
34use synapse_admin_api::rooms::list_rooms::v1::RoomDetails;
35use tuwunel_core::{Result, matrix::Event, utils::TryFutureExtExt};
36use tuwunel_service::Services;
37
38pub(crate) use self::{
39	block::{admin_get_room_block_route, admin_set_room_block_route},
40	context::admin_room_context_route,
41	delete_room::{admin_delete_room_v1_route, admin_delete_room_v2_route},
42	delete_status::{admin_delete_status_by_id_route, admin_delete_status_by_room_route},
43	forward_extremities::{
44		admin_delete_forward_extremities_route, admin_get_forward_extremities_route,
45	},
46	hierarchy::admin_room_hierarchy_route,
47	join::admin_join_room_route,
48	list_rooms::admin_list_rooms_route,
49	make_room_admin::admin_make_room_admin_route,
50	messages::admin_room_messages_route,
51	purge_history::{
52		admin_purge_history_by_event_route, admin_purge_history_route,
53		admin_purge_history_status_route,
54	},
55	room_details::admin_room_details_route,
56	room_members::admin_room_members_route,
57	state::admin_room_state_route,
58	timestamp_to_event::admin_room_timestamp_to_event_route,
59};
60
61/// Action name recorded for the async room-shutdown task, mirroring Synapse's
62/// `SHUTDOWN_AND_PURGE_ROOM`. The delete-status endpoints filter on it.
63const DELETE_ROOM_ACTION: &str = "shutdown_and_purge_room";
64
65/// Action name recorded for the history-purge task, filtered on by the
66/// purge-status endpoint.
67const PURGE_HISTORY_ACTION: &str = "purge_history";
68
69/// Assembles the shared per-room summary row returned by both the room-list and
70/// room-details endpoints.
71async fn room_row(services: &Services, room_id: &RoomId) -> RoomDetails {
72	let name = services.state_accessor.get_name(room_id).ok();
73
74	let canonical_alias = services
75		.state_accessor
76		.get_canonical_alias(room_id)
77		.ok();
78
79	let room_type = services
80		.state_accessor
81		.get_room_type(room_id)
82		.ok();
83
84	let encryption = services
85		.state_accessor
86		.get_room_encryption(room_id)
87		.map_ok(|algorithm| algorithm.to_string())
88		.ok();
89
90	let join_rules = services
91		.state_accessor
92		.get_join_rules(room_id)
93		.map(|join_rule| Some(join_rule.kind()));
94
95	let guest_access = services
96		.state_accessor
97		.room_state_get_content(room_id, &StateEventType::RoomGuestAccess, "")
98		.map_ok(|content: RoomGuestAccessEventContent| content.guest_access)
99		.ok();
100
101	let history_visibility = services
102		.state_accessor
103		.room_state_get_content(room_id, &StateEventType::RoomHistoryVisibility, "")
104		.map_ok(|content: RoomHistoryVisibilityEventContent| content.history_visibility)
105		.ok();
106
107	let create = services
108		.state_accessor
109		.get_create(room_id)
110		.map(Result::ok);
111
112	let version = services
113		.state
114		.get_room_version(room_id)
115		.map_ok(|version| version.to_string())
116		.ok();
117
118	let public = services.directory.is_public_room(room_id);
119
120	let joined_members = services
121		.state_cache
122		.room_joined_count(room_id)
123		.map(|count| count_to_uint(count.ok()));
124
125	let joined_local_members = services
126		.state_cache
127		.local_users_in_room(room_id)
128		.count()
129		.map(usize_to_uint);
130
131	let state_events = state_event_count(services, room_id);
132
133	let (
134		(name, canonical_alias, room_type, encryption, join_rules),
135		(guest_access, history_visibility, create, version, public),
136		(joined_members, joined_local_members, state_events),
137	) = join(
138		join5(name, canonical_alias, room_type, encryption, join_rules),
139		join(
140			join5(guest_access, history_visibility, create, version, public),
141			join3(joined_members, joined_local_members, state_events),
142		),
143	)
144	.map(|(head, (mid, tail))| (head, mid, tail))
145	.boxed()
146	.await;
147
148	let (creator, federatable) = create
149		.map(|create| (Some(create.sender().to_owned()), create.federate().unwrap_or(true)))
150		.unwrap_or((None, true));
151
152	RoomDetails {
153		room_id: room_id.to_owned(),
154		name,
155		canonical_alias,
156		joined_members,
157		joined_local_members,
158		version,
159		creator,
160		encryption,
161		federatable,
162		public,
163		join_rules,
164		guest_access,
165		history_visibility,
166		state_events,
167		room_type,
168	}
169}
170
171async fn state_event_count(services: &Services, room_id: &RoomId) -> UInt {
172	let Ok(shortstatehash) = services
173		.state
174		.get_room_shortstatehash(room_id)
175		.await
176	else {
177		return uint!(0);
178	};
179
180	let count = services
181		.state_accessor
182		.state_full_ids(shortstatehash)
183		.count()
184		.await;
185
186	usize_to_uint(count)
187}
188
189fn count_to_uint(count: Option<u64>) -> UInt {
190	count
191		.and_then(|count| count.try_into().ok())
192		.unwrap_or_else(|| uint!(0))
193}
194
195fn usize_to_uint(count: usize) -> UInt { count.try_into().unwrap_or_else(|_| uint!(0)) }