init
rewrite of cdb_ui in dioxus rust. goal is to integrate into a single rust codebase
This commit is contained in:
commit
b3a96ed3e3
37 changed files with 9927 additions and 0 deletions
545
src/api.rs
Normal file
545
src/api.rs
Normal file
|
@ -0,0 +1,545 @@
|
|||
/*
|
||||
import 'dart:convert';
|
||||
import 'package:cdb_ui/pages/supply.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
// todo : api errors
|
||||
|
||||
class API {
|
||||
SharedPreferences? pref;
|
||||
static final API _instance = API._internal();
|
||||
|
||||
// cache
|
||||
List<Item>? items;
|
||||
Map<String, Location>? locations;
|
||||
Map<String, FlowInfo>? flowInfos;
|
||||
|
||||
factory API() {
|
||||
return _instance;
|
||||
}
|
||||
|
||||
API._internal();
|
||||
|
||||
Future<void> init(Function refresh) async {
|
||||
pref = await SharedPreferences.getInstance();
|
||||
instance = pref!.getString("instance") ?? "";
|
||||
refresh();
|
||||
}
|
||||
|
||||
bool isInit() {
|
||||
if (pref == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return pref!.containsKey("token") && pref!.containsKey("instance");
|
||||
}
|
||||
|
||||
bool isPrefetched() {
|
||||
return items != null && locations != null && flowInfos != null;
|
||||
}
|
||||
|
||||
void save(String instance, String token) {
|
||||
pref!.setString("instance", instance);
|
||||
pref!.setString("token", token);
|
||||
this.instance = instance;
|
||||
}
|
||||
|
||||
String instance = "";
|
||||
|
||||
Future<String> getRequest(String url) async {
|
||||
var resp = await http.get(Uri.parse(url), headers: <String, String>{
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
'Token': pref!.getString("token")!
|
||||
});
|
||||
|
||||
return utf8.decode(resp.bodyBytes);
|
||||
}
|
||||
|
||||
Future<String> postRequest(String url, Map<String, dynamic> data) async {
|
||||
var resp = await http.post(Uri.parse(url),
|
||||
headers: <String, String>{
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
'Token': pref!.getString("token")!
|
||||
},
|
||||
body: jsonEncode(data));
|
||||
|
||||
return utf8.decode(resp.bodyBytes);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use dioxus::signals::Readable;
|
||||
use dioxus_sdk::storage::use_persistent;
|
||||
use serde_json::json;
|
||||
|
||||
use crate::setup::Credentials;
|
||||
|
||||
use reqwest::header::{HeaderMap, HeaderValue};
|
||||
use reqwest::Client;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub async fn api_get_auth<T>(path: String) -> Result<T, reqwest::Error>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
let creds = use_persistent("creds", || Credentials::default());
|
||||
let creds = creds.read();
|
||||
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 = use_persistent("creds", || Credentials::default());
|
||||
let creds = creds.read();
|
||||
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 = api_get_auth("/locations".to_string()).await.unwrap();
|
||||
let flow_info = api_get_auth("/flows".to_string()).await.unwrap();
|
||||
|
||||
Self {
|
||||
instance,
|
||||
items,
|
||||
locations,
|
||||
flow_info,
|
||||
}
|
||||
}
|
||||
|
||||
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)]
|
||||
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>,
|
||||
}
|
||||
|
||||
#[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,
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue