cdb_client/src/api.rs

503 lines
14 KiB
Rust

use std::collections::HashMap;
use dioxus::signals::{Readable, Writable};
use serde_json::json;
use crate::setup::Credentials;
use crate::store::{load_from_local_storage, save_to_local_storage};
use crate::try_recover_api;
use reqwest::header::{HeaderMap, HeaderValue};
use reqwest::Client;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
pub fn get_item(item: &str) -> Option<Item> {
if let Some(api) = crate::API.read().as_ref() {
api.get_item(item.to_string())
} else {
try_recover_api().get_item(item.to_string())
}
}
pub async fn api_get_auth<T>(path: String) -> Result<T, reqwest::Error>
where
T: DeserializeOwned,
{
let creds = load_from_local_storage::<Credentials>("creds").unwrap_or_default();
let token = creds.token.as_str();
let instance = creds.instance_url.as_str();
let url = format!("{instance}{path}");
let mut headers = HeaderMap::new();
headers.insert("Token", HeaderValue::from_str(token).unwrap());
let client = Client::new();
let res = client
.get(&url)
.headers(headers)
.send()
.await?
.error_for_status()?
.json::<T>()
.await?;
Ok(res)
}
pub async fn api_post_auth<T, X>(path: String, body: X) -> Result<T, reqwest::Error>
where
T: DeserializeOwned,
X: Serialize,
{
let creds = load_from_local_storage::<Credentials>("creds").unwrap_or_default();
let token = creds.token.as_str();
let instance = creds.instance_url.as_str();
let url = format!("{instance}{path}");
let mut headers = HeaderMap::new();
headers.insert("Token", HeaderValue::from_str(token).unwrap());
let client = Client::new();
let res = client
.post(&url)
.headers(headers)
.json(&body)
.send()
.await?
.error_for_status()?
.json::<T>()
.await?;
Ok(res)
}
#[derive(Debug)]
pub struct API {
pub instance: String,
pub items: Vec<Item>,
pub locations: HashMap<String, Location>,
pub flow_info: HashMap<String, FlowInfo>,
}
impl API {
pub async fn new(instance: String) -> Self {
let mut items: HashMap<String, Vec<Item>> =
api_get_auth("/items".to_string()).await.unwrap();
let items = items.remove("items").unwrap();
let locations: HashMap<String, Location> =
api_get_auth("/locations".to_string()).await.unwrap();
let flow_info: HashMap<String, FlowInfo> =
api_get_auth("/flows".to_string()).await.unwrap();
save_to_local_storage("api_items", items.clone());
save_to_local_storage("api_locations", locations.clone());
save_to_local_storage("api_flow_info", flow_info.clone());
Self {
instance,
items,
locations,
flow_info,
}
}
pub fn try_recover() -> Self {
Self {
instance: load_from_local_storage::<Credentials>("creds")
.unwrap_or_default()
.instance_url,
items: load_from_local_storage("api_items").unwrap_or_default(),
locations: load_from_local_storage("api_locations").unwrap_or_default(),
flow_info: load_from_local_storage("api_flow_info").unwrap_or_default(),
}
}
pub async fn get_items(&self) -> &[Item] {
&self.items
}
pub async fn get_global_item_stat() -> GlobalItemStat {
api_get_auth("/items/stat".to_string()).await.unwrap()
}
pub async fn get_unique_field(item: String, variant: String, field: String) -> Vec<String> {
api_get_auth(format!("/item/{item}/{variant}/unique?field={field}"))
.await
.unwrap()
}
pub fn get_locations(&self) -> &HashMap<String, Location> {
&self.locations
}
pub fn get_item(&self, item: String) -> Option<Item> {
self.items.iter().find(|x| x.uuid == item).cloned()
}
pub async fn get_transaction(id: String) -> Transaction {
api_get_auth(format!("/transaction/{id}")).await.unwrap()
}
pub async fn get_transactions_of_location(
location: String,
recursive: bool,
) -> Vec<Transaction> {
let mut url = format!("/location/{location}/inventory");
if recursive {
url.push_str("?recursive=true");
}
api_get_auth(url).await.unwrap()
}
pub async fn get_consumed_items(
item: String,
variant: String,
destination: Option<String>,
) -> Vec<Transaction> {
let mut url = format!("/item/{item}/{variant}/demand");
if let Some(dest) = destination {
url.push_str(&format!("?destination={dest}"));
}
api_get_auth(url).await.unwrap()
}
pub async fn get_inventory(item: String, origin: Option<String>) -> Vec<Transaction> {
let mut url = format!("/item/{item}/inventory");
if let Some(origin) = origin {
url.push_str(&format!("?origin={origin}"));
}
api_get_auth(url).await.unwrap()
}
pub async fn get_inventory_of_variant(item: String, variant: String) -> Vec<Transaction> {
api_get_auth(format!("/item/{item}/{variant}/inventory"))
.await
.unwrap()
}
pub async fn supply_item(
item: String,
variant: String,
price: f64,
origin: Option<String>,
location: Option<String>,
note: Option<String>,
) -> String {
let res: serde_json::Value = api_post_auth(
"/supply".to_string(),
json!({
"item": item,
"variant": variant,
"price": price,
"origin": origin,
"location": location,
"note": note,
"properties": serde_json::Value::Null,
"quanta": serde_json::Value::Null
}),
)
.await
.unwrap();
res.as_object()
.unwrap()
.get("uuid")
.unwrap()
.as_str()
.unwrap()
.to_string()
}
pub async fn consume_item(transaction: String, destination: String, price: f64) {
api_post_auth::<serde_json::Map<String, serde_json::Value>, _>(
"/demand".to_string(),
json!({
"uuid": transaction, "destination": destination, "price": price
}),
)
.await
.unwrap();
}
pub async fn get_stat(item: String, variant: String, full: bool) -> ItemVariantStat {
api_get_auth(format!(
"/item/{item}/{variant}/stat{}",
if full { "?full=true" } else { "" }
))
.await
.unwrap()
}
pub fn get_url_instance(&self, item: String) -> String {
format!("{}/{item}", self.instance)
}
pub async fn get_price_history(
item: String,
variant: String,
origin: Option<String>,
) -> Vec<f64> {
let mut url = format!("/item/{item}/{variant}/price_history");
if let Some(origin) = origin {
url.push_str(&format!("?origin={origin}"));
}
api_get_auth(url).await.unwrap()
}
pub async fn get_latest_price(item: String, variant: String, origin: Option<String>) -> f64 {
let mut url = format!("/item/{item}/{variant}/price_latest");
if let Some(origin) = origin {
url.push_str(&format!("?origin={origin}"));
}
api_get_auth(url).await.unwrap()
}
pub async fn get_flows(&self) -> &HashMap<String, FlowInfo> {
&self.flow_info
}
pub async fn get_flow_info(&self, id: String) -> Option<&FlowInfo> {
self.flow_info.iter().find(|x| *x.0 == id).map(|x| x.1)
}
pub async fn get_flow(id: String) -> Flow {
api_get_auth(format!("/flow/{id}")).await.unwrap()
}
pub async fn get_active_flows_of(id: String) -> Vec<Flow> {
api_get_auth(format!("/flow/{id}/active")).await.unwrap()
}
pub async fn start_flow(id: String, input: Option<Vec<String>>) -> String {
let res: serde_json::Value = api_post_auth(format!("/flow/{id}"), json!({"input": input}))
.await
.unwrap();
res.as_object()
.unwrap()
.get("uuid")
.unwrap()
.as_str()
.unwrap()
.to_string()
}
/*
pub async fn end_flow(id: String, produced: Option<Vec<SupplyForm>>) -> HashMap<String, Vec<String>> {
todo!()
}
// /flow/<id>/end
Future<Map<String, List<String>>?> endFlow(String id,
{List<SupplyForm>? produced}) async {
var resp = jsonDecode(await postRequest("$instance/flow/$id/end",
{"produced": produced?.map((x) => x.json()).toList()}));
if (produced != null) {
var produced = resp["produced"] as Map<String, dynamic>;
return produced.map(
(key, value) {
return MapEntry(key, (value as List<dynamic>).cast<String>());
},
);
}
return null;
}
*/
pub async fn continue_flow(id: String, input: Option<Vec<String>>) -> String {
let res: serde_json::Value =
api_post_auth(format!("/flow/{id}/continue"), json!({"input": input}))
.await
.unwrap();
res.as_object()
.unwrap()
.get("uuid")
.unwrap()
.as_str()
.unwrap()
.to_string()
}
pub async fn get_expired_items() -> Vec<Transaction> {
api_get_auth("/items/expired".to_string()).await.unwrap()
}
pub async fn get_items_under_min() -> Vec<MinItem> {
api_get_auth("/items/min".to_string()).await.unwrap()
}
pub async fn move_transaction(id: String, new_location: String) {
api_post_auth(
format!("/transaction/{id}/move"),
json!({"to": new_location}),
)
.await
.unwrap()
}
pub fn get_location(&self, id: String) -> Option<&Location> {
self.locations.get(&id)
}
pub async fn add_note_to_flow(flow_id: String, content: String) -> String {
let res: serde_json::Value =
api_post_auth(format!("/flow/{flow_id}/note"), json!({"content": content}))
.await
.unwrap();
res.as_object()
.unwrap()
.get("uuid")
.unwrap()
.as_str()
.unwrap()
.to_string()
}
pub async fn get_notes_of_flow(flow_id: String) -> Vec<FlowNote> {
api_get_auth(format!("/flow/{flow_id}/notes"))
.await
.unwrap()
}
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct FlowInfo {
pub id: String,
pub name: String,
pub depends: Vec<String>,
pub next: Option<String>,
pub produces: Option<Vec<String>>,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct Item {
pub uuid: String,
pub image: Option<String>,
pub name: String,
pub category: Option<String>,
pub variants: HashMap<String, ItemVariant>,
}
impl Item {
pub fn get_variant(&self, variant: &str) -> Option<ItemVariant> {
let var = self.variants.iter().find(|x| *x.0 == variant)?;
Some(var.1.clone())
}
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct ItemVariant {
pub item: String,
pub variant: String,
pub name: String,
pub min: Option<i64>,
pub expiry: Option<i64>,
pub barcodes: Option<Vec<i64>>,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct Transaction {
pub uuid: String,
pub item: String,
pub variant: String,
pub price: f64,
pub origin: Option<String>,
pub timestamp: i64,
pub consumed: Option<ConsumeInfo>,
pub expired: bool,
pub note: Option<String>,
pub quanta: Option<i64>,
pub properties: Option<serde_json::Value>,
pub location: Option<Location>,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct ConsumeInfo {
pub destination: String,
pub price: f64,
pub timestamp: i64,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ItemVariantStat {
pub amount: i64,
pub total_price: f64,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct Location {
pub id: String,
pub name: String,
pub parent: Option<String>,
pub conditions: Option<LocationCondition>,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct LocationCondition {
pub temperature: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct MinItem {
pub item_variant: String,
pub need: i64,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Flow {
pub id: String,
pub started: i64,
pub kind: String,
pub input: Option<Vec<String>>,
pub done: Option<FlowDone>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct FlowDone {
pub ended: i64,
pub next: Option<String>,
pub produced: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct FlowNote {
pub uuid: String,
pub timestamp: i64,
pub content: String,
pub on_flow: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct GlobalItemStat {
pub item_count: i64,
pub total_transactions: i64,
pub total_price: f64,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct FullItemVariantStat {
pub amount: i64,
pub total_price: f64,
pub expiry_rate: f64,
pub origins: HashMap<String, OriginStat>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct OriginStat {
pub average_price: f64,
pub inventory: i64,
}