implement generic K-V support for MSC4133, GET/PUT/DELETE
no PATCH still yet Signed-off-by: strawberry <strawberry@puppygock.gay>
This commit is contained in:
parent
80b72637e2
commit
d75aebc373
9 changed files with 340 additions and 20 deletions
26
Cargo.lock
generated
26
Cargo.lock
generated
|
@ -2975,7 +2975,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma"
|
name = "ruma"
|
||||||
version = "0.10.1"
|
version = "0.10.1"
|
||||||
source = "git+https://github.com/girlbossceo/ruwuma?rev=11155e576a1382783c0bcf5ad4458708777ec36e#11155e576a1382783c0bcf5ad4458708777ec36e"
|
source = "git+https://github.com/girlbossceo/ruwuma?rev=b6f82a72b6c0899d8ac8e53206d375c2c6f0a2ad#b6f82a72b6c0899d8ac8e53206d375c2c6f0a2ad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"assign",
|
"assign",
|
||||||
"js_int",
|
"js_int",
|
||||||
|
@ -2997,7 +2997,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-appservice-api"
|
name = "ruma-appservice-api"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
source = "git+https://github.com/girlbossceo/ruwuma?rev=11155e576a1382783c0bcf5ad4458708777ec36e#11155e576a1382783c0bcf5ad4458708777ec36e"
|
source = "git+https://github.com/girlbossceo/ruwuma?rev=b6f82a72b6c0899d8ac8e53206d375c2c6f0a2ad#b6f82a72b6c0899d8ac8e53206d375c2c6f0a2ad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js_int",
|
"js_int",
|
||||||
"ruma-common",
|
"ruma-common",
|
||||||
|
@ -3009,7 +3009,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-client-api"
|
name = "ruma-client-api"
|
||||||
version = "0.18.0"
|
version = "0.18.0"
|
||||||
source = "git+https://github.com/girlbossceo/ruwuma?rev=11155e576a1382783c0bcf5ad4458708777ec36e#11155e576a1382783c0bcf5ad4458708777ec36e"
|
source = "git+https://github.com/girlbossceo/ruwuma?rev=b6f82a72b6c0899d8ac8e53206d375c2c6f0a2ad#b6f82a72b6c0899d8ac8e53206d375c2c6f0a2ad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"as_variant",
|
"as_variant",
|
||||||
"assign",
|
"assign",
|
||||||
|
@ -3032,7 +3032,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-common"
|
name = "ruma-common"
|
||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
source = "git+https://github.com/girlbossceo/ruwuma?rev=11155e576a1382783c0bcf5ad4458708777ec36e#11155e576a1382783c0bcf5ad4458708777ec36e"
|
source = "git+https://github.com/girlbossceo/ruwuma?rev=b6f82a72b6c0899d8ac8e53206d375c2c6f0a2ad#b6f82a72b6c0899d8ac8e53206d375c2c6f0a2ad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"as_variant",
|
"as_variant",
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
|
@ -3062,7 +3062,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-events"
|
name = "ruma-events"
|
||||||
version = "0.28.1"
|
version = "0.28.1"
|
||||||
source = "git+https://github.com/girlbossceo/ruwuma?rev=11155e576a1382783c0bcf5ad4458708777ec36e#11155e576a1382783c0bcf5ad4458708777ec36e"
|
source = "git+https://github.com/girlbossceo/ruwuma?rev=b6f82a72b6c0899d8ac8e53206d375c2c6f0a2ad#b6f82a72b6c0899d8ac8e53206d375c2c6f0a2ad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"as_variant",
|
"as_variant",
|
||||||
"indexmap 2.5.0",
|
"indexmap 2.5.0",
|
||||||
|
@ -3086,7 +3086,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-federation-api"
|
name = "ruma-federation-api"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
source = "git+https://github.com/girlbossceo/ruwuma?rev=11155e576a1382783c0bcf5ad4458708777ec36e#11155e576a1382783c0bcf5ad4458708777ec36e"
|
source = "git+https://github.com/girlbossceo/ruwuma?rev=b6f82a72b6c0899d8ac8e53206d375c2c6f0a2ad#b6f82a72b6c0899d8ac8e53206d375c2c6f0a2ad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"http",
|
"http",
|
||||||
|
@ -3104,7 +3104,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-identifiers-validation"
|
name = "ruma-identifiers-validation"
|
||||||
version = "0.9.5"
|
version = "0.9.5"
|
||||||
source = "git+https://github.com/girlbossceo/ruwuma?rev=11155e576a1382783c0bcf5ad4458708777ec36e#11155e576a1382783c0bcf5ad4458708777ec36e"
|
source = "git+https://github.com/girlbossceo/ruwuma?rev=b6f82a72b6c0899d8ac8e53206d375c2c6f0a2ad#b6f82a72b6c0899d8ac8e53206d375c2c6f0a2ad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js_int",
|
"js_int",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
@ -3113,7 +3113,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-identity-service-api"
|
name = "ruma-identity-service-api"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
source = "git+https://github.com/girlbossceo/ruwuma?rev=11155e576a1382783c0bcf5ad4458708777ec36e#11155e576a1382783c0bcf5ad4458708777ec36e"
|
source = "git+https://github.com/girlbossceo/ruwuma?rev=b6f82a72b6c0899d8ac8e53206d375c2c6f0a2ad#b6f82a72b6c0899d8ac8e53206d375c2c6f0a2ad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js_int",
|
"js_int",
|
||||||
"ruma-common",
|
"ruma-common",
|
||||||
|
@ -3123,7 +3123,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-macros"
|
name = "ruma-macros"
|
||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
source = "git+https://github.com/girlbossceo/ruwuma?rev=11155e576a1382783c0bcf5ad4458708777ec36e#11155e576a1382783c0bcf5ad4458708777ec36e"
|
source = "git+https://github.com/girlbossceo/ruwuma?rev=b6f82a72b6c0899d8ac8e53206d375c2c6f0a2ad#b6f82a72b6c0899d8ac8e53206d375c2c6f0a2ad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
@ -3139,7 +3139,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-push-gateway-api"
|
name = "ruma-push-gateway-api"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
source = "git+https://github.com/girlbossceo/ruwuma?rev=11155e576a1382783c0bcf5ad4458708777ec36e#11155e576a1382783c0bcf5ad4458708777ec36e"
|
source = "git+https://github.com/girlbossceo/ruwuma?rev=b6f82a72b6c0899d8ac8e53206d375c2c6f0a2ad#b6f82a72b6c0899d8ac8e53206d375c2c6f0a2ad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js_int",
|
"js_int",
|
||||||
"ruma-common",
|
"ruma-common",
|
||||||
|
@ -3151,7 +3151,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-server-util"
|
name = "ruma-server-util"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
source = "git+https://github.com/girlbossceo/ruwuma?rev=11155e576a1382783c0bcf5ad4458708777ec36e#11155e576a1382783c0bcf5ad4458708777ec36e"
|
source = "git+https://github.com/girlbossceo/ruwuma?rev=b6f82a72b6c0899d8ac8e53206d375c2c6f0a2ad#b6f82a72b6c0899d8ac8e53206d375c2c6f0a2ad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"headers",
|
"headers",
|
||||||
"http",
|
"http",
|
||||||
|
@ -3164,7 +3164,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-signatures"
|
name = "ruma-signatures"
|
||||||
version = "0.15.0"
|
version = "0.15.0"
|
||||||
source = "git+https://github.com/girlbossceo/ruwuma?rev=11155e576a1382783c0bcf5ad4458708777ec36e#11155e576a1382783c0bcf5ad4458708777ec36e"
|
source = "git+https://github.com/girlbossceo/ruwuma?rev=b6f82a72b6c0899d8ac8e53206d375c2c6f0a2ad#b6f82a72b6c0899d8ac8e53206d375c2c6f0a2ad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"ed25519-dalek",
|
"ed25519-dalek",
|
||||||
|
@ -3180,7 +3180,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-state-res"
|
name = "ruma-state-res"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
source = "git+https://github.com/girlbossceo/ruwuma?rev=11155e576a1382783c0bcf5ad4458708777ec36e#11155e576a1382783c0bcf5ad4458708777ec36e"
|
source = "git+https://github.com/girlbossceo/ruwuma?rev=b6f82a72b6c0899d8ac8e53206d375c2c6f0a2ad#b6f82a72b6c0899d8ac8e53206d375c2c6f0a2ad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itertools 0.12.1",
|
"itertools 0.12.1",
|
||||||
"js_int",
|
"js_int",
|
||||||
|
|
|
@ -314,7 +314,7 @@ version = "0.1.2"
|
||||||
[workspace.dependencies.ruma]
|
[workspace.dependencies.ruma]
|
||||||
git = "https://github.com/girlbossceo/ruwuma"
|
git = "https://github.com/girlbossceo/ruwuma"
|
||||||
#branch = "conduwuit-changes"
|
#branch = "conduwuit-changes"
|
||||||
rev = "11155e576a1382783c0bcf5ad4458708777ec36e"
|
rev = "b6f82a72b6c0899d8ac8e53206d375c2c6f0a2ad"
|
||||||
features = [
|
features = [
|
||||||
"compat",
|
"compat",
|
||||||
"rand",
|
"rand",
|
||||||
|
@ -341,6 +341,7 @@ features = [
|
||||||
"unstable-msc3575",
|
"unstable-msc3575",
|
||||||
"unstable-msc4121",
|
"unstable-msc4121",
|
||||||
"unstable-msc4125",
|
"unstable-msc4125",
|
||||||
|
"unstable-msc4186",
|
||||||
"unstable-extensible-events",
|
"unstable-extensible-events",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -236,7 +236,7 @@ pub(crate) async fn join_room_by_id_or_alias_route(
|
||||||
Ok(room_id) => {
|
Ok(room_id) => {
|
||||||
banned_room_check(&services, sender_user, Some(&room_id), room_id.server_name(), client).await?;
|
banned_room_check(&services, sender_user, Some(&room_id), room_id.server_name(), client).await?;
|
||||||
|
|
||||||
let mut servers = body.server_name.clone();
|
let mut servers = body.via.clone();
|
||||||
servers.extend(
|
servers.extend(
|
||||||
services
|
services
|
||||||
.rooms
|
.rooms
|
||||||
|
@ -269,13 +269,13 @@ pub(crate) async fn join_room_by_id_or_alias_route(
|
||||||
let response = services
|
let response = services
|
||||||
.rooms
|
.rooms
|
||||||
.alias
|
.alias
|
||||||
.resolve_alias(&room_alias, Some(&body.server_name.clone()))
|
.resolve_alias(&room_alias, Some(&body.via.clone()))
|
||||||
.await?;
|
.await?;
|
||||||
let (room_id, mut pre_servers) = response;
|
let (room_id, mut pre_servers) = response;
|
||||||
|
|
||||||
banned_room_check(&services, sender_user, Some(&room_id), Some(room_alias.server_name()), client).await?;
|
banned_room_check(&services, sender_user, Some(&room_id), Some(room_alias.server_name()), client).await?;
|
||||||
|
|
||||||
let mut servers = body.server_name;
|
let mut servers = body.via;
|
||||||
if let Some(pre_servers) = &mut pre_servers {
|
if let Some(pre_servers) = &mut pre_servers {
|
||||||
servers.append(pre_servers);
|
servers.append(pre_servers);
|
||||||
}
|
}
|
||||||
|
|
|
@ -247,11 +247,18 @@ pub(crate) async fn get_profile_route(
|
||||||
.set_timezone(&body.user_id, response.tz.clone())
|
.set_timezone(&body.user_id, response.tz.clone())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
for (profile_key, profile_key_value) in &response.custom_profile_fields {
|
||||||
|
services
|
||||||
|
.users
|
||||||
|
.set_profile_key(&body.user_id, profile_key, Some(profile_key_value.clone()))?;
|
||||||
|
}
|
||||||
|
|
||||||
return Ok(get_profile::v3::Response {
|
return Ok(get_profile::v3::Response {
|
||||||
displayname: response.displayname,
|
displayname: response.displayname,
|
||||||
avatar_url: response.avatar_url,
|
avatar_url: response.avatar_url,
|
||||||
blurhash: response.blurhash,
|
blurhash: response.blurhash,
|
||||||
tz: response.tz,
|
tz: response.tz,
|
||||||
|
custom_profile_fields: response.custom_profile_fields,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -267,6 +274,11 @@ pub(crate) async fn get_profile_route(
|
||||||
blurhash: services.users.blurhash(&body.user_id)?,
|
blurhash: services.users.blurhash(&body.user_id)?,
|
||||||
displayname: services.users.displayname(&body.user_id)?,
|
displayname: services.users.displayname(&body.user_id)?,
|
||||||
tz: services.users.timezone(&body.user_id)?,
|
tz: services.users.timezone(&body.user_id)?,
|
||||||
|
custom_profile_fields: services
|
||||||
|
.users
|
||||||
|
.all_profile_keys(&body.user_id)
|
||||||
|
.filter_map(Result::ok)
|
||||||
|
.collect(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
use axum_client_ip::InsecureClientIp;
|
use axum_client_ip::InsecureClientIp;
|
||||||
use conduit::{warn, Err};
|
use conduit::{warn, Err};
|
||||||
|
@ -6,7 +8,10 @@ use ruma::{
|
||||||
client::{
|
client::{
|
||||||
error::ErrorKind,
|
error::ErrorKind,
|
||||||
membership::mutual_rooms,
|
membership::mutual_rooms,
|
||||||
profile::{delete_timezone_key, get_timezone_key, set_timezone_key},
|
profile::{
|
||||||
|
delete_profile_key, delete_timezone_key, get_profile_key, get_timezone_key, set_profile_key,
|
||||||
|
set_timezone_key,
|
||||||
|
},
|
||||||
room::get_summary,
|
room::get_summary,
|
||||||
},
|
},
|
||||||
federation,
|
federation,
|
||||||
|
@ -16,6 +21,7 @@ use ruma::{
|
||||||
OwnedRoomId,
|
OwnedRoomId,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::{update_avatar_url, update_displayname};
|
||||||
use crate::{Error, Result, Ruma, RumaResponse};
|
use crate::{Error, Result, Ruma, RumaResponse};
|
||||||
|
|
||||||
/// # `GET /_matrix/client/unstable/uk.half-shot.msc2666/user/mutual_rooms`
|
/// # `GET /_matrix/client/unstable/uk.half-shot.msc2666/user/mutual_rooms`
|
||||||
|
@ -226,6 +232,138 @@ pub(crate) async fn set_timezone_key_route(
|
||||||
Ok(set_timezone_key::unstable::Response {})
|
Ok(set_timezone_key::unstable::Response {})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// # `PUT /_matrix/client/unstable/uk.tcpip.msc4133/profile/{user_id}/{field}`
|
||||||
|
///
|
||||||
|
/// Updates the profile key-value field of a user, as per MSC4133.
|
||||||
|
///
|
||||||
|
/// This also handles the avatar_url and displayname being updated.
|
||||||
|
pub(crate) async fn set_profile_key_route(
|
||||||
|
State(services): State<crate::State>, body: Ruma<set_profile_key::unstable::Request>,
|
||||||
|
) -> Result<set_profile_key::unstable::Response> {
|
||||||
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
|
if *sender_user != body.user_id && body.appservice_info.is_none() {
|
||||||
|
return Err!(Request(Forbidden("You cannot update the profile of another user")));
|
||||||
|
}
|
||||||
|
|
||||||
|
if body.kv_pair.is_empty() {
|
||||||
|
return Err!(Request(BadJson(
|
||||||
|
"The key-value pair JSON body is empty. Use DELETE to delete a key"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if body.kv_pair.len() > 1 {
|
||||||
|
// TODO: support PATCH or "recursively" adding keys in some sort
|
||||||
|
return Err!(Request(BadJson("This endpoint can only take one key-value pair at a time")));
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(profile_key_value) = body.kv_pair.get(&body.key) else {
|
||||||
|
return Err!(Request(BadJson(
|
||||||
|
"The key does not match the URL field key, or JSON body is empty (use DELETE)"
|
||||||
|
)));
|
||||||
|
};
|
||||||
|
|
||||||
|
if body
|
||||||
|
.kv_pair
|
||||||
|
.keys()
|
||||||
|
.any(|key| key.starts_with("u.") && !profile_key_value.is_string())
|
||||||
|
{
|
||||||
|
return Err!(Request(BadJson("u.* profile key fields must be strings")));
|
||||||
|
}
|
||||||
|
|
||||||
|
if body.kv_pair.keys().any(|key| key.len() > 128) {
|
||||||
|
return Err!(Request(BadJson("Key names cannot be longer than 128 bytes")));
|
||||||
|
}
|
||||||
|
|
||||||
|
if body.key == "displayname" {
|
||||||
|
let all_joined_rooms: Vec<OwnedRoomId> = services
|
||||||
|
.rooms
|
||||||
|
.state_cache
|
||||||
|
.rooms_joined(&body.user_id)
|
||||||
|
.filter_map(Result::ok)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
update_displayname(&services, &body.user_id, Some(profile_key_value.to_string()), all_joined_rooms).await?;
|
||||||
|
} else if body.key == "avatar_url" {
|
||||||
|
let mxc = ruma::OwnedMxcUri::from(profile_key_value.to_string());
|
||||||
|
|
||||||
|
let all_joined_rooms: Vec<OwnedRoomId> = services
|
||||||
|
.rooms
|
||||||
|
.state_cache
|
||||||
|
.rooms_joined(&body.user_id)
|
||||||
|
.filter_map(Result::ok)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
update_avatar_url(&services, &body.user_id, Some(mxc), None, all_joined_rooms).await?;
|
||||||
|
} else {
|
||||||
|
services
|
||||||
|
.users
|
||||||
|
.set_profile_key(&body.user_id, &body.key, Some(profile_key_value.clone()))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if services.globals.allow_local_presence() {
|
||||||
|
// Presence update
|
||||||
|
services
|
||||||
|
.presence
|
||||||
|
.ping_presence(&body.user_id, &PresenceState::Online)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(set_profile_key::unstable::Response {})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # `DELETE /_matrix/client/unstable/uk.tcpip.msc4133/profile/{user_id}/{field}`
|
||||||
|
///
|
||||||
|
/// Deletes the profile key-value field of a user, as per MSC4133.
|
||||||
|
///
|
||||||
|
/// This also handles the avatar_url and displayname being updated.
|
||||||
|
pub(crate) async fn delete_profile_key_route(
|
||||||
|
State(services): State<crate::State>, body: Ruma<delete_profile_key::unstable::Request>,
|
||||||
|
) -> Result<delete_profile_key::unstable::Response> {
|
||||||
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
|
if *sender_user != body.user_id && body.appservice_info.is_none() {
|
||||||
|
return Err!(Request(Forbidden("You cannot update the profile of another user")));
|
||||||
|
}
|
||||||
|
|
||||||
|
if body.kv_pair.len() > 1 {
|
||||||
|
// TODO: support PATCH or "recursively" adding keys in some sort
|
||||||
|
return Err!(Request(BadJson("This endpoint can only take one key-value pair at a time")));
|
||||||
|
}
|
||||||
|
|
||||||
|
if body.key == "displayname" {
|
||||||
|
let all_joined_rooms: Vec<OwnedRoomId> = services
|
||||||
|
.rooms
|
||||||
|
.state_cache
|
||||||
|
.rooms_joined(&body.user_id)
|
||||||
|
.filter_map(Result::ok)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
update_displayname(&services, &body.user_id, None, all_joined_rooms).await?;
|
||||||
|
} else if body.key == "avatar_url" {
|
||||||
|
let all_joined_rooms: Vec<OwnedRoomId> = services
|
||||||
|
.rooms
|
||||||
|
.state_cache
|
||||||
|
.rooms_joined(&body.user_id)
|
||||||
|
.filter_map(Result::ok)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
update_avatar_url(&services, &body.user_id, None, None, all_joined_rooms).await?;
|
||||||
|
} else {
|
||||||
|
services
|
||||||
|
.users
|
||||||
|
.set_profile_key(&body.user_id, &body.key, None)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if services.globals.allow_local_presence() {
|
||||||
|
// Presence update
|
||||||
|
services
|
||||||
|
.presence
|
||||||
|
.ping_presence(&body.user_id, &PresenceState::Online)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(delete_profile_key::unstable::Response {})
|
||||||
|
}
|
||||||
|
|
||||||
/// # `GET /_matrix/client/unstable/uk.tcpip.msc4133/profile/:user_id/us.cloke.msc4175.tz`
|
/// # `GET /_matrix/client/unstable/uk.tcpip.msc4133/profile/:user_id/us.cloke.msc4175.tz`
|
||||||
///
|
///
|
||||||
/// Returns the `timezone` of the user as per MSC4133 and MSC4175.
|
/// Returns the `timezone` of the user as per MSC4133 and MSC4175.
|
||||||
|
@ -285,3 +423,80 @@ pub(crate) async fn get_timezone_key_route(
|
||||||
tz: services.users.timezone(&body.user_id)?,
|
tz: services.users.timezone(&body.user_id)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// # `GET /_matrix/client/unstable/uk.tcpip.msc4133/profile/{userId}/{field}}`
|
||||||
|
///
|
||||||
|
/// Gets the profile key-value field of a user, as per MSC4133.
|
||||||
|
///
|
||||||
|
/// - If user is on another server and we do not have a local copy already fetch
|
||||||
|
/// `timezone` over federation
|
||||||
|
pub(crate) async fn get_profile_key_route(
|
||||||
|
State(services): State<crate::State>, body: Ruma<get_profile_key::unstable::Request>,
|
||||||
|
) -> Result<get_profile_key::unstable::Response> {
|
||||||
|
let mut profile_key_value: BTreeMap<String, serde_json::Value> = BTreeMap::new();
|
||||||
|
|
||||||
|
if !services.globals.user_is_local(&body.user_id) {
|
||||||
|
// Create and update our local copy of the user
|
||||||
|
if let Ok(response) = services
|
||||||
|
.sending
|
||||||
|
.send_federation_request(
|
||||||
|
body.user_id.server_name(),
|
||||||
|
federation::query::get_profile_information::v1::Request {
|
||||||
|
user_id: body.user_id.clone(),
|
||||||
|
field: None, // we want the full user's profile to update locally as well
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
if !services.users.exists(&body.user_id)? {
|
||||||
|
services.users.create(&body.user_id, None)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
services
|
||||||
|
.users
|
||||||
|
.set_displayname(&body.user_id, response.displayname.clone())
|
||||||
|
.await?;
|
||||||
|
services
|
||||||
|
.users
|
||||||
|
.set_avatar_url(&body.user_id, response.avatar_url.clone())
|
||||||
|
.await?;
|
||||||
|
services
|
||||||
|
.users
|
||||||
|
.set_blurhash(&body.user_id, response.blurhash.clone())
|
||||||
|
.await?;
|
||||||
|
services
|
||||||
|
.users
|
||||||
|
.set_timezone(&body.user_id, response.tz.clone())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if let Some(value) = response.custom_profile_fields.get(&body.key) {
|
||||||
|
profile_key_value.insert(body.key.clone(), value.clone());
|
||||||
|
services
|
||||||
|
.users
|
||||||
|
.set_profile_key(&body.user_id, &body.key, Some(value.clone()))?;
|
||||||
|
} else {
|
||||||
|
return Err!(Request(NotFound("The requested profile key does not exist.")));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(get_profile_key::unstable::Response {
|
||||||
|
value: profile_key_value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !services.users.exists(&body.user_id)? {
|
||||||
|
// Return 404 if this user doesn't exist and we couldn't fetch it over
|
||||||
|
// federation
|
||||||
|
return Err(Error::BadRequest(ErrorKind::NotFound, "Profile was not found."));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(value) = services.users.profile_key(&body.user_id, &body.key)? {
|
||||||
|
profile_key_value.insert(body.key.clone(), value);
|
||||||
|
} else {
|
||||||
|
return Err!(Request(NotFound("The requested profile key does not exist.")));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(get_profile_key::unstable::Response {
|
||||||
|
value: profile_key_value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -23,6 +23,9 @@ pub fn build(router: Router<State>, server: &Server) -> Router<State> {
|
||||||
let config = &server.config;
|
let config = &server.config;
|
||||||
let mut router = router
|
let mut router = router
|
||||||
.ruma_route(client::get_timezone_key_route)
|
.ruma_route(client::get_timezone_key_route)
|
||||||
|
.ruma_route(client::get_profile_key_route)
|
||||||
|
.ruma_route(client::set_profile_key_route)
|
||||||
|
.ruma_route(client::delete_profile_key_route)
|
||||||
.ruma_route(client::set_timezone_key_route)
|
.ruma_route(client::set_timezone_key_route)
|
||||||
.ruma_route(client::delete_timezone_key_route)
|
.ruma_route(client::delete_timezone_key_route)
|
||||||
.ruma_route(client::appservice_ping)
|
.ruma_route(client::appservice_ping)
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
use conduit::{Error, Result};
|
use conduit::{Error, Result};
|
||||||
use get_profile_information::v1::ProfileField;
|
use get_profile_information::v1::ProfileField;
|
||||||
|
@ -76,6 +78,7 @@ pub(crate) async fn get_profile_information_route(
|
||||||
let mut avatar_url = None;
|
let mut avatar_url = None;
|
||||||
let mut blurhash = None;
|
let mut blurhash = None;
|
||||||
let mut tz = None;
|
let mut tz = None;
|
||||||
|
let mut custom_profile_fields = BTreeMap::new();
|
||||||
|
|
||||||
match &body.field {
|
match &body.field {
|
||||||
Some(ProfileField::DisplayName) => {
|
Some(ProfileField::DisplayName) => {
|
||||||
|
@ -85,13 +88,24 @@ pub(crate) async fn get_profile_information_route(
|
||||||
avatar_url = services.users.avatar_url(&body.user_id)?;
|
avatar_url = services.users.avatar_url(&body.user_id)?;
|
||||||
blurhash = services.users.blurhash(&body.user_id)?;
|
blurhash = services.users.blurhash(&body.user_id)?;
|
||||||
},
|
},
|
||||||
// TODO: what to do with custom
|
Some(custom_field) => {
|
||||||
Some(_) => {},
|
if let Some(value) = services
|
||||||
|
.users
|
||||||
|
.profile_key(&body.user_id, custom_field.as_str())?
|
||||||
|
{
|
||||||
|
custom_profile_fields.insert(custom_field.to_string(), value);
|
||||||
|
}
|
||||||
|
},
|
||||||
None => {
|
None => {
|
||||||
displayname = services.users.displayname(&body.user_id)?;
|
displayname = services.users.displayname(&body.user_id)?;
|
||||||
avatar_url = services.users.avatar_url(&body.user_id)?;
|
avatar_url = services.users.avatar_url(&body.user_id)?;
|
||||||
blurhash = services.users.blurhash(&body.user_id)?;
|
blurhash = services.users.blurhash(&body.user_id)?;
|
||||||
tz = services.users.timezone(&body.user_id)?;
|
tz = services.users.timezone(&body.user_id)?;
|
||||||
|
custom_profile_fields = services
|
||||||
|
.users
|
||||||
|
.all_profile_keys(&body.user_id)
|
||||||
|
.filter_map(Result::ok)
|
||||||
|
.collect();
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,5 +114,6 @@ pub(crate) async fn get_profile_information_route(
|
||||||
avatar_url,
|
avatar_url,
|
||||||
blurhash,
|
blurhash,
|
||||||
tz,
|
tz,
|
||||||
|
custom_profile_fields,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -233,6 +233,60 @@ impl Data {
|
||||||
.transpose()
|
.transpose()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets a specific user profile key
|
||||||
|
pub(super) fn profile_key(&self, user_id: &UserId, profile_key: &str) -> Result<Option<serde_json::Value>> {
|
||||||
|
let mut key = user_id.as_bytes().to_vec();
|
||||||
|
key.push(0xFF);
|
||||||
|
key.extend_from_slice(profile_key.as_bytes());
|
||||||
|
|
||||||
|
self.useridprofilekey_value
|
||||||
|
.get(&key)?
|
||||||
|
.map_or(Ok(None), |bytes| Ok(Some(serde_json::from_slice(&bytes).unwrap())))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets all the user's profile keys and values in an iterator
|
||||||
|
pub(super) fn all_profile_keys<'a>(
|
||||||
|
&'a self, user_id: &UserId,
|
||||||
|
) -> Box<dyn Iterator<Item = Result<(String, serde_json::Value)>> + 'a + Send> {
|
||||||
|
let prefix = user_id.as_bytes().to_vec();
|
||||||
|
|
||||||
|
Box::new(
|
||||||
|
self.useridprofilekey_value
|
||||||
|
.scan_prefix(prefix)
|
||||||
|
.map(|(key, value)| {
|
||||||
|
let profile_key_name = utils::string_from_bytes(
|
||||||
|
key.rsplit(|&b| b == 0xFF)
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| err!(Database("Profile key in db is invalid")))?,
|
||||||
|
)
|
||||||
|
.map_err(|e| err!(Database("Profile key in db is invalid. {e}")))?;
|
||||||
|
|
||||||
|
let profile_key_value = serde_json::from_slice(&value)
|
||||||
|
.map_err(|e| err!(Database("Profile key in db is invalid. {e}")))?;
|
||||||
|
|
||||||
|
Ok((profile_key_name, profile_key_value))
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets a new profile key value, removes the key if value is None
|
||||||
|
pub(super) fn set_profile_key(
|
||||||
|
&self, user_id: &UserId, profile_key: &str, profile_key_value: Option<serde_json::Value>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let mut key = user_id.as_bytes().to_vec();
|
||||||
|
key.push(0xFF);
|
||||||
|
key.extend_from_slice(profile_key.as_bytes());
|
||||||
|
|
||||||
|
// TODO: insert to the stable MSC4175 key when it's stable
|
||||||
|
if let Some(value) = profile_key_value {
|
||||||
|
let value = serde_json::to_vec(&value).unwrap();
|
||||||
|
|
||||||
|
self.useridprofilekey_value.insert(&key, &value)
|
||||||
|
} else {
|
||||||
|
self.useridprofilekey_value.remove(&key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the timezone of a user.
|
/// Get the timezone of a user.
|
||||||
pub(super) fn timezone(&self, user_id: &UserId) -> Result<Option<String>> {
|
pub(super) fn timezone(&self, user_id: &UserId) -> Result<Option<String>> {
|
||||||
// first check the unstable prefix
|
// first check the unstable prefix
|
||||||
|
|
|
@ -329,6 +329,26 @@ impl Service {
|
||||||
|
|
||||||
pub fn timezone(&self, user_id: &UserId) -> Result<Option<String>> { self.db.timezone(user_id) }
|
pub fn timezone(&self, user_id: &UserId) -> Result<Option<String>> { self.db.timezone(user_id) }
|
||||||
|
|
||||||
|
/// Gets a specific user profile key
|
||||||
|
pub fn profile_key(&self, user_id: &UserId, profile_key: &str) -> Result<Option<serde_json::Value>> {
|
||||||
|
self.db.profile_key(user_id, profile_key)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets all the user's profile keys and values in an iterator
|
||||||
|
pub fn all_profile_keys<'a>(
|
||||||
|
&'a self, user_id: &UserId,
|
||||||
|
) -> Box<dyn Iterator<Item = Result<(String, serde_json::Value)>> + 'a + Send> {
|
||||||
|
self.db.all_profile_keys(user_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets a new profile key value, removes the key if value is None
|
||||||
|
pub fn set_profile_key(
|
||||||
|
&self, user_id: &UserId, profile_key: &str, profile_key_value: Option<serde_json::Value>,
|
||||||
|
) -> Result<()> {
|
||||||
|
self.db
|
||||||
|
.set_profile_key(user_id, profile_key, profile_key_value)
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets a new tz or removes it if tz is None.
|
/// Sets a new tz or removes it if tz is None.
|
||||||
pub async fn set_timezone(&self, user_id: &UserId, tz: Option<String>) -> Result<()> {
|
pub async fn set_timezone(&self, user_id: &UserId, tz: Option<String>) -> Result<()> {
|
||||||
self.db.set_timezone(user_id, tz)
|
self.db.set_timezone(user_id, tz)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue