use std::sync::Arc;
use base64::{prelude::BASE64_STANDARD, Engine};
use reqwest::header::{HeaderMap, HeaderValue};
mod date;
mod error;
mod settlement;
mod ticket;
pub use {date::Date, error::Error, settlement::Settlement, ticket::Ticket};
const PROD_URL: &str = "https://parkright.se";
#[allow(dead_code)]
const TEST_URL: &str = "https://test.parkright.se";
#[derive(Clone)]
pub struct Client {
c: reqwest::Client,
base_url: Arc<String>,
}
impl Client {
pub fn from_env() -> Self {
let username =
std::env::var("PARKRIGHT_USERNAME").expect("env var PARKRIGHT_USERNAME not set");
let password =
std::env::var("PARKRIGHT_PASSWORD").expect("env var PARKRIGHT_PASSWORD not set");
println!("username {username}");
println!("password {password}");
Self::new(username, password)
}
pub fn new(username: impl AsRef<str>, password: impl AsRef<str>) -> Self {
let auth_key =
BASE64_STANDARD.encode(format!("{}:{}", username.as_ref(), password.as_ref()));
let auth_header_value = format!("Basic {auth_key}");
let mut headers = HeaderMap::new();
headers.insert(
reqwest::header::AUTHORIZATION,
HeaderValue::from_str(&auth_header_value).expect("Basc auth header value"),
);
headers.insert(
reqwest::header::ACCEPT,
HeaderValue::from_static("application/json"),
);
let c = reqwest::ClientBuilder::new()
.user_agent(format!("Parkando Parkright v{}", env!("CARGO_PKG_VERSION")))
.default_headers(headers)
.build()
.expect("building");
Self {
c,
base_url: Arc::new(String::from(PROD_URL)),
}
}
async fn fetch_and_parse<T>(&self, url: &str) -> Result<T, Error>
where
T: serde::de::DeserializeOwned,
{
let res = self.c.get(url).send().await?;
let status = res.status();
if !status.is_success() {
return Err(Error::NonOkReply {
status: status.as_u16(),
});
}
let bs = res.bytes().await?;
match serde_json::from_slice::<T>(&bs) {
Ok(res) => Ok(res),
Err(err) => Err(Error::Deserializing { err }),
}
}
pub async fn fetch_settlements(
&self,
from: time::Date,
until: time::Date,
) -> Result<Vec<Settlement>, Error> {
#[derive(serde::Deserialize)]
struct Wrapper {
#[serde(rename = "AvailableSettlements")]
settlements: Vec<Settlement>,
}
let from = Date::from(from);
let until = Date::from(until);
let base_url = self.base_url.as_str();
let url = format!(
"{base_url}/api/v1/ReviewSettlementCasesOffStreet/availablesettlements?startDate={from}&endDate={until}",
);
println!("{url}");
let res = self.fetch_and_parse::<Wrapper>(&url).await?;
Ok(res.settlements)
}
pub async fn fetch_tickets(&self, settlement_ids: &[i64]) -> Result<Vec<Ticket>, Error> {
if settlement_ids.is_empty() {
return Ok(vec![]);
}
let param_ids = settlement_ids
.iter()
.map(|id| id.to_string())
.collect::<Vec<_>>()
.join(",");
let url = format!(
"{}/api/v1/ReviewSettlementCasesOffStreet/report?settlementIds={}",
self.base_url.as_str(),
param_ids
);
let res = self.fetch_and_parse::<Vec<Ticket>>(&url).await?;
Ok(res)
}
}