tuwunel_api/client/
profile.rs1use axum::extract::State;
2use futures::StreamExt;
3use ruma::{
4 api::client::profile::{
5 PropagateTo, delete_profile_field, get_profile, get_profile_field, set_profile_field,
6 },
7 presence::PresenceState,
8 profile::ProfileFieldValue,
9};
10use tuwunel_core::{Err, Result, err};
11use tuwunel_service::profile::Propagation;
12
13use crate::{ClientIp, Ruma};
14
15pub(super) fn resolve_propagation(propagate_to: &PropagateTo) -> Propagation {
20 match propagate_to {
21 | PropagateTo::Unchanged => Propagation::Unchanged,
22 | PropagateTo::None => Propagation::None,
23 | _ => Propagation::All,
24 }
25}
26
27pub(crate) async fn get_profile_route(
34 State(services): State<crate::State>,
35 body: Ruma<get_profile::v3::Request>,
36) -> Result<get_profile::v3::Response> {
37 if !services.globals.user_is_local(&body.user_id) {
38 services
39 .profile
40 .fetch_remote_profile(&body.user_id)
41 .await?;
42 }
43
44 if !services.users.exists(&body.user_id).await {
45 return Err!(Request(NotFound("Profile was not found.")));
48 }
49
50 let response = services
51 .profile
52 .all_profile_keys(&body.user_id)
53 .collect()
54 .await;
55
56 Ok(response)
57}
58
59pub(crate) async fn get_profile_field_route(
66 State(services): State<crate::State>,
67 body: Ruma<get_profile_field::v3::Request>,
68) -> Result<get_profile_field::v3::Response> {
69 if !services.globals.user_is_local(&body.user_id) {
70 services
71 .profile
72 .fetch_remote_profile(&body.user_id)
73 .await?;
74 }
75
76 if !services.users.exists(&body.user_id).await {
77 return Err!(Request(NotFound("Profile was not found.")));
80 }
81
82 let value = services
83 .profile
84 .profile_key(&body.user_id, &body.field)
85 .await?;
86
87 let profile_value = ProfileFieldValue::new(body.field.as_str(), value).map_err(|_| {
88 err!(Database(
89 error!(user_id = %body.user_id, key = %body.field, "Invalid json in database profile value")
90 ))
91 })?;
92
93 Ok(get_profile_field::v3::Response { value: Some(profile_value) })
94}
95
96pub(crate) async fn set_profile_field_route(
104 State(services): State<crate::State>,
105 ClientIp(client): ClientIp,
106 body: Ruma<set_profile_field::v3::Request>,
107) -> Result<set_profile_field::v3::Response> {
108 let sender_user = body.sender_user();
109
110 if *sender_user != body.user_id
111 && !body
112 .appservice_info
113 .as_ref()
114 .is_some_and(|registration| registration.is_user_match(&body.user_id))
115 {
116 return Err!(Request(Forbidden("You cannot update the profile of another user")));
117 }
118
119 let propagation = resolve_propagation(&body.propagate_to);
120
121 services
122 .profile
123 .set_profile_keys(
124 &body.user_id,
125 &[(body.value.field_name(), Some(body.value.value().into_owned()))],
126 Some(propagation),
127 )
128 .await?;
129
130 services
132 .presence
133 .maybe_ping_presence(
134 &body.user_id,
135 body.sender_device.as_deref(),
136 Some(client),
137 &PresenceState::Online,
138 )
139 .await?;
140
141 Ok(set_profile_field::v3::Response {})
142}
143
144pub(crate) async fn delete_profile_field_route(
150 State(services): State<crate::State>,
151 ClientIp(client): ClientIp,
152 body: Ruma<delete_profile_field::v3::Request>,
153) -> Result<delete_profile_field::v3::Response> {
154 let sender_user = body.sender_user();
155
156 if *sender_user != body.user_id
157 && !body
158 .appservice_info
159 .as_ref()
160 .is_some_and(|registration| registration.is_user_match(&body.user_id))
161 {
162 return Err!(Request(Forbidden("You cannot update the profile of another user")));
163 }
164
165 let propagation = resolve_propagation(&body.propagate_to);
166
167 services
168 .profile
169 .set_profile_keys(&body.user_id, &[(body.field.clone(), None)], Some(propagation))
170 .await?;
171
172 services
174 .presence
175 .maybe_ping_presence(
176 &body.user_id,
177 body.sender_device.as_deref(),
178 Some(client),
179 &PresenceState::Online,
180 )
181 .await?;
182
183 Ok(delete_profile_field::v3::Response {})
184}