From 962143c95d749cb2dbbb9bf57dd6ec210dade448 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Dom=C3=ADnguez?= Date: Fri, 1 Feb 2019 23:01:06 +0100 Subject: [PATCH 1/4] API, directory: Separate endpoint connection from query build --- fractal-gtk/src/appop/directory.rs | 2 +- fractal-matrix-api/src/backend/directory.rs | 178 ++++++++++-------- fractal-matrix-api/src/backend/mod.rs | 4 +- fractal-matrix-api/src/backend/types.rs | 2 +- fractal-matrix-api/src/lib.rs | 2 + fractal-matrix-api/src/meson.build | 7 +- fractal-matrix-api/src/model/mod.rs | 1 - fractal-matrix-api/src/model/room.rs | 56 +----- fractal-matrix-api/src/r0.rs | 2 + fractal-matrix-api/src/r0/directory.rs | 1 + .../src/r0/directory/post_public_rooms.rs | 80 ++++++++ fractal-matrix-api/src/r0/thirdparty.rs | 1 + .../thirdparty/get_supported_protocols.rs} | 22 ++- fractal-matrix-api/src/ser.rs | 12 ++ fractal-matrix-api/src/types.rs | 6 - fractal-matrix-api/src/util.rs | 2 +- 16 files changed, 228 insertions(+), 150 deletions(-) create mode 100644 fractal-matrix-api/src/r0.rs create mode 100644 fractal-matrix-api/src/r0/directory.rs create mode 100644 fractal-matrix-api/src/r0/directory/post_public_rooms.rs create mode 100644 fractal-matrix-api/src/r0/thirdparty.rs rename fractal-matrix-api/src/{model/protocol.rs => r0/thirdparty/get_supported_protocols.rs} (57%) create mode 100644 fractal-matrix-api/src/ser.rs diff --git a/fractal-gtk/src/appop/directory.rs b/fractal-gtk/src/appop/directory.rs index 733fd2eff..79f982d0d 100644 --- a/fractal-gtk/src/appop/directory.rs +++ b/fractal-gtk/src/appop/directory.rs @@ -7,8 +7,8 @@ use crate::appop::AppOp; use crate::backend::BKCommand; use crate::widgets; -use crate::types::ProtocolInstance; use crate::types::Room; +use fractal_api::r0::thirdparty::get_supported_protocols::ProtocolInstance; impl AppOp { pub fn init_protocols(&self) { diff --git a/fractal-matrix-api/src/backend/directory.rs b/fractal-matrix-api/src/backend/directory.rs index 99ce826ef..b54b8cdab 100644 --- a/fractal-matrix-api/src/backend/directory.rs +++ b/fractal-matrix-api/src/backend/directory.rs @@ -1,6 +1,4 @@ -use serde_json::json; -use serde_json::Value as JsonValue; -use url::Url; +use url::{Host, Url}; use crate::globals; @@ -10,45 +8,52 @@ use crate::error::Error; use std::thread; use crate::util::cache_path; -use crate::util::json_q; use crate::util::media; - -use crate::types::PublicRoomsFilter; -use crate::types::PublicRoomsRequest; -use crate::types::PublicRoomsResponse; +use crate::util::HTTP_CLIENT; + +use crate::r0::directory::post_public_rooms::request as post_public_rooms; +use crate::r0::directory::post_public_rooms::Body as PublicRoomsBody; +use crate::r0::directory::post_public_rooms::Filter as PublicRoomsFilter; +use crate::r0::directory::post_public_rooms::Parameters as PublicRoomsParameters; +use crate::r0::directory::post_public_rooms::Response as PublicRoomsResponse; +use crate::r0::directory::post_public_rooms::ThirdPartyNetworks; +use crate::r0::thirdparty::get_supported_protocols::request as get_supported_protocols; +use crate::r0::thirdparty::get_supported_protocols::Parameters as SupportedProtocolsParameters; +use crate::r0::thirdparty::get_supported_protocols::Response as SupportedProtocolsResponse; use crate::types::Room; -use crate::types::SupportedProtocols; -use crate::types::ThirdPartyNetworks; pub fn protocols(bk: &Backend) { - let baseu = bk.get_base_url(); - let tk = bk.data.lock().unwrap().access_token.clone(); - let mut url = baseu - .join("/_matrix/client/r0/thirdparty/protocols") - .expect("Wrong URL in protocols()"); - url.query_pairs_mut() - .clear() - .append_pair("access_token", &tk); - let tx = bk.tx.clone(); - get!( - &url, - move |r: JsonValue| { - let protocols = serde_json::from_value(r) - .map(|protocols: SupportedProtocols| { - protocols - .into_iter() - .flat_map(|(_, protocol)| protocol.instances.into_iter()) - .collect() - }) - .unwrap_or_default(); - - tx.send(BKResponse::DirectoryProtocols(protocols)).unwrap(); - }, - |err| { - tx.send(BKResponse::DirectoryError(err)).unwrap(); + let access_token = bk.data.lock().unwrap().access_token.clone(); + + let base = bk.get_base_url(); + let params = SupportedProtocolsParameters { access_token }; + + thread::spawn(move || { + let query = get_supported_protocols(base, ¶ms) + .map_err(Into::into) + .and_then(|request| { + HTTP_CLIENT + .get_client()? + .execute(request)? + .json::() + .map_err(Into::into) + }); + + match query { + Ok(response) => { + let protocols = response + .into_iter() + .flat_map(|(_, protocol)| protocol.instances.into_iter()) + .collect(); + + let _ = tx.send(BKResponse::DirectoryProtocols(protocols)); + } + Err(err) => { + let _ = tx.send(BKResponse::DirectoryError(err)); + } } - ); + }); } pub fn room_search( @@ -58,69 +63,84 @@ pub fn room_search( third_party: Option, more: bool, ) -> Result<(), Error> { - let mut params: Vec<(&str, String)> = Vec::new(); - - if let Some(mut hs) = homeserver { - // Extract the hostname if `homeserver` is an URL - if let Ok(homeserver_url) = Url::parse(&hs) { - hs = homeserver_url.host_str().unwrap_or_default().to_string(); - } + let tx = bk.tx.clone(); + let data = bk.data.clone(); - params.push(("server", hs)); - } + // TODO: use transpose() when it is stabilized + let server = homeserver + .map(|hs| { + Url::parse(&hs) + .ok() + .as_ref() + .and_then(Url::host) + .as_ref() + .map(Host::to_owned) + .map(Ok) + .unwrap_or(Host::parse(&hs)) + .map(Some) + }) + .unwrap_or(Ok(None))?; - let url = bk.url("publicRooms", params)?; let base = bk.get_base_url(); + let access_token = data.lock().unwrap().access_token.clone(); let since = if more { - Some(bk.data.lock().unwrap().rooms_since.clone()) + Some(data.lock().unwrap().rooms_since.clone()) } else { None }; - let request = PublicRoomsRequest { + let params = PublicRoomsParameters { + access_token, + server, + }; + + let body = PublicRoomsBody { limit: Some(globals::ROOM_DIRECTORY_LIMIT), filter: Some(PublicRoomsFilter { generic_search_term, }), since, third_party_networks: third_party - .map(|tp| ThirdPartyNetworks::Only(tp)) + .map(ThirdPartyNetworks::Only) .unwrap_or_default(), }; - let attrs = serde_json::to_value(request).expect("Failed to serialize the search request"); - - let tx = bk.tx.clone(); - let data = bk.data.clone(); - post!( - &url, - &attrs, - move |r: JsonValue| { - let rooms = serde_json::from_value(r) - .map(|pr: PublicRoomsResponse| { - data.lock().unwrap().rooms_since = pr.next_batch.unwrap_or_default(); - - pr.chunk - .into_iter() - .map(Into::into) - .inspect(|r: &Room| { - if let Some(avatar) = r.avatar.clone() { - if let Ok(dest) = cache_path(&r.id) { - media(&base.clone(), &avatar, Some(&dest)).unwrap_or_default(); - } + thread::spawn(move || { + let query = post_public_rooms(base.clone(), ¶ms, &body) + .map_err(Into::into) + .and_then(|request| { + HTTP_CLIENT + .get_client()? + .execute(request)? + .json::() + .map_err(Into::into) + }); + + match query { + Ok(response) => { + data.lock().unwrap().rooms_since = response.next_batch.unwrap_or_default(); + + let rooms = response + .chunk + .into_iter() + .map(Into::into) + .inspect(|r: &Room| { + if let Some(avatar) = r.avatar.clone() { + if let Ok(dest) = cache_path(&r.id) { + let _ = media(&base, &avatar, Some(&dest)); } - }) - .collect() - }) - .unwrap_or_default(); - - tx.send(BKResponse::DirectorySearch(rooms)).unwrap(); - }, - |err| { - tx.send(BKResponse::DirectoryError(err)).unwrap(); + } + }) + .collect(); + + let _ = tx.send(BKResponse::DirectorySearch(rooms)); + } + Err(err) => { + let _ = tx.send(BKResponse::DirectoryError(err)); + } } - ); + }); Ok(()) } diff --git a/fractal-matrix-api/src/backend/mod.rs b/fractal-matrix-api/src/backend/mod.rs index 78137ae76..39c6c80da 100644 --- a/fractal-matrix-api/src/backend/mod.rs +++ b/fractal-matrix-api/src/backend/mod.rs @@ -303,9 +303,7 @@ impl Backend { } // Directory module - Ok(BKCommand::DirectoryProtocols) => { - directory::protocols(self); - } + Ok(BKCommand::DirectoryProtocols) => directory::protocols(self), Ok(BKCommand::DirectorySearch(dhs, dq, dtp, more)) => { let hs = match dhs { ref a if a.is_empty() => None, diff --git a/fractal-matrix-api/src/backend/types.rs b/fractal-matrix-api/src/backend/types.rs index 76855c360..2b7f6704b 100644 --- a/fractal-matrix-api/src/backend/types.rs +++ b/fractal-matrix-api/src/backend/types.rs @@ -4,11 +4,11 @@ use std::sync::{Arc, Condvar, Mutex}; use crate::error::Error; +use crate::r0::thirdparty::get_supported_protocols::ProtocolInstance; use crate::types::Event; use crate::types::Medium; use crate::types::Member; use crate::types::Message; -use crate::types::ProtocolInstance; use crate::types::Room; use crate::types::Sticker; use crate::types::StickerGroup; diff --git a/fractal-matrix-api/src/lib.rs b/fractal-matrix-api/src/lib.rs index a302385b2..8f600d45c 100644 --- a/fractal-matrix-api/src/lib.rs +++ b/fractal-matrix-api/src/lib.rs @@ -7,6 +7,8 @@ pub mod backend; pub mod cache; mod client; mod model; +pub mod r0; +mod ser; pub mod types; #[cfg(test)] diff --git a/fractal-matrix-api/src/meson.build b/fractal-matrix-api/src/meson.build index 8e6eea475..958b39b78 100644 --- a/fractal-matrix-api/src/meson.build +++ b/fractal-matrix-api/src/meson.build @@ -14,17 +14,22 @@ api_sources = files( 'model/member.rs', 'model/message.rs', 'model/mod.rs', - 'model/protocol.rs', 'model/register.rs', 'model/room.rs', 'model/stickers.rs', 'model/sync.rs', 'model/user.rs', + 'r0/directory/post_public_rooms.rs', + 'r0/thirdparty/get_supported_protocols.rs', + 'r0/directory.rs', + 'r0/thirdparty.rs', 'cache.rs', 'client.rs', 'error.rs', 'globals.rs', 'lib.rs', + 'r0.rs', + 'ser.rs', 'types.rs', 'util.rs' ) diff --git a/fractal-matrix-api/src/model/mod.rs b/fractal-matrix-api/src/model/mod.rs index 758a0e278..0a005e21f 100644 --- a/fractal-matrix-api/src/model/mod.rs +++ b/fractal-matrix-api/src/model/mod.rs @@ -3,7 +3,6 @@ pub mod fileinfo; pub mod filter; pub mod member; pub mod message; -pub mod protocol; pub mod register; pub mod room; pub mod stickers; diff --git a/fractal-matrix-api/src/model/room.rs b/fractal-matrix-api/src/model/room.rs index 6c30afaf0..6e52517f7 100644 --- a/fractal-matrix-api/src/model/room.rs +++ b/fractal-matrix-api/src/model/room.rs @@ -3,6 +3,7 @@ use serde_json::Value as JsonValue; use crate::model::member::Member; use crate::model::member::MemberList; use crate::model::message::Message; +use crate::r0::directory::post_public_rooms::Chunk as PublicRoomsChunk; use crate::types::SyncResponse; use crate::util::get_user_avatar; use crate::util::parse_m_direct; @@ -297,61 +298,6 @@ impl PartialEq for Room { pub type RoomList = HashMap; -#[derive(Clone, Debug, Serialize)] -pub struct PublicRoomsRequest { - #[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 filter: Option, - #[serde(flatten)] - pub third_party_networks: ThirdPartyNetworks, -} - -#[derive(Clone, Debug, Serialize)] -pub struct PublicRoomsFilter { - pub generic_search_term: Option, -} - -#[derive(Clone, Debug, Serialize)] -#[serde(tag = "include_all_networks", content = "third_party_instance_id")] -pub enum ThirdPartyNetworks { - #[serde(rename = "false")] - None, - #[serde(rename = "false")] - Only(String), - #[serde(rename = "true")] - All, -} - -impl Default for ThirdPartyNetworks { - fn default() -> Self { - ThirdPartyNetworks::None - } -} - -#[derive(Clone, Debug, Deserialize)] -pub struct PublicRoomsResponse { - pub chunk: Vec, - pub next_batch: Option, - pub prev_batch: Option, - pub total_room_count_estimate: Option, -} - -#[derive(Clone, Debug, Deserialize)] -pub struct PublicRoomsChunk { - pub aliases: Option>, - pub avatar_url: Option, - pub canonical_alias: Option, - pub guest_can_join: bool, - pub name: Option, - pub num_joined_members: i32, - pub room_id: String, - pub topic: Option, - pub world_readable: bool, -} - fn evc(events: &Vec, t: &str, field: &str) -> Option { events .iter() diff --git a/fractal-matrix-api/src/r0.rs b/fractal-matrix-api/src/r0.rs new file mode 100644 index 000000000..d87d88dfe --- /dev/null +++ b/fractal-matrix-api/src/r0.rs @@ -0,0 +1,2 @@ +pub mod directory; +pub mod thirdparty; diff --git a/fractal-matrix-api/src/r0/directory.rs b/fractal-matrix-api/src/r0/directory.rs new file mode 100644 index 000000000..b7b38fa45 --- /dev/null +++ b/fractal-matrix-api/src/r0/directory.rs @@ -0,0 +1 @@ +pub mod post_public_rooms; diff --git a/fractal-matrix-api/src/r0/directory/post_public_rooms.rs b/fractal-matrix-api/src/r0/directory/post_public_rooms.rs new file mode 100644 index 000000000..145b7acb7 --- /dev/null +++ b/fractal-matrix-api/src/r0/directory/post_public_rooms.rs @@ -0,0 +1,80 @@ +use crate::ser::serialize_option_host; +use reqwest::Client; +use reqwest::Error; +use reqwest::Request; +use serde::{Deserialize, Serialize}; +use url::Host; +use url::Url; + +#[derive(Clone, Debug, Serialize)] +pub struct Parameters { + pub access_token: String, + #[serde(serialize_with = "serialize_option_host")] + #[serde(skip_serializing_if = "Option::is_none")] + pub server: Option>, +} + +#[derive(Clone, Debug, Serialize)] +pub struct Body { + #[serde(skip_serializing_if = "Option::is_none")] + pub limit: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub since: Option, + // This field doesn't follow the spec but for some reason + // it fails with matrix.org if it's not set this way + #[serde(skip_serializing_if = "Option::is_none")] + pub filter: Option, + #[serde(flatten)] + pub third_party_networks: ThirdPartyNetworks, +} + +#[derive(Clone, Debug, Serialize)] +#[serde(tag = "include_all_networks", content = "third_party_instance_id")] +pub enum ThirdPartyNetworks { + #[serde(rename = "false")] + None, + #[serde(rename = "false")] + Only(String), + #[serde(rename = "true")] + All, +} + +impl Default for ThirdPartyNetworks { + fn default() -> Self { + ThirdPartyNetworks::None + } +} + +#[derive(Clone, Debug, Serialize)] +pub struct Filter { + pub generic_search_term: Option, +} + +#[derive(Clone, Debug, Deserialize)] +pub struct Response { + pub chunk: Vec, + pub next_batch: Option, + pub prev_batch: Option, + pub total_room_count_estimate: Option, +} + +#[derive(Clone, Debug, Deserialize)] +pub struct Chunk { + pub aliases: Option>, + pub avatar_url: Option, + pub canonical_alias: Option, + pub guest_can_join: bool, + pub name: Option, + pub num_joined_members: i32, + pub room_id: String, + pub topic: Option, + pub world_readable: bool, +} + +pub fn request(base: Url, params: &Parameters, body: &Body) -> Result { + let url = base + .join("/_matrix/client/r0/publicRooms") + .expect("Malformed URL in post_public_rooms"); + + Client::new().post(url).query(params).json(body).build() +} diff --git a/fractal-matrix-api/src/r0/thirdparty.rs b/fractal-matrix-api/src/r0/thirdparty.rs new file mode 100644 index 000000000..b23380271 --- /dev/null +++ b/fractal-matrix-api/src/r0/thirdparty.rs @@ -0,0 +1 @@ +pub mod get_supported_protocols; diff --git a/fractal-matrix-api/src/model/protocol.rs b/fractal-matrix-api/src/r0/thirdparty/get_supported_protocols.rs similarity index 57% rename from fractal-matrix-api/src/model/protocol.rs rename to fractal-matrix-api/src/r0/thirdparty/get_supported_protocols.rs index 040ed1d86..65100d34b 100644 --- a/fractal-matrix-api/src/model/protocol.rs +++ b/fractal-matrix-api/src/r0/thirdparty/get_supported_protocols.rs @@ -1,8 +1,17 @@ -use serde::Deserialize; +use reqwest::Client; +use reqwest::Error; +use reqwest::Request; +use serde::{Deserialize, Serialize}; use serde_json::Value as JsonValue; use std::collections::BTreeMap; +use url::Url; -pub type SupportedProtocols = BTreeMap; +#[derive(Debug, Clone, Serialize)] +pub struct Parameters { + pub access_token: String, +} + +pub type Response = BTreeMap; #[derive(Debug, Clone, Deserialize)] pub struct Protocol { @@ -23,9 +32,18 @@ pub struct FieldType { #[derive(Debug, Clone, Deserialize)] pub struct ProtocolInstance { + // TODO: Avoid this rename #[serde(rename = "network_id")] pub id: String, pub desc: String, pub icon: Option, pub fields: JsonValue, } + +pub fn request(base: Url, params: &Parameters) -> Result { + let url = base + .join("/_matrix/client/r0/thirdparty/protocols") + .expect("Wrong URL in get_supported_protocols"); + + Client::new().get(url).query(params).build() +} diff --git a/fractal-matrix-api/src/ser.rs b/fractal-matrix-api/src/ser.rs new file mode 100644 index 000000000..2029a499a --- /dev/null +++ b/fractal-matrix-api/src/ser.rs @@ -0,0 +1,12 @@ +use serde::Serializer; +use url::Host; + +pub fn serialize_option_host(host: &Option, ser: S) -> Result +where + S: Serializer, +{ + match host { + Some(h) => ser.serialize_str(&h.to_string()), + None => ser.serialize_none(), + } +} diff --git a/fractal-matrix-api/src/types.rs b/fractal-matrix-api/src/types.rs index eb451f2c9..3f6d76f57 100644 --- a/fractal-matrix-api/src/types.rs +++ b/fractal-matrix-api/src/types.rs @@ -8,18 +8,12 @@ pub use crate::model::filter::RoomFilter; pub use crate::model::member::Member; pub use crate::model::member::MemberList; pub use crate::model::message::Message; -pub use crate::model::protocol::ProtocolInstance; -pub use crate::model::protocol::SupportedProtocols; pub use crate::model::register::*; -pub use crate::model::room::PublicRoomsFilter; -pub use crate::model::room::PublicRoomsRequest; -pub use crate::model::room::PublicRoomsResponse; pub use crate::model::room::Reason; pub use crate::model::room::Room; pub use crate::model::room::RoomList; pub use crate::model::room::RoomMembership; pub use crate::model::room::RoomTag; -pub use crate::model::room::ThirdPartyNetworks; pub use crate::model::stickers::Sticker; pub use crate::model::stickers::StickerGroup; pub use crate::model::sync::JoinedRoom; diff --git a/fractal-matrix-api/src/util.rs b/fractal-matrix-api/src/util.rs index f4664fd22..bba8f3a3b 100644 --- a/fractal-matrix-api/src/util.rs +++ b/fractal-matrix-api/src/util.rs @@ -29,7 +29,7 @@ use reqwest::header::CONTENT_TYPE; use crate::globals; lazy_static! { - static ref HTTP_CLIENT: Client = Client::new(); + pub static ref HTTP_CLIENT: Client = Client::new(); } pub fn semaphore(thread_count: Arc<(Mutex, Condvar)>, func: F) -- GitLab From 1142c83e9ac029414478e1865ee14475893b9b81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Dom=C3=ADnguez?= Date: Sat, 2 Feb 2019 18:17:48 +0100 Subject: [PATCH 2/4] API, sync and filter: Separate endpoint connection from query build --- fractal-matrix-api/src/backend/mod.rs | 10 +- fractal-matrix-api/src/backend/room.rs | 2 +- fractal-matrix-api/src/backend/sync.rs | 148 +++++++++--------- fractal-matrix-api/src/client.rs | 32 ++-- fractal-matrix-api/src/meson.build | 5 +- fractal-matrix-api/src/model/mod.rs | 2 - fractal-matrix-api/src/model/room.rs | 2 +- fractal-matrix-api/src/r0.rs | 2 + .../src/{model => r0}/filter.rs | 31 +++- fractal-matrix-api/src/r0/sync.rs | 1 + .../{model/sync.rs => r0/sync/sync_events.rs} | 75 ++++++++- fractal-matrix-api/src/ser.rs | 9 ++ fractal-matrix-api/src/types.rs | 7 - fractal-matrix-api/src/util.rs | 2 +- 14 files changed, 212 insertions(+), 116 deletions(-) rename fractal-matrix-api/src/{model => r0}/filter.rs (78%) create mode 100644 fractal-matrix-api/src/r0/sync.rs rename fractal-matrix-api/src/{model/sync.rs => r0/sync/sync_events.rs} (61%) diff --git a/fractal-matrix-api/src/backend/mod.rs b/fractal-matrix-api/src/backend/mod.rs index 39c6c80da..60c7e5ca6 100644 --- a/fractal-matrix-api/src/backend/mod.rs +++ b/fractal-matrix-api/src/backend/mod.rs @@ -173,14 +173,8 @@ impl Backend { } // Sync module - Ok(BKCommand::Sync(since, initial)) => { - let r = sync::sync(self, since, initial); - bkerror!(r, tx, BKResponse::SyncError); - } - Ok(BKCommand::SyncForced) => { - let r = sync::force_sync(self); - bkerror!(r, tx, BKResponse::SyncError); - } + Ok(BKCommand::Sync(since, initial)) => sync::sync(self, since, initial), + Ok(BKCommand::SyncForced) => sync::force_sync(self), // Room module Ok(BKCommand::GetRoomMembers(room)) => { diff --git a/fractal-matrix-api/src/backend/room.rs b/fractal-matrix-api/src/backend/room.rs index 9dfefbe4e..7d38167ef 100644 --- a/fractal-matrix-api/src/backend/room.rs +++ b/fractal-matrix-api/src/backend/room.rs @@ -26,10 +26,10 @@ use crate::backend::types::Backend; use crate::backend::types::BackendData; use crate::backend::types::RoomType; +use crate::r0::filter::RoomEventFilter; use crate::types::ExtraContent; use crate::types::Member; use crate::types::Message; -use crate::types::RoomEventFilter; use crate::types::{Room, RoomMembership, RoomTag}; use serde_json::Value as JsonValue; diff --git a/fractal-matrix-api/src/backend/sync.rs b/fractal-matrix-api/src/backend/sync.rs index ab400bf1b..a3de0d478 100644 --- a/fractal-matrix-api/src/backend/sync.rs +++ b/fractal-matrix-api/src/backend/sync.rs @@ -1,48 +1,53 @@ use crate::backend::types::BKResponse; use crate::backend::types::Backend; -use crate::error::Error; +use crate::client::ProxySettings; use crate::globals; +use crate::r0::filter::EventFilter; +use crate::r0::filter::Filter; +use crate::r0::filter::RoomEventFilter; +use crate::r0::filter::RoomFilter; +use crate::r0::sync::sync_events::request as sync_events; +use crate::r0::sync::sync_events::IncludeState; +use crate::r0::sync::sync_events::Parameters as SyncParameters; +use crate::r0::sync::sync_events::Response as SyncResponse; +use crate::r0::sync::sync_events::UnreadNotificationsCount; use crate::types::Event; -use crate::types::EventFilter; -use crate::types::Filter; use crate::types::Member; use crate::types::Message; use crate::types::Room; -use crate::types::RoomEventFilter; -use crate::types::RoomFilter; use crate::types::RoomMembership; use crate::types::RoomTag; -use crate::types::SyncResponse; -use crate::types::UnreadNotificationsCount; -use crate::util::json_q; use crate::util::parse_m_direct; use log::error; -use serde_json::json; +use reqwest::Client; use serde_json::value::from_value; -use serde_json::Value as JsonValue; -use std::{thread, time}; +use std::{ + thread, + time::{self, Duration}, +}; -pub fn sync(bk: &Backend, new_since: Option, initial: bool) -> Result<(), Error> { - let tk = bk.data.lock().unwrap().access_token.clone(); - if tk.is_empty() { - return Err(Error::BackendError); - } - - let since = bk.data.lock().unwrap().since.clone().or(new_since); +pub fn sync(bk: &Backend, new_since: Option, initial: bool) { + let tx = bk.tx.clone(); + let data = bk.data.clone(); let userid = bk.data.lock().unwrap().user_id.clone(); - let mut params = vec![("full_state", String::from("false"))]; - - if let Some(since) = since.clone() { - params.push(("since", since)); - } - - if initial { + let since = bk + .data + .lock() + .unwrap() + .since + .clone() + .filter(|s| !s.is_empty()) + .or(new_since); + + let (timeout, filter) = if !initial { + (time::Duration::from_secs(30), Default::default()) + } else { let filter = Filter { room: Some(RoomFilter { state: Some(RoomEventFilter { - lazy_load_members: Some(true), + lazy_load_members: true, types: Some(vec!["m.room.*"]), ..Default::default() }), @@ -71,33 +76,42 @@ pub fn sync(bk: &Backend, new_since: Option, initial: bool) -> Result<() ]), ..Default::default() }; - let filter_str = - serde_json::to_string(&filter).expect("Failed to serialize sync request filter"); - params.push(("filter", filter_str)); - }; - - let timeout = time::Duration::from_secs(30); - params.push(("timeout", timeout.as_millis().to_string())); - let baseu = bk.get_base_url(); - let url = bk.url("sync", params)?; - - let tx = bk.tx.clone(); - let data = bk.data.clone(); + (Default::default(), filter) + }; - let attrs = json!(null); + let base = bk.get_base_url(); + let params = SyncParameters { + access_token: data.lock().unwrap().access_token.clone(), + filter, + since: since.clone(), + include_state: IncludeState::Changed(timeout), + ..Default::default() + }; - get!( - &url, - &attrs, - |r: JsonValue| { - if let Ok(response) = serde_json::from_value::(r) { + thread::spawn(move || { + let client_builder_timeout = + Client::builder().timeout(Some(Duration::from_secs(globals::TIMEOUT) + timeout)); + + let query = ProxySettings::current().and_then(|proxy_settings| { + let client = proxy_settings + .apply_to_client_builder(client_builder_timeout)? + .build()?; + let request = sync_events(base.clone(), ¶ms)?; + client + .execute(request)? + .json::() + .map_err(Into::into) + }); + + match query { + Ok(response) => { if since.is_some() { let join = &response.rooms.join; // New rooms - let rs = Room::from_sync_response(&response, &userid, &baseu); - tx.send(BKResponse::UpdateRooms(rs)).unwrap(); + let rs = Room::from_sync_response(&response, &userid, &base); + let _ = tx.send(BKResponse::UpdateRooms(rs)); // Message events let msgs = join @@ -107,7 +121,7 @@ pub fn sync(bk: &Backend, new_since: Option, initial: bool) -> Result<() Message::from_json_events_iter(&k, events).into_iter() }) .collect(); - tx.send(BKResponse::RoomMessages(msgs)).unwrap(); + let _ = tx.send(BKResponse::RoomMessages(msgs)); // Room notifications for (k, room) in join.iter() { @@ -115,8 +129,7 @@ pub fn sync(bk: &Backend, new_since: Option, initial: bool) -> Result<() highlight_count: h, notification_count: n, } = room.unread_notifications; - tx.send(BKResponse::RoomNotifications(k.clone(), n, h)) - .unwrap(); + let _ = tx.send(BKResponse::RoomNotifications(k.clone(), n, h)); } // Typing notifications @@ -178,21 +191,20 @@ pub fn sync(bk: &Backend, new_since: Option, initial: bool) -> Result<() .as_str() .map(Into::into) .unwrap_or_default(); - tx.send(BKResponse::RoomName(ev.room.clone(), name)) - .unwrap(); + let _ = tx.send(BKResponse::RoomName(ev.room.clone(), name)); } "m.room.topic" => { let t = ev.content["topic"] .as_str() .map(Into::into) .unwrap_or_default(); - tx.send(BKResponse::RoomTopic(ev.room.clone(), t)).unwrap(); + let _ = tx.send(BKResponse::RoomTopic(ev.room.clone(), t)); } "m.room.avatar" => { - tx.send(BKResponse::NewRoomAvatar(ev.room.clone())).unwrap(); + let _ = tx.send(BKResponse::NewRoomAvatar(ev.room.clone())); } "m.room.member" => { - tx.send(BKResponse::RoomMemberEvent(ev)).unwrap(); + let _ = tx.send(BKResponse::RoomMemberEvent(ev)); } "m.sticker" => { // This event is managed in the room list @@ -205,36 +217,32 @@ pub fn sync(bk: &Backend, new_since: Option, initial: bool) -> Result<() } else { data.lock().unwrap().m_direct = parse_m_direct(&response.account_data.events); - let rooms = Room::from_sync_response(&response, &userid, &baseu); + let rooms = Room::from_sync_response(&response, &userid, &base); let jtr = data.lock().unwrap().join_to_room.clone(); let def = if !jtr.is_empty() { rooms.iter().find(|x| x.id == jtr).cloned() } else { None }; - tx.send(BKResponse::Rooms(rooms, def)).unwrap(); + let _ = tx.send(BKResponse::Rooms(rooms, def)); } let next_batch = response.next_batch; - tx.send(BKResponse::Sync(next_batch.clone())).unwrap(); - data.lock().unwrap().since = Some(next_batch).filter(|s| !s.is_empty()); - } else { - tx.send(BKResponse::SyncError(Error::BackendError)).unwrap(); + data.lock().unwrap().since = Some(next_batch.clone()).filter(|s| !s.is_empty()); + let _ = tx.send(BKResponse::Sync(next_batch)); } - }, - |err| { - // we wait if there's an error to avoid 100% CPU - error!("Sync Error, waiting 10 seconds to respond for the next sync"); - thread::sleep(time::Duration::from_secs(10)); + Err(err) => { + // we wait if there's an error to avoid 100% CPU + error!("Sync Error, waiting 10 seconds to respond for the next sync"); + thread::sleep(time::Duration::from_secs(10)); - tx.send(BKResponse::SyncError(err)).unwrap(); + tx.send(BKResponse::SyncError(err)).unwrap(); + } } - ); - - Ok(()) + }); } -pub fn force_sync(bk: &Backend) -> Result<(), Error> { +pub fn force_sync(bk: &Backend) { bk.data.lock().unwrap().since = None; sync(bk, None, true) } diff --git a/fractal-matrix-api/src/client.rs b/fractal-matrix-api/src/client.rs index 24c6fc202..bf1b4714b 100644 --- a/fractal-matrix-api/src/client.rs +++ b/fractal-matrix-api/src/client.rs @@ -12,7 +12,7 @@ use std::time::Duration; const PROXY_DIRECT_URI: &str = "direct://"; #[derive(Debug, Eq, PartialEq)] -struct ProxySettings { +pub struct ProxySettings { http_proxy: Vec, https_proxy: Vec, } @@ -32,7 +32,22 @@ impl ProxySettings { ) } - fn apply_to_client_builder( + pub fn current() -> Result { + Ok(Self::new( + PROXY_RESOLVER + .with(|resolver| resolver.lookup("http://", gio::NONE_CANCELLABLE))? + .iter() + .map(ToString::to_string) + .collect(), + PROXY_RESOLVER + .with(|resolver| resolver.lookup("https://", gio::NONE_CANCELLABLE))? + .iter() + .map(ToString::to_string) + .collect(), + )) + } + + pub fn apply_to_client_builder( &self, mut builder: reqwest::ClientBuilder, ) -> Result { @@ -82,18 +97,7 @@ impl Client { // Lock first so we don't overwrite proxy settings with outdated information let mut inner = self.inner.lock().unwrap(); - let http_proxy = PROXY_RESOLVER - .with(|resolver| resolver.lookup("http://", gio::NONE_CANCELLABLE))? - .iter() - .map(ToString::to_string) - .collect(); - let https_proxy = PROXY_RESOLVER - .with(|resolver| resolver.lookup("https://", gio::NONE_CANCELLABLE))? - .iter() - .map(ToString::to_string) - .collect(); - - let new_proxy_settings = ProxySettings::new(http_proxy, https_proxy); + let new_proxy_settings = ProxySettings::current()?; if inner.proxy_settings == new_proxy_settings { Ok(inner.client.clone()) diff --git a/fractal-matrix-api/src/meson.build b/fractal-matrix-api/src/meson.build index 958b39b78..fe58b6a89 100644 --- a/fractal-matrix-api/src/meson.build +++ b/fractal-matrix-api/src/meson.build @@ -10,18 +10,19 @@ api_sources = files( 'backend/user.rs', 'model/event.rs', 'model/fileinfo.rs', - 'model/filter.rs', 'model/member.rs', 'model/message.rs', 'model/mod.rs', 'model/register.rs', 'model/room.rs', 'model/stickers.rs', - 'model/sync.rs', 'model/user.rs', 'r0/directory/post_public_rooms.rs', + 'r0/sync/sync_events.rs', 'r0/thirdparty/get_supported_protocols.rs', 'r0/directory.rs', + 'r0/filter.rs', + 'r0/sync.rs', 'r0/thirdparty.rs', 'cache.rs', 'client.rs', diff --git a/fractal-matrix-api/src/model/mod.rs b/fractal-matrix-api/src/model/mod.rs index 0a005e21f..669038637 100644 --- a/fractal-matrix-api/src/model/mod.rs +++ b/fractal-matrix-api/src/model/mod.rs @@ -1,12 +1,10 @@ pub mod event; pub mod fileinfo; -pub mod filter; pub mod member; pub mod message; pub mod register; pub mod room; pub mod stickers; -pub mod sync; pub mod user; use serde::{Deserialize, Serialize}; diff --git a/fractal-matrix-api/src/model/room.rs b/fractal-matrix-api/src/model/room.rs index 6e52517f7..8405a187b 100644 --- a/fractal-matrix-api/src/model/room.rs +++ b/fractal-matrix-api/src/model/room.rs @@ -4,7 +4,7 @@ use crate::model::member::Member; use crate::model::member::MemberList; use crate::model::message::Message; use crate::r0::directory::post_public_rooms::Chunk as PublicRoomsChunk; -use crate::types::SyncResponse; +use crate::r0::sync::sync_events::Response as SyncResponse; use crate::util::get_user_avatar; use crate::util::parse_m_direct; use log::{debug, info}; diff --git a/fractal-matrix-api/src/r0.rs b/fractal-matrix-api/src/r0.rs index d87d88dfe..949e16b13 100644 --- a/fractal-matrix-api/src/r0.rs +++ b/fractal-matrix-api/src/r0.rs @@ -1,2 +1,4 @@ pub mod directory; +pub mod filter; +pub mod sync; pub mod thirdparty; diff --git a/fractal-matrix-api/src/model/filter.rs b/fractal-matrix-api/src/r0/filter.rs similarity index 78% rename from fractal-matrix-api/src/model/filter.rs rename to fractal-matrix-api/src/r0/filter.rs index 07fb55710..f2364ff13 100644 --- a/fractal-matrix-api/src/model/filter.rs +++ b/fractal-matrix-api/src/r0/filter.rs @@ -1,7 +1,7 @@ -use serde::Serialize; +use serde::{Serialize, Serializer}; use std::ops::Not; -#[derive(Clone, Debug, Default, Serialize)] +#[derive(Clone, Debug, Default, PartialEq, Serialize)] pub struct Filter<'a> { #[serde(skip_serializing_if = "Option::is_none")] pub event_fields: Option>, @@ -15,7 +15,13 @@ pub struct Filter<'a> { pub room: Option>, } -#[derive(Clone, Debug, Serialize)] +impl<'a> Filter<'a> { + pub fn is_default(&self) -> bool { + *self == Default::default() + } +} + +#[derive(Clone, Debug, PartialEq, Serialize)] #[serde(rename_all = "lowercase")] pub enum EventFormat { Client, @@ -28,7 +34,7 @@ impl Default for EventFormat { } } -#[derive(Clone, Debug, Default, Serialize)] +#[derive(Clone, Debug, Default, PartialEq, Serialize)] pub struct EventFilter<'a> { #[serde(skip_serializing_if = "Option::is_none")] pub limit: Option, @@ -42,7 +48,7 @@ pub struct EventFilter<'a> { pub types: Option>, } -#[derive(Clone, Debug, Default, Serialize)] +#[derive(Clone, Debug, Default, PartialEq, Serialize)] pub struct RoomFilter<'a> { #[serde(skip_serializing_if = "Vec::is_empty")] pub not_rooms: Vec<&'a str>, @@ -60,10 +66,10 @@ pub struct RoomFilter<'a> { pub account_data: Option>, } -#[derive(Clone, Debug, Default, Serialize)] +#[derive(Clone, Debug, Default, PartialEq, Serialize)] pub struct RoomEventFilter<'a> { - #[serde(skip_serializing_if = "Option::is_none")] - pub lazy_load_members: Option, + #[serde(skip_serializing_if = "Not::not")] + pub lazy_load_members: bool, #[serde(skip_serializing_if = "Option::is_none")] pub limit: Option, #[serde(skip_serializing_if = "Vec::is_empty")] @@ -81,3 +87,12 @@ pub struct RoomEventFilter<'a> { #[serde(skip_serializing_if = "Not::not")] pub contains_url: bool, } + +pub fn serialize_filter_as_str(filter: &Filter, ser: S) -> Result +where + S: Serializer, +{ + let filter_str = serde_json::to_string(filter).expect("Malformed filter"); + + ser.serialize_str(&filter_str) +} diff --git a/fractal-matrix-api/src/r0/sync.rs b/fractal-matrix-api/src/r0/sync.rs new file mode 100644 index 000000000..78aeaf20f --- /dev/null +++ b/fractal-matrix-api/src/r0/sync.rs @@ -0,0 +1 @@ +pub mod sync_events; diff --git a/fractal-matrix-api/src/model/sync.rs b/fractal-matrix-api/src/r0/sync/sync_events.rs similarity index 61% rename from fractal-matrix-api/src/model/sync.rs rename to fractal-matrix-api/src/r0/sync/sync_events.rs index 1b740ead4..6b6dc1641 100644 --- a/fractal-matrix-api/src/model/sync.rs +++ b/fractal-matrix-api/src/r0/sync/sync_events.rs @@ -1,9 +1,72 @@ -use serde::Deserialize; +use crate::r0::filter::{serialize_filter_as_str, Filter}; +use crate::ser::serialize_duration_as_millis; +use reqwest::Client; +use reqwest::Error; +use reqwest::Request; +use serde::{Deserialize, Serialize}; use serde_json::Value as JsonValue; use std::collections::HashMap; +use std::time::Duration; +use url::Url; + +#[derive(Clone, Debug, Default, Serialize)] +pub struct Parameters<'a> { + pub access_token: String, + #[serde(serialize_with = "serialize_filter_as_str")] + #[serde(skip_serializing_if = "Filter::is_default")] + pub filter: Filter<'a>, + #[serde(skip_serializing_if = "Option::is_none")] + pub since: Option, + #[serde(flatten)] + pub include_state: IncludeState, + #[serde(skip_serializing_if = "MarkPresence::is_default")] + pub set_presence: MarkPresence, +} + +#[derive(Clone, Copy, Debug, PartialEq, Serialize)] +#[serde(tag = "full_state", content = "timeout")] +pub enum IncludeState { + #[serde(rename = "false")] + #[serde(serialize_with = "serialize_duration_as_millis")] + Changed(Duration), + #[serde(rename = "true")] + Full, +} + +impl Default for IncludeState { + fn default() -> Self { + IncludeState::Changed(Default::default()) + } +} + +impl IncludeState { + pub fn is_default(&self) -> bool { + *self == Default::default() + } +} + +#[derive(Clone, Debug, PartialEq, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum MarkPresence { + Offline, + Unavailable, + Online, +} + +impl Default for MarkPresence { + fn default() -> Self { + MarkPresence::Online + } +} + +impl MarkPresence { + pub fn is_default(&self) -> bool { + *self == Default::default() + } +} #[derive(Clone, Debug, Deserialize)] -pub struct SyncResponse { +pub struct Response { pub next_batch: String, #[serde(default)] pub rooms: Rooms, @@ -123,3 +186,11 @@ pub struct DeviceLists { #[serde(default)] pub left: Vec, } + +pub fn request(base: Url, params: &Parameters) -> Result { + let url = base + .join("/_matrix/client/r0/sync") + .expect("Malformed URL in sync_events"); + + Client::new().get(url).query(params).build() +} diff --git a/fractal-matrix-api/src/ser.rs b/fractal-matrix-api/src/ser.rs index 2029a499a..0bb56e884 100644 --- a/fractal-matrix-api/src/ser.rs +++ b/fractal-matrix-api/src/ser.rs @@ -1,4 +1,5 @@ use serde::Serializer; +use std::time::Duration; use url::Host; pub fn serialize_option_host(host: &Option, ser: S) -> Result @@ -10,3 +11,11 @@ where None => ser.serialize_none(), } } + +// TODO: use as_millis when duration_as_u128 is stable +pub fn serialize_duration_as_millis(duration: &Duration, ser: S) -> Result +where + S: Serializer, +{ + ser.serialize_u64(duration.as_secs() * 1000 + (duration.subsec_millis() as u64)) +} diff --git a/fractal-matrix-api/src/types.rs b/fractal-matrix-api/src/types.rs index 3f6d76f57..7d950f222 100644 --- a/fractal-matrix-api/src/types.rs +++ b/fractal-matrix-api/src/types.rs @@ -1,10 +1,6 @@ pub use crate::model::event::Event; pub use crate::model::fileinfo::ExtraContent; pub use crate::model::fileinfo::Info; -pub use crate::model::filter::EventFilter; -pub use crate::model::filter::Filter; -pub use crate::model::filter::RoomEventFilter; -pub use crate::model::filter::RoomFilter; pub use crate::model::member::Member; pub use crate::model::member::MemberList; pub use crate::model::message::Message; @@ -16,8 +12,5 @@ pub use crate::model::room::RoomMembership; pub use crate::model::room::RoomTag; pub use crate::model::stickers::Sticker; pub use crate::model::stickers::StickerGroup; -pub use crate::model::sync::JoinedRoom; -pub use crate::model::sync::SyncResponse; -pub use crate::model::sync::UnreadNotificationsCount; pub use crate::model::user::*; pub use crate::model::*; diff --git a/fractal-matrix-api/src/util.rs b/fractal-matrix-api/src/util.rs index bba8f3a3b..fd3c867d1 100644 --- a/fractal-matrix-api/src/util.rs +++ b/fractal-matrix-api/src/util.rs @@ -20,8 +20,8 @@ use std::thread; use crate::client::Client; use crate::error::Error; +use crate::r0::filter::RoomEventFilter; use crate::types::Message; -use crate::types::RoomEventFilter; use reqwest::header::CONTENT_LENGTH; use reqwest::header::CONTENT_TYPE; -- GitLab From 587a8c3cafe521753556117d5198d6ccb99e1b91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Dom=C3=ADnguez?= Date: Sat, 2 Feb 2019 04:01:08 +0100 Subject: [PATCH 3/4] API, register: Separate endpoint connection from query build --- fractal-gtk/src/appop/account.rs | 2 +- fractal-gtk/src/widgets/address.rs | 2 +- fractal-gtk/src/widgets/login.rs | 4 +- fractal-matrix-api/src/backend/mod.rs | 5 +- fractal-matrix-api/src/backend/register.rs | 308 ++++++++++-------- fractal-matrix-api/src/backend/types.rs | 2 +- fractal-matrix-api/src/backend/user.rs | 10 +- fractal-matrix-api/src/meson.build | 7 +- fractal-matrix-api/src/model/mod.rs | 111 ------- fractal-matrix-api/src/model/register.rs | 131 -------- fractal-matrix-api/src/model/user.rs | 2 +- fractal-matrix-api/src/r0.rs | 2 + fractal-matrix-api/src/r0/account.rs | 113 +++++++ fractal-matrix-api/src/r0/account/login.rs | 42 +++ fractal-matrix-api/src/r0/account/logout.rs | 18 + fractal-matrix-api/src/r0/account/register.rs | 66 ++++ fractal-matrix-api/src/r0/server.rs | 1 + .../src/r0/server/domain_info.rs | 31 ++ fractal-matrix-api/src/types.rs | 2 - 19 files changed, 471 insertions(+), 388 deletions(-) delete mode 100644 fractal-matrix-api/src/model/register.rs create mode 100644 fractal-matrix-api/src/r0/account.rs create mode 100644 fractal-matrix-api/src/r0/account/login.rs create mode 100644 fractal-matrix-api/src/r0/account/logout.rs create mode 100644 fractal-matrix-api/src/r0/account/register.rs create mode 100644 fractal-matrix-api/src/r0/server.rs create mode 100644 fractal-matrix-api/src/r0/server/domain_info.rs diff --git a/fractal-gtk/src/appop/account.rs b/fractal-gtk/src/appop/account.rs index f7e5b2b6c..c12dabc2f 100644 --- a/fractal-gtk/src/appop/account.rs +++ b/fractal-gtk/src/appop/account.rs @@ -12,7 +12,7 @@ use crate::widgets; use crate::widgets::AvatarExt; use crate::cache::download_to_cache; -use fractal_api::types::Medium; +use fractal_api::r0::account::Medium; use fractal_api::types::ThirdPartyIdentifier; impl AppOp { diff --git a/fractal-gtk/src/widgets/address.rs b/fractal-gtk/src/widgets/address.rs index 4b3fcd062..d99a9aa4b 100644 --- a/fractal-gtk/src/widgets/address.rs +++ b/fractal-gtk/src/widgets/address.rs @@ -1,4 +1,4 @@ -use fractal_api::types::Medium; +use fractal_api::r0::account::Medium; use glib::signal; use gtk; use gtk::prelude::*; diff --git a/fractal-gtk/src/widgets/login.rs b/fractal-gtk/src/widgets/login.rs index eeb36086f..040822f90 100644 --- a/fractal-gtk/src/widgets/login.rs +++ b/fractal-gtk/src/widgets/login.rs @@ -97,8 +97,8 @@ impl LoginWidget { match get_well_known(&txt) { Ok(response) => { info!("Got well-known response from {}: {:#?}", &txt, response); - homeserver_url = response.homeserver.unwrap_or(txt); - idserver = response.identity_server; + homeserver_url = response.homeserver.base_url; + idserver = response.identity_server.map(|ids| ids.base_url); } Err(e) => info!("Failed to .well-known request: {:#?}", e), }; diff --git a/fractal-matrix-api/src/backend/mod.rs b/fractal-matrix-api/src/backend/mod.rs index 60c7e5ca6..3ff9b45c0 100644 --- a/fractal-matrix-api/src/backend/mod.rs +++ b/fractal-matrix-api/src/backend/mod.rs @@ -90,10 +90,7 @@ impl Backend { let r = register::login(self, user, passwd, &server); bkerror!(r, tx, BKResponse::LoginError); } - Ok(BKCommand::Logout) => { - let r = register::logout(self); - bkerror!(r, tx, BKResponse::LogoutError); - } + Ok(BKCommand::Logout) => register::logout(self), Ok(BKCommand::Register(user, passwd, server)) => { let r = register::register(self, user, passwd, &server); bkerror!(r, tx, BKResponse::LoginError); diff --git a/fractal-matrix-api/src/backend/register.rs b/fractal-matrix-api/src/backend/register.rs index a393093bb..6d32ebb29 100644 --- a/fractal-matrix-api/src/backend/register.rs +++ b/fractal-matrix-api/src/backend/register.rs @@ -1,95 +1,131 @@ -use serde_json::json; -use serde_json::Value as JsonValue; - use std::thread; use url::Url; use crate::error::Error; -use crate::util::json_q; -use crate::types::LoginRequest; -use crate::types::LoginResponse; -use crate::types::RegisterRequest; -use crate::types::RegisterResponse; -use crate::types::WellKnownResponse; +use crate::globals; +use crate::r0::account::login::request as login_req; +use crate::r0::account::login::Auth; +use crate::r0::account::login::Body as LoginBody; +use crate::r0::account::login::Response as LoginResponse; +use crate::r0::account::logout::request as logout_req; +use crate::r0::account::logout::Parameters as LogoutParameters; +use crate::r0::account::register::request as register_req; +use crate::r0::account::register::Body as RegisterBody; +use crate::r0::account::register::Parameters as RegisterParameters; +use crate::r0::account::register::RegistrationKind; +use crate::r0::account::register::Response as RegisterResponse; +use crate::r0::account::Identifier; +use crate::r0::account::Medium; +use crate::r0::account::UserIdentifier; +use crate::r0::server::domain_info::request as domain_info; +use crate::r0::server::domain_info::Response as DomainInfoResponse; +use crate::util::HTTP_CLIENT; use crate::backend::types::BKResponse; use crate::backend::types::Backend; -use crate::globals; - pub fn guest(bk: &Backend, server: &str) -> Result<(), Error> { - let baseu = Url::parse(server)?; - let url = baseu - .join("/_matrix/client/r0/register?kind=guest") - .expect("Wrong URL in guest()"); - bk.data.lock().unwrap().server_url = baseu; - let data = bk.data.clone(); let tx = bk.tx.clone(); - let attrs = RegisterRequest::default(); - let attrs_json = - serde_json::to_value(attrs).expect("Failed to serialize guest register request"); - post!( - &url, - &attrs_json, - |r: JsonValue| if let Ok(response) = serde_json::from_value::(r) { - let uid = response.user_id; - let tk = response.access_token.unwrap_or_default(); - let dev = response.device_id; - - data.lock().unwrap().user_id = uid.clone(); - data.lock().unwrap().access_token = tk.clone(); - data.lock().unwrap().since = None; - tx.send(BKResponse::Token(uid, tk, dev)).unwrap(); - tx.send(BKResponse::Rooms(vec![], None)).unwrap(); - } else { - tx.send(BKResponse::GuestLoginError(Error::BackendError)) - .unwrap(); - }, - |err| tx.send(BKResponse::GuestLoginError(err)).unwrap() - ); + + let base = Url::parse(server)?; + data.lock().unwrap().server_url = base.clone(); + + let params = RegisterParameters { + kind: RegistrationKind::Guest, + }; + let body = Default::default(); + + thread::spawn(move || { + let query = register_req(base, ¶ms, &body) + .map_err(Into::into) + .and_then(|request| { + HTTP_CLIENT + .get_client()? + .execute(request)? + .json::() + .map_err(Into::into) + }); + + match query { + Ok(response) => { + let uid = response.user_id; + let tk = response.access_token.unwrap_or_default(); + let dev = response.device_id; + + data.lock().unwrap().user_id = uid.clone(); + data.lock().unwrap().access_token = tk.clone(); + data.lock().unwrap().since = None; + let _ = tx.send(BKResponse::Token(uid, tk, dev)); + let _ = tx.send(BKResponse::Rooms(vec![], None)); + } + Err(err) => { + let _ = tx.send(BKResponse::GuestLoginError(err)); + } + } + }); Ok(()) } pub fn login(bk: &Backend, user: String, password: String, server: &str) -> Result<(), Error> { - bk.data.lock().unwrap().server_url = Url::parse(server)?; - let url = bk.url("login", vec![])?; - - let attrs = LoginRequest::new( - user.clone(), - password, - Some(globals::DEVICE_NAME.into()), - None, - ); - let attrs_json = serde_json::to_value(attrs).expect("Failed to serialize login request"); let data = bk.data.clone(); - let tx = bk.tx.clone(); - post!( - &url, - &attrs_json, - |r: JsonValue| if let Ok(response) = serde_json::from_value::(r) { - let uid = response.user_id.unwrap_or(user); - let tk = response.access_token.unwrap_or_default(); - let dev = response.device_id; - - if uid.is_empty() || tk.is_empty() { - tx.send(BKResponse::LoginError(Error::BackendError)) - .unwrap(); - } else { - data.lock().unwrap().user_id = uid.clone(); - data.lock().unwrap().access_token = tk.clone(); - data.lock().unwrap().since = None; - tx.send(BKResponse::Token(uid, tk, dev)).unwrap(); + + let base = Url::parse(server)?; + data.lock().unwrap().server_url = base.clone(); + + let body = if globals::EMAIL_RE.is_match(&user) { + LoginBody { + auth: Auth::Password { password }, + identifier: Identifier::new(UserIdentifier::ThirdParty { + medium: Medium::Email, + address: user.clone(), + }), + initial_device_display_name: Some(globals::DEVICE_NAME.into()), + device_id: None, + } + } else { + LoginBody { + auth: Auth::Password { password }, + identifier: Identifier::new(UserIdentifier::User { user: user.clone() }), + initial_device_display_name: Some(globals::DEVICE_NAME.into()), + device_id: None, + } + }; + + thread::spawn(move || { + let query = login_req(base, &body) + .map_err(Into::into) + .and_then(|request| { + HTTP_CLIENT + .get_client()? + .execute(request)? + .json::() + .map_err(Into::into) + }); + + match query { + Ok(response) => { + let uid = response.user_id.unwrap_or(user); + let tk = response.access_token.unwrap_or_default(); + let dev = response.device_id; + + if uid.is_empty() || tk.is_empty() { + let _ = tx.send(BKResponse::LoginError(Error::BackendError)); + } else { + data.lock().unwrap().user_id = uid.clone(); + data.lock().unwrap().access_token = tk.clone(); + data.lock().unwrap().since = None; + let _ = tx.send(BKResponse::Token(uid, tk, dev)); + } + } + Err(err) => { + let _ = tx.send(BKResponse::LoginError(err)); } - } else { - tx.send(BKResponse::LoginError(Error::BackendError)) - .unwrap(); - }, - |err| tx.send(BKResponse::LoginError(err)).unwrap() - ); + } + }); Ok(()) } @@ -104,75 +140,91 @@ pub fn set_token(bk: &Backend, token: String, uid: String, server: &str) -> Resu Ok(()) } -pub fn logout(bk: &Backend) -> Result<(), Error> { - let url = bk.url("logout", vec![])?; - let attrs = json!({}); - +pub fn logout(bk: &Backend) { let data = bk.data.clone(); let tx = bk.tx.clone(); - post!( - &url, - &attrs, - |_| { - data.lock().unwrap().user_id = String::new(); - data.lock().unwrap().access_token = String::new(); - data.lock().unwrap().since = None; - tx.send(BKResponse::Logout).unwrap(); - }, - |err| tx.send(BKResponse::LogoutError(err)).unwrap() - ); - Ok(()) + + let base = bk.get_base_url(); + let params = LogoutParameters { + access_token: data.lock().unwrap().access_token.clone(), + }; + + thread::spawn(move || { + let query = logout_req(base, ¶ms) + .map_err(Into::into) + .and_then(|request| { + HTTP_CLIENT + .get_client()? + .execute(request) + .map_err(Into::into) + }); + + match query { + Ok(_) => { + data.lock().unwrap().user_id = Default::default(); + data.lock().unwrap().access_token = Default::default(); + data.lock().unwrap().since = None; + let _ = tx.send(BKResponse::Logout); + } + Err(err) => { + let _ = tx.send(BKResponse::LogoutError(err)); + } + } + }); } pub fn register(bk: &Backend, user: String, password: String, server: &str) -> Result<(), Error> { - bk.data.lock().unwrap().server_url = Url::parse(server)?; - let url = bk.url("register", vec![("kind", String::from("user"))])?; + let data = bk.data.clone(); + let tx = bk.tx.clone(); - let attrs = RegisterRequest { + let base = Url::parse(server)?; + data.lock().unwrap().server_url = base.clone(); + let params = Default::default(); + let body = RegisterBody { username: Some(user), password: Some(password), ..Default::default() }; - let attrs_json = - serde_json::to_value(attrs).expect("Failed to serialize user register request"); - let data = bk.data.clone(); - let tx = bk.tx.clone(); - post!( - &url, - &attrs_json, - |r: JsonValue| if let Ok(response) = serde_json::from_value::(r) { - let uid = response.user_id; - let tk = response.access_token.unwrap_or_default(); - let dev = response.device_id; - - data.lock().unwrap().user_id = uid.clone(); - data.lock().unwrap().access_token = tk.clone(); - data.lock().unwrap().since = None; - tx.send(BKResponse::Token(uid, tk, dev)).unwrap(); - } else { - tx.send(BKResponse::LoginError(Error::BackendError)) - .unwrap(); - }, - |err| tx.send(BKResponse::LoginError(err)).unwrap() - ); + thread::spawn(move || { + let query = register_req(base, ¶ms, &body) + .map_err(Into::into) + .and_then(|request| { + HTTP_CLIENT + .get_client()? + .execute(request)? + .json::() + .map_err(Into::into) + }); + + match query { + Ok(response) => { + let uid = response.user_id; + let tk = response.access_token.unwrap_or_default(); + let dev = response.device_id; + + data.lock().unwrap().user_id = uid.clone(); + data.lock().unwrap().access_token = tk.clone(); + data.lock().unwrap().since = None; + let _ = tx.send(BKResponse::Token(uid, tk, dev)); + } + Err(err) => { + let _ = tx.send(BKResponse::LoginError(err)); + } + } + }); Ok(()) } -pub fn get_well_known(domain: &str) -> Result { - let well_known = Url::parse(domain)?.join(".well-known/matrix/client")?; - - // NOTE: The query! macro doesn't like what we're - // trying to do, so this implements what we need - - let handle = thread::spawn(move || json_q("get", &well_known, &json!(null))); - - match handle.join() { - Ok(r) => match r { - Ok(val) => serde_json::from_value(val).map_err(|_| Error::BackendError), - Err(e) => Err(e.into()), - }, - _ => Err(Error::BackendError), - } +pub fn get_well_known(domain: &str) -> Result { + domain_info(Url::parse(domain)?) + .map_err(Into::into) + .and_then(|request| { + HTTP_CLIENT + .get_client()? + .execute(request)? + .json::() + .map_err(Into::into) + }) } diff --git a/fractal-matrix-api/src/backend/types.rs b/fractal-matrix-api/src/backend/types.rs index 2b7f6704b..0b4a1aad2 100644 --- a/fractal-matrix-api/src/backend/types.rs +++ b/fractal-matrix-api/src/backend/types.rs @@ -4,9 +4,9 @@ use std::sync::{Arc, Condvar, Mutex}; use crate::error::Error; +use crate::r0::account::Medium; use crate::r0::thirdparty::get_supported_protocols::ProtocolInstance; use crate::types::Event; -use crate::types::Medium; use crate::types::Member; use crate::types::Message; use crate::types::Room; diff --git a/fractal-matrix-api/src/backend/user.rs b/fractal-matrix-api/src/backend/user.rs index 293aa2bcd..9acb8c941 100644 --- a/fractal-matrix-api/src/backend/user.rs +++ b/fractal-matrix-api/src/backend/user.rs @@ -18,15 +18,17 @@ use std::sync::{Arc, Mutex}; use std::thread; use url::Url; +use crate::r0::account::AuthenticationData; +use crate::r0::account::Identifier; +use crate::r0::account::Medium; +use crate::r0::account::ThreePIDCredentials; +use crate::r0::account::UserIdentifier; use crate::types::AddThreePIDRequest; -use crate::types::AuthenticationData; use crate::types::ChangePasswordRequest; use crate::types::DeactivateAccountRequest; use crate::types::DeleteThreePIDRequest; use crate::types::EmailTokenRequest; use crate::types::GetDisplayNameResponse; -use crate::types::Identifier; -use crate::types::Medium; use crate::types::Member; use crate::types::PhoneTokenRequest; use crate::types::PutDisplayNameRequest; @@ -36,8 +38,6 @@ use crate::types::SubmitPhoneTokenRequest; use crate::types::SubmitPhoneTokenResponse; use crate::types::ThirdPartyIDResponse; use crate::types::ThirdPartyTokenResponse; -use crate::types::ThreePIDCredentials; -use crate::types::UserIdentifier; use serde_json; use serde_json::Value as JsonValue; diff --git a/fractal-matrix-api/src/meson.build b/fractal-matrix-api/src/meson.build index fe58b6a89..8a65d647f 100644 --- a/fractal-matrix-api/src/meson.build +++ b/fractal-matrix-api/src/meson.build @@ -13,15 +13,20 @@ api_sources = files( 'model/member.rs', 'model/message.rs', 'model/mod.rs', - 'model/register.rs', 'model/room.rs', 'model/stickers.rs', 'model/user.rs', + 'r0/account/login.rs', + 'r0/account/logout.rs', + 'r0/account/register.rs', 'r0/directory/post_public_rooms.rs', + 'r0/server/domain_info.rs', 'r0/sync/sync_events.rs', 'r0/thirdparty/get_supported_protocols.rs', + 'r0/account.rs', 'r0/directory.rs', 'r0/filter.rs', + 'r0/server.rs', 'r0/sync.rs', 'r0/thirdparty.rs', 'cache.rs', diff --git a/fractal-matrix-api/src/model/mod.rs b/fractal-matrix-api/src/model/mod.rs index 669038637..14db54565 100644 --- a/fractal-matrix-api/src/model/mod.rs +++ b/fractal-matrix-api/src/model/mod.rs @@ -2,117 +2,6 @@ pub mod event; pub mod fileinfo; pub mod member; pub mod message; -pub mod register; pub mod room; pub mod stickers; pub mod user; - -use serde::{Deserialize, Serialize}; - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub enum Medium { - #[serde(rename = "email")] - Email, - #[serde(rename = "msisdn")] - MsIsdn, -} - -#[derive(Clone, Debug, 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 }, -} - -#[derive(Clone, Debug, Serialize)] -enum LegacyMedium { - #[serde(rename = "email")] - Email, -} - -#[derive(Clone, Debug, Serialize)] -#[serde(untagged)] -enum LegacyIdentifier { - User { - user: String, - }, - Email { - medium: LegacyMedium, - address: String, - }, -} - -#[derive(Clone, Debug, Serialize)] -pub struct Identifier { - identifier: UserIdentifier, - #[serde(flatten)] - legacy_identifier: Option, -} - -impl Identifier { - pub fn new(identifier: UserIdentifier) -> Self { - Self { - identifier: identifier.clone(), - legacy_identifier: match identifier { - UserIdentifier::User { user } => Some(LegacyIdentifier::User { user }), - UserIdentifier::ThirdParty { medium: _, address } => { - Some(LegacyIdentifier::Email { - medium: LegacyMedium::Email, - address, - }) - } - UserIdentifier::Phone { .. } => None, - }, - } - } -} - -#[derive(Clone, Debug, Serialize)] -pub struct ThreePIDCredentials { - pub client_secret: String, - pub id_server: String, - pub sid: String, -} - -#[derive(Clone, Debug, Serialize)] -#[serde(tag = "type")] -pub enum AuthenticationData { - #[serde(rename = "m.login.password")] - Password { - #[serde(flatten)] - identifier: Identifier, - password: String, - #[serde(skip_serializing_if = "Option::is_none")] - session: Option, - }, - #[serde(rename = "m.login.recaptcha")] - Recaptcha { - response: String, - #[serde(skip_serializing_if = "Option::is_none")] - session: Option, - }, - #[serde(rename = "m.login.token")] - Token { - token: String, - txn_id: String, - #[serde(skip_serializing_if = "Option::is_none")] - session: Option, - }, - #[serde(rename = "m.login.oauth2")] - OAuth2 { uri: String }, - #[serde(rename = "m.login.email.identity")] - Email { - threepid_creds: ThreePIDCredentials, - #[serde(skip_serializing_if = "Option::is_none")] - session: Option, - }, - #[serde(rename = "m.login.dummy")] - Dummy { - #[serde(skip_serializing_if = "Option::is_none")] - session: Option, - }, -} diff --git a/fractal-matrix-api/src/model/register.rs b/fractal-matrix-api/src/model/register.rs deleted file mode 100644 index cbd7a666b..000000000 --- a/fractal-matrix-api/src/model/register.rs +++ /dev/null @@ -1,131 +0,0 @@ -use super::{AuthenticationData, Identifier, Medium, UserIdentifier}; -use crate::globals; -use serde::{de, Deserialize, Deserializer, Serialize}; -use std::ops::Not; - -#[derive(Clone, Debug, Serialize)] -pub struct LoginRequest { - #[serde(flatten)] - pub identifier: Identifier, - #[serde(flatten)] - pub auth: Auth, - #[serde(skip_serializing_if = "Option::is_none")] - pub device_id: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub initial_device_display_name: Option, -} - -#[derive(Clone, Debug, Deserialize)] -pub struct LoginResponse { - pub access_token: Option, - pub user_id: Option, - pub device_id: Option, -} - -#[derive(Clone, Debug, Serialize)] -#[serde(tag = "type")] -pub enum Auth { - #[serde(rename = "m.login.password")] - Password { password: String }, - #[serde(rename = "m.login.token")] - Token { token: String }, -} - -impl LoginRequest { - pub fn new( - user: String, - password: String, - initial_device_display_name: Option, - device_id: Option, - ) -> Self { - if globals::EMAIL_RE.is_match(&user) { - Self { - auth: Auth::Password { password }, - initial_device_display_name, - identifier: Identifier::new(UserIdentifier::ThirdParty { - medium: Medium::Email, - address: user, - }), - device_id, - } - } else { - Self { - auth: Auth::Password { password }, - initial_device_display_name, - identifier: Identifier::new(UserIdentifier::User { user }), - device_id, - } - } - } -} - -#[derive(Clone, Debug, Default, Serialize)] -pub struct RegisterRequest { - #[serde(skip_serializing_if = "Option::is_none")] - pub auth: Option, - #[serde(skip_serializing_if = "Not::not")] - pub bind_email: bool, - #[serde(skip_serializing_if = "Option::is_none")] - pub password: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub username: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub device_id: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub initial_device_display_name: Option, - #[serde(skip_serializing_if = "Not::not")] - pub inhibit_login: bool, -} - -#[derive(Clone, Debug, Deserialize)] -pub struct RegisterResponse { - pub user_id: String, - pub access_token: Option, - pub device_id: Option, -} - -#[derive(Clone, Debug, Deserialize)] -#[serde(default, tag = "type")] -pub struct WellKnownResponse { - #[serde(deserialize_with = "extract_base_url", rename = "m.homeserver")] - pub homeserver: Option, - #[serde(deserialize_with = "extract_base_url", rename = "m.identity_server")] - pub identity_server: Option, -} - -impl Default for WellKnownResponse { - fn default() -> Self { - // Identity server is usually vector.im if not specified - Self { - homeserver: None, - identity_server: Some("https://vector.im".to_owned()), - } - } -} - -fn extract_base_url<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - struct BaseUrlVisitor; - - impl<'de> de::Visitor<'de> for BaseUrlVisitor { - type Value = Option; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("an object with a base_url key") - } - - fn visit_map(self, mut map: A) -> Result - where - A: de::MapAccess<'de>, - { - let res = map - .next_entry::()? - .and_then(|(key, value)| if key == "base_url" { Some(value) } else { None }); - Ok(res) - } - } - - deserializer.deserialize_any(BaseUrlVisitor) -} diff --git a/fractal-matrix-api/src/model/user.rs b/fractal-matrix-api/src/model/user.rs index a6a5e6da3..94a6cd2fa 100644 --- a/fractal-matrix-api/src/model/user.rs +++ b/fractal-matrix-api/src/model/user.rs @@ -1,4 +1,4 @@ -use super::{AuthenticationData, Medium, ThreePIDCredentials}; +use crate::r0::account::{AuthenticationData, Medium, ThreePIDCredentials}; use serde::{Deserialize, Serialize}; use std::ops::Not; diff --git a/fractal-matrix-api/src/r0.rs b/fractal-matrix-api/src/r0.rs index 949e16b13..13090a6c1 100644 --- a/fractal-matrix-api/src/r0.rs +++ b/fractal-matrix-api/src/r0.rs @@ -1,4 +1,6 @@ +pub mod account; pub mod directory; pub mod filter; +pub mod server; pub mod sync; pub mod thirdparty; diff --git a/fractal-matrix-api/src/r0/account.rs b/fractal-matrix-api/src/r0/account.rs new file mode 100644 index 000000000..3cad3482a --- /dev/null +++ b/fractal-matrix-api/src/r0/account.rs @@ -0,0 +1,113 @@ +pub mod login; +pub mod logout; +pub mod register; + +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum Medium { + #[serde(rename = "email")] + Email, + #[serde(rename = "msisdn")] + MsIsdn, +} + +#[derive(Clone, Debug, 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 }, +} + +#[derive(Clone, Debug, Serialize)] +enum LegacyMedium { + #[serde(rename = "email")] + Email, +} + +#[derive(Clone, Debug, Serialize)] +#[serde(untagged)] +enum LegacyIdentifier { + User { + user: String, + }, + Email { + medium: LegacyMedium, + address: String, + }, +} + +#[derive(Clone, Debug, Serialize)] +pub struct Identifier { + identifier: UserIdentifier, + #[serde(flatten)] + legacy_identifier: Option, +} + +impl Identifier { + pub fn new(identifier: UserIdentifier) -> Self { + Self { + identifier: identifier.clone(), + legacy_identifier: match identifier { + UserIdentifier::User { user } => Some(LegacyIdentifier::User { user }), + UserIdentifier::ThirdParty { medium: _, address } => { + Some(LegacyIdentifier::Email { + medium: LegacyMedium::Email, + address, + }) + } + UserIdentifier::Phone { .. } => None, + }, + } + } +} + +#[derive(Clone, Debug, Serialize)] +pub struct ThreePIDCredentials { + pub client_secret: String, + pub id_server: String, + pub sid: String, +} + +#[derive(Clone, Debug, Serialize)] +#[serde(tag = "type")] +pub enum AuthenticationData { + #[serde(rename = "m.login.password")] + Password { + #[serde(flatten)] + identifier: Identifier, + password: String, + #[serde(skip_serializing_if = "Option::is_none")] + session: Option, + }, + #[serde(rename = "m.login.recaptcha")] + Recaptcha { + response: String, + #[serde(skip_serializing_if = "Option::is_none")] + session: Option, + }, + #[serde(rename = "m.login.token")] + Token { + token: String, + txn_id: String, + #[serde(skip_serializing_if = "Option::is_none")] + session: Option, + }, + #[serde(rename = "m.login.oauth2")] + OAuth2 { uri: String }, + #[serde(rename = "m.login.email.identity")] + Email { + threepid_creds: ThreePIDCredentials, + #[serde(skip_serializing_if = "Option::is_none")] + session: Option, + }, + #[serde(rename = "m.login.dummy")] + Dummy { + #[serde(skip_serializing_if = "Option::is_none")] + session: Option, + }, +} diff --git a/fractal-matrix-api/src/r0/account/login.rs b/fractal-matrix-api/src/r0/account/login.rs new file mode 100644 index 000000000..763d40b22 --- /dev/null +++ b/fractal-matrix-api/src/r0/account/login.rs @@ -0,0 +1,42 @@ +use super::Identifier; +use reqwest::Client; +use reqwest::Error; +use reqwest::Request; +use serde::{Deserialize, Serialize}; +use url::Url; + +#[derive(Clone, Debug, Serialize)] +pub struct Body { + #[serde(flatten)] + pub identifier: Identifier, + #[serde(flatten)] + pub auth: Auth, + #[serde(skip_serializing_if = "Option::is_none")] + pub device_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub initial_device_display_name: Option, +} + +#[derive(Clone, Debug, Serialize)] +#[serde(tag = "type")] +pub enum Auth { + #[serde(rename = "m.login.password")] + Password { password: String }, + #[serde(rename = "m.login.token")] + Token { token: String }, +} + +#[derive(Clone, Debug, Deserialize)] +pub struct Response { + pub access_token: Option, + pub user_id: Option, + pub device_id: Option, +} + +pub fn request(base: Url, body: &Body) -> Result { + let url = base + .join("/_matrix/client/r0/login") + .expect("Malformed URL in login"); + + Client::new().post(url).json(body).build() +} diff --git a/fractal-matrix-api/src/r0/account/logout.rs b/fractal-matrix-api/src/r0/account/logout.rs new file mode 100644 index 000000000..114869535 --- /dev/null +++ b/fractal-matrix-api/src/r0/account/logout.rs @@ -0,0 +1,18 @@ +use reqwest::Client; +use reqwest::Error; +use reqwest::Request; +use serde::Serialize; +use url::Url; + +#[derive(Clone, Debug, Serialize)] +pub struct Parameters { + pub access_token: String, +} + +pub fn request(base: Url, params: &Parameters) -> Result { + let url = base + .join("/_matrix/client/r0/logout") + .expect("Malformed URL in logout"); + + Client::new().post(url).query(params).build() +} diff --git a/fractal-matrix-api/src/r0/account/register.rs b/fractal-matrix-api/src/r0/account/register.rs new file mode 100644 index 000000000..0f75dc788 --- /dev/null +++ b/fractal-matrix-api/src/r0/account/register.rs @@ -0,0 +1,66 @@ +use super::AuthenticationData; +use reqwest::Client; +use reqwest::Error; +use reqwest::Request; +use serde::{Deserialize, Serialize}; +use std::ops::Not; +use url::Url; + +#[derive(Clone, Debug, Default, Serialize)] +pub struct Parameters { + #[serde(skip_serializing_if = "RegistrationKind::is_default")] + pub kind: RegistrationKind, +} + +#[derive(Clone, Debug, PartialEq, Serialize)] +pub enum RegistrationKind { + #[serde(rename = "guest")] + Guest, + #[serde(rename = "user")] + User, +} + +impl Default for RegistrationKind { + fn default() -> Self { + RegistrationKind::User + } +} + +impl RegistrationKind { + pub fn is_default(&self) -> bool { + *self == Default::default() + } +} + +#[derive(Clone, Debug, Default, Serialize)] +pub struct Body { + #[serde(skip_serializing_if = "Option::is_none")] + pub auth: Option, + #[serde(skip_serializing_if = "Not::not")] + pub bind_email: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub password: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub username: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub device_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub initial_device_display_name: Option, + #[serde(skip_serializing_if = "Not::not")] + pub inhibit_login: bool, +} + +#[derive(Clone, Debug, Deserialize)] +pub struct Response { + pub user_id: String, + pub access_token: Option, + pub device_id: Option, +} + +pub fn request(base: Url, params: &Parameters, body: &Body) -> Result { + let url = base + .join("/_matrix/client/r0/register") + .expect("Malformed URL in register"); + + Client::new().post(url).query(params).json(body).build() +} diff --git a/fractal-matrix-api/src/r0/server.rs b/fractal-matrix-api/src/r0/server.rs new file mode 100644 index 000000000..58ac3d997 --- /dev/null +++ b/fractal-matrix-api/src/r0/server.rs @@ -0,0 +1 @@ +pub mod domain_info; diff --git a/fractal-matrix-api/src/r0/server/domain_info.rs b/fractal-matrix-api/src/r0/server/domain_info.rs new file mode 100644 index 000000000..d9e0c3610 --- /dev/null +++ b/fractal-matrix-api/src/r0/server/domain_info.rs @@ -0,0 +1,31 @@ +use reqwest::Client; +use reqwest::Error; +use reqwest::Request; +use serde::Deserialize; +use url::Url; + +#[derive(Clone, Debug, Deserialize)] +pub struct Response { + #[serde(rename = "m.homeserver")] + pub homeserver: HomeserverInfo, + #[serde(rename = "m.identity_server")] + pub identity_server: Option, +} + +#[derive(Clone, Debug, Deserialize)] +pub struct HomeserverInfo { + pub base_url: String, +} + +#[derive(Clone, Debug, Deserialize)] +pub struct IDServerInfo { + pub base_url: String, +} + +pub fn request(base: Url) -> Result { + let url = base + .join("/.well-known/matrix/client") + .expect("Malformed URL in domain_info"); + + Client::new().post(url).build() +} diff --git a/fractal-matrix-api/src/types.rs b/fractal-matrix-api/src/types.rs index 7d950f222..9d02a8fbf 100644 --- a/fractal-matrix-api/src/types.rs +++ b/fractal-matrix-api/src/types.rs @@ -4,7 +4,6 @@ pub use crate::model::fileinfo::Info; pub use crate::model::member::Member; pub use crate::model::member::MemberList; pub use crate::model::message::Message; -pub use crate::model::register::*; pub use crate::model::room::Reason; pub use crate::model::room::Room; pub use crate::model::room::RoomList; @@ -13,4 +12,3 @@ pub use crate::model::room::RoomTag; pub use crate::model::stickers::Sticker; pub use crate::model::stickers::StickerGroup; pub use crate::model::user::*; -pub use crate::model::*; -- GitLab From 50803772f2834b792fad02951cad9347119fe0f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Dom=C3=ADnguez?= Date: Tue, 12 Mar 2019 12:21:58 +0100 Subject: [PATCH 4/4] API, user: Separate endpoint connection from query build --- fractal-gtk/src/appop/account.rs | 4 +- fractal-gtk/src/widgets/address.rs | 2 +- fractal-matrix-api/src/backend/mod.rs | 63 +- fractal-matrix-api/src/backend/register.rs | 2 +- fractal-matrix-api/src/backend/types.rs | 4 +- fractal-matrix-api/src/backend/user.rs | 792 ++++++++++-------- fractal-matrix-api/src/identity.rs | 1 + fractal-matrix-api/src/identity/r0.rs | 1 + .../src/identity/r0/association.rs | 1 + .../src/identity/r0/association/msisdn.rs | 1 + .../r0/association/msisdn/submit_token.rs | 25 + fractal-matrix-api/src/lib.rs | 1 + fractal-matrix-api/src/meson.build | 23 +- fractal-matrix-api/src/model/member.rs | 2 +- fractal-matrix-api/src/model/mod.rs | 1 - fractal-matrix-api/src/model/user.rs | 126 --- fractal-matrix-api/src/r0.rs | 20 + fractal-matrix-api/src/r0/account.rs | 20 +- .../src/r0/account/change_password.rs | 27 + .../src/r0/account/deactivate.rs | 26 + fractal-matrix-api/src/r0/contact.rs | 5 + fractal-matrix-api/src/r0/contact/create.rs | 27 + fractal-matrix-api/src/r0/contact/delete.rs | 25 + .../src/r0/contact/get_identifiers.rs | 33 + .../request_verification_token_email.rs | 33 + .../request_verification_token_msisdn.rs | 34 + fractal-matrix-api/src/r0/media.rs | 1 + fractal-matrix-api/src/r0/media/create.rs | 40 + fractal-matrix-api/src/r0/profile.rs | 4 + .../src/r0/profile/get_display_name.rs | 21 + .../src/r0/profile/get_profile.rs | 19 + .../src/r0/profile/set_avatar_url.rs | 31 + .../src/r0/profile/set_display_name.rs | 32 + fractal-matrix-api/src/r0/search.rs | 1 + fractal-matrix-api/src/r0/search/user.rs | 53 ++ fractal-matrix-api/src/types.rs | 1 - fractal-matrix-api/src/util.rs | 64 +- 37 files changed, 971 insertions(+), 595 deletions(-) create mode 100644 fractal-matrix-api/src/identity.rs create mode 100644 fractal-matrix-api/src/identity/r0.rs create mode 100644 fractal-matrix-api/src/identity/r0/association.rs create mode 100644 fractal-matrix-api/src/identity/r0/association/msisdn.rs create mode 100644 fractal-matrix-api/src/identity/r0/association/msisdn/submit_token.rs delete mode 100644 fractal-matrix-api/src/model/user.rs create mode 100644 fractal-matrix-api/src/r0/account/change_password.rs create mode 100644 fractal-matrix-api/src/r0/account/deactivate.rs create mode 100644 fractal-matrix-api/src/r0/contact.rs create mode 100644 fractal-matrix-api/src/r0/contact/create.rs create mode 100644 fractal-matrix-api/src/r0/contact/delete.rs create mode 100644 fractal-matrix-api/src/r0/contact/get_identifiers.rs create mode 100644 fractal-matrix-api/src/r0/contact/request_verification_token_email.rs create mode 100644 fractal-matrix-api/src/r0/contact/request_verification_token_msisdn.rs create mode 100644 fractal-matrix-api/src/r0/media.rs create mode 100644 fractal-matrix-api/src/r0/media/create.rs create mode 100644 fractal-matrix-api/src/r0/profile.rs create mode 100644 fractal-matrix-api/src/r0/profile/get_display_name.rs create mode 100644 fractal-matrix-api/src/r0/profile/get_profile.rs create mode 100644 fractal-matrix-api/src/r0/profile/set_avatar_url.rs create mode 100644 fractal-matrix-api/src/r0/profile/set_display_name.rs create mode 100644 fractal-matrix-api/src/r0/search.rs create mode 100644 fractal-matrix-api/src/r0/search/user.rs diff --git a/fractal-gtk/src/appop/account.rs b/fractal-gtk/src/appop/account.rs index c12dabc2f..5a4533a24 100644 --- a/fractal-gtk/src/appop/account.rs +++ b/fractal-gtk/src/appop/account.rs @@ -12,8 +12,8 @@ use crate::widgets; use crate::widgets::AvatarExt; use crate::cache::download_to_cache; -use fractal_api::r0::account::Medium; -use fractal_api::types::ThirdPartyIdentifier; +use fractal_api::r0::contact::get_identifiers::ThirdPartyIdentifier; +use fractal_api::r0::Medium; impl AppOp { pub fn set_three_pid(&self, data: Option>) { diff --git a/fractal-gtk/src/widgets/address.rs b/fractal-gtk/src/widgets/address.rs index d99a9aa4b..ed191a1df 100644 --- a/fractal-gtk/src/widgets/address.rs +++ b/fractal-gtk/src/widgets/address.rs @@ -1,4 +1,4 @@ -use fractal_api::r0::account::Medium; +use fractal_api::r0::Medium; use glib::signal; use gtk; use gtk::prelude::*; diff --git a/fractal-matrix-api/src/backend/mod.rs b/fractal-matrix-api/src/backend/mod.rs index 3ff9b45c0..3212fc38e 100644 --- a/fractal-matrix-api/src/backend/mod.rs +++ b/fractal-matrix-api/src/backend/mod.rs @@ -105,69 +105,40 @@ impl Backend { } // User module - Ok(BKCommand::GetUsername) => { - let r = user::get_username(self); - bkerror!(r, tx, BKResponse::UserNameError); - } - Ok(BKCommand::SetUserName(name)) => { - let r = user::set_username(self, name); - bkerror!(r, tx, BKResponse::SetUserNameError); - } - Ok(BKCommand::GetThreePID) => { - let r = user::get_threepid(self); - bkerror!(r, tx, BKResponse::GetThreePIDError); - } + Ok(BKCommand::GetUsername) => user::get_username(self), + Ok(BKCommand::SetUserName(name)) => user::set_username(self, name), + Ok(BKCommand::GetThreePID) => user::get_threepid(self), Ok(BKCommand::GetTokenEmail(identity, email, client_secret)) => { - let r = user::get_email_token(self, identity, email, client_secret); - bkerror!(r, tx, BKResponse::GetTokenEmailError); + user::get_email_token(self, identity, email, client_secret) } Ok(BKCommand::GetTokenPhone(identity, phone, client_secret)) => { - let r = user::get_phone_token(self, identity, phone, client_secret); - bkerror!(r, tx, BKResponse::GetTokenEmailError); + user::get_phone_token(self, identity, phone, client_secret) } - Ok(BKCommand::SubmitPhoneToken(identity, client_secret, sid, token)) => { - let r = user::submit_phone_token(self, &identity, client_secret, sid, token); - bkerror!(r, tx, BKResponse::SubmitPhoneTokenError); + Ok(BKCommand::SubmitPhoneToken(_, client_secret, sid, token)) => { + user::submit_phone_token(self, client_secret, sid, token) } Ok(BKCommand::AddThreePID(identity, client_secret, sid)) => { - let r = user::add_threepid(self, identity, client_secret, sid); - bkerror!(r, tx, BKResponse::AddThreePIDError); + user::add_threepid(self, identity, client_secret, sid) } Ok(BKCommand::DeleteThreePID(medium, address)) => { - user::delete_three_pid(self, medium, address); + user::delete_three_pid(self, medium, address) } Ok(BKCommand::ChangePassword(username, old_password, new_password)) => { - let r = user::change_password(self, username, old_password, new_password); - bkerror!(r, tx, BKResponse::ChangePasswordError); + user::change_password(self, username, old_password, new_password) } Ok(BKCommand::AccountDestruction(username, password, _)) => { - let r = user::account_destruction(self, username, password); - bkerror!(r, tx, BKResponse::AccountDestructionError); - } - Ok(BKCommand::GetAvatar) => { - let r = user::get_avatar(self); - bkerror!(r, tx, BKResponse::AvatarError); - } - Ok(BKCommand::SetUserAvatar(file)) => { - let r = user::set_user_avatar(self, file); - bkerror!(r, tx, BKResponse::SetUserAvatarError); - } - Ok(BKCommand::GetAvatarAsync(member, ctx)) => { - let r = user::get_avatar_async(self, member, ctx); - bkerror!(r, tx, BKResponse::CommandError); + user::account_destruction(self, username, password) } + Ok(BKCommand::GetAvatar) => user::get_avatar(self), + Ok(BKCommand::SetUserAvatar(file)) => user::set_user_avatar(self, file), + Ok(BKCommand::GetAvatarAsync(member, ctx)) => user::get_avatar_async(self, member, ctx), Ok(BKCommand::GetUserInfoAsync(sender, ctx)) => { - let r = user::get_user_info_async(self, &sender, ctx); - bkerror!(r, tx, BKResponse::CommandError); + user::get_user_info_async(self, &sender, ctx) } Ok(BKCommand::GetUserNameAsync(sender, ctx)) => { - let r = user::get_username_async(self, sender, ctx); - bkerror!(r, tx, BKResponse::CommandError); - } - Ok(BKCommand::UserSearch(term)) => { - let r = user::search(self, term); - bkerror!(r, tx, BKResponse::CommandError); + user::get_username_async(self, sender, ctx) } + Ok(BKCommand::UserSearch(term)) => user::search(self, term), // Sync module Ok(BKCommand::Sync(since, initial)) => sync::sync(self, since, initial), diff --git a/fractal-matrix-api/src/backend/register.rs b/fractal-matrix-api/src/backend/register.rs index 6d32ebb29..5e42043f6 100644 --- a/fractal-matrix-api/src/backend/register.rs +++ b/fractal-matrix-api/src/backend/register.rs @@ -16,10 +16,10 @@ use crate::r0::account::register::Parameters as RegisterParameters; use crate::r0::account::register::RegistrationKind; use crate::r0::account::register::Response as RegisterResponse; use crate::r0::account::Identifier; -use crate::r0::account::Medium; use crate::r0::account::UserIdentifier; use crate::r0::server::domain_info::request as domain_info; use crate::r0::server::domain_info::Response as DomainInfoResponse; +use crate::r0::Medium; use crate::util::HTTP_CLIENT; use crate::backend::types::BKResponse; diff --git a/fractal-matrix-api/src/backend/types.rs b/fractal-matrix-api/src/backend/types.rs index 0b4a1aad2..afb66cb60 100644 --- a/fractal-matrix-api/src/backend/types.rs +++ b/fractal-matrix-api/src/backend/types.rs @@ -4,15 +4,15 @@ use std::sync::{Arc, Condvar, Mutex}; use crate::error::Error; -use crate::r0::account::Medium; +use crate::r0::contact::get_identifiers::ThirdPartyIdentifier; use crate::r0::thirdparty::get_supported_protocols::ProtocolInstance; +use crate::r0::Medium; use crate::types::Event; use crate::types::Member; use crate::types::Message; use crate::types::Room; use crate::types::Sticker; use crate::types::StickerGroup; -use crate::types::ThirdPartyIdentifier; use crate::cache::CacheMap; use url::Url; diff --git a/fractal-matrix-api/src/backend/user.rs b/fractal-matrix-api/src/backend/user.rs index 9acb8c941..3dcbe51e2 100644 --- a/fractal-matrix-api/src/backend/user.rs +++ b/fractal-matrix-api/src/backend/user.rs @@ -1,7 +1,4 @@ -use log::info; -use serde_json::json; -use std::fs::File; -use std::io::prelude::*; +use std::fs; use crate::backend::types::BKResponse; use crate::backend::types::Backend; @@ -9,155 +6,226 @@ use crate::error::Error; use crate::util::encode_uid; use crate::util::get_user_avatar; use crate::util::get_user_avatar_img; -use crate::util::json_q; -use crate::util::put_media; use crate::util::semaphore; -use crate::util::{build_url, media_url}; +use crate::util::HTTP_CLIENT; +use reqwest::header::HeaderValue; use std::sync::mpsc::Sender; use std::sync::{Arc, Mutex}; use std::thread; -use url::Url; +use crate::identity::r0::association::msisdn::submit_token::request as submit_phone_token_req; +use crate::identity::r0::association::msisdn::submit_token::Body as SubmitPhoneTokenBody; +use crate::identity::r0::association::msisdn::submit_token::Response as SubmitPhoneTokenResponse; +use crate::r0::account::change_password::request as change_password_req; +use crate::r0::account::change_password::Body as ChangePasswordBody; +use crate::r0::account::change_password::Parameters as ChangePasswordParameters; +use crate::r0::account::deactivate::request as deactivate; +use crate::r0::account::deactivate::Body as DeactivateBody; +use crate::r0::account::deactivate::Parameters as DeactivateParameters; use crate::r0::account::AuthenticationData; use crate::r0::account::Identifier; -use crate::r0::account::Medium; -use crate::r0::account::ThreePIDCredentials; use crate::r0::account::UserIdentifier; -use crate::types::AddThreePIDRequest; -use crate::types::ChangePasswordRequest; -use crate::types::DeactivateAccountRequest; -use crate::types::DeleteThreePIDRequest; -use crate::types::EmailTokenRequest; -use crate::types::GetDisplayNameResponse; +use crate::r0::contact::create::request as create_contact; +use crate::r0::contact::create::Body as AddThreePIDBody; +use crate::r0::contact::create::Parameters as AddThreePIDParameters; +use crate::r0::contact::delete::request as delete_contact; +use crate::r0::contact::delete::Body as DeleteThreePIDBody; +use crate::r0::contact::delete::Parameters as DeleteThreePIDParameters; +use crate::r0::contact::get_identifiers::request as get_identifiers; +use crate::r0::contact::get_identifiers::Parameters as ThirdPartyIDParameters; +use crate::r0::contact::get_identifiers::Response as ThirdPartyIDResponse; +use crate::r0::contact::request_verification_token_email::request as request_contact_verification_token_email; +use crate::r0::contact::request_verification_token_email::Body as EmailTokenBody; +use crate::r0::contact::request_verification_token_email::Parameters as EmailTokenParameters; +use crate::r0::contact::request_verification_token_email::Response as EmailTokenResponse; +use crate::r0::contact::request_verification_token_msisdn::request as request_contact_verification_token_msisdn; +use crate::r0::contact::request_verification_token_msisdn::Body as PhoneTokenBody; +use crate::r0::contact::request_verification_token_msisdn::Parameters as PhoneTokenParameters; +use crate::r0::contact::request_verification_token_msisdn::Response as PhoneTokenResponse; +use crate::r0::media::create::request as create_content; +use crate::r0::media::create::Parameters as CreateContentParameters; +use crate::r0::media::create::Response as CreateContentResponse; +use crate::r0::profile::get_display_name::request as get_display_name; +use crate::r0::profile::get_display_name::Response as GetDisplayNameResponse; +use crate::r0::profile::set_avatar_url::request as set_avatar_url; +use crate::r0::profile::set_avatar_url::Body as SetAvatarUrlBody; +use crate::r0::profile::set_avatar_url::Parameters as SetAvatarUrlParameters; +use crate::r0::profile::set_display_name::request as set_display_name; +use crate::r0::profile::set_display_name::Body as SetDisplayNameBody; +use crate::r0::profile::set_display_name::Parameters as SetDisplayNameParameters; +use crate::r0::search::user::request as user_directory; +use crate::r0::search::user::Body as UserDirectoryBody; +use crate::r0::search::user::Parameters as UserDirectoryParameters; +use crate::r0::search::user::Response as UserDirectoryResponse; +use crate::r0::Medium; +use crate::r0::ThreePIDCredentials; use crate::types::Member; -use crate::types::PhoneTokenRequest; -use crate::types::PutDisplayNameRequest; -use crate::types::SearchUserRequest; -use crate::types::SearchUserResponse; -use crate::types::SubmitPhoneTokenRequest; -use crate::types::SubmitPhoneTokenResponse; -use crate::types::ThirdPartyIDResponse; -use crate::types::ThirdPartyTokenResponse; - -use serde_json; -use serde_json::Value as JsonValue; - -pub fn get_username(bk: &Backend) -> Result<(), Error> { - let id = bk.data.lock().unwrap().user_id.clone(); - let url = bk.url(&format!("profile/{}/displayname", encode_uid(&id)), vec![])?; + +pub fn get_username(bk: &Backend) { let tx = bk.tx.clone(); - get!( - &url, - |r: JsonValue| if let Ok(response) = serde_json::from_value::(r) { - let name = response.displayname.unwrap_or(id); - tx.send(BKResponse::Name(name)).unwrap(); - } else { - tx.send(BKResponse::UserNameError(Error::BackendError)) - .unwrap(); - }, - |err| tx.send(BKResponse::UserNameError(err)).unwrap() - ); + let uid = bk.data.lock().unwrap().user_id.clone(); + let base = bk.get_base_url(); + + thread::spawn(move || { + let query = get_display_name(base, &encode_uid(&uid)) + .map_err(Into::into) + .and_then(|request| { + HTTP_CLIENT + .get_client()? + .execute(request)? + .json::() + .map_err(Into::into) + }); - Ok(()) + match query { + Ok(response) => { + let name = response.displayname.unwrap_or(uid); + let _ = tx.send(BKResponse::Name(name)); + } + Err(err) => { + let _ = tx.send(BKResponse::UserNameError(err)); + } + } + }); } -pub fn set_username(bk: &Backend, name: String) -> Result<(), Error> { - let id = bk.data.lock().unwrap().user_id.clone(); - let url = bk.url(&format!("profile/{}/displayname", encode_uid(&id)), vec![])?; +// FIXME: This function manages errors *really* wrong and isn't more async +// than the normal function. It should be removed. +pub fn get_username_async(bk: &Backend, uid: String, tx: Sender) { + let base = bk.get_base_url(); + + thread::spawn(move || { + let query = get_display_name(base, &encode_uid(&uid)) + .map_err::(Into::into) + .and_then(|request| { + HTTP_CLIENT + .get_client()? + .execute(request)? + .json::() + .map_err(Into::into) + }); + + match query { + Ok(response) => { + let name = response.displayname.unwrap_or(uid); + let _ = tx.send(name); + } + Err(_) => { + let _ = tx.send(uid); + } + } + }); +} - let attrs = PutDisplayNameRequest { +pub fn set_username(bk: &Backend, name: String) { + let tx = bk.tx.clone(); + + let base = bk.get_base_url(); + let access_token = bk.data.lock().unwrap().access_token.clone(); + let uid = bk.data.lock().unwrap().user_id.clone(); + let params = SetDisplayNameParameters { access_token }; + let body = SetDisplayNameBody { displayname: Some(name.clone()), }; - let attrs_json = - serde_json::to_value(attrs).expect("Failed to serialize display name setting request"); - let tx = bk.tx.clone(); - query!( - "put", - &url, - &attrs_json, - |_| { - tx.send(BKResponse::SetUserName(name)).unwrap(); - }, - |err| { - tx.send(BKResponse::SetUserNameError(err)).unwrap(); - } - ); + thread::spawn(move || { + let query = set_display_name(base, ¶ms, &body, &encode_uid(&uid)) + .map_err(Into::into) + .and_then(|request| { + HTTP_CLIENT + .get_client()? + .execute(request) + .map_err(Into::into) + }); - Ok(()) + match query { + Ok(_) => { + let _ = tx.send(BKResponse::SetUserName(name)); + } + Err(err) => { + let _ = tx.send(BKResponse::SetUserNameError(err)); + } + } + }); } -pub fn get_threepid(bk: &Backend) -> Result<(), Error> { - let url = bk.url(&format!("account/3pid"), vec![])?; +pub fn get_threepid(bk: &Backend) { let tx = bk.tx.clone(); - get!( - &url, - |r: JsonValue| if let Ok(response) = serde_json::from_value::(r) { - tx.send(BKResponse::GetThreePID(response.threepids)) - .unwrap(); - } else { - tx.send(BKResponse::GetThreePIDError(Error::BackendError)) - .unwrap(); - }, - |err| tx.send(BKResponse::GetThreePIDError(err)).unwrap() - ); - Ok(()) + let base = bk.get_base_url(); + let access_token = bk.data.lock().unwrap().access_token.clone(); + let params = ThirdPartyIDParameters { access_token }; + + thread::spawn(move || { + let query = get_identifiers(base, ¶ms) + .map_err(Into::into) + .and_then(|request| { + HTTP_CLIENT + .get_client()? + .execute(request)? + .json::() + .map_err(Into::into) + }); + + match query { + Ok(response) => { + let _ = tx.send(BKResponse::GetThreePID(response.threepids)); + } + Err(err) => { + let _ = tx.send(BKResponse::GetThreePIDError(err)); + } + } + }); } -pub fn get_email_token( - bk: &Backend, - identity: String, - email: String, - client_secret: String, -) -> Result<(), Error> { - let url = bk.url("account/3pid/email/requestToken", vec![])?; +pub fn get_email_token(bk: &Backend, identity: String, email: String, client_secret: String) { + let tx = bk.tx.clone(); - let attrs = EmailTokenRequest { + let base = bk.get_base_url(); + let access_token = bk.data.lock().unwrap().access_token.clone(); + let params = EmailTokenParameters { access_token }; + let body = EmailTokenBody { id_server: identity[8..].into(), client_secret: client_secret.clone(), - email: email, + email, send_attempt: 1, next_link: None, }; - let attrs_json = serde_json::to_value(attrs).expect("Failed to serialize email token request"); + thread::spawn(move || { + let query = request_contact_verification_token_email(base, ¶ms, &body) + .map_err(Into::into) + .and_then(|request| { + HTTP_CLIENT + .get_client()? + .execute(request)? + .json::() + .map_err(Into::into) + }); - let tx = bk.tx.clone(); - post!( - &url, - &attrs_json, - |r: JsonValue| if let Ok(response) = serde_json::from_value::(r) { - tx.send(BKResponse::GetTokenEmail(response.sid, client_secret)) - .unwrap(); - } else { - tx.send(BKResponse::GetTokenEmailError(Error::BackendError)) - .unwrap(); - }, - |err| match err { - Error::MatrixError(ref js) + match query { + Ok(response) => { + let _ = tx.send(BKResponse::GetTokenEmail(response.sid, client_secret)); + } + Err(Error::MatrixError(ref js)) if js["errcode"].as_str().unwrap_or_default() == "M_THREEPID_IN_USE" => { - tx.send(BKResponse::GetTokenEmailUsed).unwrap(); + let _ = tx.send(BKResponse::GetTokenEmailUsed); } - _ => { - tx.send(BKResponse::GetTokenEmailError(err)).unwrap(); + Err(err) => { + let _ = tx.send(BKResponse::GetTokenEmailError(err)); } } - ); - - Ok(()) + }); } -pub fn get_phone_token( - bk: &Backend, - identity: String, - phone: String, - client_secret: String, -) -> Result<(), Error> { - let url = bk.url(&format!("account/3pid/msisdn/requestToken"), vec![])?; +pub fn get_phone_token(bk: &Backend, identity: String, phone: String, client_secret: String) { + let tx = bk.tx.clone(); - let attrs = PhoneTokenRequest { + let base = bk.get_base_url(); + let access_token = bk.data.lock().unwrap().access_token.clone(); + let params = PhoneTokenParameters { access_token }; + let body = PhoneTokenBody { id_server: identity[8..].into(), client_secret: client_secret.clone(), phone_number: phone, @@ -166,42 +234,40 @@ pub fn get_phone_token( next_link: None, }; - let attrs_json = serde_json::to_value(attrs).expect("Failed to serialize phone token request"); + thread::spawn(move || { + let query = request_contact_verification_token_msisdn(base, ¶ms, &body) + .map_err(Into::into) + .and_then(|request| { + HTTP_CLIENT + .get_client()? + .execute(request)? + .json::() + .map_err(Into::into) + }); - let tx = bk.tx.clone(); - post!( - &url, - &attrs_json, - |r: JsonValue| if let Ok(response) = serde_json::from_value::(r) { - tx.send(BKResponse::GetTokenPhone(response.sid, client_secret)) - .unwrap(); - } else { - tx.send(BKResponse::GetTokenPhoneError(Error::BackendError)) - .unwrap(); - }, - |err| match err { - Error::MatrixError(ref js) + match query { + Ok(response) => { + let _ = tx.send(BKResponse::GetTokenPhone(response.sid, client_secret)); + } + Err(Error::MatrixError(ref js)) if js["errcode"].as_str().unwrap_or_default() == "M_THREEPID_IN_USE" => { - tx.send(BKResponse::GetTokenPhoneUsed).unwrap(); + let _ = tx.send(BKResponse::GetTokenPhoneUsed); } - _ => { - tx.send(BKResponse::GetTokenPhoneError(err)).unwrap(); + Err(err) => { + let _ = tx.send(BKResponse::GetTokenPhoneError(err)); } } - ); - - Ok(()) + }); } -pub fn add_threepid( - bk: &Backend, - identity: String, - client_secret: String, - sid: String, -) -> Result<(), Error> { - let url = bk.url(&format!("account/3pid"), vec![])?; - let attrs = AddThreePIDRequest { +pub fn add_threepid(bk: &Backend, identity: String, client_secret: String, sid: String) { + let tx = bk.tx.clone(); + + let base = bk.get_base_url(); + let access_token = bk.data.lock().unwrap().access_token.clone(); + let params = AddThreePIDParameters { access_token }; + let body = AddThreePIDBody { three_pid_creds: ThreePIDCredentials { id_server: identity[8..].into(), sid: sid.clone(), @@ -210,97 +276,96 @@ pub fn add_threepid( bind: true, }; - let attrs_json = serde_json::to_value(attrs) - .expect("Failed to serialize add third party information request"); + thread::spawn(move || { + let query = create_contact(base, ¶ms, &body) + .map_err(Into::into) + .and_then(|request| { + HTTP_CLIENT + .get_client()? + .execute(request) + .map_err(Into::into) + }); - let tx = bk.tx.clone(); - post!( - &url, - &attrs_json, - |_| { - tx.send(BKResponse::AddThreePID(sid)).unwrap(); - }, - |err| { - tx.send(BKResponse::AddThreePIDError(err)).unwrap(); + match query { + Ok(_) => { + let _ = tx.send(BKResponse::AddThreePID(sid)); + } + Err(err) => { + let _ = tx.send(BKResponse::AddThreePIDError(err)); + } } - ); - - Ok(()) + }); } -pub fn submit_phone_token( - bk: &Backend, - url: &str, - client_secret: String, - sid: String, - token: String, -) -> Result<(), Error> { - let path = "/_matrix/identity/api/v1/validate/msisdn/submitToken"; - let url = build_url(&Url::parse(url)?, path, &[])?; - - let attrs = SubmitPhoneTokenRequest { +pub fn submit_phone_token(bk: &Backend, client_secret: String, sid: String, token: String) { + let tx = bk.tx.clone(); + + let base = bk.get_base_url(); + let body = SubmitPhoneTokenBody { sid: sid.clone(), client_secret: client_secret.clone(), token, }; - let attrs_json = - serde_json::to_value(attrs).expect("Failed to serialize phone token submit request"); - let tx = bk.tx.clone(); - post!( - &url, - &attrs_json, - |r: JsonValue| if let Ok(response) = serde_json::from_value::(r) { - let result = Some(sid).filter(|_| response.success); - tx.send(BKResponse::SubmitPhoneToken(result, client_secret)) - .unwrap(); - } else { - tx.send(BKResponse::SubmitPhoneTokenError(Error::BackendError)) - .unwrap(); - }, - |err| { - tx.send(BKResponse::SubmitPhoneTokenError(err)).unwrap(); - } - ); + thread::spawn(move || { + let query = submit_phone_token_req(base, &body) + .map_err(Into::into) + .and_then(|request| { + HTTP_CLIENT + .get_client()? + .execute(request)? + .json::() + .map_err(Into::into) + }); - Ok(()) + match query { + Ok(response) => { + let result = Some(sid).filter(|_| response.success); + let _ = tx.send(BKResponse::SubmitPhoneToken(result, client_secret)); + } + Err(err) => { + let _ = tx.send(BKResponse::SubmitPhoneTokenError(err)); + } + } + }); } pub fn delete_three_pid(bk: &Backend, medium: Medium, address: String) { - let baseu = bk.get_base_url(); - let tk = bk.data.lock().unwrap().access_token.clone(); - let mut url = baseu - .join("/_matrix/client/r0/account/3pid/delete") - .expect("Wrong URL in delete_three_pid()"); - url.query_pairs_mut() - .clear() - .append_pair("access_token", &tk); - let attrs = DeleteThreePIDRequest { medium, address }; - - let attrs_json = - serde_json::to_value(attrs).expect("Failed to serialize third party ID delete request"); let tx = bk.tx.clone(); - post!( - &url, - &attrs_json, - |_r: JsonValue| { - tx.send(BKResponse::DeleteThreePID).unwrap(); - }, - |err| { - tx.send(BKResponse::DeleteThreePIDError(err)).unwrap(); + + let base = bk.get_base_url(); + let access_token = bk.data.lock().unwrap().access_token.clone(); + let params = DeleteThreePIDParameters { access_token }; + let body = DeleteThreePIDBody { address, medium }; + + thread::spawn(move || { + let query = delete_contact(base, ¶ms, &body) + .map_err(Into::into) + .and_then(|request| { + HTTP_CLIENT + .get_client()? + .execute(request) + .map_err(Into::into) + }); + + match query { + Ok(_) => { + let _ = tx.send(BKResponse::DeleteThreePID); + } + Err(err) => { + let _ = tx.send(BKResponse::DeleteThreePIDError(err)); + } } - ); + }); } -pub fn change_password( - bk: &Backend, - user: String, - old_password: String, - new_password: String, -) -> Result<(), Error> { - let url = bk.url(&format!("account/password"), vec![])?; +pub fn change_password(bk: &Backend, user: String, old_password: String, new_password: String) { + let tx = bk.tx.clone(); - let attrs = ChangePasswordRequest { + let access_token = bk.data.lock().unwrap().access_token.clone(); + let base = bk.get_base_url(); + let params = ChangePasswordParameters { access_token }; + let body = ChangePasswordBody { new_password, auth: Some(AuthenticationData::Password { identifier: Identifier::new(UserIdentifier::User { user }), @@ -309,28 +374,34 @@ pub fn change_password( }), }; - let attrs_json = - serde_json::to_value(attrs).expect("Failed to serialize password change request"); - let tx = bk.tx.clone(); - post!( - &url, - &attrs_json, - |r: JsonValue| { - info!("{}", r); - tx.send(BKResponse::ChangePassword).unwrap(); - }, - |err| { - tx.send(BKResponse::ChangePasswordError(err)).unwrap(); - } - ); + thread::spawn(move || { + let query = change_password_req(base, ¶ms, &body) + .map_err(Into::into) + .and_then(|request| { + HTTP_CLIENT + .get_client()? + .execute(request) + .map_err(Into::into) + }); - Ok(()) + match query { + Ok(_) => { + let _ = tx.send(BKResponse::ChangePassword); + } + Err(err) => { + let _ = tx.send(BKResponse::ChangePasswordError(err)); + } + } + }); } -pub fn account_destruction(bk: &Backend, user: String, password: String) -> Result<(), Error> { - let url = bk.url(&format!("account/deactivate"), vec![])?; +pub fn account_destruction(bk: &Backend, user: String, password: String) { + let tx = bk.tx.clone(); - let attrs = DeactivateAccountRequest { + let base = bk.get_base_url(); + let access_token = bk.data.lock().unwrap().access_token.clone(); + let params = DeactivateParameters { access_token }; + let body = DeactivateBody { auth: Some(AuthenticationData::Password { identifier: Identifier::new(UserIdentifier::User { user }), password, @@ -338,49 +409,120 @@ pub fn account_destruction(bk: &Backend, user: String, password: String) -> Resu }), }; - let attrs_json = - serde_json::to_value(attrs).expect("Failed to serialize account deactivation request"); - let tx = bk.tx.clone(); - post!( - &url, - &attrs_json, - |r: JsonValue| { - info!("{}", r); - tx.send(BKResponse::AccountDestruction).unwrap(); - }, - |err| { - tx.send(BKResponse::AccountDestructionError(err)).unwrap(); - } - ); + thread::spawn(move || { + let query = deactivate(base, ¶ms, &body) + .map_err(Into::into) + .and_then(|request| { + HTTP_CLIENT + .get_client()? + .execute(request) + .map_err(Into::into) + }); - Ok(()) + match query { + Ok(_) => { + let _ = tx.send(BKResponse::AccountDestruction); + } + Err(err) => { + let _ = tx.send(BKResponse::AccountDestructionError(err)); + } + } + }); } -pub fn get_avatar(bk: &Backend) -> Result<(), Error> { - let baseu = bk.get_base_url(); +pub fn get_avatar(bk: &Backend) { + let base = bk.get_base_url(); let userid = bk.data.lock().unwrap().user_id.clone(); let tx = bk.tx.clone(); - thread::spawn(move || match get_user_avatar(&baseu, &userid) { + thread::spawn(move || match get_user_avatar(&base, &userid) { Ok((_, fname)) => { - tx.send(BKResponse::Avatar(fname)).unwrap(); + let _ = tx.send(BKResponse::Avatar(fname)); } Err(err) => { - tx.send(BKResponse::AvatarError(err)).unwrap(); + let _ = tx.send(BKResponse::AvatarError(err)); } }); +} + +pub fn get_avatar_async(bk: &Backend, member: Option, tx: Sender) { + if let Some(member) = member { + let base = bk.get_base_url(); + let uid = member.uid.clone(); + let avatar = member.avatar.clone().unwrap_or_default(); + + semaphore( + bk.limit_threads.clone(), + move || match get_user_avatar_img(&base, &uid, &avatar) { + Ok(fname) => { + let _ = tx.send(fname); + } + Err(_) => { + let _ = tx.send(Default::default()); + } + }, + ); + } else { + let _ = tx.send(Default::default()); + } +} + +pub fn set_user_avatar(bk: &Backend, avatar: String) { + let tx = bk.tx.clone(); - Ok(()) + let base = bk.get_base_url(); + let id = bk.data.lock().unwrap().user_id.clone(); + let access_token = bk.data.lock().unwrap().access_token.clone(); + let params_upload = CreateContentParameters { + access_token: access_token.clone(), + filename: None, + }; + + thread::spawn(move || { + let query = fs::read(&avatar).map_err(Into::into).and_then(|contents| { + let (mime, _) = gio::content_type_guess(None, &contents); + let mime_value = HeaderValue::from_str(&mime).or(Err(Error::BackendError))?; + let upload_response = + create_content(base.clone(), ¶ms_upload, contents, Some(mime_value)) + .map_err::(Into::into) + .and_then(|request| { + HTTP_CLIENT + .get_client()? + .execute(request)? + .json::() + .map_err(Into::into) + })?; + + let params_avatar = SetAvatarUrlParameters { access_token }; + let body = SetAvatarUrlBody { + avatar_url: Some(upload_response.content_uri), + }; + + set_avatar_url(base, ¶ms_avatar, &body, &encode_uid(&id)) + .map_err(Into::into) + .and_then(|request| { + HTTP_CLIENT + .get_client()? + .execute(request) + .map_err(Into::into) + }) + }); + + match query { + Ok(_) => { + let _ = tx.send(BKResponse::SetUserAvatar(avatar)); + } + Err(err) => { + let _ = tx.send(BKResponse::SetUserAvatarError(err)); + } + } + }); } -pub fn get_user_info_async( - bk: &mut Backend, - uid: &str, - tx: Option>, -) -> Result<(), Error> { +pub fn get_user_info_async(bk: &mut Backend, uid: &str, tx: Option>) { let baseu = bk.get_base_url(); - let u = String::from(uid); + let u = uid.to_string(); if let Some(info) = bk.user_info_cache.get(&u) { if let Some(tx) = tx.clone() { @@ -390,7 +532,7 @@ pub fn get_user_info_async( tx.send(i).unwrap(); }); } - return Ok(()); + return; } let info = Arc::new(Mutex::new((String::new(), String::new()))); @@ -417,118 +559,38 @@ pub fn get_user_info_async( }); bk.user_info_cache.insert(cache_key, cache_value); - - Ok(()) -} - -pub fn get_username_async(bk: &Backend, uid: String, tx: Sender) -> Result<(), Error> { - let url = bk.url(&format!("profile/{}/displayname", encode_uid(&uid)), vec![])?; - get!( - &url, - |r: JsonValue| if let Ok(response) = serde_json::from_value::(r) { - let name = response.displayname.unwrap_or(uid); - tx.send(name).unwrap(); - } else { - tx.send(uid.to_string()).unwrap(); - }, - |_| tx.send(uid.to_string()).unwrap() - ); - - Ok(()) -} - -pub fn get_avatar_async( - bk: &Backend, - member: Option, - tx: Sender, -) -> Result<(), Error> { - let baseu = bk.get_base_url(); - - if member.is_none() { - tx.send(String::new()).unwrap(); - return Ok(()); - } - - let m = member.unwrap(); - - let uid = m.uid.clone(); - let avatar = m.avatar.clone(); - - semaphore(bk.limit_threads.clone(), move || match get_user_avatar_img( - &baseu, - &uid, - &avatar.unwrap_or_default(), - ) { - Ok(fname) => { - tx.send(fname.clone()).unwrap(); - } - Err(_) => { - tx.send(String::new()).unwrap(); - } - }); - - Ok(()) } -pub fn set_user_avatar(bk: &Backend, avatar: String) -> Result<(), Error> { - let baseu = bk.get_base_url(); - let id = bk.data.lock().unwrap().user_id.clone(); - let tk = bk.data.lock().unwrap().access_token.clone(); - let params = &[("access_token", tk.clone())]; - let mediaurl = media_url(&baseu, "upload", params)?; - let url = bk.url(&format!("profile/{}/avatar_url", encode_uid(&id)), vec![])?; - - let mut file = File::open(&avatar)?; - let mut contents: Vec = vec![]; - file.read_to_end(&mut contents)?; - +pub fn search(bk: &Backend, search_term: String) { let tx = bk.tx.clone(); - thread::spawn(move || { - match put_media(mediaurl.as_str(), contents) { - Err(err) => { - tx.send(BKResponse::SetUserAvatarError(err)).unwrap(); - } - Ok(js) => { - let uri = js["content_uri"].as_str().unwrap_or_default(); - let attrs = json!({ "avatar_url": uri }); - put!( - &url, - &attrs, - |_| tx.send(BKResponse::SetUserAvatar(avatar)).unwrap(), - |err| tx.send(BKResponse::SetUserAvatarError(err)).unwrap() - ); - } - }; - }); - - Ok(()) -} -pub fn search(bk: &Backend, search_term: String) -> Result<(), Error> { - let url = bk.url(&format!("user_directory/search"), vec![])?; - - let attrs = SearchUserRequest { + let base = bk.get_base_url(); + let access_token = bk.data.lock().unwrap().access_token.clone(); + let params = UserDirectoryParameters { access_token }; + let body = UserDirectoryBody { search_term, ..Default::default() }; - let attrs_json = - serde_json::to_value(attrs).expect("Failed to serialize user directory search request"); - let tx = bk.tx.clone(); - post!( - &url, - &attrs_json, - |r: JsonValue| if let Ok(response) = serde_json::from_value::(r) { - let users = response.results.into_iter().map(Into::into).collect(); - tx.send(BKResponse::UserSearch(users)).unwrap(); - } else { - tx.send(BKResponse::CommandError(Error::BackendError)) - .unwrap(); - }, - |err| { - tx.send(BKResponse::CommandError(err)).unwrap(); - } - ); + thread::spawn(move || { + let query = user_directory(base, ¶ms, &body) + .map_err(Into::into) + .and_then(|request| { + HTTP_CLIENT + .get_client()? + .execute(request)? + .json::() + .map_err(Into::into) + }); - Ok(()) + match query { + Ok(response) => { + let users = response.results.into_iter().map(Into::into).collect(); + let _ = tx.send(BKResponse::UserSearch(users)); + } + Err(err) => { + let _ = tx.send(BKResponse::CommandError(err)); + } + } + }); } diff --git a/fractal-matrix-api/src/identity.rs b/fractal-matrix-api/src/identity.rs new file mode 100644 index 000000000..026b99763 --- /dev/null +++ b/fractal-matrix-api/src/identity.rs @@ -0,0 +1 @@ +pub mod r0; diff --git a/fractal-matrix-api/src/identity/r0.rs b/fractal-matrix-api/src/identity/r0.rs new file mode 100644 index 000000000..6cddc6e68 --- /dev/null +++ b/fractal-matrix-api/src/identity/r0.rs @@ -0,0 +1 @@ +pub mod association; diff --git a/fractal-matrix-api/src/identity/r0/association.rs b/fractal-matrix-api/src/identity/r0/association.rs new file mode 100644 index 000000000..6872e520b --- /dev/null +++ b/fractal-matrix-api/src/identity/r0/association.rs @@ -0,0 +1 @@ +pub mod msisdn; diff --git a/fractal-matrix-api/src/identity/r0/association/msisdn.rs b/fractal-matrix-api/src/identity/r0/association/msisdn.rs new file mode 100644 index 000000000..73f1eccd5 --- /dev/null +++ b/fractal-matrix-api/src/identity/r0/association/msisdn.rs @@ -0,0 +1 @@ +pub mod submit_token; diff --git a/fractal-matrix-api/src/identity/r0/association/msisdn/submit_token.rs b/fractal-matrix-api/src/identity/r0/association/msisdn/submit_token.rs new file mode 100644 index 000000000..7d7a47eb7 --- /dev/null +++ b/fractal-matrix-api/src/identity/r0/association/msisdn/submit_token.rs @@ -0,0 +1,25 @@ +use reqwest::Client; +use reqwest::Error; +use reqwest::Request; +use serde::{Deserialize, Serialize}; +use url::Url; + +#[derive(Clone, Debug, Serialize)] +pub struct Body { + pub sid: String, + pub client_secret: String, + pub token: String, +} + +#[derive(Clone, Debug, Deserialize)] +pub struct Response { + pub success: bool, +} + +pub fn request(base: Url, body: &Body) -> Result { + let url = base + .join("/_matrix/identity/api/v1/validate/msisdn/submitToken") + .expect("Malformed URL in msisdn submit_token"); + + Client::new().post(url).json(body).build() +} diff --git a/fractal-matrix-api/src/lib.rs b/fractal-matrix-api/src/lib.rs index 8f600d45c..bc71a688a 100644 --- a/fractal-matrix-api/src/lib.rs +++ b/fractal-matrix-api/src/lib.rs @@ -6,6 +6,7 @@ pub mod globals; pub mod backend; pub mod cache; mod client; +pub mod identity; mod model; pub mod r0; mod ser; diff --git a/fractal-matrix-api/src/meson.build b/fractal-matrix-api/src/meson.build index 8a65d647f..937aff554 100644 --- a/fractal-matrix-api/src/meson.build +++ b/fractal-matrix-api/src/meson.build @@ -8,6 +8,10 @@ api_sources = files( 'backend/sync.rs', 'backend/types.rs', 'backend/user.rs', + 'identity/r0/association/msisdn/submit_token.rs', + 'identity/r0/association/msisdn.rs', + 'identity/r0/association.rs', + 'identity/r0.rs', 'model/event.rs', 'model/fileinfo.rs', 'model/member.rs', @@ -15,17 +19,33 @@ api_sources = files( 'model/mod.rs', 'model/room.rs', 'model/stickers.rs', - 'model/user.rs', + 'r0/account/change_password.rs', + 'r0/account/deactivate.rs', 'r0/account/login.rs', 'r0/account/logout.rs', 'r0/account/register.rs', + 'r0/contact/create.rs', + 'r0/contact/delete.rs', + 'r0/contact/get_identifiers.rs', + 'r0/contact/request_verification_token_email.rs', + 'r0/contact/request_verification_token_msisdn.rs', 'r0/directory/post_public_rooms.rs', + 'r0/media/create.rs', + 'r0/profile/get_display_name.rs', + 'r0/profile/get_profile.rs', + 'r0/profile/set_avatar_url.rs', + 'r0/profile/set_display_name.rs', + 'r0/search/user.rs', 'r0/server/domain_info.rs', 'r0/sync/sync_events.rs', 'r0/thirdparty/get_supported_protocols.rs', 'r0/account.rs', + 'r0/contact.rs', 'r0/directory.rs', 'r0/filter.rs', + 'r0/media.rs', + 'r0/profile.rs', + 'r0/search.rs', 'r0/server.rs', 'r0/sync.rs', 'r0/thirdparty.rs', @@ -33,6 +53,7 @@ api_sources = files( 'client.rs', 'error.rs', 'globals.rs', + 'identity.rs', 'lib.rs', 'r0.rs', 'ser.rs', diff --git a/fractal-matrix-api/src/model/member.rs b/fractal-matrix-api/src/model/member.rs index a2ff4b1b7..fd1fbfd51 100644 --- a/fractal-matrix-api/src/model/member.rs +++ b/fractal-matrix-api/src/model/member.rs @@ -1,4 +1,4 @@ -use crate::types::User; +use crate::r0::search::user::User; use serde::{Deserialize, Serialize}; use std::collections::HashMap; diff --git a/fractal-matrix-api/src/model/mod.rs b/fractal-matrix-api/src/model/mod.rs index 14db54565..836fd9353 100644 --- a/fractal-matrix-api/src/model/mod.rs +++ b/fractal-matrix-api/src/model/mod.rs @@ -4,4 +4,3 @@ pub mod member; pub mod message; pub mod room; pub mod stickers; -pub mod user; diff --git a/fractal-matrix-api/src/model/user.rs b/fractal-matrix-api/src/model/user.rs deleted file mode 100644 index 94a6cd2fa..000000000 --- a/fractal-matrix-api/src/model/user.rs +++ /dev/null @@ -1,126 +0,0 @@ -use crate::r0::account::{AuthenticationData, Medium, ThreePIDCredentials}; -use serde::{Deserialize, Serialize}; -use std::ops::Not; - -#[derive(Clone, Debug, Deserialize)] -pub struct GetDisplayNameResponse { - pub displayname: Option, -} - -#[derive(Clone, Debug, Serialize)] -pub struct PutDisplayNameRequest { - #[serde(skip_serializing_if = "Option::is_none")] - pub displayname: Option, -} - -#[derive(Debug, Clone, Deserialize)] -pub struct ThirdPartyIDResponse { - pub threepids: Vec, -} - -#[derive(Debug, Clone, Deserialize)] -pub struct ThirdPartyIdentifier { - pub added_at: u64, - pub medium: Medium, - pub validated_at: u64, - pub address: String, -} - -#[derive(Clone, Debug, Serialize)] -pub struct EmailTokenRequest { - pub client_secret: String, - pub email: String, - pub id_server: String, - pub send_attempt: u64, - #[serde(skip_serializing_if = "Option::is_none")] - pub next_link: Option, -} - -#[derive(Clone, Debug, Serialize)] -pub struct PhoneTokenRequest { - pub client_secret: String, - pub phone_number: String, - pub country: String, - pub id_server: String, - pub send_attempt: u64, - #[serde(skip_serializing_if = "Option::is_none")] - pub next_link: Option, -} - -#[derive(Clone, Debug, Deserialize)] -pub struct ThirdPartyTokenResponse { - pub sid: String, -} - -#[derive(Clone, Debug, Serialize)] -pub struct AddThreePIDRequest { - pub three_pid_creds: ThreePIDCredentials, - #[serde(skip_serializing_if = "Not::not")] - pub bind: bool, -} - -#[derive(Clone, Debug, Serialize)] -pub struct SubmitPhoneTokenRequest { - pub sid: String, - pub client_secret: String, - pub token: String, -} - -#[derive(Clone, Debug, Deserialize)] -pub struct SubmitPhoneTokenResponse { - pub success: bool, -} - -#[derive(Clone, Debug, Serialize)] -pub struct DeleteThreePIDRequest { - pub medium: Medium, - pub address: String, -} - -#[derive(Clone, Debug, Serialize)] -pub struct ChangePasswordRequest { - pub new_password: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub auth: Option, -} - -#[derive(Clone, Debug, Serialize)] -pub struct DeactivateAccountRequest { - #[serde(skip_serializing_if = "Option::is_none")] - pub auth: Option, -} - -#[derive(Clone, Debug, Serialize)] -pub struct SearchUserRequest { - pub search_term: String, - #[serde(skip_serializing_if = "u64_is_10")] - pub limit: u64, -} - -impl Default for SearchUserRequest { - fn default() -> Self { - Self { - search_term: Default::default(), - limit: 10, - } - } -} - -#[derive(Clone, Debug, Deserialize)] -pub struct SearchUserResponse { - pub results: Vec, - pub limited: bool, -} - -#[derive(Clone, Debug, Deserialize)] -pub struct User { - pub user_id: String, - #[serde(default)] - pub display_name: Option, - #[serde(default)] - pub avatar_url: Option, -} - -fn u64_is_10(number: &u64) -> bool { - number == &10 -} diff --git a/fractal-matrix-api/src/r0.rs b/fractal-matrix-api/src/r0.rs index 13090a6c1..86c74a3a0 100644 --- a/fractal-matrix-api/src/r0.rs +++ b/fractal-matrix-api/src/r0.rs @@ -1,6 +1,26 @@ pub mod account; +pub mod contact; pub mod directory; pub mod filter; +pub mod media; +pub mod profile; +pub mod search; pub mod server; pub mod sync; pub mod thirdparty; + +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename = "lowercase")] +pub enum Medium { + Email, + MsIsdn, +} + +#[derive(Clone, Debug, Serialize)] +pub struct ThreePIDCredentials { + pub client_secret: String, + pub id_server: String, + pub sid: String, +} diff --git a/fractal-matrix-api/src/r0/account.rs b/fractal-matrix-api/src/r0/account.rs index 3cad3482a..9e4b667d0 100644 --- a/fractal-matrix-api/src/r0/account.rs +++ b/fractal-matrix-api/src/r0/account.rs @@ -1,16 +1,11 @@ +pub mod change_password; +pub mod deactivate; pub mod login; pub mod logout; pub mod register; -use serde::{Deserialize, Serialize}; - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub enum Medium { - #[serde(rename = "email")] - Email, - #[serde(rename = "msisdn")] - MsIsdn, -} +use crate::r0::{Medium, ThreePIDCredentials}; +use serde::Serialize; #[derive(Clone, Debug, Serialize)] #[serde(tag = "type")] @@ -66,13 +61,6 @@ impl Identifier { } } -#[derive(Clone, Debug, Serialize)] -pub struct ThreePIDCredentials { - pub client_secret: String, - pub id_server: String, - pub sid: String, -} - #[derive(Clone, Debug, Serialize)] #[serde(tag = "type")] pub enum AuthenticationData { diff --git a/fractal-matrix-api/src/r0/account/change_password.rs b/fractal-matrix-api/src/r0/account/change_password.rs new file mode 100644 index 000000000..21e9cd019 --- /dev/null +++ b/fractal-matrix-api/src/r0/account/change_password.rs @@ -0,0 +1,27 @@ +use super::AuthenticationData; +use reqwest::Client; +use reqwest::Error; +use reqwest::Request; +use serde::Serialize; +use url::Url; + +#[derive(Clone, Debug, Serialize)] +pub struct Parameters { + #[serde(skip_serializing_if = "String::is_empty")] + pub access_token: String, +} + +#[derive(Clone, Debug, Serialize)] +pub struct Body { + pub new_password: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub auth: Option, +} + +pub fn request(base: Url, params: &Parameters, body: &Body) -> Result { + let url = base + .join("/_matrix/client/r0/account/password") + .expect("Malformed URL in change_password"); + + Client::new().post(url).query(params).json(body).build() +} diff --git a/fractal-matrix-api/src/r0/account/deactivate.rs b/fractal-matrix-api/src/r0/account/deactivate.rs new file mode 100644 index 000000000..6a322a805 --- /dev/null +++ b/fractal-matrix-api/src/r0/account/deactivate.rs @@ -0,0 +1,26 @@ +use super::AuthenticationData; +use reqwest::Client; +use reqwest::Error; +use reqwest::Request; +use serde::Serialize; +use url::Url; + +#[derive(Clone, Debug, Serialize)] +pub struct Parameters { + #[serde(skip_serializing_if = "String::is_empty")] + pub access_token: String, +} + +#[derive(Clone, Debug, Serialize)] +pub struct Body { + #[serde(skip_serializing_if = "Option::is_none")] + pub auth: Option, +} + +pub fn request(base: Url, params: &Parameters, body: &Body) -> Result { + let url = base + .join("/_matrix/client/r0/account/deactivate") + .expect("Malformed URL in deactivate"); + + Client::new().post(url).query(params).json(body).build() +} diff --git a/fractal-matrix-api/src/r0/contact.rs b/fractal-matrix-api/src/r0/contact.rs new file mode 100644 index 000000000..972b7381e --- /dev/null +++ b/fractal-matrix-api/src/r0/contact.rs @@ -0,0 +1,5 @@ +pub mod create; +pub mod delete; +pub mod get_identifiers; +pub mod request_verification_token_email; +pub mod request_verification_token_msisdn; diff --git a/fractal-matrix-api/src/r0/contact/create.rs b/fractal-matrix-api/src/r0/contact/create.rs new file mode 100644 index 000000000..76db18929 --- /dev/null +++ b/fractal-matrix-api/src/r0/contact/create.rs @@ -0,0 +1,27 @@ +use crate::r0::ThreePIDCredentials; +use reqwest::Client; +use reqwest::Error; +use reqwest::Request; +use serde::Serialize; +use std::ops::Not; +use url::Url; + +#[derive(Debug, Clone, Serialize)] +pub struct Parameters { + pub access_token: String, +} + +#[derive(Clone, Debug, Serialize)] +pub struct Body { + pub three_pid_creds: ThreePIDCredentials, + #[serde(skip_serializing_if = "Not::not")] + pub bind: bool, +} + +pub fn request(base: Url, params: &Parameters, body: &Body) -> Result { + let url = base + .join("/_matrix/client/r0/account/3pid") + .expect("Malformed URL in contact create"); + + Client::new().post(url).query(params).json(body).build() +} diff --git a/fractal-matrix-api/src/r0/contact/delete.rs b/fractal-matrix-api/src/r0/contact/delete.rs new file mode 100644 index 000000000..541b0058a --- /dev/null +++ b/fractal-matrix-api/src/r0/contact/delete.rs @@ -0,0 +1,25 @@ +use crate::r0::Medium; +use reqwest::Client; +use reqwest::Error; +use reqwest::Request; +use serde::Serialize; +use url::Url; + +#[derive(Debug, Clone, Serialize)] +pub struct Parameters { + pub access_token: String, +} + +#[derive(Clone, Debug, Serialize)] +pub struct Body { + pub address: String, + pub medium: Medium, +} + +pub fn request(base: Url, params: &Parameters, body: &Body) -> Result { + let url = base + .join("/_matrix/client/r0/account/3pid/delete") + .expect("Malformed URL in contact delete"); + + Client::new().post(url).query(params).json(body).build() +} diff --git a/fractal-matrix-api/src/r0/contact/get_identifiers.rs b/fractal-matrix-api/src/r0/contact/get_identifiers.rs new file mode 100644 index 000000000..50d1d1d62 --- /dev/null +++ b/fractal-matrix-api/src/r0/contact/get_identifiers.rs @@ -0,0 +1,33 @@ +use crate::r0::Medium; +use reqwest::Client; +use reqwest::Error; +use reqwest::Request; +use serde::{Deserialize, Serialize}; +use url::Url; + +#[derive(Debug, Clone, Serialize)] +pub struct Parameters { + pub access_token: String, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct Response { + #[serde(default)] + pub threepids: Vec, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct ThirdPartyIdentifier { + pub added_at: u64, + pub medium: Medium, + pub validated_at: u64, + pub address: String, +} + +pub fn request(base: Url, params: &Parameters) -> Result { + let url = base + .join("/_matrix/client/r0/account/3pid") + .expect("Malformed URL in get_identifiers"); + + Client::new().get(url).query(params).build() +} diff --git a/fractal-matrix-api/src/r0/contact/request_verification_token_email.rs b/fractal-matrix-api/src/r0/contact/request_verification_token_email.rs new file mode 100644 index 000000000..7ae47cc36 --- /dev/null +++ b/fractal-matrix-api/src/r0/contact/request_verification_token_email.rs @@ -0,0 +1,33 @@ +use reqwest::Client; +use reqwest::Error; +use reqwest::Request; +use serde::{Deserialize, Serialize}; +use url::Url; + +#[derive(Clone, Debug, Serialize)] +pub struct Parameters { + pub access_token: String, +} + +#[derive(Clone, Debug, Serialize)] +pub struct Body { + pub client_secret: String, + pub email: String, + pub id_server: String, + pub send_attempt: u64, + #[serde(skip_serializing_if = "Option::is_none")] + pub next_link: Option, +} + +#[derive(Clone, Debug, Deserialize)] +pub struct Response { + pub sid: String, +} + +pub fn request(base: Url, params: &Parameters, body: &Body) -> Result { + let url = base + .join("/_matrix/client/r0/account/3pid/email/requestToken") + .expect("Malformed URL in request_verification_token_email"); + + Client::new().post(url).query(params).json(body).build() +} diff --git a/fractal-matrix-api/src/r0/contact/request_verification_token_msisdn.rs b/fractal-matrix-api/src/r0/contact/request_verification_token_msisdn.rs new file mode 100644 index 000000000..c1dae91ea --- /dev/null +++ b/fractal-matrix-api/src/r0/contact/request_verification_token_msisdn.rs @@ -0,0 +1,34 @@ +use reqwest::Client; +use reqwest::Error; +use reqwest::Request; +use serde::{Deserialize, Serialize}; +use url::Url; + +#[derive(Clone, Debug, Serialize)] +pub struct Parameters { + pub access_token: String, +} + +#[derive(Clone, Debug, Serialize)] +pub struct Body { + pub client_secret: String, + pub phone_number: String, + pub country: String, + pub id_server: String, + pub send_attempt: u64, + #[serde(skip_serializing_if = "Option::is_none")] + pub next_link: Option, +} + +#[derive(Clone, Debug, Deserialize)] +pub struct Response { + pub sid: String, +} + +pub fn request(base: Url, params: &Parameters, body: &Body) -> Result { + let url = base + .join("/_matrix/client/r0/account/3pid/msisdn/requestToken") + .expect("Malformed URL in request_verification_token_msisdn"); + + Client::new().post(url).query(params).json(body).build() +} diff --git a/fractal-matrix-api/src/r0/media.rs b/fractal-matrix-api/src/r0/media.rs new file mode 100644 index 000000000..c5fb369c1 --- /dev/null +++ b/fractal-matrix-api/src/r0/media.rs @@ -0,0 +1 @@ +pub mod create; diff --git a/fractal-matrix-api/src/r0/media/create.rs b/fractal-matrix-api/src/r0/media/create.rs new file mode 100644 index 000000000..8e8b0fc80 --- /dev/null +++ b/fractal-matrix-api/src/r0/media/create.rs @@ -0,0 +1,40 @@ +use reqwest::header::{HeaderValue, CONTENT_TYPE}; +use reqwest::Client; +use reqwest::Error; +use reqwest::Request; +use serde::{Deserialize, Serialize}; +use url::Url; + +#[derive(Clone, Debug, Serialize)] +pub struct Parameters { + pub access_token: String, + pub filename: Option, +} + +#[derive(Clone, Debug, Deserialize)] +pub struct Response { + pub content_uri: String, +} + +pub fn request( + base: Url, + params: &Parameters, + file: Vec, + content_type: Option, +) -> Result { + let header = content_type + .map(|mime| (CONTENT_TYPE, mime)) + .into_iter() + .collect(); + + let url = base + .join("/_matrix/media/r0/upload") + .expect("Malformed URL in upload"); + + Client::new() + .post(url) + .query(params) + .body(file) + .headers(header) + .build() +} diff --git a/fractal-matrix-api/src/r0/profile.rs b/fractal-matrix-api/src/r0/profile.rs new file mode 100644 index 000000000..c556486ed --- /dev/null +++ b/fractal-matrix-api/src/r0/profile.rs @@ -0,0 +1,4 @@ +pub mod get_display_name; +pub mod get_profile; +pub mod set_avatar_url; +pub mod set_display_name; diff --git a/fractal-matrix-api/src/r0/profile/get_display_name.rs b/fractal-matrix-api/src/r0/profile/get_display_name.rs new file mode 100644 index 000000000..a871b55c6 --- /dev/null +++ b/fractal-matrix-api/src/r0/profile/get_display_name.rs @@ -0,0 +1,21 @@ +use reqwest::Client; +use reqwest::Error; +use reqwest::Request; +use serde::Deserialize; +use url::Url; + +#[derive(Clone, Debug, Deserialize)] +pub struct Response { + pub displayname: Option, +} + +pub fn request(base: Url, user_id: &str) -> Result { + let url = base + .join(&format!( + "/_matrix/client/r0/profile/{}/displayname", + user_id + )) + .expect("Malformed URL in get_display_name"); + + Client::new().get(url).build() +} diff --git a/fractal-matrix-api/src/r0/profile/get_profile.rs b/fractal-matrix-api/src/r0/profile/get_profile.rs new file mode 100644 index 000000000..50afd9684 --- /dev/null +++ b/fractal-matrix-api/src/r0/profile/get_profile.rs @@ -0,0 +1,19 @@ +use reqwest::Client; +use reqwest::Error; +use reqwest::Request; +use serde::Deserialize; +use url::Url; + +#[derive(Clone, Debug, Deserialize)] +pub struct Response { + pub avatar_url: Option, + pub displayname: Option, +} + +pub fn request(base: Url, user_id: &str) -> Result { + let url = base + .join(&format!("/_matrix/client/r0/profile/{}", user_id)) + .expect("Malformed URL in get_profile_avatar"); + + Client::new().get(url).build() +} diff --git a/fractal-matrix-api/src/r0/profile/set_avatar_url.rs b/fractal-matrix-api/src/r0/profile/set_avatar_url.rs new file mode 100644 index 000000000..0d2a87cec --- /dev/null +++ b/fractal-matrix-api/src/r0/profile/set_avatar_url.rs @@ -0,0 +1,31 @@ +use reqwest::Client; +use reqwest::Error; +use reqwest::Request; +use serde::Serialize; +use url::Url; + +#[derive(Clone, Debug, Serialize)] +pub struct Parameters { + pub access_token: String, +} + +#[derive(Clone, Debug, Serialize)] +pub struct Body { + pub avatar_url: Option, +} + +pub fn request( + base: Url, + params: &Parameters, + body: &Body, + user_id: &str, +) -> Result { + let url = base + .join(&format!( + "/_matrix/client/r0/profile/{}/avatar_url", + user_id + )) + .expect("Malformed URL in set_avatar_url"); + + Client::new().put(url).query(params).json(body).build() +} diff --git a/fractal-matrix-api/src/r0/profile/set_display_name.rs b/fractal-matrix-api/src/r0/profile/set_display_name.rs new file mode 100644 index 000000000..198c53ede --- /dev/null +++ b/fractal-matrix-api/src/r0/profile/set_display_name.rs @@ -0,0 +1,32 @@ +use reqwest::Client; +use reqwest::Error; +use reqwest::Request; +use serde::Serialize; +use url::Url; + +#[derive(Clone, Debug, Serialize)] +pub struct Parameters { + pub access_token: String, +} + +#[derive(Clone, Debug, Serialize)] +pub struct Body { + #[serde(skip_serializing_if = "Option::is_none")] + pub displayname: Option, +} + +pub fn request( + base: Url, + params: &Parameters, + body: &Body, + user_id: &str, +) -> Result { + let url = base + .join(&format!( + "/_matrix/client/r0/profile/{}/displayname", + user_id + )) + .expect("Malformed URL in set_display_name"); + + Client::new().put(url).query(params).json(body).build() +} diff --git a/fractal-matrix-api/src/r0/search.rs b/fractal-matrix-api/src/r0/search.rs new file mode 100644 index 000000000..22d12a382 --- /dev/null +++ b/fractal-matrix-api/src/r0/search.rs @@ -0,0 +1 @@ +pub mod user; diff --git a/fractal-matrix-api/src/r0/search/user.rs b/fractal-matrix-api/src/r0/search/user.rs new file mode 100644 index 000000000..65ad510c5 --- /dev/null +++ b/fractal-matrix-api/src/r0/search/user.rs @@ -0,0 +1,53 @@ +use reqwest::Client; +use reqwest::Error; +use reqwest::Request; +use serde::{Deserialize, Serialize}; +use url::Url; + +#[derive(Clone, Debug, Serialize)] +pub struct Parameters { + pub access_token: String, +} + +#[derive(Clone, Debug, Serialize)] +pub struct Body { + pub search_term: String, + #[serde(skip_serializing_if = "u64_is_10")] + pub limit: u64, +} + +impl Default for Body { + fn default() -> Self { + Self { + search_term: Default::default(), + limit: 10, + } + } +} + +#[derive(Clone, Debug, Deserialize)] +pub struct Response { + pub results: Vec, + pub limited: bool, +} + +#[derive(Clone, Debug, Deserialize)] +pub struct User { + pub user_id: String, + #[serde(default)] + pub display_name: Option, + #[serde(default)] + pub avatar_url: Option, +} + +fn u64_is_10(number: &u64) -> bool { + number == &10 +} + +pub fn request(base: Url, params: &Parameters, body: &Body) -> Result { + let url = base + .join("/_matrix/client/r0/user_directory/search") + .expect("Malformed URL in user_directory"); + + Client::new().post(url).query(params).json(body).build() +} diff --git a/fractal-matrix-api/src/types.rs b/fractal-matrix-api/src/types.rs index 9d02a8fbf..28cc3307f 100644 --- a/fractal-matrix-api/src/types.rs +++ b/fractal-matrix-api/src/types.rs @@ -11,4 +11,3 @@ pub use crate::model::room::RoomMembership; pub use crate::model::room::RoomTag; pub use crate::model::stickers::Sticker; pub use crate::model::stickers::StickerGroup; -pub use crate::model::user::*; diff --git a/fractal-matrix-api/src/util.rs b/fractal-matrix-api/src/util.rs index fd3c867d1..f310d74d0 100644 --- a/fractal-matrix-api/src/util.rs +++ b/fractal-matrix-api/src/util.rs @@ -21,6 +21,8 @@ use std::thread; use crate::client::Client; use crate::error::Error; use crate::r0::filter::RoomEventFilter; +use crate::r0::profile::get_profile::request as get_profile; +use crate::r0::profile::get_profile::Response as GetProfileResponse; use crate::types::Message; use reqwest::header::CONTENT_LENGTH; @@ -238,20 +240,16 @@ pub fn get_media(url: &str) -> Result, Error> { } pub fn put_media(url: &str, file: Vec) -> Result { - let (mime, _) = gio::content_type_guess(None, file.as_slice()); + let (mime, _) = gio::content_type_guess(None, &file); - let conn = HTTP_CLIENT + HTTP_CLIENT .get_client()? .post(url) .body(file) - .header(CONTENT_TYPE, mime.to_string()); - - let mut res = conn.send()?; - - match res.json() { - Ok(js) => Ok(js), - Err(_) => Err(Error::BackendError), - } + .header(CONTENT_TYPE, mime.to_string()) + .send()? + .json() + .or(Err(Error::BackendError)) } pub fn resolve_media_url(base: &Url, url: &str, thumb: bool, w: i32, h: i32) -> Result { @@ -391,29 +389,31 @@ pub fn json_q(method: &str, url: &Url, attrs: &JsonValue) -> Result Result<(String, String), Error> { - let url = client_url(baseu, &format!("profile/{}", encode_uid(userid)), &[])?; - let attrs = json!(null); +pub fn get_user_avatar(base: &Url, userid: &str) -> Result<(String, String), Error> { + let response = get_profile(base.clone(), &encode_uid(userid)) + .map_err::(Into::into) + .and_then(|request| { + HTTP_CLIENT + .get_client()? + .execute(request)? + .json::() + .map_err(Into::into) + })?; + + let name = response + .displayname + .filter(|n| !n.is_empty()) + .unwrap_or(userid.to_string()); + + let img = response + .avatar_url + .map(|url| { + let dest = cache_path(userid)?; + thumb(base, &url, Some(&dest)) + }) + .unwrap_or(Ok(Default::default()))?; - match json_q("get", &url, &attrs) { - Ok(js) => { - let name = match js["displayname"].as_str() { - Some(n) if n.is_empty() => userid.to_string(), - Some(n) => n.to_string(), - None => userid.to_string(), - }; - - match js["avatar_url"].as_str() { - Some(url) => { - let dest = cache_path(userid)?; - let img = thumb(baseu, &url, Some(&dest))?; - Ok((name.clone(), img)) - } - None => Ok((name.clone(), String::new())), - } - } - Err(_) => Ok((String::from(userid), String::new())), - } + Ok((name, img)) } pub fn build_url(base: &Url, path: &str, params: &[(&str, String)]) -> Result { -- GitLab