From a21737e2b7f8d1e426726bdd7536033f299d476a Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Tue, 20 Apr 2021 21:53:07 +0100 Subject: Use cargo workspaces (#37) * Switch to Cargo workspaces Breaking things into "client", "server" and "common" makes managing the codebase much easier! client - anything running on a user's machine for adding history server - handles storing/syncing history and running a HTTP server common - request/response API definitions, common utils, etc * Update dockerfile --- atuin-client/src/api_client.rs | 96 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 atuin-client/src/api_client.rs (limited to 'atuin-client/src/api_client.rs') diff --git a/atuin-client/src/api_client.rs b/atuin-client/src/api_client.rs new file mode 100644 index 00000000..db2802c3 --- /dev/null +++ b/atuin-client/src/api_client.rs @@ -0,0 +1,96 @@ +use chrono::Utc; +use eyre::Result; +use reqwest::header::{HeaderMap, AUTHORIZATION}; +use reqwest::Url; +use sodiumoxide::crypto::secretbox; + +use atuin_common::api::{AddHistoryRequest, CountResponse, SyncHistoryResponse}; +use atuin_common::utils::hash_str; + +use crate::encryption::decrypt; +use crate::history::History; + +pub struct Client<'a> { + sync_addr: &'a str, + token: &'a str, + key: secretbox::Key, + client: reqwest::Client, +} + +impl<'a> Client<'a> { + pub fn new(sync_addr: &'a str, token: &'a str, key: secretbox::Key) -> Self { + Client { + sync_addr, + token, + key, + client: reqwest::Client::new(), + } + } + + pub async fn count(&self) -> Result { + let url = format!("{}/sync/count", self.sync_addr); + let url = Url::parse(url.as_str())?; + let token = format!("Token {}", self.token); + let token = token.parse()?; + + let mut headers = HeaderMap::new(); + headers.insert(AUTHORIZATION, token); + + let resp = self.client.get(url).headers(headers).send().await?; + + let count = resp.json::().await?; + + Ok(count.count) + } + + pub async fn get_history( + &self, + sync_ts: chrono::DateTime, + history_ts: chrono::DateTime, + host: Option, + ) -> Result> { + let host = match host { + None => hash_str(&format!("{}:{}", whoami::hostname(), whoami::username())), + Some(h) => h, + }; + + let url = format!( + "{}/sync/history?sync_ts={}&history_ts={}&host={}", + self.sync_addr, + urlencoding::encode(sync_ts.to_rfc3339().as_str()), + urlencoding::encode(history_ts.to_rfc3339().as_str()), + host, + ); + + let resp = self + .client + .get(url) + .header(AUTHORIZATION, format!("Token {}", self.token)) + .send() + .await?; + + let history = resp.json::().await?; + let history = history + .history + .iter() + .map(|h| serde_json::from_str(h).expect("invalid base64")) + .map(|h| decrypt(&h, &self.key).expect("failed to decrypt history! check your key")) + .collect(); + + Ok(history) + } + + pub async fn post_history(&self, history: &[AddHistoryRequest]) -> Result<()> { + let url = format!("{}/history", self.sync_addr); + let url = Url::parse(url.as_str())?; + + self.client + .post(url) + .json(history) + .header(AUTHORIZATION, format!("Token {}", self.token)) + .send() + .await?; + + Ok(()) + } +} -- cgit v1.3.1