From 95cc472037fcb3207b510e67f1a44af4e2a2cae9 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Thu, 18 Apr 2024 16:41:28 +0100 Subject: chore: move crates into crates/ dir (#1958) I'd like to tidy up the root a little, and it's nice to have all the rust crates in one place --- atuin-client/src/api_client.rs | 415 ----------------------------------------- 1 file changed, 415 deletions(-) delete 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 deleted file mode 100644 index f31a796e..00000000 --- a/atuin-client/src/api_client.rs +++ /dev/null @@ -1,415 +0,0 @@ -use std::collections::HashMap; -use std::env; -use std::time::Duration; - -use eyre::{bail, Result}; -use reqwest::{ - header::{HeaderMap, AUTHORIZATION, USER_AGENT}, - Response, StatusCode, Url, -}; - -use atuin_common::{ - api::{ - AddHistoryRequest, ChangePasswordRequest, CountResponse, DeleteHistoryRequest, - ErrorResponse, LoginRequest, LoginResponse, MeResponse, RegisterResponse, StatusResponse, - SyncHistoryResponse, - }, - record::RecordStatus, -}; -use atuin_common::{ - api::{ATUIN_CARGO_VERSION, ATUIN_HEADER_VERSION, ATUIN_VERSION}, - record::{EncryptedData, HostId, Record, RecordIdx}, -}; - -use semver::Version; -use time::format_description::well_known::Rfc3339; -use time::OffsetDateTime; - -use crate::{history::History, sync::hash_str, utils::get_host_user}; - -static APP_USER_AGENT: &str = concat!("atuin/", env!("CARGO_PKG_VERSION"),); - -pub struct Client<'a> { - sync_addr: &'a str, - client: reqwest::Client, -} - -pub async fn register( - address: &str, - username: &str, - email: &str, - password: &str, -) -> Result { - let mut map = HashMap::new(); - map.insert("username", username); - map.insert("email", email); - map.insert("password", password); - - let url = format!("{address}/user/{username}"); - let resp = reqwest::get(url).await?; - - if resp.status().is_success() { - bail!("username already in use"); - } - - let url = format!("{address}/register"); - let client = reqwest::Client::new(); - let resp = client - .post(url) - .header(USER_AGENT, APP_USER_AGENT) - .header(ATUIN_HEADER_VERSION, ATUIN_CARGO_VERSION) - .json(&map) - .send() - .await?; - - if !ensure_version(&resp)? { - bail!("could not register user due to version mismatch"); - } - - if !resp.status().is_success() { - let error = resp.json::().await?; - bail!("failed to register user: {}", error.reason); - } - - let session = resp.json::().await?; - Ok(session) -} - -pub async fn login(address: &str, req: LoginRequest) -> Result { - let url = format!("{address}/login"); - let client = reqwest::Client::new(); - - let resp = client - .post(url) - .header(USER_AGENT, APP_USER_AGENT) - .json(&req) - .send() - .await?; - - if !ensure_version(&resp)? { - bail!("could not login due to version mismatch"); - } - - if resp.status() != reqwest::StatusCode::OK { - let error = resp.json::().await?; - bail!("invalid login details: {}", error.reason); - } - - let session = resp.json::().await?; - Ok(session) -} - -#[cfg(feature = "check-update")] -pub async fn latest_version() -> Result { - use atuin_common::api::IndexResponse; - - let url = "https://api.atuin.sh"; - let client = reqwest::Client::new(); - - let resp = client - .get(url) - .header(USER_AGENT, APP_USER_AGENT) - .send() - .await?; - - if resp.status() != reqwest::StatusCode::OK { - let error = resp.json::().await?; - bail!("failed to check latest version: {}", error.reason); - } - - let index = resp.json::().await?; - let version = Version::parse(index.version.as_str())?; - - Ok(version) -} - -pub fn ensure_version(response: &Response) -> Result { - let version = response.headers().get(ATUIN_HEADER_VERSION); - - let version = if let Some(version) = version { - match version.to_str() { - Ok(v) => Version::parse(v), - Err(e) => bail!("failed to parse server version: {:?}", e), - } - } else { - // if there is no version header, then the newest this server can possibly be is 17.1.0 - Version::parse("17.1.0") - }?; - - // If the client is newer than the server - if version.major < ATUIN_VERSION.major { - println!("Atuin version mismatch! In order to successfully sync, the server needs to run a newer version of Atuin"); - println!("Client: {}", ATUIN_CARGO_VERSION); - println!("Server: {}", version); - - return Ok(false); - } - - Ok(true) -} - -async fn handle_resp_error(resp: Response) -> Result { - let status = resp.status(); - - if status == StatusCode::SERVICE_UNAVAILABLE { - bail!( - "Service unavailable: check https://status.atuin.sh (or get in touch with your host)" - ); - } - - if !status.is_success() { - if let Ok(error) = resp.json::().await { - let reason = error.reason; - - if status.is_client_error() { - bail!("Could not fetch history, client error {status}: {reason}.") - } - - bail!("There was an error with the atuin sync service, server error {status}: {reason}.\nIf the problem persists, contact the host") - } - - bail!("There was an error with the atuin sync service: Status {status:?}.\nIf the problem persists, contact the host") - } - - Ok(resp) -} - -impl<'a> Client<'a> { - pub fn new( - sync_addr: &'a str, - session_token: &str, - connect_timeout: u64, - timeout: u64, - ) -> Result { - let mut headers = HeaderMap::new(); - headers.insert(AUTHORIZATION, format!("Token {session_token}").parse()?); - - // used for semver server check - headers.insert(ATUIN_HEADER_VERSION, ATUIN_CARGO_VERSION.parse()?); - - Ok(Client { - sync_addr, - client: reqwest::Client::builder() - .user_agent(APP_USER_AGENT) - .default_headers(headers) - .connect_timeout(Duration::new(connect_timeout, 0)) - .timeout(Duration::new(timeout, 0)) - .build()?, - }) - } - - pub async fn count(&self) -> Result { - let url = format!("{}/sync/count", self.sync_addr); - let url = Url::parse(url.as_str())?; - - let resp = self.client.get(url).send().await?; - let resp = handle_resp_error(resp).await?; - - if !ensure_version(&resp)? { - bail!("could not sync due to version mismatch"); - } - - if resp.status() != StatusCode::OK { - bail!("failed to get count (are you logged in?)"); - } - - let count = resp.json::().await?; - - Ok(count.count) - } - - pub async fn status(&self) -> Result { - let url = format!("{}/sync/status", self.sync_addr); - let url = Url::parse(url.as_str())?; - - let resp = self.client.get(url).send().await?; - let resp = handle_resp_error(resp).await?; - - if !ensure_version(&resp)? { - bail!("could not sync due to version mismatch"); - } - - let status = resp.json::().await?; - - Ok(status) - } - - pub async fn me(&self) -> Result { - let url = format!("{}/api/v0/me", self.sync_addr); - let url = Url::parse(url.as_str())?; - - let resp = self.client.get(url).send().await?; - let resp = handle_resp_error(resp).await?; - - let status = resp.json::().await?; - - Ok(status) - } - - pub async fn get_history( - &self, - sync_ts: OffsetDateTime, - history_ts: OffsetDateTime, - host: Option, - ) -> Result { - let host = host.unwrap_or_else(|| hash_str(&get_host_user())); - - let url = format!( - "{}/sync/history?sync_ts={}&history_ts={}&host={}", - self.sync_addr, - urlencoding::encode(sync_ts.format(&Rfc3339)?.as_str()), - urlencoding::encode(history_ts.format(&Rfc3339)?.as_str()), - host, - ); - - let resp = self.client.get(url).send().await?; - let resp = handle_resp_error(resp).await?; - - let history = resp.json::().await?; - 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())?; - - let resp = self.client.post(url).json(history).send().await?; - handle_resp_error(resp).await?; - - Ok(()) - } - - pub async fn delete_history(&self, h: History) -> Result<()> { - let url = format!("{}/history", self.sync_addr); - let url = Url::parse(url.as_str())?; - - let resp = self - .client - .delete(url) - .json(&DeleteHistoryRequest { - client_id: h.id.to_string(), - }) - .send() - .await?; - - handle_resp_error(resp).await?; - - Ok(()) - } - - pub async fn delete_store(&self) -> Result<()> { - let url = format!("{}/api/v0/store", self.sync_addr); - let url = Url::parse(url.as_str())?; - - let resp = self.client.delete(url).send().await?; - - handle_resp_error(resp).await?; - - Ok(()) - } - - pub async fn post_records(&self, records: &[Record]) -> Result<()> { - let url = format!("{}/api/v0/record", self.sync_addr); - let url = Url::parse(url.as_str())?; - - debug!("uploading {} records to {url}", records.len()); - - let resp = self.client.post(url).json(records).send().await?; - handle_resp_error(resp).await?; - - Ok(()) - } - - pub async fn next_records( - &self, - host: HostId, - tag: String, - start: RecordIdx, - count: u64, - ) -> Result>> { - debug!( - "fetching record/s from host {}/{}/{}", - host.0.to_string(), - tag, - start - ); - - let url = format!( - "{}/api/v0/record/next?host={}&tag={}&count={}&start={}", - self.sync_addr, host.0, tag, count, start - ); - - let url = Url::parse(url.as_str())?; - - let resp = self.client.get(url).send().await?; - let resp = handle_resp_error(resp).await?; - - let records = resp.json::>>().await?; - - Ok(records) - } - - pub async fn record_status(&self) -> Result { - let url = format!("{}/api/v0/record", self.sync_addr); - let url = Url::parse(url.as_str())?; - - let resp = self.client.get(url).send().await?; - let resp = handle_resp_error(resp).await?; - - if !ensure_version(&resp)? { - bail!("could not sync records due to version mismatch"); - } - - let index = resp.json().await?; - - debug!("got remote index {:?}", index); - - Ok(index) - } - - pub async fn delete(&self) -> Result<()> { - let url = format!("{}/account", self.sync_addr); - let url = Url::parse(url.as_str())?; - - let resp = self.client.delete(url).send().await?; - - if resp.status() == 403 { - bail!("invalid login details"); - } else if resp.status() == 200 { - Ok(()) - } else { - bail!("Unknown error"); - } - } - - pub async fn change_password( - &self, - current_password: String, - new_password: String, - ) -> Result<()> { - let url = format!("{}/account/password", self.sync_addr); - let url = Url::parse(url.as_str())?; - - let resp = self - .client - .patch(url) - .json(&ChangePasswordRequest { - current_password, - new_password, - }) - .send() - .await?; - - dbg!(&resp); - - if resp.status() == 401 { - bail!("current password is incorrect") - } else if resp.status() == 403 { - bail!("invalid login details"); - } else if resp.status() == 200 { - Ok(()) - } else { - bail!("Unknown error"); - } - } -} -- cgit v1.3.1