tuwunel_api/server/
query.rs1use axum::extract::State;
2use futures::{
3 StreamExt,
4 future::{join, join5},
5};
6use rand::seq::SliceRandom;
7use ruma::{
8 OwnedServerName,
9 api::federation::query::{
10 get_profile_information::{self, v1::Response as ProfileResponse},
11 get_room_information,
12 },
13 profile::{ProfileFieldName, ProfileFieldValue},
14};
15use serde_json::Value as JsonValue;
16use tuwunel_core::{Err, Result, err, utils::future::TryExtExt};
17
18use crate::Ruma;
19
20pub(crate) async fn get_room_information_route(
24 State(services): State<crate::State>,
25 body: Ruma<get_room_information::v1::Request>,
26) -> Result<get_room_information::v1::Response> {
27 let room_id = services
28 .alias
29 .resolve_local_alias(&body.room_alias)
30 .await
31 .map_err(|_| err!(Request(NotFound("Room alias not found."))))?;
32
33 let mut servers: Vec<OwnedServerName> = services
34 .state_cache
35 .room_servers(&room_id)
36 .map(ToOwned::to_owned)
37 .collect()
38 .await;
39
40 servers.sort_unstable();
41 servers.dedup();
42
43 servers.shuffle(&mut rand::rng());
44
45 if let Some(server_index) = servers
47 .iter()
48 .position(|server| server == services.globals.server_name())
49 {
50 servers.swap_remove(server_index);
51 servers.insert(0, services.globals.server_name().to_owned());
52 }
53
54 Ok(get_room_information::v1::Response { room_id, servers })
55}
56
57pub(crate) async fn get_profile_information_route(
62 State(services): State<crate::State>,
63 body: Ruma<get_profile_information::v1::Request>,
64) -> Result<ProfileResponse> {
65 if !services
66 .server
67 .config
68 .allow_inbound_profile_lookup_federation_requests
69 {
70 return Err!(Request(Forbidden(
71 "Profile lookup over federation is not allowed on this homeserver.",
72 )));
73 }
74
75 if !services.globals.user_is_local(&body.user_id) {
76 return Err!(Request(InvalidParam("User does not belong to this server.",)));
77 }
78
79 match &body.field {
80 | Some(ProfileFieldName::AvatarUrl | ProfileFieldName::DisplayName) => {
81 let avatar_url = services.users.avatar_url(&body.user_id).ok();
82 let displayname = services.users.displayname(&body.user_id).ok();
83 let (avatar_url, displayname) = join(avatar_url, displayname).await;
84
85 Ok([
86 avatar_url.map(ProfileFieldValue::AvatarUrl),
87 displayname.map(ProfileFieldValue::DisplayName),
88 ]
89 .into_iter()
90 .flatten()
91 .collect())
92 },
93 | Some(custom_field) => {
94 let value = services
95 .users
96 .profile_key(&body.user_id, custom_field.as_str())
97 .await
98 .ok();
99
100 let entry = value
101 .as_ref()
102 .map(|raw| serde_json::to_value(raw.json()))
103 .transpose()?
104 .map(|json| (custom_field.as_str().to_owned(), json));
105
106 Ok(entry.into_iter().collect())
107 },
108 | None => {
109 let avatar_url = services.users.avatar_url(&body.user_id).ok();
110 let blurhash = services.users.blurhash(&body.user_id).ok();
111 let displayname = services.users.displayname(&body.user_id).ok();
112 let tz = services.users.timezone(&body.user_id).ok();
113 let custom = services
114 .users
115 .all_profile_keys(&body.user_id)
116 .collect::<Vec<_>>();
117
118 let (avatar_url, blurhash, custom, displayname, tz) =
119 join5(avatar_url, blurhash, custom, displayname, tz).await;
120
121 let mut response: ProfileResponse = [
122 avatar_url.map(ProfileFieldValue::AvatarUrl),
123 displayname.map(ProfileFieldValue::DisplayName),
124 tz.map(ProfileFieldValue::TimeZone),
125 ]
126 .into_iter()
127 .flatten()
128 .collect();
129
130 if let Some(blurhash) = blurhash {
131 response.set("blurhash".to_owned(), JsonValue::String(blurhash));
132 }
133
134 response.extend(
135 custom
136 .into_iter()
137 .filter_map(|(k, v)| serde_json::to_value(v).ok().map(|v| (k, v))),
138 );
139
140 Ok(response)
141 },
142 }
143}