tuwunel_api_admin/rooms/
room_details.rs1use axum::extract::State;
2use futures::{FutureExt, StreamExt, TryFutureExt, future::join4};
3use ruma::{
4 RoomId, UInt,
5 events::{StateEventType, room::tombstone::RoomTombstoneEventContent},
6 uint,
7};
8use synapse_admin_api::rooms::room_details::v1::{Request, Response};
9use tuwunel_core::{
10 Err, Result,
11 utils::{
12 TryFutureExtExt,
13 stream::{BroadbandExt, ReadyExt},
14 },
15};
16use tuwunel_service::Services;
17
18use super::{room_row, usize_to_uint};
19use crate::{Ruma, require_admin};
20
21pub(crate) async fn admin_room_details_route(
26 State(services): State<crate::State>,
27 body: Ruma<Request>,
28) -> Result<Response> {
29 require_admin(&services, body.sender_user()).await?;
30
31 let room_id: &RoomId = &body.room_id;
32
33 if !services.metadata.exists(room_id).await {
34 return Err!(Request(NotFound("Room not found")));
35 }
36
37 let row = room_row(&services, room_id);
38
39 let topic = services
40 .state_accessor
41 .get_room_topic(room_id)
42 .ok();
43
44 let avatar = services
45 .state_accessor
46 .get_avatar(room_id)
47 .map_ok(|content| content.url)
48 .map(|url| url.ok().flatten());
49
50 let tombstone = services
51 .state_accessor
52 .room_state_get_content(room_id, &StateEventType::RoomTombstone, "")
53 .map(|content: Result<RoomTombstoneEventContent>| content.ok());
54
55 let (row, topic, avatar, tombstone) = join4(row, topic, avatar, tombstone).boxed().await;
57
58 let joined_local_devices = local_device_count(&services, room_id).await;
59
60 let forgotten = row.joined_local_members == uint!(0)
61 && services
62 .state_cache
63 .room_useroncejoined(room_id)
64 .ready_any(|user_id| services.globals.user_is_local(user_id))
65 .await;
66
67 let (tombstoned, replacement_room) = tombstone
68 .map(|content| (true, Some(content.replacement_room)))
69 .unwrap_or((false, None));
70
71 Ok(Response {
72 room_id: row.room_id,
73 name: row.name,
74 topic,
75 avatar,
76 canonical_alias: row.canonical_alias,
77 joined_members: row.joined_members,
78 joined_local_members: row.joined_local_members,
79 joined_local_devices,
80 version: row.version,
81 creator: row.creator,
82 encryption: row.encryption,
83 federatable: row.federatable,
84 public: row.public,
85 join_rules: row.join_rules,
86 guest_access: row.guest_access,
87 history_visibility: row.history_visibility,
88 state_events: row.state_events,
89 room_type: row.room_type,
90 forgotten,
91 tombstoned,
92 replacement_room,
93 })
94}
95
96async fn local_device_count(services: &Services, room_id: &RoomId) -> UInt {
98 let count: usize = services
99 .state_cache
100 .local_users_in_room(room_id)
101 .map(ToOwned::to_owned)
102 .broad_then(async |user_id| {
103 services
104 .users
105 .all_device_ids(&user_id)
106 .count()
107 .await
108 })
109 .ready_fold(0_usize, usize::saturating_add)
110 .await;
111
112 usize_to_uint(count)
113}