Skip to main content

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