aboutsummaryrefslogtreecommitdiffstats
path: root/crates/turtle/src/atuin_client/api_client.rs
diff options
context:
space:
mode:
authorBenedikt Peetz <benedikt.peetz@b-peetz.de>2026-06-12 01:54:21 +0200
committerBenedikt Peetz <benedikt.peetz@b-peetz.de>2026-06-12 01:54:21 +0200
commitbbdf38018b47328b5faa2cef635c37095045be72 (patch)
tree8983817d547551ae12508a8ae8731b622d990af4 /crates/turtle/src/atuin_client/api_client.rs
parentfeat(server): Make user stuff stateless (diff)
downloadatuin-bbdf38018b47328b5faa2cef635c37095045be72.zip
feat(server): Really make users stateless (with tests)
This commit also remove another load of unneeded features.
Diffstat (limited to 'crates/turtle/src/atuin_client/api_client.rs')
-rw-r--r--crates/turtle/src/atuin_client/api_client.rs60
1 files changed, 15 insertions, 45 deletions
diff --git a/crates/turtle/src/atuin_client/api_client.rs b/crates/turtle/src/atuin_client/api_client.rs
index b4657a47..15d96d93 100644
--- a/crates/turtle/src/atuin_client/api_client.rs
+++ b/crates/turtle/src/atuin_client/api_client.rs
@@ -2,52 +2,33 @@ use std::env;
use std::time::Duration;
use eyre::{Result, bail, eyre};
-use reqwest::{
- Response, StatusCode, Url,
- header::{AUTHORIZATION, HeaderMap},
-};
+use reqwest::{Response, StatusCode, Url, header::HeaderMap};
use tracing::debug;
+use uuid::Uuid;
+use crate::atuin_common::{api::ErrorResponse, record::RecordStatus};
use crate::atuin_common::{
api::{ATUIN_CARGO_VERSION, ATUIN_HEADER_VERSION, ATUIN_VERSION},
record::{EncryptedData, HostId, Record, RecordIdx},
tls::ensure_crypto_provider,
};
-use crate::atuin_common::{
- api::{ErrorResponse, MeResponse},
- record::RecordStatus,
-};
use semver::Version;
static APP_USER_AGENT: &str = concat!("atuin/", env!("CARGO_PKG_VERSION"),);
-/// Authentication token for sync API requests.
-///
-/// Used with `Token <token>` header.
-#[derive(Debug, Clone)]
-pub(crate) struct AuthToken(pub(crate) String);
-
-impl AuthToken {
- /// Format the token as an Authorization header value
- fn to_header_value(&self) -> String {
- format!("Token {}", self.0)
- }
-}
-
pub(crate) struct Client<'a> {
sync_addr: &'a str,
client: reqwest::Client,
+ user_id: Uuid,
}
-fn make_url(address: &str, path: &str) -> Result<String> {
+fn make_url(address: &str, path: &str, user_id: Uuid) -> Result<String> {
+ let address = address.strip_suffix('/').unwrap_or(address);
+
// `join()` expects a trailing `/` in order to join paths
// e.g. it treats `http://host:port/subdir` as a file called `subdir`
- let address = if address.ends_with('/') {
- address
- } else {
- &format!("{address}/")
- };
+ let address = &format!("{address}/api/v0/{}/", user_id.to_string());
// passing a path with a leading `/` will cause `join()` to replace the entire URL path
let path = path.strip_prefix("/").unwrap_or(path);
@@ -123,18 +104,18 @@ async fn handle_resp_error(resp: Response) -> Result<Response> {
impl<'a> Client<'a> {
pub(crate) fn new(
sync_addr: &'a str,
- auth: AuthToken,
connect_timeout: u64,
timeout: u64,
+ user_id: Uuid,
) -> Result<Self> {
ensure_crypto_provider();
let mut headers = HeaderMap::new();
- headers.insert(AUTHORIZATION, auth.to_header_value().parse()?);
// used for semver server check
headers.insert(ATUIN_HEADER_VERSION, ATUIN_CARGO_VERSION.parse()?);
Ok(Client {
+ user_id,
sync_addr,
client: reqwest::Client::builder()
.user_agent(APP_USER_AGENT)
@@ -145,20 +126,8 @@ impl<'a> Client<'a> {
})
}
- pub(crate) async fn me(&self) -> Result<MeResponse> {
- let url = make_url(self.sync_addr, "/api/v0/me")?;
- 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::<MeResponse>().await?;
-
- Ok(status)
- }
-
pub(crate) async fn delete_store(&self) -> Result<()> {
- let url = make_url(self.sync_addr, "/api/v0/store")?;
+ let url = make_url(self.sync_addr, "/store", self.user_id)?;
let url = Url::parse(url.as_str())?;
let resp = self.client.delete(url).send().await?;
@@ -169,7 +138,7 @@ impl<'a> Client<'a> {
}
pub(crate) async fn post_records(&self, records: &[Record<EncryptedData>]) -> Result<()> {
- let url = make_url(self.sync_addr, "/api/v0/record")?;
+ let url = make_url(self.sync_addr, "/record", self.user_id)?;
let url = Url::parse(url.as_str())?;
debug!("uploading {} records to {url}", records.len());
@@ -192,9 +161,10 @@ impl<'a> Client<'a> {
let url = make_url(
self.sync_addr,
&format!(
- "/api/v0/record/next?host={}&tag={}&count={}&start={}",
+ "/record/next?host={}&tag={}&count={}&start={}",
host.0, tag, count, start
),
+ self.user_id,
)?;
let url = Url::parse(url.as_str())?;
@@ -208,7 +178,7 @@ impl<'a> Client<'a> {
}
pub(crate) async fn record_status(&self) -> Result<RecordStatus> {
- let url = make_url(self.sync_addr, "/api/v0/record")?;
+ let url = make_url(self.sync_addr, "/record", self.user_id)?;
let url = Url::parse(url.as_str())?;
let resp = self.client.get(url).send().await?;