tuwunel_api/client/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, client::admin::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;
56
57 let joined_local_devices = local_device_count(&services, room_id).await;
58
59 let forgotten = row.joined_local_members == uint!(0)
60 && services
61 .state_cache
62 .room_useroncejoined(room_id)
63 .ready_any(|user_id| services.globals.user_is_local(user_id))
64 .await;
65
66 let (tombstoned, replacement_room) = tombstone
67 .map(|content| (true, Some(content.replacement_room)))
68 .unwrap_or((false, None));
69
70 Ok(Response {
71 room_id: row.room_id,
72 name: row.name,
73 topic,
74 avatar,
75 canonical_alias: row.canonical_alias,
76 joined_members: row.joined_members,
77 joined_local_members: row.joined_local_members,
78 joined_local_devices,
79 version: row.version,
80 creator: row.creator,
81 encryption: row.encryption,
82 federatable: row.federatable,
83 public: row.public,
84 join_rules: row.join_rules,
85 guest_access: row.guest_access,
86 history_visibility: row.history_visibility,
87 state_events: row.state_events,
88 room_type: row.room_type,
89 forgotten,
90 tombstoned,
91 replacement_room,
92 })
93}
94
95async fn local_device_count(services: &Services, room_id: &RoomId) -> UInt {
97 let count: usize = services
98 .state_cache
99 .local_users_in_room(room_id)
100 .map(ToOwned::to_owned)
101 .broad_then(async |user_id| {
102 services
103 .users
104 .all_device_ids(&user_id)
105 .count()
106 .await
107 })
108 .ready_fold(0_usize, usize::saturating_add)
109 .await;
110
111 usize_to_uint(count)
112}