[fractal] API, user: Separate endpoint connection from query build



commit 50803772f2834b792fad02951cad9347119fe0f1
Author: Alejandro Domínguez <adomu net-c com>
Date:   Tue Mar 12 12:21:58 2019 +0100

    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 +
 fractal-matrix-api/src/identity/r0/association.rs  |   1 +
 .../src/identity/r0/association/msisdn.rs          |   1 +
 .../identity/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 +
 fractal-matrix-api/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 +
 .../r0/contact/request_verification_token_email.rs |  33 +
 .../contact/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 +
 fractal-matrix-api/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(-)
---
diff --git a/fractal-gtk/src/appop/account.rs b/fractal-gtk/src/appop/account.rs
index c12dabc2..5a4533a2 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<Vec<ThirdPartyIdentifier>>) {
diff --git a/fractal-gtk/src/widgets/address.rs b/fractal-gtk/src/widgets/address.rs
index d99a9aa4..ed191a1d 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 3ff9b45c..3212fc38 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 6d32ebb2..5e42043f 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 0b4a1aad..afb66cb6 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 9acb8c94..3dcbe51e 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::<GetDisplayNameResponse>(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::<GetDisplayNameResponse>()
+                    .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<String>) {
+    let base = bk.get_base_url();
+
+    thread::spawn(move || {
+        let query = get_display_name(base, &encode_uid(&uid))
+            .map_err::<Error, _>(Into::into)
+            .and_then(|request| {
+                HTTP_CLIENT
+                    .get_client()?
+                    .execute(request)?
+                    .json::<GetDisplayNameResponse>()
+                    .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, &params, &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::<ThirdPartyIDResponse>(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, &params)
+            .map_err(Into::into)
+            .and_then(|request| {
+                HTTP_CLIENT
+                    .get_client()?
+                    .execute(request)?
+                    .json::<ThirdPartyIDResponse>()
+                    .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, &params, &body)
+            .map_err(Into::into)
+            .and_then(|request| {
+                HTTP_CLIENT
+                    .get_client()?
+                    .execute(request)?
+                    .json::<EmailTokenResponse>()
+                    .map_err(Into::into)
+            });
 
-    let tx = bk.tx.clone();
-    post!(
-        &url,
-        &attrs_json,
-        |r: JsonValue| if let Ok(response) = serde_json::from_value::<ThirdPartyTokenResponse>(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, &params, &body)
+            .map_err(Into::into)
+            .and_then(|request| {
+                HTTP_CLIENT
+                    .get_client()?
+                    .execute(request)?
+                    .json::<PhoneTokenResponse>()
+                    .map_err(Into::into)
+            });
 
-    let tx = bk.tx.clone();
-    post!(
-        &url,
-        &attrs_json,
-        |r: JsonValue| if let Ok(response) = serde_json::from_value::<ThirdPartyTokenResponse>(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, &params, &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::<SubmitPhoneTokenResponse>(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::<SubmitPhoneTokenResponse>()
+                    .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, &params, &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, &params, &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, &params, &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<Member>, tx: Sender<String>) {
+    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(), &params_upload, contents, Some(mime_value))
+                    .map_err::<Error, _>(Into::into)
+                    .and_then(|request| {
+                        HTTP_CLIENT
+                            .get_client()?
+                            .execute(request)?
+                            .json::<CreateContentResponse>()
+                            .map_err(Into::into)
+                    })?;
+
+            let params_avatar = SetAvatarUrlParameters { access_token };
+            let body = SetAvatarUrlBody {
+                avatar_url: Some(upload_response.content_uri),
+            };
+
+            set_avatar_url(base, &params_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<Sender<(String, String)>>,
-) -> Result<(), Error> {
+pub fn get_user_info_async(bk: &mut Backend, uid: &str, tx: Option<Sender<(String, String)>>) {
     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<String>) -> 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::<GetDisplayNameResponse>(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<Member>,
-    tx: Sender<String>,
-) -> 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<u8> = 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::<SearchUserResponse>(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, &params, &body)
+            .map_err(Into::into)
+            .and_then(|request| {
+                HTTP_CLIENT
+                    .get_client()?
+                    .execute(request)?
+                    .json::<UserDirectoryResponse>()
+                    .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 00000000..026b9976
--- /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 00000000..6cddc6e6
--- /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 00000000..6872e520
--- /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 00000000..73f1eccd
--- /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 00000000..7d7a47eb
--- /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<Request, Error> {
+    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 8f600d45..bc71a688 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 8a65d647..937aff55 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 a2ff4b1b..fd1fbfd5 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 14db5456..836fd935 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/r0.rs b/fractal-matrix-api/src/r0.rs
index 13090a6c..86c74a3a 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 3cad3482..9e4b667d 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 00000000..21e9cd01
--- /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<AuthenticationData>,
+}
+
+pub fn request(base: Url, params: &Parameters, body: &Body) -> Result<Request, Error> {
+    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 00000000..6a322a80
--- /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<AuthenticationData>,
+}
+
+pub fn request(base: Url, params: &Parameters, body: &Body) -> Result<Request, Error> {
+    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 00000000..972b7381
--- /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 00000000..76db1892
--- /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<Request, Error> {
+    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 00000000..541b0058
--- /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<Request, Error> {
+    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 00000000..50d1d1d6
--- /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<ThirdPartyIdentifier>,
+}
+
+#[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<Request, Error> {
+    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 00000000..7ae47cc3
--- /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<String>,
+}
+
+#[derive(Clone, Debug, Deserialize)]
+pub struct Response {
+    pub sid: String,
+}
+
+pub fn request(base: Url, params: &Parameters, body: &Body) -> Result<Request, Error> {
+    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 00000000..c1dae91e
--- /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<String>,
+}
+
+#[derive(Clone, Debug, Deserialize)]
+pub struct Response {
+    pub sid: String,
+}
+
+pub fn request(base: Url, params: &Parameters, body: &Body) -> Result<Request, Error> {
+    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 00000000..c5fb369c
--- /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 00000000..8e8b0fc8
--- /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<String>,
+}
+
+#[derive(Clone, Debug, Deserialize)]
+pub struct Response {
+    pub content_uri: String,
+}
+
+pub fn request(
+    base: Url,
+    params: &Parameters,
+    file: Vec<u8>,
+    content_type: Option<HeaderValue>,
+) -> Result<Request, Error> {
+    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 00000000..c556486e
--- /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 00000000..a871b55c
--- /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<String>,
+}
+
+pub fn request(base: Url, user_id: &str) -> Result<Request, Error> {
+    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 00000000..50afd968
--- /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<String>,
+    pub displayname: Option<String>,
+}
+
+pub fn request(base: Url, user_id: &str) -> Result<Request, Error> {
+    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 00000000..0d2a87ce
--- /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<String>,
+}
+
+pub fn request(
+    base: Url,
+    params: &Parameters,
+    body: &Body,
+    user_id: &str,
+) -> Result<Request, Error> {
+    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 00000000..198c53ed
--- /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<String>,
+}
+
+pub fn request(
+    base: Url,
+    params: &Parameters,
+    body: &Body,
+    user_id: &str,
+) -> Result<Request, Error> {
+    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 00000000..22d12a38
--- /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 00000000..65ad510c
--- /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<User>,
+    pub limited: bool,
+}
+
+#[derive(Clone, Debug, Deserialize)]
+pub struct User {
+    pub user_id: String,
+    #[serde(default)]
+    pub display_name: Option<String>,
+    #[serde(default)]
+    pub avatar_url: Option<String>,
+}
+
+fn u64_is_10(number: &u64) -> bool {
+    number == &10
+}
+
+pub fn request(base: Url, params: &Parameters, body: &Body) -> Result<Request, Error> {
+    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 9d02a8fb..28cc3307 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 fd3c867d..f310d74d 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<Vec<u8>, Error> {
 }
 
 pub fn put_media(url: &str, file: Vec<u8>) -> Result<JsonValue, Error> {
-    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<Url, Error> {
@@ -391,29 +389,31 @@ pub fn json_q(method: &str, url: &Url, attrs: &JsonValue) -> Result<JsonValue, E
     }
 }
 
-pub fn get_user_avatar(baseu: &Url, userid: &str) -> 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::<Error, _>(Into::into)
+        .and_then(|request| {
+            HTTP_CLIENT
+                .get_client()?
+                .execute(request)?
+                .json::<GetProfileResponse>()
+                .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<Url, Error> {


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]