tuwunel_api/client/admin/users/
account_data.rs1use std::collections::BTreeMap;
2
3use axum::extract::State;
4use futures::StreamExt;
5use ruma::{
6 OwnedRoomId, UserId,
7 events::{
8 AnyGlobalAccountDataEventContent, AnyRawAccountDataEvent, AnyRoomAccountDataEventContent,
9 },
10 serde::Raw,
11};
12use synapse_admin_api::users::account_data::v1 as account_data;
13use tuwunel_core::{
14 Err, Result, extract_variant,
15 utils::{ReadyExt, stream::BroadbandExt},
16};
17
18use crate::{Ruma, client::admin::require_admin};
19
20type GlobalData = BTreeMap<String, Raw<AnyGlobalAccountDataEventContent>>;
21type RoomData = BTreeMap<String, Raw<AnyRoomAccountDataEventContent>>;
22
23pub(crate) async fn admin_account_data_route(
25 State(services): State<crate::State>,
26 body: Ruma<account_data::Request>,
27) -> Result<account_data::Response> {
28 require_admin(&services, body.sender_user()).await?;
29
30 if !services.globals.user_is_local(&body.user_id) {
31 return Err!(Request(InvalidParam("Can only look up local users")));
32 }
33
34 if !services.users.exists(&body.user_id).await {
35 return Err!(Request(NotFound("User not found")));
36 }
37
38 let global: GlobalData = services
39 .account_data
40 .changes_since(None, &body.user_id, 0, None)
41 .ready_filter_map(|event| extract_variant!(event, AnyRawAccountDataEvent::Global))
42 .ready_filter_map(|event| split_type_content(&event))
43 .collect()
44 .await;
45
46 let rooms = services
49 .state_cache
50 .user_memberships(&body.user_id, None)
51 .map(|(_, room_id)| room_id.to_owned())
52 .broad_filter_map(async |room_id| {
53 room_account_data(services, &body.user_id, room_id).await
54 })
55 .collect()
56 .await;
57
58 let data = account_data::AccountData { global, rooms };
59
60 Ok(account_data::Response::new(data))
61}
62
63async fn room_account_data(
64 services: crate::State,
65 user_id: &UserId,
66 room_id: OwnedRoomId,
67) -> Option<(OwnedRoomId, RoomData)> {
68 let data: RoomData = services
69 .account_data
70 .changes_since(Some(&room_id), user_id, 0, None)
71 .ready_filter_map(|event| extract_variant!(event, AnyRawAccountDataEvent::Room))
72 .ready_filter_map(|event| split_type_content(&event))
73 .collect()
74 .await;
75
76 (!data.is_empty()).then_some((room_id, data))
77}
78
79fn split_type_content<E, C>(event: &Raw<E>) -> Option<(String, Raw<C>)> {
82 let event_type = event.get_field::<String>("type").ok().flatten()?;
83 let content = event
84 .get_field::<Raw<C>>("content")
85 .ok()
86 .flatten()?;
87
88 Some((event_type, content))
89}