503 lines
14 KiB
Rust
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,
|
|
}
|