dooris/ssh-cli/src/api_client.rs
lilly bc0c1b85ca
All checks were successful
Build Container / Build Container (push) Successful in 4m17s
improve ssh-cli interaction surface
- better CLI parameter parsing
- an actual help message
- a new command to list currently known locks
2026-07-19 20:19:47 +02:00

78 lines
3.2 KiB
Rust

use eyre::Context;
use eyre::eyre;
use reqwest::Url;
use reqwest::header::HeaderValue;
use crate::api_models::ApiLock;
use crate::api_models::DesiredLockState;
use crate::api_models::LockOperationRequest;
/// Client for interacting with the dooris API
pub struct ApiClient {
client: reqwest::Client,
authorization: HeaderValue,
server_url: Url,
}
impl ApiClient {
pub fn new_from_env() -> eyre::Result<Self> {
let client = reqwest::Client::builder()
.user_agent("dooris-ssh-cli")
.redirect(reqwest::redirect::Policy::none())
.http1_title_case_headers()
.build()
.expect("Could not construct http client even though all values are statically known to be valid");
let api_token = std::env::var("DOORIS_API_TOKEN").map_err(|_| eyre!("This ssh-cli is not configured correctly and does not have it's DOORIS_API_TOKEN environment variable set correctly. Contact your nearest server maid to fix the dooris setup."))?;
let api_token = format!("Static-Token {api_token}")
.try_into()
.context("Could not convert HTTP API Token to a valid HTTP header")?;
let server_url = std::env::var("DOORIS_SERVER_URL").map_err(|_| eyre!("This ssh-cli is not configured correctly and does not have it's DOORIS_SERVER_URL environment variable set correctly. Contact your nearest server maid to fix the dooris setup."))?;
let server_url = Url::parse(&server_url).context("Configured DOORIS_SERVER_URL is not a valid URL. Contact your nearest server maid to fix the dooris setup")?;
tracing::debug!("Client is connected to dooris at {server_url}");
Ok(Self {
client,
authorization: api_token,
server_url,
})
}
/// Fetch the current list of locks along with their state from the API
pub async fn fetch_locks(&self) -> eyre::Result<Vec<ApiLock>> {
tracing::debug!("Fetching locks from dooris API");
let url = self.server_url.join("api/locks/")?;
self.client
.get(url)
.header(reqwest::header::AUTHORIZATION, self.authorization.clone())
.send()
.await?
.error_for_status()
.context("Could not get list of locks from the dooris API")?
.json()
.await
.context("Could not parse list-locks response from dooris API")
}
/// Put the lock identified by `lock_id` into the given desired state.
///
/// Returns as soon as the operation has been handed of to the backend and does not wait for physical lock movements.
pub async fn operate_lock(
&self,
lock_id: &str,
desired_state: DesiredLockState,
) -> eyre::Result<()> {
tracing::debug!("Calling API to put {lock_id:?} into state {desired_state:?}");
let url = self.server_url.join(&format!("api/locks/{lock_id}"))?;
self.client
.patch(url)
.header(reqwest::header::AUTHORIZATION, self.authorization.clone())
.json(&LockOperationRequest { desired_state })
.send()
.await?
.error_for_status()
.context("Could not operate lock")?;
Ok(())
}
}