This commit is contained in:
JMARyA 2024-10-07 20:53:58 +02:00
parent 584ffb6b11
commit 1faa3b9668
Signed by: jmarya
GPG key ID: 901B2ADDF27C2263
19 changed files with 1058 additions and 1244 deletions

View file

@ -1,18 +1,16 @@
use std::collections::HashMap;
use mongod::{reference_of, vec_to_api, Model, Referencable, Sort, ToAPI};
use mongodb::bson::doc;
use rocket::{get, post, serde::json::Json, State};
use serde::{Deserialize, Serialize};
use serde_json::json;
use std::{collections::HashMap, str::FromStr};
use crate::{
check_auth,
config::Config,
flow::{Flow, FlowInfo, FlowNote},
get_pg,
json_store::JSONStore,
routes::{api_error, FallibleApiResponse, Token},
transaction::{Price, Transaction},
routes::{api_error, vec_to_api, FallibleApiResponse, ToAPI, Token},
transaction::Transaction,
};
use super::{item::SupplyForm, ApiError};
@ -42,16 +40,11 @@ pub async fn active_flows_route(
let flowinfo = flows.get(id).ok_or_else(|| api_error("Flow not found"))?;
let flow = Flow::find(
doc! {
"kind": flowinfo.reference(),
"done": { "$not": { "$type": "object" } }
},
None,
None,
)
.await
.unwrap();
let flow: Vec<Flow> = sqlx::query_as("SELECT * FROM flows WHERE kind = $1 AND ended IS NULL")
.bind(&flowinfo.id)
.fetch_all(get_pg!())
.await
.unwrap();
Ok(json!(vec_to_api(&flow).await))
}
@ -85,9 +78,10 @@ pub async fn create_flow(
// verify valid input transactions
if let Some(input) = &form.input {
for t in input {
let t = Transaction::get(t)
.await
.ok_or_else(|| api_error(&format!("No such transaction {t}")))?;
let t =
Transaction::get(&uuid::Uuid::from_str(t).map_err(|_| api_error("Invalid UUID"))?)
.await
.ok_or_else(|| api_error(&format!("No such transaction {t}")))?;
let item_variant = format!("{}::{}", t.item, t.variant);
@ -109,14 +103,13 @@ pub async fn create_flow(
if input_ref.is_empty() {
None
} else {
Some(input_ref.iter().map(|x| x.reference()).collect())
Some(input_ref.iter().map(|x| x.id).collect())
},
)
.await;
for t in input_ref {
t.consume(Price::zero(), &format!("flow::{kind}::{}", flow._id))
.await;
t.consume(0.0, &format!("flow::{kind}::{}", flow.id)).await;
}
Ok(flow)
@ -139,12 +132,12 @@ pub async fn create_flow_route(
flows: &State<JSONStore<FlowInfo>>,
) -> FallibleApiResponse {
let flow = create_flow(id, flows, &form).await?;
Ok(json!({"uuid": flow._id }))
Ok(json!({"uuid": flow.id }))
}
#[get("/flow/<id>")]
pub async fn flow_api_route(id: &str) -> FallibleApiResponse {
let flow = Flow::get(id)
let flow = Flow::get(&uuid::Uuid::from_str(id).map_err(|_| api_error("Invalid UUID"))?)
.await
.ok_or_else(|| api_error("No such flow"))?;
Ok(flow.api().await)
@ -152,11 +145,11 @@ pub async fn flow_api_route(id: &str) -> FallibleApiResponse {
#[post("/flow/<id>/end", data = "<form>")]
pub async fn end_flow_route(id: &str, form: Json<EndFlow>) -> FallibleApiResponse {
let flow = Flow::get(id)
let flow = Flow::get(&uuid::Uuid::from_str(id).map_err(|_| api_error("Invalid UUID"))?)
.await
.ok_or_else(|| api_error("Flow not found"))?;
if flow.done.is_some() {
if flow.ended.is_some() {
return Err(api_error("Flow already ended"));
}
@ -175,17 +168,17 @@ pub async fn continue_flow_route(
flows: &State<JSONStore<FlowInfo>>,
form: Json<CreateFlow>,
) -> FallibleApiResponse {
let this_flow = Flow::get(id)
let this_flow = Flow::get(&uuid::Uuid::from_str(id).map_err(|_| api_error("Invalid UUID"))?)
.await
.ok_or_else(|| api_error("Flow not found"))?;
if this_flow.done.is_some() {
if this_flow.ended.is_some() {
return Err(api_error("Flow already ended"));
}
// create next flow
let next_kind = flows
.get(this_flow.kind.id())
.get(&this_flow.kind)
.ok_or_else(|| api_error("Flow not found"))?
.next
.clone()
@ -195,20 +188,12 @@ pub async fn continue_flow_route(
// end current flow
this_flow.continue_next(&next_flow).await;
Ok(json!({"uuid": next_flow._id}))
Ok(json!({"uuid": next_flow.id}))
}
#[get("/flow/<id>/notes")]
pub async fn flow_notes_route(id: &str) -> FallibleApiResponse {
let notes = FlowNote::find(
doc! {
"on_flow": reference_of!(Flow, id).ok_or_else(|| api_error("No such flow"))?
},
None,
Some(doc! { "timestamp": Sort::Descending }),
)
.await
.unwrap();
let notes = FlowNote::find_of(id).await;
Ok(json!(vec_to_api(&notes).await))
}
@ -222,8 +207,8 @@ pub struct NoteAdd {
pub async fn create_flow_note_route(id: &str, form: Json<NoteAdd>) -> FallibleApiResponse {
let note = FlowNote::create(
&form.content,
reference_of!(Flow, id).ok_or_else(|| api_error("No such flow"))?,
&uuid::Uuid::from_str(id).map_err(|_| api_error("Invalid UUID"))?,
)
.await;
Ok(json!({"uuid": note._id }))
Ok(json!({"uuid": note.id }))
}

View file

@ -1,14 +1,14 @@
use mongod::{Model, ToAPI};
use std::str::FromStr;
use rocket::serde::json::Json;
use rocket::{get, post, State};
use serde::Deserialize;
use serde_json::json;
use crate::check_auth;
use crate::config::{Config, Webhook};
use crate::item::Item;
use crate::routes::Token;
use crate::routes::{ToAPI, Token};
use crate::variant::Variant;
use crate::{check_auth, get_itemdb};
use crate::{
db::ItemDB,
routes::{api_error, FallibleApiResponse},
@ -20,7 +20,7 @@ use super::{item_does_not_exist_error, variant_does_not_exist_error};
pub struct DemandForm {
uuid: String,
destination: String,
price: String,
price: f64,
}
/// Consumes a Transaction with Price and Destination
@ -29,11 +29,8 @@ pub async fn demand_route(f: Json<DemandForm>, t: Token, c: &State<Config>) -> F
check_auth!(t, c);
let transaction = Variant::demand(
&f.uuid,
f.price
.clone()
.try_into()
.map_err(|()| api_error("Price malformed"))?,
&uuid::Uuid::from_str(&f.uuid).map_err(|_| api_error("Invalid UUID"))?,
f.price,
&f.destination,
)
.await
@ -45,8 +42,8 @@ pub async fn demand_route(f: Json<DemandForm>, t: Token, c: &State<Config>) -> F
}
if let Some(url) = &hook.item_below_minimum {
let variant = Item::get(&transaction.item)
.await
let variant = get_itemdb!()
.get_item(&transaction.item)
.unwrap()
.variant(&transaction.variant)
.unwrap();

View file

@ -1,6 +1,5 @@
use std::collections::HashMap;
use mongod::{vec_to_api, ToAPI};
use rocket::{get, State};
use serde_json::json;
@ -9,7 +8,7 @@ use crate::{
config::Config,
json_store::JSONStore,
location::Location,
routes::{api_error, FallibleApiResponse, Token},
routes::{api_error, vec_to_api, FallibleApiResponse, ToAPI, Token},
transaction::Transaction,
};
@ -121,11 +120,6 @@ pub async fn location_inventory(
}
Ok(json!(
vec_to_api(
&Transaction::in_location(location)
.await
.ok_or_else(|| api_error("No such location"))?
)
.await
vec_to_api(&Transaction::in_location(location).await).await
))
}

View file

@ -4,12 +4,11 @@ mod location;
mod stat;
mod supply;
use std::str::FromStr;
pub use demand::*;
pub use error::*;
pub use location::*;
use mongod::reference_of;
use mongod::Model;
use mongod::ToAPI;
use rocket::post;
use rocket::serde::json::Json;
use serde::Deserialize;
@ -25,7 +24,9 @@ use crate::check_auth;
use crate::config::Config;
use crate::db::get_items_without_min_satisfied;
use crate::db::ItemDB;
use crate::location::Location;
use crate::get_locations;
use crate::get_pg;
use crate::routes::ToAPI;
use crate::routes::Token;
use crate::transaction::Transaction;
@ -91,9 +92,11 @@ pub async fn transaction_route(
) -> FallibleApiResponse {
check_auth!(t, c);
let t = Transaction::get(transaction)
.await
.ok_or_else(|| api_error("No transaction with this UUID"))?;
let t = Transaction::get(
&uuid::Uuid::from_str(&transaction).map_err(|_| api_error("Invalid UUID"))?,
)
.await
.ok_or_else(|| api_error("No transaction with this UUID"))?;
Ok(t.api().await)
}
@ -184,16 +187,16 @@ pub struct MoveTransaction {
#[post("/transaction/<id>/move", data = "<form>")]
pub async fn move_transaction_route(id: &str, form: Json<MoveTransaction>) -> FallibleApiResponse {
let new_loc = &form.to;
Transaction::get(id)
.await
.ok_or_else(|| api_error("No such transaction"))?
.change()
.location(if form.to.is_empty() {
None
} else {
Some(reference_of!(Location, new_loc).ok_or_else(|| api_error("No such location"))?)
})
.update()
let locations = get_locations!();
if !locations.contains_key(new_loc) {
return Err(api_error("No such location"));
}
sqlx::query("UPDATE transactions SET location = $1 WHERE id = $2")
.bind(new_loc)
.bind(uuid::Uuid::from_str(id).map_err(|_| api_error("Invalid UUID"))?)
.execute(get_pg!())
.await
.unwrap();

View file

@ -73,7 +73,7 @@ pub async fn item_stat_route(
let item_var = itemdb.get_item(&item).unwrap().variant(var).unwrap();
for t in item_var.inventory().await {
transaction_count += 1;
total_price += t.price.value;
total_price += t.price;
}
}
}

View file

@ -1,4 +1,3 @@
use mongod::ToAPI;
use rocket::serde::json::Json;
use rocket::{get, post, State};
use serde::{Deserialize, Serialize};
@ -6,7 +5,7 @@ use serde_json::json;
use crate::check_auth;
use crate::config::{Config, Webhook};
use crate::routes::Token;
use crate::routes::{vec_to_api, ToAPI, Token};
use crate::{
db::ItemDB,
routes::{api_error, FallibleApiResponse},
@ -18,7 +17,7 @@ use super::{item_does_not_exist_error, variant_does_not_exist_error};
pub struct SupplyForm {
pub item: String,
pub variant: String,
pub price: String,
pub price: f64,
pub origin: Option<String>,
pub location: Option<String>,
pub note: Option<String>,
@ -42,10 +41,7 @@ pub async fn supply_route(
let transaction = variant
.supply(
form.price
.clone()
.try_into()
.map_err(|()| api_error("Price malformed"))?,
form.price,
form.origin.as_deref(),
form.location.as_deref(),
form.note.as_deref(),
@ -58,7 +54,7 @@ pub async fn supply_route(
}
}
Ok(json!({"uuid": transaction._id}))
Ok(json!({"uuid": transaction.id}))
}
/// Returns a list of Transaction UUIDs for the Item Variant
@ -104,7 +100,7 @@ pub async fn inventory_route(
item.inventory().await
};
Ok(json!(mongod::vec_to_api(&transactions).await))
Ok(json!(vec_to_api(&transactions).await))
}
/// Returns current active Transactions for Item Variant
@ -126,7 +122,7 @@ pub async fn inventory_route_variant(
let transactions = variant.inventory().await;
Ok(json!(mongod::vec_to_api(&transactions).await))
Ok(json!(vec_to_api(&transactions).await))
}
/// Returns statistics for the Item Variant

View file

@ -37,3 +37,20 @@ impl<'r> FromRequest<'r> for Token {
}
}
}
/// A trait to generate a Model API representation in JSON format.
pub trait ToAPI: Sized {
/// Generate public API JSON
fn api(&self) -> impl std::future::Future<Output = serde_json::Value>;
}
/// Converts a slice of items implementing the `ToAPI` trait into a `Vec` of JSON values.
pub async fn vec_to_api(items: &[impl ToAPI]) -> Vec<serde_json::Value> {
let mut ret = Vec::with_capacity(items.len());
for e in items {
ret.push(e.api().await);
}
ret
}