rewrite of cdb_ui in dioxus rust. goal is to integrate into a single rust codebase
This commit is contained in:
JMARyA 2025-05-25 20:03:42 +02:00
commit b3a96ed3e3
Signed by: jmarya
GPG key ID: 901B2ADDF27C2263
37 changed files with 9927 additions and 0 deletions

545
src/api.rs Normal file
View 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,
}