From 535055b1c182cf29612745a4775fe2d6d97f2e62 Mon Sep 17 00:00:00 2001 From: mikoto Date: Tue, 16 Apr 2024 18:09:44 +0200 Subject: [PATCH] feat: retrieving profiles and updating profiles --- crates/core/src/membership/ban.rs | 0 crates/core/src/membership/join.rs | 12 +- crates/core/src/membership/kick.rs | 0 crates/core/src/profile.rs | 20 +- crates/core/src/profile/avatar.rs | 41 -- crates/core/src/profile/avatar_url.rs | 27 + crates/core/src/profile/display_name.rs | 38 -- crates/core/src/profile/displayname.rs | 24 + crates/core/src/spaces/channels/create.rs | 3 +- .../matrix/src/client-backup/account.rs.bk.bk | 3 - .../src/client-backup/events.rs.bk.bk.bk | 310 ---------- .../src/client-backup/membership.rs.bk.bk | 5 - crates/matrix/src/client-backup/mod.rs.bk.bk | 10 - .../matrix/src/client-backup/mxc.rs.bk.bk.bk | 184 ------ .../matrix/src/client-backup/rooms.rs.bk.bk | 6 - .../src/client-backup/session.rs.bk.bk.bk | 2 - crates/matrix/src/client-backup/sync.rs.bk.bk | 564 ------------------ crates/matrix/src/client-backup/uiaa.rs.bk.bk | 164 ----- crates/matrix/src/client/profile.rs | 37 +- .../matrix/src/client/profile/avatar_url.rs | 39 +- .../src/client/profile/avatar_url/get.rs | 31 - .../src/client/profile/avatar_url/update.rs | 36 -- .../matrix/src/client/profile/display_name.rs | 2 - .../src/client/profile/display_name/get.rs | 32 - .../update.rs => displayname.rs} | 0 crates/router/src/api.rs | 3 +- crates/router/src/api/account.rs | 2 - crates/router/src/api/membership/join.rs | 6 +- crates/router/src/api/profile.rs | 22 + .../avatar.rs => profile/avatar_url.rs} | 10 +- .../displayname.rs} | 10 +- crates/router/src/api/profile/root.rs | 19 + crates/router/src/lib.rs | 19 +- crates/test/Cargo.toml | 1 + crates/test/src/api.rs | 1 + crates/test/src/api/account.rs | 2 - crates/test/src/api/account/avatar.rs | 35 -- crates/test/src/api/account/avatar_url.rs | 65 ++ crates/test/src/api/profile.rs | 22 + crates/test/src/api/profile/avatar_url.rs | 43 ++ crates/test/src/api/profile/displayname.rs | 43 ++ crates/test/src/api/relative/register.rs | 2 +- crates/test/src/util.rs | 19 +- 43 files changed, 411 insertions(+), 1503 deletions(-) create mode 100644 crates/core/src/membership/ban.rs create mode 100644 crates/core/src/membership/kick.rs delete mode 100644 crates/core/src/profile/avatar.rs create mode 100644 crates/core/src/profile/avatar_url.rs delete mode 100644 crates/core/src/profile/display_name.rs create mode 100644 crates/core/src/profile/displayname.rs delete mode 100644 crates/matrix/src/client-backup/account.rs.bk.bk delete mode 100644 crates/matrix/src/client-backup/events.rs.bk.bk.bk delete mode 100644 crates/matrix/src/client-backup/membership.rs.bk.bk delete mode 100644 crates/matrix/src/client-backup/mod.rs.bk.bk delete mode 100644 crates/matrix/src/client-backup/mxc.rs.bk.bk.bk delete mode 100644 crates/matrix/src/client-backup/rooms.rs.bk.bk delete mode 100644 crates/matrix/src/client-backup/session.rs.bk.bk.bk delete mode 100644 crates/matrix/src/client-backup/sync.rs.bk.bk delete mode 100644 crates/matrix/src/client-backup/uiaa.rs.bk.bk delete mode 100644 crates/matrix/src/client/profile/avatar_url/get.rs delete mode 100644 crates/matrix/src/client/profile/avatar_url/update.rs delete mode 100644 crates/matrix/src/client/profile/display_name.rs delete mode 100644 crates/matrix/src/client/profile/display_name/get.rs rename crates/matrix/src/client/profile/{display_name/update.rs => displayname.rs} (100%) create mode 100644 crates/router/src/api/profile.rs rename crates/router/src/api/{account/avatar.rs => profile/avatar_url.rs} (67%) rename crates/router/src/api/{account/display_name.rs => profile/displayname.rs} (63%) create mode 100644 crates/router/src/api/profile/root.rs delete mode 100644 crates/test/src/api/account/avatar.rs create mode 100644 crates/test/src/api/account/avatar_url.rs create mode 100644 crates/test/src/api/profile.rs create mode 100644 crates/test/src/api/profile/avatar_url.rs create mode 100644 crates/test/src/api/profile/displayname.rs diff --git a/crates/core/src/membership/ban.rs b/crates/core/src/membership/ban.rs new file mode 100644 index 0000000..e69de29 diff --git a/crates/core/src/membership/join.rs b/crates/core/src/membership/join.rs index c26d19d..69f9c96 100644 --- a/crates/core/src/membership/join.rs +++ b/crates/core/src/membership/join.rs @@ -1,15 +1,21 @@ use matrix::{ client::membership::join::{Request, Response}, - ruma_common::OwnedRoomOrAliasId, + ruma_common::OwnedRoomId, }; -use crate::{commune, error::Error}; +use crate::{commune, error::Error, util::opaque_id::OpaqueId}; pub async fn service( access_token: impl AsRef, - room_id: impl Into, + space_id: impl Into, reason: Option, ) -> Result { + let server_name = &commune().config.matrix.server_name; + + let space_id = space_id.into(); + let room_id = OwnedRoomId::try_from(format!("!{space_id}:{server_name}")) + .expect("Parsing space identifier should never panic"); + let req = Request::new(room_id.into(), reason); commune() diff --git a/crates/core/src/membership/kick.rs b/crates/core/src/membership/kick.rs new file mode 100644 index 0000000..e69de29 diff --git a/crates/core/src/profile.rs b/crates/core/src/profile.rs index bea0178..3e01625 100644 --- a/crates/core/src/profile.rs +++ b/crates/core/src/profile.rs @@ -1,2 +1,18 @@ -pub mod avatar; -pub mod display_name; +pub mod avatar_url; +pub mod displayname; + +use matrix::{ + client::profile::{Request, Response}, + ruma_common::OwnedUserId, +}; + +use crate::{commune, error::Error}; + +pub async fn service(user_id: impl Into) -> Result { + let req = Request::new(user_id.into()); + + commune() + .send_matrix_request(req, None) + .await + .map_err(Into::into) +} diff --git a/crates/core/src/profile/avatar.rs b/crates/core/src/profile/avatar.rs deleted file mode 100644 index 42c6a97..0000000 --- a/crates/core/src/profile/avatar.rs +++ /dev/null @@ -1,41 +0,0 @@ -pub mod get { - use matrix::{client::profile::avatar_url::get::*, ruma_common::OwnedUserId}; - - use crate::{commune, error::Error}; - - pub async fn service(user_id: impl Into) -> Result { - let req = Request::new(user_id.into()); - - commune() - .send_matrix_request(req, None) - .await - .map_err(Into::into) - } -} - -pub mod update { - use matrix::{ - client::{account::whoami, profile::avatar_url::update::*}, - ruma_common::OwnedMxcUri, - }; - - use crate::{commune, error::Error}; - - pub async fn service( - access_token: impl AsRef, - mxc_uri: impl Into, - ) -> Result { - let req = whoami::Request::new(); - - let whoami::Response { user_id, .. } = commune() - .send_matrix_request(req, Some(access_token.as_ref())) - .await?; - - let req = Request::new(user_id, mxc_uri.into()); - - commune() - .send_matrix_request(req, Some(access_token.as_ref())) - .await - .map_err(Into::into) - } -} diff --git a/crates/core/src/profile/avatar_url.rs b/crates/core/src/profile/avatar_url.rs new file mode 100644 index 0000000..5c44ca8 --- /dev/null +++ b/crates/core/src/profile/avatar_url.rs @@ -0,0 +1,27 @@ +use matrix::{ + client::{ + account::whoami, + profile::avatar_url::{Request, Response}, + }, + ruma_common::OwnedMxcUri, +}; + +use crate::{commune, error::Error}; + +pub async fn service( + access_token: impl AsRef, + avatar_url: impl Into, +) -> Result { + let req = whoami::Request::new(); + + let whoami::Response { user_id, .. } = commune() + .send_matrix_request(req, Some(access_token.as_ref())) + .await?; + + let req = Request::new(user_id, avatar_url.into()); + + commune() + .send_matrix_request(req, Some(access_token.as_ref())) + .await + .map_err(Into::into) +} diff --git a/crates/core/src/profile/display_name.rs b/crates/core/src/profile/display_name.rs deleted file mode 100644 index 74dccff..0000000 --- a/crates/core/src/profile/display_name.rs +++ /dev/null @@ -1,38 +0,0 @@ -pub mod get { - use matrix::{client::profile::display_name::get::*, ruma_common::OwnedUserId}; - - use crate::{commune, error::Error}; - - pub async fn service(user_id: impl Into) -> Result { - let req = Request::new(user_id.into()); - - commune() - .send_matrix_request(req, None) - .await - .map_err(Into::into) - } -} - -pub mod update { - use matrix::client::{account::whoami, profile::display_name::update::*}; - - use crate::{commune, error::Error}; - - pub async fn service( - access_token: impl AsRef, - display_name: impl Into, - ) -> Result { - let req = whoami::Request::new(); - - let whoami::Response { user_id, .. } = commune() - .send_matrix_request(req, Some(access_token.as_ref())) - .await?; - - let req = Request::new(user_id, display_name.into()); - - commune() - .send_matrix_request(req, Some(access_token.as_ref())) - .await - .map_err(Into::into) - } -} diff --git a/crates/core/src/profile/displayname.rs b/crates/core/src/profile/displayname.rs new file mode 100644 index 0000000..6b1ecef --- /dev/null +++ b/crates/core/src/profile/displayname.rs @@ -0,0 +1,24 @@ +use matrix::client::{ + account::whoami, + profile::displayname::{Request, Response}, +}; + +use crate::{commune, error::Error}; + +pub async fn service( + access_token: impl AsRef, + displayname: impl Into, +) -> Result { + let req = whoami::Request::new(); + + let whoami::Response { user_id, .. } = commune() + .send_matrix_request(req, Some(access_token.as_ref())) + .await?; + + let req = Request::new(user_id, displayname.into()); + + commune() + .send_matrix_request(req, Some(access_token.as_ref())) + .await + .map_err(Into::into) +} diff --git a/crates/core/src/spaces/channels/create.rs b/crates/core/src/spaces/channels/create.rs index 822f517..120ce07 100644 --- a/crates/core/src/spaces/channels/create.rs +++ b/crates/core/src/spaces/channels/create.rs @@ -19,9 +19,8 @@ pub async fn service( topic: Option, ) -> Result { let server_name = &commune().config.matrix.server_name; - // this should never panic let space_id = OwnedRoomId::try_from(format!("!{space_id}:{server_name}")) - .expect("failed to parse room ID"); + .expect("Parsing space identifier should never panic"); let req = Request::new( Some(RoomCreationContent { diff --git a/crates/matrix/src/client-backup/account.rs.bk.bk b/crates/matrix/src/client-backup/account.rs.bk.bk deleted file mode 100644 index d48afb7..0000000 --- a/crates/matrix/src/client-backup/account.rs.bk.bk +++ /dev/null @@ -1,3 +0,0 @@ -pub mod create; -pub mod password; -pub mod whoami; diff --git a/crates/matrix/src/client-backup/events.rs.bk.bk.bk b/crates/matrix/src/client-backup/events.rs.bk.bk.bk deleted file mode 100644 index 2953b2b..0000000 --- a/crates/matrix/src/client-backup/events.rs.bk.bk.bk +++ /dev/null @@ -1,310 +0,0 @@ -use anyhow::Result; -use ruma_common::{serde::Raw, EventId, OwnedEventId, OwnedTransactionId, RoomId}; - -use ruma_events::{ - relation::RelationType, AnyMessageLikeEvent, AnyStateEvent, AnyStateEventContent, - AnyTimelineEvent, MessageLikeEventContent, MessageLikeEventType, StateEventContent, - StateEventType, -}; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use tracing::instrument; - -use crate::{admin::resources::room::Direction, error::MatrixError, Client}; - -pub struct EventsService; - -#[derive(Debug, Default, Clone, Serialize)] -pub struct GetMessagesQuery { - #[serde(skip_serializing_if = "Option::is_none")] - pub from: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub to: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub limit: Option, - - pub dir: Direction, - - #[serde(skip_serializing_if = "String::is_empty")] - pub filter: String, -} - -#[derive(Debug, Default, Clone, Serialize)] -pub struct GetRelationsQuery { - #[serde(skip_serializing_if = "Option::is_none")] - pub from: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub to: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub limit: Option, - - pub dir: Direction, -} - -#[derive(Debug, Deserialize)] -pub struct GetMessagesResponse { - pub chunk: Vec>, - pub start: String, - pub end: String, - pub state: Option>>, -} - -#[derive(Debug, Deserialize)] -#[serde(transparent)] -pub struct GetStateResponse(pub Vec>); - -#[derive(Debug, Deserialize)] -pub struct GetRelationsResponse { - pub chunk: Vec>, - pub prev_batch: Option, - pub next_batch: Option, -} - -#[derive(Debug, Default, Serialize)] -pub struct SendRedactionBody { - #[serde(skip_serializing_if = "String::is_empty")] - pub reason: String, -} - -#[derive(Debug, Deserialize)] -pub struct SendMessageResponse { - pub event_id: OwnedEventId, -} - -#[derive(Debug, Deserialize)] -pub struct SendStateResponse { - pub event_id: OwnedEventId, -} - -#[derive(Debug, Deserialize)] -pub struct SendRedactionResponse { - pub event_id: OwnedEventId, -} - -impl EventsService { - #[instrument(skip(client, access_token))] - pub async fn get_event( - client: &Client, - access_token: impl Into, - room_id: &RoomId, - event_id: &EventId, - ) -> Result> { - let mut tmp = (*client).clone(); - tmp.set_token(access_token)?; - - let resp = tmp - .get(format!( - "/_matrix/client/v3/rooms/{room_id}/event/{event_id}", - room_id = room_id, - event_id = event_id, - )) - .await?; - - Ok(resp.json().await?) - } - - #[instrument(skip(client, access_token))] - pub async fn get_messages( - client: &Client, - access_token: impl Into, - room_id: &RoomId, - query: GetMessagesQuery, - ) -> Result { - let mut tmp = (*client).clone(); - tmp.set_token(access_token)?; - - let resp = tmp - .get_query( - format!( - "/_matrix/client/v3/rooms/{room_id}/messages", - room_id = room_id, - ), - &query, - ) - .await?; - - Ok(resp.json().await?) - } - - #[instrument(skip(client, access_token))] - pub async fn get_state( - client: &Client, - access_token: impl Into, - room_id: &RoomId, - ) -> Result { - let mut tmp = (*client).clone(); - tmp.set_token(access_token)?; - - let resp = tmp - .get(format!( - "/_matrix/client/v3/rooms/{room_id}/state", - room_id = room_id, - )) - .await?; - - Ok(resp.json().await?) - } - - #[instrument(skip(client, access_token))] - pub async fn get_state_content( - client: &Client, - access_token: impl Into, - room_id: &RoomId, - event_type: StateEventType, - state_key: Option, - ) -> Result> { - let mut tmp = (*client).clone(); - tmp.set_token(access_token)?; - - let mut path = format!( - "/_matrix/client/v3/rooms/{room_id}/state/{event_type}", - room_id = room_id, - event_type = event_type, - ); - - if let Some(state_key) = state_key { - path.push_str(&format!("/{state_key}", state_key = state_key)) - } - - let resp = tmp.get(path).await?; - - Ok(resp.json().await?) - } - - #[instrument(skip(client, access_token))] - pub async fn get_relations( - client: &Client, - access_token: impl Into, - room_id: &RoomId, - event_id: &EventId, - rel_type: Option>, - event_type: Option, - query: GetRelationsQuery, - ) -> Result { - let mut tmp = (*client).clone(); - tmp.set_token(access_token)?; - - let mut path = format!( - "/_matrix/client/v3/rooms/{room_id}/relations/{event_id}", - room_id = room_id, - event_id = event_id, - ); - - if let Some(rel_type) = rel_type { - path.push_str(&format!( - "/{rel_type}", - rel_type = rel_type - .as_ref() - .map_or("m.in_reply_to".into(), ToString::to_string) - )); - - if let Some(event_type) = event_type { - path.push_str(&format!("/{event_type}", event_type = event_type)) - } - } - - let resp = tmp.get_query(path, &query).await?; - - Ok(resp.json().await?) - } - - #[instrument(skip(client, access_token, body))] - pub async fn send_message( - client: &Client, - access_token: impl Into, - room_id: &RoomId, - txn_id: OwnedTransactionId, - body: T, - ) -> Result { - let mut tmp = (*client).clone(); - tmp.set_token(access_token)?; - - let resp = tmp - .put_json( - format!( - "/_matrix/client/v3/rooms/{room_id}/send/{event_type}/{txn_id}", - room_id = room_id, - event_type = body.event_type(), - txn_id = txn_id, - ), - &body, - ) - .await?; - - if resp.status().is_success() { - return Ok(resp.json().await?); - } - - let error = resp.json::().await?; - - Err(anyhow::anyhow!(error.error)) - } - - #[instrument(skip(client, access_token, body))] - pub async fn send_state( - client: &Client, - access_token: impl Into, - room_id: &RoomId, - state_key: Option, - body: T, - ) -> Result { - let mut tmp = (*client).clone(); - tmp.set_token(access_token)?; - - let mut path = format!( - "/_matrix/client/v3/rooms/{room_id}/state/{event_type}", - room_id = room_id, - event_type = body.event_type(), - ); - - if let Some(state_key) = state_key { - path.push_str(&format!("/{state_key}", state_key = state_key)) - } - - let resp = tmp.put_json(path, &body).await?; - - if resp.status().is_success() { - return Ok(resp.json().await?); - } - - let error = resp.json::().await?; - - Err(anyhow::anyhow!(error.error)) - } - - #[instrument(skip(client, access_token, body))] - pub async fn send_redaction( - client: &Client, - access_token: impl Into, - room_id: &RoomId, - event_id: &EventId, - txn_id: OwnedTransactionId, - body: SendRedactionBody, - ) -> Result { - let mut tmp = (*client).clone(); - tmp.set_token(access_token)?; - - let resp = tmp - .put_json( - format!( - "/_matrix/client/v3/rooms/{room_id}/redact/{event_id}/{txn_id}", - room_id = room_id, - event_id = event_id, - txn_id = txn_id, - ), - &body, - ) - .await?; - - if resp.status().is_success() { - return Ok(resp.json().await?); - } - - let error = resp.json::().await?; - - Err(anyhow::anyhow!(error.error)) - } -} diff --git a/crates/matrix/src/client-backup/membership.rs.bk.bk b/crates/matrix/src/client-backup/membership.rs.bk.bk deleted file mode 100644 index 86c3778..0000000 --- a/crates/matrix/src/client-backup/membership.rs.bk.bk +++ /dev/null @@ -1,5 +0,0 @@ -pub mod ban; -pub mod join; -pub mod kick; -pub mod leave; -pub mod unban; diff --git a/crates/matrix/src/client-backup/mod.rs.bk.bk b/crates/matrix/src/client-backup/mod.rs.bk.bk deleted file mode 100644 index cc296e6..0000000 --- a/crates/matrix/src/client-backup/mod.rs.bk.bk +++ /dev/null @@ -1,10 +0,0 @@ -//! This module is the root of the client-server API. -//! -//! reference: https://spec.matrix.org/unstable/client-server-api - -pub mod rooms; -pub mod session; -pub mod membership; -pub mod uiaa; -pub mod sync; -pub mod account; diff --git a/crates/matrix/src/client-backup/mxc.rs.bk.bk.bk b/crates/matrix/src/client-backup/mxc.rs.bk.bk.bk deleted file mode 100644 index 7dc669a..0000000 --- a/crates/matrix/src/client-backup/mxc.rs.bk.bk.bk +++ /dev/null @@ -1,184 +0,0 @@ -use std::str::FromStr; - -use anyhow::Result; -use mime::Mime; -use ruma_common::{MxcUri, OwnedMxcUri}; -use serde::{de, Deserialize, Deserializer, Serialize}; -use tracing::instrument; - -use chrono::{serde::ts_microseconds_option, DateTime, Utc}; - -use crate::error::MatrixError; - -fn parse_mime_opt<'de, D>(d: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - Option::<&str>::deserialize(d)? - .map(::from_str) - .transpose() - .map_err(de::Error::custom) -} - -#[derive(Debug, Serialize)] -pub struct GetPreviewUrlQuery { - pub url: url::Url, - pub ts: DateTime, -} - -#[derive(Debug, Deserialize)] -pub struct CreateMxcUriResponse { - pub content_uri: String, - - #[serde(with = "ts_microseconds_option")] - pub unused_expires_at: Option>, -} - -#[derive(Debug, Deserialize)] -pub struct GetPreviewUrlResponse { - #[serde(rename = "matrix:image_size")] - pub image_size: Option, - - #[serde(rename = "og:description")] - pub description: Option, - - #[serde(rename = "og:image")] - pub image: Option, - - #[serde(rename = "og:image:height")] - pub height: Option, - - #[serde(rename = "og:image:width")] - pub width: Option, - - #[serde(rename = "og:image:type", deserialize_with = "parse_mime_opt")] - pub kind: Option, - - #[serde(rename = "og:title")] - pub title: Option, -} - -#[derive(Debug, Deserialize)] -pub struct GetConfigResponse { - #[serde(rename = "m.upload.size")] - pub upload_size: Option, -} - -#[derive(Debug, Serialize)] -pub enum ResizeMethod { - Crop, - Scale, -} - -pub struct MxcService; - -#[derive(Debug, Deserialize)] -pub struct MxcError { - #[serde(flatten)] - pub inner: MatrixError, - - pub retry_after_ms: u64, -} - -impl MxcService { - /// Creates a new `MxcUri`, independently of the content being uploaded - /// - /// Refer: https://spec.matrix.org/v1.9/client-server-api/#post_matrixmediav1create - #[instrument(skip(client, access_token))] - pub async fn create( - client: &crate::http::Client, - access_token: impl Into, - ) -> Result { - let mut tmp = (*client).clone(); - tmp.set_token(access_token)?; - - let resp = tmp.post("/_matrix/media/v1/create").await?; - - if resp.status().is_success() { - return Ok(resp.json().await?); - } - - let error = resp.json::().await?; - - Err(anyhow::anyhow!(error.inner.error)) - } - - /// Retrieve the configuration of the content repository - /// - /// Refer: https://spec.matrix.org/v1.9/client-server-api/#get_matrixmediav3config - #[instrument(skip(client, access_token))] - pub async fn get_config( - client: &crate::http::Client, - access_token: impl Into, - ) -> Result { - let mut tmp = (*client).clone(); - tmp.set_token(access_token)?; - - let resp = tmp.get("/_matrix/media/v3/config").await?; - - if resp.status().is_success() { - return Ok(resp.json().await?); - } - - let error = resp.json::().await?; - - Err(anyhow::anyhow!(error.inner.error)) - } - - /// Retrieve a URL to download content from the content repository, - /// optionally replacing the name of the file. - /// - /// Refer: https://spec.matrix.org/v1.9/client-server-api/#get_matrixmediav3downloadservernamemediaid - #[instrument(skip(client, access_token))] - pub async fn get_download_url( - client: &crate::http::Client, - access_token: impl Into, - mxc_uri: &MxcUri, - mut base_url: url::Url, - file_name: Option, - ) -> Result { - let mut tmp = (*client).clone(); - tmp.set_token(access_token)?; - - let (server_name, media_id) = mxc_uri.parts().unwrap(); - - let mut path = format!( - "/_matrix/media/v3/download/{server_name}/{media_id}", - server_name = server_name, - media_id = media_id, - ); - - if let Some(file_name) = file_name { - path.push_str(&format!("/{file_name}", file_name = file_name)) - } - - base_url.set_path(&path); - - Ok(base_url) - } - - /// - /// - /// Refer: https://spec.matrix.org/v1.9/client-server-api/#get_matrixmediav3preview_url - #[instrument(skip(client, access_token))] - pub async fn get_preview( - client: &crate::http::Client, - access_token: impl Into, - query: GetPreviewUrlQuery, - ) -> Result { - let mut tmp = (*client).clone(); - tmp.set_token(access_token)?; - - let resp = tmp - .get_query("/_matrix/media/v3/preview_url".to_string(), &query) - .await?; - - if resp.status().is_success() { - return Ok(resp.json().await?); - } - - let error = resp.json::().await?; - - Err(anyhow::anyhow!(error.inner.error)) - } -} diff --git a/crates/matrix/src/client-backup/rooms.rs.bk.bk b/crates/matrix/src/client-backup/rooms.rs.bk.bk deleted file mode 100644 index 2d4cdf8..0000000 --- a/crates/matrix/src/client-backup/rooms.rs.bk.bk +++ /dev/null @@ -1,6 +0,0 @@ -//! This module contains handlers to interact with rooms. -//! -//! reference: https://spec.matrix.org/unstable/client-server-api/#rooms - -pub mod create; -pub mod forget; diff --git a/crates/matrix/src/client-backup/session.rs.bk.bk.bk b/crates/matrix/src/client-backup/session.rs.bk.bk.bk deleted file mode 100644 index 6bceee3..0000000 --- a/crates/matrix/src/client-backup/session.rs.bk.bk.bk +++ /dev/null @@ -1,2 +0,0 @@ -pub mod create; -pub mod invalidate; diff --git a/crates/matrix/src/client-backup/sync.rs.bk.bk b/crates/matrix/src/client-backup/sync.rs.bk.bk deleted file mode 100644 index 906f9cf..0000000 --- a/crates/matrix/src/client-backup/sync.rs.bk.bk +++ /dev/null @@ -1,564 +0,0 @@ -//! This module contains handlers for getting and synchronizing events. -//! -//! reference: https://github.com/matrix-org/matrix-spec-proposals/pull/3575 - -use std::{collections::BTreeMap, time::Duration}; - -use ruma_common::{ - api::{request, response, Metadata}, - metadata, - serde::{deserialize_cow_str, duration::opt_ms, Raw}, - DeviceKeyAlgorithm, MilliSecondsSinceUnixEpoch, OwnedMxcUri, OwnedRoomId, OwnedUserId, RoomId, -}; -use ruma_events::{ - receipt::SyncReceiptEvent, typing::SyncTypingEvent, AnyGlobalAccountDataEvent, - AnyRoomAccountDataEvent, AnyStrippedStateEvent, AnySyncStateEvent, AnySyncTimelineEvent, - AnyToDeviceEvent, StateEventType, TimelineEventType, -}; -use serde::{self, de::Error as _, Deserialize, Serialize}; - -const METADATA: Metadata = metadata! { - method: POST, - rate_limited: false, - authentication: AccessToken, - history: { - unstable => "/_matrix/client/unstable/org.matrix.msc3575/sync", - // 1.4 => "/_matrix/client/v4/sync", - } -}; - -#[request(error = crate::Error)] -#[derive(Default)] -pub struct Request { - #[serde(skip_serializing_if = "Option::is_none")] - #[ruma_api(query)] - pub pos: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub delta_token: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub conn_id: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub txn_id: Option, - - #[serde(with = "opt_ms", default, skip_serializing_if = "Option::is_none")] - #[ruma_api(query)] - pub timeout: Option, - - #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] - pub lists: BTreeMap, - - #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] - pub room_subscriptions: BTreeMap, - - #[serde(default, skip_serializing_if = "<[_]>::is_empty")] - pub unsubscribe_rooms: Vec, - - #[serde(default, skip_serializing_if = "ExtensionsConfig::is_empty")] - pub extensions: ExtensionsConfig, -} - -#[response(error = crate::Error)] -pub struct Response { - #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")] - pub initial: bool, - - #[serde(skip_serializing_if = "Option::is_none")] - pub txn_id: Option, - - pub pos: String, - - #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] - pub lists: BTreeMap, - - #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] - pub rooms: BTreeMap, - - #[serde(default, skip_serializing_if = "Extensions::is_empty")] - pub extensions: Extensions, - - pub delta_token: Option, -} - -#[derive(Clone, Debug, Default, Deserialize, Serialize)] -pub struct UnreadNotificationsCount { - #[serde(skip_serializing_if = "Option::is_none")] - pub highlight_count: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub notification_count: Option, -} - -impl UnreadNotificationsCount { - pub fn is_empty(&self) -> bool { - self.highlight_count.is_none() && self.notification_count.is_none() - } -} - -#[derive(Clone, Debug, Default, Deserialize, Serialize)] -pub struct DeviceLists { - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub changed: Vec, - - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub left: Vec, -} - -impl DeviceLists { - pub fn is_empty(&self) -> bool { - self.changed.is_empty() && self.left.is_empty() - } -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize)] -pub struct SyncRequestListFilters { - #[serde(skip_serializing_if = "Option::is_none")] - pub is_dm: Option, - - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub spaces: Vec, - - #[serde(skip_serializing_if = "Option::is_none")] - pub is_encrypted: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub is_invite: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub is_tombstoned: Option, - - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub room_types: Vec, - - #[serde(default, skip_serializing_if = "<[_]>::is_empty")] - pub not_room_types: Vec, - - #[serde(skip_serializing_if = "Option::is_none")] - pub room_name_like: Option, - - #[serde(default, skip_serializing_if = "<[_]>::is_empty")] - pub tags: Vec, - - #[serde(default, skip_serializing_if = "<[_]>::is_empty")] - pub not_tags: Vec, - - #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")] - pub extensions: BTreeMap, -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize)] -pub struct SyncRequestList { - #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")] - pub slow_get_all_rooms: bool, - - pub ranges: Vec<(usize, usize)>, - - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub sort: Vec, - - #[serde(flatten)] - pub room_details: RoomDetailsConfig, - - #[serde(skip_serializing_if = "Option::is_none")] - pub include_old_rooms: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub filters: Option, - - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub bump_event_types: Vec, -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize)] -pub struct RoomDetailsConfig { - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub required_state: Vec<(StateEventType, String)>, - - #[serde(skip_serializing_if = "Option::is_none")] - pub timeline_limit: Option, -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize)] -pub struct IncludeOldRooms { - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub required_state: Vec<(StateEventType, String)>, - - #[serde(skip_serializing_if = "Option::is_none")] - pub timeline_limit: Option, -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize)] -pub struct RoomSubscription { - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub required_state: Vec<(StateEventType, String)>, - - #[serde(skip_serializing_if = "Option::is_none")] - pub timeline_limit: Option, -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -#[serde(rename_all = "UPPERCASE")] -pub enum SlidingOp { - Sync, - - Insert, - - Delete, - - Invalidate, -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct SyncList { - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub ops: Vec, - - pub count: usize, -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct SyncOp { - pub op: SlidingOp, - - pub range: Option<(usize, usize)>, - - pub index: Option, - - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub room_ids: Vec, - - pub room_id: Option, -} - -#[derive(Clone, Debug, Default, Deserialize, Serialize)] -pub struct SlidingSyncRoom { - #[serde(skip_serializing_if = "Option::is_none")] - pub name: Option, - - #[serde(default, skip_serializing_if = "Option::is_none")] - pub avatar: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub initial: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub is_dm: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub invite_state: Option>>, - - #[serde( - flatten, - default, - skip_serializing_if = "UnreadNotificationsCount::is_empty" - )] - pub unread_notifications: UnreadNotificationsCount, - - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub timeline: Vec>, - - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub required_state: Vec>, - - #[serde(skip_serializing_if = "Option::is_none")] - pub prev_batch: Option, - - #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")] - pub limited: bool, - - #[serde(skip_serializing_if = "Option::is_none")] - pub joined_count: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub invited_count: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub num_live: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub timestamp: Option, -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] -pub struct ExtensionsConfig { - #[serde(default, skip_serializing_if = "ToDeviceConfig::is_empty")] - pub to_device: ToDeviceConfig, - - #[serde(default, skip_serializing_if = "E2EEConfig::is_empty")] - pub e2ee: E2EEConfig, - - #[serde(default, skip_serializing_if = "AccountDataConfig::is_empty")] - pub account_data: AccountDataConfig, - - #[serde(default, skip_serializing_if = "ReceiptsConfig::is_empty")] - pub receipts: ReceiptsConfig, - - #[serde(default, skip_serializing_if = "TypingConfig::is_empty")] - pub typing: TypingConfig, - - #[serde(flatten)] - other: BTreeMap, -} - -impl ExtensionsConfig { - pub fn is_empty(&self) -> bool { - self.to_device.is_empty() - && self.e2ee.is_empty() - && self.account_data.is_empty() - && self.receipts.is_empty() - && self.typing.is_empty() - && self.other.is_empty() - } -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize)] -pub struct Extensions { - #[serde(skip_serializing_if = "Option::is_none")] - pub to_device: Option, - - #[serde(default, skip_serializing_if = "E2EE::is_empty")] - pub e2ee: E2EE, - - #[serde(default, skip_serializing_if = "AccountData::is_empty")] - pub account_data: AccountData, - - #[serde(default, skip_serializing_if = "Receipts::is_empty")] - pub receipts: Receipts, - - #[serde(default, skip_serializing_if = "Typing::is_empty")] - pub typing: Typing, -} - -impl Extensions { - pub fn is_empty(&self) -> bool { - self.to_device.is_none() - && self.e2ee.is_empty() - && self.account_data.is_empty() - && self.receipts.is_empty() - && self.typing.is_empty() - } -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] -pub struct ToDeviceConfig { - #[serde(skip_serializing_if = "Option::is_none")] - pub enabled: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub limit: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub since: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub lists: Option>, - - #[serde(skip_serializing_if = "Option::is_none")] - pub rooms: Option>, -} - -impl ToDeviceConfig { - pub fn is_empty(&self) -> bool { - self.enabled.is_none() && self.limit.is_none() && self.since.is_none() - } -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize)] -pub struct ToDevice { - pub next_batch: String, - - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub events: Vec>, -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] -pub struct E2EEConfig { - #[serde(skip_serializing_if = "Option::is_none")] - pub enabled: Option, -} - -impl E2EEConfig { - pub fn is_empty(&self) -> bool { - self.enabled.is_none() - } -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize)] -pub struct E2EE { - #[serde(default, skip_serializing_if = "DeviceLists::is_empty")] - pub device_lists: DeviceLists, - - #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] - pub device_one_time_keys_count: BTreeMap, - - #[serde(skip_serializing_if = "Option::is_none")] - pub device_unused_fallback_key_types: Option>, -} - -impl E2EE { - pub fn is_empty(&self) -> bool { - self.device_lists.is_empty() - && self.device_one_time_keys_count.is_empty() - && self.device_unused_fallback_key_types.is_none() - } -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] -pub struct AccountDataConfig { - #[serde(skip_serializing_if = "Option::is_none")] - pub enabled: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub lists: Option>, - - #[serde(skip_serializing_if = "Option::is_none")] - pub rooms: Option>, -} - -impl AccountDataConfig { - pub fn is_empty(&self) -> bool { - self.enabled.is_none() - } -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize)] -pub struct AccountData { - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub global: Vec>, - - #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] - pub rooms: BTreeMap>>, -} - -impl AccountData { - pub fn is_empty(&self) -> bool { - self.global.is_empty() && self.rooms.is_empty() - } -} - -#[derive(Clone, Debug, PartialEq)] -pub enum RoomReceiptConfig { - AllSubscribed, - - Room(OwnedRoomId), -} - -impl Serialize for RoomReceiptConfig { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - match self { - RoomReceiptConfig::AllSubscribed => serializer.serialize_str("*"), - RoomReceiptConfig::Room(r) => r.serialize(serializer), - } - } -} - -impl<'de> Deserialize<'de> for RoomReceiptConfig { - fn deserialize(deserializer: D) -> Result - where - D: serde::de::Deserializer<'de>, - { - match deserialize_cow_str(deserializer)?.as_ref() { - "*" => Ok(RoomReceiptConfig::AllSubscribed), - other => Ok(RoomReceiptConfig::Room( - RoomId::parse(other).map_err(D::Error::custom)?.to_owned(), - )), - } - } -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] -pub struct ReceiptsConfig { - #[serde(skip_serializing_if = "Option::is_none")] - pub enabled: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub lists: Option>, - - #[serde(skip_serializing_if = "Option::is_none")] - pub rooms: Option>, -} - -impl ReceiptsConfig { - pub fn is_empty(&self) -> bool { - self.enabled.is_none() - } -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize)] -pub struct Receipts { - #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] - pub rooms: BTreeMap>, -} - -impl Receipts { - pub fn is_empty(&self) -> bool { - self.rooms.is_empty() - } -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] -pub struct TypingConfig { - #[serde(skip_serializing_if = "Option::is_none")] - pub enabled: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub lists: Option>, - - #[serde(skip_serializing_if = "Option::is_none")] - pub rooms: Option>, -} - -impl TypingConfig { - pub fn is_empty(&self) -> bool { - self.enabled.is_none() - } -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize)] -pub struct Typing { - #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] - pub rooms: BTreeMap>, -} - -impl Typing { - pub fn is_empty(&self) -> bool { - self.rooms.is_empty() - } -} - -#[cfg(test)] -mod tests { - use ruma_common::owned_room_id; - - use super::RoomReceiptConfig; - - #[test] - fn serialize_room_receipt_config() { - let entry = RoomReceiptConfig::AllSubscribed; - assert_eq!(serde_json::to_string(&entry).unwrap().as_str(), r#""*""#); - - let entry = RoomReceiptConfig::Room(owned_room_id!("!n8f893n9:example.com")); - assert_eq!( - serde_json::to_string(&entry).unwrap().as_str(), - r#""!n8f893n9:example.com""# - ); - } - - #[test] - fn deserialize_room_receipt_config() { - assert_eq!( - serde_json::from_str::(r#""*""#).unwrap(), - RoomReceiptConfig::AllSubscribed - ); - - assert_eq!( - serde_json::from_str::(r#""!n8f893n9:example.com""#).unwrap(), - RoomReceiptConfig::Room(owned_room_id!("!n8f893n9:example.com")) - ); - } -} diff --git a/crates/matrix/src/client-backup/uiaa.rs.bk.bk b/crates/matrix/src/client-backup/uiaa.rs.bk.bk deleted file mode 100644 index c417d30..0000000 --- a/crates/matrix/src/client-backup/uiaa.rs.bk.bk +++ /dev/null @@ -1,164 +0,0 @@ -//! Module for [User-Interactive Authentication API][uiaa] types. -//! -//! [uiaa]: https://spec.matrix.org/latest/client-server-api/#user-interactive-authentication-api - -use ruma_common::{thirdparty::Medium, OwnedSessionId, OwnedUserId, UserId}; -use serde::{Deserialize, Serialize}; - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct UiaaResponse { - pub flows: Vec, - - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub completed: Vec, - - pub params: Box, - - #[serde(skip_serializing_if = "Option::is_none")] - pub session: Option, - // #[serde(flatten, skip_serializing_if = "Option::is_none")] - // pub auth_error: Option, -} - -/// Ordered list of stages required to complete authentication. -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct AuthFlow { - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub stages: Vec, -} - -impl AuthFlow { - pub fn new(stages: Vec) -> Self { - Self { stages } - } -} - -/// Information for one authentication stage. -#[derive(Clone, Debug, Deserialize, Serialize)] -#[non_exhaustive] -pub enum AuthType { - /// Password-based authentication (`m.login.password`). - #[serde(rename = "m.login.password")] - Password, - - /// Google ReCaptcha 2.0 authentication (`m.login.recaptcha`). - #[serde(rename = "m.login.recaptcha")] - ReCaptcha, - - /// Email-based authentication (`m.login.email.identity`). - #[serde(rename = "m.login.email.identity")] - EmailIdentity, - - /// Phone number-based authentication (`m.login.msisdn`). - #[serde(rename = "m.login.msisdn")] - Msisdn, - - /// SSO-based authentication (`m.login.sso`). - #[serde(rename = "m.login.sso")] - Sso, - - /// Dummy authentication (`m.login.dummy`). - #[serde(rename = "m.login.dummy")] - Dummy, - - /// Registration token-based authentication (`m.login.registration_token`). - #[serde(rename = "m.login.registration_token")] - RegistrationToken, -} - -#[derive(Clone, Debug, Serialize)] -#[non_exhaustive] -#[serde(untagged)] -pub enum AuthData { - // Password-based authentication (`m.login.password`). - Password(Password), - - // Google ReCaptcha 2.0 authentication (`m.login.recaptcha`). - // ReCaptcha(ReCaptcha), - - // Email-based authentication (`m.login.email.identity`). - // EmailIdentity(EmailIdentity), - - // Phone number-based authentication (`m.login.msisdn`). - // Msisdn(Msisdn), - - // Dummy authentication (`m.login.dummy`). - Dummy(Dummy), - // Registration token-based authentication (`m.login.registration_token`). - // RegistrationToken(RegistrationToken), - - // Fallback acknowledgement. - // FallbackAcknowledgement(FallbackAcknowledgement), -} - -impl AuthData { - fn kind(&self) -> AuthType { - match self { - AuthData::Password(_) => AuthType::Password, - AuthData::Dummy(_) => AuthType::Dummy, - } - } -} - -#[derive(Clone, Debug, Default, Deserialize, Serialize)] -#[serde(tag = "type", rename = "m.login.dummy")] -pub struct Dummy {} - -impl Dummy { - pub fn new() -> Self { - Self::default() - } -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -#[serde(tag = "type", rename = "m.login.password")] -pub struct Password { - identifier: UserIdentifier, - password: String, -} - -impl Password { - pub fn new>(user_id: impl Into, password: S) -> Self { - let user: &UserId = &user_id.into(); - - Self { - identifier: UserIdentifier::User { - user: user.localpart().to_owned(), - }, - password: password.into(), - } - } -} - -#[derive(Clone, Debug, Serialize)] -pub struct UiaaRequest { - session: Option, - - kind: AuthType, - - #[serde(flatten)] - data: AuthData, -} - -impl UiaaRequest { - pub fn new(data: AuthData, session: Option) -> Self { - Self { - session, - kind: data.kind(), - data, - } - } -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -#[serde(tag = "type")] -pub enum UserIdentifier { - #[serde(rename = "m.id.user")] - User { user: String }, - - #[serde(rename = "m.id.thirdparty")] - ThirdParty { medium: Medium, address: String }, - - #[serde(rename = "m.id.phone")] - Phone { country: String, phone: String }, -} diff --git a/crates/matrix/src/client/profile.rs b/crates/matrix/src/client/profile.rs index 58428f4..ae1dd05 100644 --- a/crates/matrix/src/client/profile.rs +++ b/crates/matrix/src/client/profile.rs @@ -1,2 +1,37 @@ pub mod avatar_url; -pub mod display_name; +pub mod displayname; + +use ruma_common::{ + api::{request, response, Metadata}, + metadata, OwnedMxcUri, OwnedUserId, +}; +use serde::{Deserialize, Serialize}; + +#[allow(dead_code)] +const METADATA: Metadata = metadata! { + method: GET, + rate_limited: false, + authentication: None, + history: { + unstable => "/_matrix/client/v3/profile/:user_id", + } +}; + +#[request(error = crate::Error)] +pub struct Request { + #[ruma_api(path)] + pub user_id: OwnedUserId, +} + +impl Request { + pub fn new(user_id: OwnedUserId) -> Self { + Self { user_id } + } +} + +#[response(error = crate::Error)] +#[derive(Deserialize, Serialize)] +pub struct Response { + pub displayname: Option, + pub avatar_url: Option, +} diff --git a/crates/matrix/src/client/profile/avatar_url.rs b/crates/matrix/src/client/profile/avatar_url.rs index 0e93baa..065935c 100644 --- a/crates/matrix/src/client/profile/avatar_url.rs +++ b/crates/matrix/src/client/profile/avatar_url.rs @@ -1,2 +1,37 @@ -pub mod get; -pub mod update; +use ruma_common::{ + api::{request, response, Metadata}, + metadata, OwnedMxcUri, OwnedUserId, +}; +use serde::{Deserialize, Serialize}; + +#[allow(dead_code)] +const METADATA: Metadata = metadata! { + method: PUT, + rate_limited: true, + authentication: AccessToken, + history: { + unstable => "/_matrix/client/v3/profile/:user_id/avatar_url", + } +}; + +#[request(error = crate::Error)] +#[derive(Serialize)] +pub struct Request { + #[ruma_api(path)] + pub user_id: OwnedUserId, + + pub avatar_url: OwnedMxcUri, +} + +impl Request { + pub fn new(user_id: OwnedUserId, avatar_url: OwnedMxcUri) -> Self { + Self { + user_id, + avatar_url, + } + } +} + +#[response(error = crate::Error)] +#[derive(Deserialize)] +pub struct Response {} diff --git a/crates/matrix/src/client/profile/avatar_url/get.rs b/crates/matrix/src/client/profile/avatar_url/get.rs deleted file mode 100644 index 1d18ad6..0000000 --- a/crates/matrix/src/client/profile/avatar_url/get.rs +++ /dev/null @@ -1,31 +0,0 @@ -use ruma_common::{ - api::{request, response, Metadata}, - metadata, OwnedMxcUri, OwnedUserId, -}; - -#[allow(dead_code)] -const METADATA: Metadata = metadata! { - method: GET, - rate_limited: false, - authentication: None, - history: { - unstable => "/_matrix/client/v3/profile/:user_id/avatar_url", - } -}; - -#[request(error = crate::Error)] -pub struct Request { - #[ruma_api(path)] - pub user_id: OwnedUserId, -} - -impl Request { - pub fn new(user_id: OwnedUserId) -> Self { - Self { user_id } - } -} - -#[response(error = crate::Error)] -pub struct Response { - pub avatar_url: OwnedMxcUri, -} diff --git a/crates/matrix/src/client/profile/avatar_url/update.rs b/crates/matrix/src/client/profile/avatar_url/update.rs deleted file mode 100644 index dfcbd13..0000000 --- a/crates/matrix/src/client/profile/avatar_url/update.rs +++ /dev/null @@ -1,36 +0,0 @@ -use ruma_common::{ - api::{request, response, Metadata}, - metadata, OwnedMxcUri, OwnedUserId, -}; -use serde::Serialize; - -#[allow(dead_code)] -const METADATA: Metadata = metadata! { - method: PUT, - rate_limited: true, - authentication: AccessToken, - history: { - unstable => "/_matrix/client/v3/profile/:user_id/avatar_url", - } -}; - -#[request(error = crate::Error)] -#[derive(Serialize)] -pub struct Request { - #[ruma_api(path)] - pub user_id: OwnedUserId, - - pub avatar_url: OwnedMxcUri, -} - -impl Request { - pub fn new(user_id: OwnedUserId, avatar_url: OwnedMxcUri) -> Self { - Self { - user_id, - avatar_url, - } - } -} - -#[response(error = crate::Error)] -pub struct Response {} diff --git a/crates/matrix/src/client/profile/display_name.rs b/crates/matrix/src/client/profile/display_name.rs deleted file mode 100644 index 0e93baa..0000000 --- a/crates/matrix/src/client/profile/display_name.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod get; -pub mod update; diff --git a/crates/matrix/src/client/profile/display_name/get.rs b/crates/matrix/src/client/profile/display_name/get.rs deleted file mode 100644 index 7ce9d9a..0000000 --- a/crates/matrix/src/client/profile/display_name/get.rs +++ /dev/null @@ -1,32 +0,0 @@ -use ruma_common::{ - api::{request, response, Metadata}, - metadata, OwnedUserId, -}; - -#[allow(dead_code)] -const METADATA: Metadata = metadata! { - method: GET, - rate_limited: false, - authentication: None, - history: { - unstable => "/_matrix/client/v3/profile/:user_id/displayname", - } -}; - -#[request(error = crate::Error)] -pub struct Request { - #[ruma_api(path)] - pub user_id: OwnedUserId, -} - -impl Request { - pub fn new(user_id: OwnedUserId) -> Self { - Self { user_id } - } -} - -#[response(error = crate::Error)] -pub struct Response { - #[serde(rename = "displayname")] - pub display_name: String, -} diff --git a/crates/matrix/src/client/profile/display_name/update.rs b/crates/matrix/src/client/profile/displayname.rs similarity index 100% rename from crates/matrix/src/client/profile/display_name/update.rs rename to crates/matrix/src/client/profile/displayname.rs diff --git a/crates/router/src/api.rs b/crates/router/src/api.rs index 21a185e..c16870e 100644 --- a/crates/router/src/api.rs +++ b/crates/router/src/api.rs @@ -1,10 +1,11 @@ -//! This module is the root of the client-server API. +//! This module is the root of Commune's client-server API. //! //! reference: https://spec.matrix.org/unstable/client-server-api pub mod account; pub mod direct; pub mod membership; +pub mod profile; pub mod register; pub mod relative; pub mod spaces; diff --git a/crates/router/src/api/account.rs b/crates/router/src/api/account.rs index 080944c..921a3ab 100644 --- a/crates/router/src/api/account.rs +++ b/crates/router/src/api/account.rs @@ -1,5 +1,3 @@ -pub mod avatar; -pub mod display_name; pub mod email; pub mod password; pub mod whoami; diff --git a/crates/router/src/api/membership/join.rs b/crates/router/src/api/membership/join.rs index 1b38b71..d166799 100644 --- a/crates/router/src/api/membership/join.rs +++ b/crates/router/src/api/membership/join.rs @@ -7,15 +7,15 @@ use axum_extra::{ headers::{authorization::Bearer, Authorization}, TypedHeader, }; -use matrix::ruma_common::OwnedRoomOrAliasId; +use commune::util::opaque_id::OpaqueId; pub async fn handler( TypedHeader(access_token): TypedHeader>, - Path(room_or_alias_id): Path, + Path(space_id): Path, ) -> Response { use commune::membership::join::service; - match service(access_token.token(), room_or_alias_id, None).await { + match service(access_token.token(), space_id, None).await { Ok(resp) => Json(resp).into_response(), Err(e) => { tracing::warn!(?e, "failed to join space"); diff --git a/crates/router/src/api/profile.rs b/crates/router/src/api/profile.rs new file mode 100644 index 0000000..fe7f537 --- /dev/null +++ b/crates/router/src/api/profile.rs @@ -0,0 +1,22 @@ +pub mod avatar_url; +pub mod displayname; + +use axum::{ + extract::Path, + response::{IntoResponse, Response}, + Json, +}; +use matrix::ruma_common::OwnedUserId; + +pub async fn handler(Path(user_id): Path) -> Response { + use commune::profile::service; + + match service(&*user_id).await { + Ok(resp) => Json(resp).into_response(), + Err(e) => { + tracing::warn!(?e, "failed to retrieve profile of {user_id}"); + + e.into_response() + } + } +} diff --git a/crates/router/src/api/account/avatar.rs b/crates/router/src/api/profile/avatar_url.rs similarity index 67% rename from crates/router/src/api/account/avatar.rs rename to crates/router/src/api/profile/avatar_url.rs index 7e759d2..3b606cc 100644 --- a/crates/router/src/api/account/avatar.rs +++ b/crates/router/src/api/profile/avatar_url.rs @@ -9,19 +9,19 @@ use axum_extra::{ use matrix::ruma_common::OwnedMxcUri; use serde::{Deserialize, Serialize}; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Deserialize, Serialize)] pub struct Payload { - pub mxc_uri: OwnedMxcUri, + pub avatar_url: OwnedMxcUri, } pub async fn handler( TypedHeader(access_token): TypedHeader>, Json(payload): Json, ) -> Response { - use commune::profile::avatar::update::service; + use commune::profile::avatar_url::service; - match service(access_token.token(), payload.mxc_uri).await { - Ok(_) => ().into_response(), + match service(access_token.token(), payload.avatar_url).await { + Ok(_) => Json(crate::EmptyBody {}).into_response(), Err(e) => { tracing::warn!(?e, "failed to update avatar"); diff --git a/crates/router/src/api/account/display_name.rs b/crates/router/src/api/profile/displayname.rs similarity index 63% rename from crates/router/src/api/account/display_name.rs rename to crates/router/src/api/profile/displayname.rs index e774436..475bb06 100644 --- a/crates/router/src/api/account/display_name.rs +++ b/crates/router/src/api/profile/displayname.rs @@ -10,19 +10,19 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Deserialize, Serialize)] pub struct Payload { - pub display_name: String, + pub displayname: String, } pub async fn handler( TypedHeader(access_token): TypedHeader>, Json(payload): Json, ) -> Response { - use commune::profile::avatar::update::service; + use commune::profile::displayname::service; - match service(access_token.token(), payload.display_name).await { - Ok(_) => ().into_response(), + match service(access_token.token(), payload.displayname).await { + Ok(_) => Json(crate::EmptyBody {}).into_response(), Err(e) => { - tracing::warn!(?e, "failed to update display name"); + tracing::warn!(?e, "failed to update displayname"); e.into_response() } diff --git a/crates/router/src/api/profile/root.rs b/crates/router/src/api/profile/root.rs new file mode 100644 index 0000000..5c32d0b --- /dev/null +++ b/crates/router/src/api/profile/root.rs @@ -0,0 +1,19 @@ +use axum::{ + extract::Path, + response::{IntoResponse, Response}, + Json, +}; +use matrix::ruma_common::OwnedUserId; + +pub async fn handler(Path(user_id): Path) -> Response { + use commune::profile::service; + + match service(&*user_id).await { + Ok(resp) => Json(resp).into_response(), + Err(e) => { + tracing::warn!(?e, "failed to retrieve profile of {user_id}"); + + e.into_response() + } + } +} diff --git a/crates/router/src/lib.rs b/crates/router/src/lib.rs index b099914..f230536 100644 --- a/crates/router/src/lib.rs +++ b/crates/router/src/lib.rs @@ -4,10 +4,14 @@ use axum::{ routing::{get, post, put}, Router, }; +use serde::Serialize; use tokio::net::TcpListener; pub mod api; +#[derive(Serialize)] +pub struct EmptyBody {} + pub async fn routes() -> Router { let router = Router::new() .route("/login", post(api::relative::login::handler)) @@ -27,8 +31,13 @@ pub async fn routes() -> Router { Router::new() .route("/whoami", get(api::account::whoami::handler)) .route("/password", put(api::account::password::handler)) - .route("/display_name", put(api::account::display_name::handler)) - .route("/avatar", put(api::account::avatar::handler)), + ) + .nest( + "/profile", + Router::new() + .route("/:user_id", get(api::profile::handler)) + .route("/displayname", put(api::profile::displayname::handler)) + .route("/avatar_url", put(api::profile::avatar_url::handler)), ) .nest( "/direct", @@ -42,7 +51,11 @@ pub async fn routes() -> Router { "/:space_id/channels", post(api::spaces::channels::create::handler), ), - ); + ) + // .nest("/join", + // Router::new().route("/join/:space_id", put(api::membership::join)), + // ) + ; Router::new().nest("/_commune/client/r0", router) } diff --git a/crates/test/Cargo.toml b/crates/test/Cargo.toml index a9caff2..0e59c7d 100644 --- a/crates/test/Cargo.toml +++ b/crates/test/Cargo.toml @@ -21,6 +21,7 @@ rand = { workspace = true } tracing = { workspace = true } tracing-subscriber = { workspace = true } base64 = { workspace = true } +serde_json = { workspace = true } # Local Dependencies core = { path = "../core" } diff --git a/crates/test/src/api.rs b/crates/test/src/api.rs index 893e4de..b8a4db4 100644 --- a/crates/test/src/api.rs +++ b/crates/test/src/api.rs @@ -3,6 +3,7 @@ //! reference: https://spec.matrix.org/unstable/client-server-api pub mod account; +pub mod profile; pub mod relative; pub mod rooms; pub mod spaces; diff --git a/crates/test/src/api/account.rs b/crates/test/src/api/account.rs index 080944c..921a3ab 100644 --- a/crates/test/src/api/account.rs +++ b/crates/test/src/api/account.rs @@ -1,5 +1,3 @@ -pub mod avatar; -pub mod display_name; pub mod email; pub mod password; pub mod whoami; diff --git a/crates/test/src/api/account/avatar.rs b/crates/test/src/api/account/avatar.rs deleted file mode 100644 index 030b696..0000000 --- a/crates/test/src/api/account/avatar.rs +++ /dev/null @@ -1,35 +0,0 @@ -use matrix::ruma_common::OwnedMxcUri; -use router::api::account::avatar::Payload; - -use crate::{api::relative::login, env::Env}; - -pub async fn update_avatar(client: &Env) -> Result { - let login_resp = login::login(&client).await.unwrap(); - - tracing::info!(?login_resp); - - let resp = client - .put("/_commune/client/r0/account/avatar") - .json(&Payload { - mxc_uri: OwnedMxcUri::try_from("mxc://example.org/SEsfnsuifSDFSSEF").unwrap(), - }) - .header( - reqwest::header::AUTHORIZATION, - format!("Bearer {}", &login_resp.access_token), - ) - .send() - .await?; - - Ok(resp.status().is_success()) -} - -#[tokio::test] -async fn update_avatar_test() { - let client = Env::new().await; - - let resp = update_avatar(&client).await.unwrap(); - - tracing::info!(?resp); - - assert_eq!(resp, true); -} diff --git a/crates/test/src/api/account/avatar_url.rs b/crates/test/src/api/account/avatar_url.rs new file mode 100644 index 0000000..46066b6 --- /dev/null +++ b/crates/test/src/api/account/avatar_url.rs @@ -0,0 +1,65 @@ +use matrix::{ + client::{login, profile::avatar_url::get}, + ruma_common::OwnedMxcUri, +}; +use url::form_urlencoded::byte_serialize; + +use crate::env::Env; + +pub async fn update_avatar( + client: &Env, + login_resp: login::Response, +) -> Result<(Payload, reqwest::Response), reqwest::Error> { + let req = Payload { + mxc_uri: OwnedMxcUri::try_from("mxc://example.org/SEsfnsuifSDFSSEF").unwrap(), + }; + let resp = client + .put("/_commune/client/r0/account/avatar") + .json(&req) + .header( + reqwest::header::AUTHORIZATION, + format!("Bearer {}", &login_resp.access_token), + ) + .send() + .await + .map_err(Into::into)?; + + Ok((req, resp)) +} + +pub async fn get_avatar( + client: &Env, + login_resp: login::Response, +) -> Result<((), get::Response), reqwest::Error> { + let resp = client + .get(&format!( + "/_commune/client/r0/account/{}/avatar", + byte_serialize(login_resp.user_id.as_bytes()).collect::() + )) + .header( + reqwest::header::AUTHORIZATION, + format!("Bearer {}", &login_resp.access_token), + ) + .send() + .await?; + + tracing::info!(?resp); + + Ok(((), resp.json().await?)) +} + +#[tokio::test] +async fn avatar_test() { + let client = Env::new().await; + + let login_resp = crate::api::relative::login::login(&client).await.unwrap(); + tracing::info!(?login_resp); + + let (update_req, update_resp) = update_avatar(&client, login_resp.clone()).await.unwrap(); + tracing::info!(?update_resp); + + let (_, get_resp) = get_avatar(&client, login_resp.clone()).await.unwrap(); + tracing::info!(?get_resp); + + assert_eq!(update_req.mxc_uri, get_resp.avatar_url); +} diff --git a/crates/test/src/api/profile.rs b/crates/test/src/api/profile.rs new file mode 100644 index 0000000..5a04b4d --- /dev/null +++ b/crates/test/src/api/profile.rs @@ -0,0 +1,22 @@ +pub mod avatar_url; +pub mod displayname; + +use matrix::client::profile::Response; +use url::form_urlencoded::byte_serialize; + +use crate::env::Env; + +pub async fn get_profile( + client: &Env, + login_resp: &matrix::client::login::Response, +) -> Result { + let resp = client + .get(&format!( + "/_commune/client/r0/profile/{}", + byte_serialize(login_resp.user_id.as_bytes()).collect::(), + )) + .send() + .await?; + + resp.json().await +} diff --git a/crates/test/src/api/profile/avatar_url.rs b/crates/test/src/api/profile/avatar_url.rs new file mode 100644 index 0000000..2969086 --- /dev/null +++ b/crates/test/src/api/profile/avatar_url.rs @@ -0,0 +1,43 @@ +use matrix::{client::login, ruma_common::OwnedMxcUri}; +use router::api::profile::avatar_url::Payload; + +use crate::{api::profile::get_profile, env::Env}; + +pub async fn update_avatar_url( + client: &Env, + login_resp: &login::Response, +) -> Result { + let req = Payload { + avatar_url: OwnedMxcUri::try_from("mxc://example.org/SEsfnsuifSDFSSEF").unwrap(), + }; + + client + .put("/_commune/client/r0/profile/avatar_url") + .json(&req) + .header( + reqwest::header::AUTHORIZATION, + format!("Bearer {}", &login_resp.access_token), + ) + .send() + .await + .map_err(Into::into) +} + +#[tokio::test] +async fn update_avatar_url_test() { + let client = Env::new().await; + + let login_resp = crate::api::relative::login::login(&client).await.unwrap(); + tracing::info!(?login_resp); + + let update_resp = update_avatar_url(&client, &login_resp).await.unwrap(); + tracing::info!(?update_resp); + + let profile_resp = get_profile(&client, &login_resp).await.unwrap(); + tracing::info!(?profile_resp); + + assert_eq!( + profile_resp.avatar_url, + Some(OwnedMxcUri::try_from("mxc://example.org/SEsfnsuifSDFSSEF").unwrap()) + ); +} diff --git a/crates/test/src/api/profile/displayname.rs b/crates/test/src/api/profile/displayname.rs new file mode 100644 index 0000000..39d92df --- /dev/null +++ b/crates/test/src/api/profile/displayname.rs @@ -0,0 +1,43 @@ +use matrix::client::login; +use router::api::profile::displayname::Payload; + +use crate::{api::profile::get_profile, env::Env}; + +pub async fn update_displayname( + client: &Env, + login_resp: &login::Response, +) -> Result { + let req = Payload { + displayname: "Some displayname".to_owned(), + }; + + client + .put("/_commune/client/r0/profile/displayname") + .json(&req) + .header( + reqwest::header::AUTHORIZATION, + format!("Bearer {}", &login_resp.access_token), + ) + .send() + .await + .map_err(Into::into) +} + +#[tokio::test] +async fn update_displayname_test() { + let client = Env::new().await; + + let login_resp = crate::api::relative::login::login(&client).await.unwrap(); + tracing::info!(?login_resp); + + let update_resp = update_displayname(&client, &login_resp).await.unwrap(); + tracing::info!(?update_resp); + + let profile_resp = get_profile(&client, &login_resp).await.unwrap(); + tracing::info!(?profile_resp); + + assert_eq!( + profile_resp.displayname, + Some("Some displayname".to_owned()) + ); +} diff --git a/crates/test/src/api/relative/register.rs b/crates/test/src/api/relative/register.rs index 0905007..dc4951d 100644 --- a/crates/test/src/api/relative/register.rs +++ b/crates/test/src/api/relative/register.rs @@ -1,6 +1,6 @@ use commune::util::secret::Secret; -use matrix::{client::register::root::*, ClientError}; +use matrix::client::register::root::Response; use router::api::register::root as register; use crate::{env::Env, util::generate_comforming_localpart}; diff --git a/crates/test/src/util.rs b/crates/test/src/util.rs index d69ac35..77bf783 100644 --- a/crates/test/src/util.rs +++ b/crates/test/src/util.rs @@ -1,11 +1,14 @@ -use rand::seq::IteratorRandom; +use rand::{distributions::Alphanumeric, Rng}; pub fn generate_comforming_localpart() -> String { - let allowed = ('0'..='9') - .chain('a'..='z') - .chain(['-', '.', '=', '_', '/', '+']); - allowed - .choose_multiple(&mut rand::thread_rng(), 8) - .into_iter() - .collect() + // Synapse does not allow usernames to start with '_' despite + // the specification doing so. + + let s: String = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(32) + .map(char::from) // From link above, this is needed in later versions + .collect(); + + s.to_lowercase() }