refactor
This commit is contained in:
parent
897d23f917
commit
c752e09f1a
7 changed files with 380 additions and 363 deletions
40
src/cache.rs
Normal file
40
src/cache.rs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
use mongodb::{bson::doc, ClientSession};
|
||||||
|
|
||||||
|
struct InventoryCache {}
|
||||||
|
|
||||||
|
impl InventoryCache {
|
||||||
|
pub async fn push(uuid: &str, ses: &mut ClientSession) {
|
||||||
|
// todo : if not exists?
|
||||||
|
let update = doc! { "$push": { "transactions": uuid } };
|
||||||
|
|
||||||
|
let options = mongodb::options::FindOneAndUpdateOptions::builder()
|
||||||
|
.upsert(true)
|
||||||
|
.return_document(mongodb::options::ReturnDocument::After)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let result = ses
|
||||||
|
.client()
|
||||||
|
.database("cdb")
|
||||||
|
.collection::<mongodb::bson::Document>("cache")
|
||||||
|
.find_one_and_update_with_session(doc! { "_id": "inventory"}, update, options, ses)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn remove(uuid: &str, ses: &mut ClientSession) {
|
||||||
|
let update = doc! { "$pull": { "transactions": uuid}};
|
||||||
|
|
||||||
|
let options = mongodb::options::FindOneAndUpdateOptions::builder()
|
||||||
|
.upsert(true)
|
||||||
|
.return_document(mongodb::options::ReturnDocument::After)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let result = ses
|
||||||
|
.client()
|
||||||
|
.database("cdb")
|
||||||
|
.collection::<mongodb::bson::Document>("cache")
|
||||||
|
.find_one_and_update_with_session(doc! { "_id": "inventory"}, update, options, ses)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
73
src/db.rs
Normal file
73
src/db.rs
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
use crate::item::{Item, ItemEntry};
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! collect_results {
|
||||||
|
($res:expr) => {{
|
||||||
|
let mut ret = vec![];
|
||||||
|
|
||||||
|
while let Some(doc) = $res.try_next().await.unwrap() {
|
||||||
|
ret.push(doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
ret
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! cdb_col {
|
||||||
|
($db:expr, $col:expr) => {
|
||||||
|
$db.database("cdb")
|
||||||
|
.collection::<mongodb::bson::Document>($col)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! get_mongo {
|
||||||
|
() => {
|
||||||
|
mongodb::Client::with_uri_str(std::env::var("DB_URI").unwrap())
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ItemDB {
|
||||||
|
index: mdq::Index,
|
||||||
|
mongodb: mongodb::Client,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ItemDB {
|
||||||
|
pub async fn new(dir: &str, mongodb: &str) -> Self {
|
||||||
|
// scan for markdown item entries
|
||||||
|
let index = mdq::Index::new(dir, true);
|
||||||
|
let mongodb = get_mongo!();
|
||||||
|
|
||||||
|
for item in &index.documents {
|
||||||
|
let item = ItemEntry::new(item.clone());
|
||||||
|
item.init_db(&mongodb).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
Self { index, mongodb }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves an item by name
|
||||||
|
pub fn get_item(&self, item: &str) -> Option<Item> {
|
||||||
|
Some(Item::new(
|
||||||
|
self.mongodb.clone(),
|
||||||
|
self.index
|
||||||
|
.documents
|
||||||
|
.iter()
|
||||||
|
.map(|x| ItemEntry::new(x.clone()))
|
||||||
|
.find(|x| x.name == item)?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get all items
|
||||||
|
pub fn items(&self) -> Vec<String> {
|
||||||
|
let mut ret = vec![];
|
||||||
|
for item in &self.index.documents {
|
||||||
|
let item = ItemEntry::new(item.clone());
|
||||||
|
ret.push(item.name);
|
||||||
|
}
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
}
|
363
src/item.rs
363
src/item.rs
|
@ -1,8 +1,11 @@
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use crate::collect_results;
|
||||||
use futures::TryStreamExt;
|
use futures::TryStreamExt;
|
||||||
use mongodb::{bson::doc, ClientSession, Collection};
|
use mongodb::{bson::doc, ClientSession, Collection};
|
||||||
|
|
||||||
|
use crate::variant::Variant;
|
||||||
|
|
||||||
// todo : api key auth
|
// todo : api key auth
|
||||||
|
|
||||||
// ITEM
|
// ITEM
|
||||||
|
@ -13,33 +16,6 @@ use mongodb::{bson::doc, ClientSession, Collection};
|
||||||
// DEMAND STATS
|
// DEMAND STATS
|
||||||
// SEASONAL REVIEWS
|
// SEASONAL REVIEWS
|
||||||
|
|
||||||
macro_rules! collect_results {
|
|
||||||
($res:expr) => {{
|
|
||||||
let mut ret = vec![];
|
|
||||||
|
|
||||||
while let Some(doc) = $res.try_next().await.unwrap() {
|
|
||||||
ret.push(doc);
|
|
||||||
}
|
|
||||||
|
|
||||||
ret
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! cdb_col {
|
|
||||||
($db:expr, $col:expr) => {
|
|
||||||
$db.database("cdb")
|
|
||||||
.collection::<mongodb::bson::Document>($col)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! get_mongo {
|
|
||||||
() => {
|
|
||||||
mongodb::Client::with_uri_str(std::env::var("DB_URI").unwrap())
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(serde::Deserialize, serde::Serialize)]
|
#[derive(serde::Deserialize, serde::Serialize)]
|
||||||
pub struct ItemVariantEntry {
|
pub struct ItemVariantEntry {
|
||||||
pub item_name: String,
|
pub item_name: String,
|
||||||
|
@ -202,336 +178,3 @@ impl Item {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Price {
|
|
||||||
pub value: f64,
|
|
||||||
pub currency: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Price {
|
|
||||||
pub fn new(value: f64, currency: &str) -> Self {
|
|
||||||
Self {
|
|
||||||
value,
|
|
||||||
currency: currency.to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse(price: &str) -> Option<Self> {
|
|
||||||
let (value, currency) = price.split_once(' ')?;
|
|
||||||
|
|
||||||
Some(Self {
|
|
||||||
value: value.parse().ok()?,
|
|
||||||
currency: currency.to_string(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_bson(&self) -> mongodb::bson::Bson {
|
|
||||||
mongodb::bson::bson!({
|
|
||||||
"value": self.value,
|
|
||||||
"currency": self.currency.clone()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<mongodb::bson::Document> for Price {
|
|
||||||
fn from(value: mongodb::bson::Document) -> Self {
|
|
||||||
Self {
|
|
||||||
value: value.get_f64("value").unwrap(),
|
|
||||||
currency: value.get_str("currency").unwrap().to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<String> for Price {
|
|
||||||
type Error = ();
|
|
||||||
|
|
||||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
|
||||||
Self::parse(&value).ok_or(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents a specific instance of an item with potential variations.
|
|
||||||
///
|
|
||||||
/// This struct is used to describe a particular variation or instance of an item
|
|
||||||
/// in the real world. It may include attributes or properties that deviate from
|
|
||||||
/// the standard definition of the item. For example, different colors, sizes, or
|
|
||||||
/// configurations.
|
|
||||||
pub struct Variant {
|
|
||||||
pub item: String,
|
|
||||||
pub variant: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct BatchTransaction {
|
|
||||||
pub uuid: String,
|
|
||||||
pub transactions: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BatchTransaction {
|
|
||||||
pub fn new(transactions: Vec<String>) -> Self {
|
|
||||||
Self {
|
|
||||||
uuid: uuid::Uuid::new_v4().to_string(),
|
|
||||||
transactions,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_doc(&self) -> mongodb::bson::Document {
|
|
||||||
mongodb::bson::doc! {
|
|
||||||
"_id": &self.uuid,
|
|
||||||
"kind": "batch",
|
|
||||||
"transactions": &self.transactions
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from(b: mongodb::bson::Document) -> Self {
|
|
||||||
let uuid = b.get_str("_id").unwrap().to_string();
|
|
||||||
let transactions = b
|
|
||||||
.get_array("transactions")
|
|
||||||
.unwrap()
|
|
||||||
.into_iter()
|
|
||||||
.map(|x| x.as_str().unwrap().to_string())
|
|
||||||
.collect();
|
|
||||||
Self { uuid, transactions }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum TransactionType {
|
|
||||||
Batch(BatchTransaction),
|
|
||||||
Normal(Transaction),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TransactionType {
|
|
||||||
pub fn from(b: mongodb::bson::Document) -> Self {
|
|
||||||
if let Ok(kind) = b.get_str("kind") {
|
|
||||||
if kind == "batch" {
|
|
||||||
return Self::Batch(BatchTransaction::from(b));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Self::Normal(Transaction::from(b))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Transaction {
|
|
||||||
pub uuid: String,
|
|
||||||
pub item: String,
|
|
||||||
pub variant: String,
|
|
||||||
pub price: Price,
|
|
||||||
pub origin: String,
|
|
||||||
pub timestamp: i64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Transaction {
|
|
||||||
pub fn new(item: &str, variant: &str, price: Price, origin: &str) -> Self {
|
|
||||||
Self {
|
|
||||||
uuid: uuid::Uuid::new_v4().to_string(),
|
|
||||||
item: item.to_string(),
|
|
||||||
variant: variant.to_string(),
|
|
||||||
price,
|
|
||||||
origin: origin.to_string(),
|
|
||||||
timestamp: chrono::Utc::now().timestamp(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_doc(&self) -> mongodb::bson::Document {
|
|
||||||
mongodb::bson::doc! {
|
|
||||||
"_id": &self.uuid,
|
|
||||||
"item": &self.item,
|
|
||||||
"variant": &self.variant,
|
|
||||||
"price": self.price.as_bson(),
|
|
||||||
"origin": &self.origin,
|
|
||||||
"timestamp": self.timestamp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from(b: mongodb::bson::Document) -> Self {
|
|
||||||
let uuid = b.get_str("oid").unwrap().to_string();
|
|
||||||
let item = b.get_str("item").unwrap().to_string();
|
|
||||||
let variant = b.get_str("variant").unwrap().to_string();
|
|
||||||
let origin = b.get_str("origin").unwrap().to_string();
|
|
||||||
let timestamp = b.get_i64("timestamp").unwrap();
|
|
||||||
let price = Price::from(b);
|
|
||||||
Self {
|
|
||||||
uuid,
|
|
||||||
item,
|
|
||||||
variant,
|
|
||||||
price,
|
|
||||||
origin,
|
|
||||||
timestamp,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Variant {
|
|
||||||
pub async fn demand(&self, uuid: &str, price: Price, destination: String) -> Option<String> {
|
|
||||||
let db = get_mongo!();
|
|
||||||
|
|
||||||
// check if supply transaction exists
|
|
||||||
let supply_t = cdb_col!(db, "supply");
|
|
||||||
if supply_t
|
|
||||||
.find_one(doc! { "_id": uuid }, None)
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.is_none()
|
|
||||||
{
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo : demand batch
|
|
||||||
|
|
||||||
// mark as used
|
|
||||||
let demand_t = cdb_col!(db, "demand");
|
|
||||||
demand_t
|
|
||||||
.insert_one(
|
|
||||||
doc! {
|
|
||||||
"_id": uuid,
|
|
||||||
"destination": destination,
|
|
||||||
"price": price.as_bson()
|
|
||||||
},
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
Some(uuid.to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Records a supply transaction in the database.
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// * `amount` - The quantity of items supplied.
|
|
||||||
/// * `price` - The price of the supplied items.
|
|
||||||
/// * `origin` - The origin or source of the supplied items.
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// Returns a UUID string representing the transaction.
|
|
||||||
pub async fn supply(&self, amount: usize, price: Price, origin: &str) -> String {
|
|
||||||
let db = get_mongo!();
|
|
||||||
|
|
||||||
let mut ses = db.start_session(None).await.unwrap();
|
|
||||||
|
|
||||||
let col: mongodb::Collection<mongodb::bson::Document> =
|
|
||||||
ses.client().database("cdb").collection("supply");
|
|
||||||
|
|
||||||
let mut transactions = vec![];
|
|
||||||
for _ in 0..amount {
|
|
||||||
transactions.push(Transaction::new(
|
|
||||||
&self.item,
|
|
||||||
&self.variant,
|
|
||||||
price.clone(),
|
|
||||||
origin,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
for transaction in &transactions {
|
|
||||||
let r = col
|
|
||||||
.insert_one_with_session(transaction.as_doc(), None, &mut ses)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
// batch transaction
|
|
||||||
let ret_uuid = if amount == 1 {
|
|
||||||
transactions.first().unwrap().uuid.clone()
|
|
||||||
} else {
|
|
||||||
let batch =
|
|
||||||
BatchTransaction::new(transactions.iter().map(|x| x.uuid.clone()).collect());
|
|
||||||
let col: mongodb::Collection<mongodb::bson::Document> = ses
|
|
||||||
.client()
|
|
||||||
.database("cdb")
|
|
||||||
.collection("transactions_batch");
|
|
||||||
col.insert_one_with_session(batch.as_doc(), None, &mut ses)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
batch.uuid
|
|
||||||
};
|
|
||||||
|
|
||||||
// todo : transaction overlap cache -> scale
|
|
||||||
|
|
||||||
ses.commit_transaction().await.unwrap();
|
|
||||||
|
|
||||||
ret_uuid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct InventoryCache {}
|
|
||||||
|
|
||||||
impl InventoryCache {
|
|
||||||
pub async fn push(uuid: &str, ses: &mut ClientSession) {
|
|
||||||
// todo : if not exists?
|
|
||||||
let update = doc! { "$push": { "transactions": uuid } };
|
|
||||||
|
|
||||||
let options = mongodb::options::FindOneAndUpdateOptions::builder()
|
|
||||||
.upsert(true)
|
|
||||||
.return_document(mongodb::options::ReturnDocument::After)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let result = ses
|
|
||||||
.client()
|
|
||||||
.database("cdb")
|
|
||||||
.collection::<mongodb::bson::Document>("cache")
|
|
||||||
.find_one_and_update_with_session(doc! { "_id": "inventory"}, update, options, ses)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn remove(uuid: &str, ses: &mut ClientSession) {
|
|
||||||
let update = doc! { "$pull": { "transactions": uuid}};
|
|
||||||
|
|
||||||
let options = mongodb::options::FindOneAndUpdateOptions::builder()
|
|
||||||
.upsert(true)
|
|
||||||
.return_document(mongodb::options::ReturnDocument::After)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let result = ses
|
|
||||||
.client()
|
|
||||||
.database("cdb")
|
|
||||||
.collection::<mongodb::bson::Document>("cache")
|
|
||||||
.find_one_and_update_with_session(doc! { "_id": "inventory"}, update, options, ses)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ItemDB {
|
|
||||||
index: mdq::Index,
|
|
||||||
mongodb: mongodb::Client,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ItemDB {
|
|
||||||
pub async fn new(dir: &str, mongodb: &str) -> Self {
|
|
||||||
// scan for markdown item entries
|
|
||||||
let index = mdq::Index::new(dir, true);
|
|
||||||
let mongodb = get_mongo!();
|
|
||||||
|
|
||||||
for item in &index.documents {
|
|
||||||
let item = ItemEntry::new(item.clone());
|
|
||||||
item.init_db(&mongodb).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
Self { index, mongodb }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retrieves an item by name
|
|
||||||
pub fn get_item(&self, item: &str) -> Option<Item> {
|
|
||||||
Some(Item::new(
|
|
||||||
self.mongodb.clone(),
|
|
||||||
self.index
|
|
||||||
.documents
|
|
||||||
.iter()
|
|
||||||
.map(|x| ItemEntry::new(x.clone()))
|
|
||||||
.find(|x| x.name == item)?,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get all items
|
|
||||||
pub fn items(&self) -> Vec<String> {
|
|
||||||
let mut ret = vec![];
|
|
||||||
for item in &self.index.documents {
|
|
||||||
let item = ItemEntry::new(item.clone());
|
|
||||||
ret.push(item.name);
|
|
||||||
}
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
use actix_web::{get, HttpRequest, Responder};
|
use actix_web::{get, HttpRequest, Responder};
|
||||||
use maud::html;
|
use maud::html;
|
||||||
|
|
||||||
|
mod cache;
|
||||||
|
mod db;
|
||||||
mod item;
|
mod item;
|
||||||
mod routes;
|
mod routes;
|
||||||
|
mod transaction;
|
||||||
|
mod variant;
|
||||||
|
|
||||||
// ░░░░░░░░░░▀▀▀██████▄▄▄░░░░░░░░░░
|
// ░░░░░░░░░░▀▀▀██████▄▄▄░░░░░░░░░░
|
||||||
// ░░░░░░░░░░░░░░░░░▀▀▀████▄░░░░░░░
|
// ░░░░░░░░░░░░░░░░░▀▀▀████▄░░░░░░░
|
||||||
|
@ -21,7 +25,7 @@ mod routes;
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
pub(crate) async fn index(r: HttpRequest) -> impl Responder {
|
pub(crate) async fn index(r: HttpRequest) -> impl Responder {
|
||||||
let itemdb: &actix_web::web::Data<item::ItemDB> = r.app_data().unwrap();
|
let itemdb: &actix_web::web::Data<db::ItemDB> = r.app_data().unwrap();
|
||||||
let content = "";
|
let content = "";
|
||||||
web_base::func::build_site(&r, "Index", &content)
|
web_base::func::build_site(&r, "Index", &content)
|
||||||
}
|
}
|
||||||
|
@ -30,7 +34,7 @@ pub(crate) async fn index(r: HttpRequest) -> impl Responder {
|
||||||
async fn main() -> std::io::Result<()> {
|
async fn main() -> std::io::Result<()> {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
|
|
||||||
let itemdb = item::ItemDB::new("./itemdb", "mongodb://user:pass@127.0.0.1:27017").await;
|
let itemdb = db::ItemDB::new("./itemdb", "mongodb://user:pass@127.0.0.1:27017").await;
|
||||||
let itemdb = actix_web::web::Data::new(itemdb);
|
let itemdb = actix_web::web::Data::new(itemdb);
|
||||||
|
|
||||||
web_base::map!(web_base::Site::new(), |app: actix_web::App<_>| {
|
web_base::map!(web_base::Site::new(), |app: actix_web::App<_>| {
|
||||||
|
|
|
@ -7,7 +7,7 @@ use crate::routes::bad_req;
|
||||||
|
|
||||||
macro_rules! get_itemdb {
|
macro_rules! get_itemdb {
|
||||||
($req:expr) => {{
|
($req:expr) => {{
|
||||||
let itemdb: &actix_web::web::Data<item::ItemDB> = $req.app_data().unwrap();
|
let itemdb: &actix_web::web::Data<crate::db::ItemDB> = $req.app_data().unwrap();
|
||||||
itemdb
|
itemdb
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
146
src/transaction.rs
Normal file
146
src/transaction.rs
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
pub enum TransactionType {
|
||||||
|
Batch(BatchTransaction),
|
||||||
|
Normal(Transaction),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TransactionType {
|
||||||
|
pub fn from(b: mongodb::bson::Document) -> Self {
|
||||||
|
if let Ok(kind) = b.get_str("kind") {
|
||||||
|
if kind == "batch" {
|
||||||
|
return Self::Batch(BatchTransaction::from(b));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::Normal(Transaction::from(b))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Transaction {
|
||||||
|
pub uuid: String,
|
||||||
|
pub item: String,
|
||||||
|
pub variant: String,
|
||||||
|
pub price: Price,
|
||||||
|
pub origin: String,
|
||||||
|
pub timestamp: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Transaction {
|
||||||
|
pub fn new(item: &str, variant: &str, price: Price, origin: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
uuid: uuid::Uuid::new_v4().to_string(),
|
||||||
|
item: item.to_string(),
|
||||||
|
variant: variant.to_string(),
|
||||||
|
price,
|
||||||
|
origin: origin.to_string(),
|
||||||
|
timestamp: chrono::Utc::now().timestamp(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_doc(&self) -> mongodb::bson::Document {
|
||||||
|
mongodb::bson::doc! {
|
||||||
|
"_id": &self.uuid,
|
||||||
|
"item": &self.item,
|
||||||
|
"variant": &self.variant,
|
||||||
|
"price": self.price.as_bson(),
|
||||||
|
"origin": &self.origin,
|
||||||
|
"timestamp": self.timestamp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from(b: mongodb::bson::Document) -> Self {
|
||||||
|
let uuid = b.get_str("oid").unwrap().to_string();
|
||||||
|
let item = b.get_str("item").unwrap().to_string();
|
||||||
|
let variant = b.get_str("variant").unwrap().to_string();
|
||||||
|
let origin = b.get_str("origin").unwrap().to_string();
|
||||||
|
let timestamp = b.get_i64("timestamp").unwrap();
|
||||||
|
let price = Price::from(b);
|
||||||
|
Self {
|
||||||
|
uuid,
|
||||||
|
item,
|
||||||
|
variant,
|
||||||
|
price,
|
||||||
|
origin,
|
||||||
|
timestamp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct BatchTransaction {
|
||||||
|
pub uuid: String,
|
||||||
|
pub transactions: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BatchTransaction {
|
||||||
|
pub fn new(transactions: Vec<String>) -> Self {
|
||||||
|
Self {
|
||||||
|
uuid: uuid::Uuid::new_v4().to_string(),
|
||||||
|
transactions,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_doc(&self) -> mongodb::bson::Document {
|
||||||
|
mongodb::bson::doc! {
|
||||||
|
"_id": &self.uuid,
|
||||||
|
"kind": "batch",
|
||||||
|
"transactions": &self.transactions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from(b: mongodb::bson::Document) -> Self {
|
||||||
|
let uuid = b.get_str("_id").unwrap().to_string();
|
||||||
|
let transactions = b
|
||||||
|
.get_array("transactions")
|
||||||
|
.unwrap()
|
||||||
|
.into_iter()
|
||||||
|
.map(|x| x.as_str().unwrap().to_string())
|
||||||
|
.collect();
|
||||||
|
Self { uuid, transactions }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Price {
|
||||||
|
pub value: f64,
|
||||||
|
pub currency: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Price {
|
||||||
|
pub fn new(value: f64, currency: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
value,
|
||||||
|
currency: currency.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse(price: &str) -> Option<Self> {
|
||||||
|
let (value, currency) = price.split_once(' ')?;
|
||||||
|
|
||||||
|
Some(Self {
|
||||||
|
value: value.parse().ok()?,
|
||||||
|
currency: currency.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_bson(&self) -> mongodb::bson::Bson {
|
||||||
|
mongodb::bson::bson!({
|
||||||
|
"value": self.value,
|
||||||
|
"currency": self.currency.clone()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<mongodb::bson::Document> for Price {
|
||||||
|
fn from(value: mongodb::bson::Document) -> Self {
|
||||||
|
Self {
|
||||||
|
value: value.get_f64("value").unwrap(),
|
||||||
|
currency: value.get_str("currency").unwrap().to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<String> for Price {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||||
|
Self::parse(&value).ok_or(())
|
||||||
|
}
|
||||||
|
}
|
111
src/variant.rs
Normal file
111
src/variant.rs
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
use mongodb::bson::doc;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
cdb_col, get_mongo,
|
||||||
|
transaction::{BatchTransaction, Price, Transaction},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Represents a specific instance of an item with potential variations.
|
||||||
|
///
|
||||||
|
/// This struct is used to describe a particular variation or instance of an item
|
||||||
|
/// in the real world. It may include attributes or properties that deviate from
|
||||||
|
/// the standard definition of the item. For example, different colors, sizes, or
|
||||||
|
/// configurations.
|
||||||
|
pub struct Variant {
|
||||||
|
pub item: String,
|
||||||
|
pub variant: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Variant {
|
||||||
|
pub async fn demand(&self, uuid: &str, price: Price, destination: String) -> Option<String> {
|
||||||
|
let db = get_mongo!();
|
||||||
|
|
||||||
|
// check if supply transaction exists
|
||||||
|
let supply_t = cdb_col!(db, "supply");
|
||||||
|
if supply_t
|
||||||
|
.find_one(doc! { "_id": uuid }, None)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.is_none()
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo : demand batch
|
||||||
|
|
||||||
|
// mark as used
|
||||||
|
let demand_t = cdb_col!(db, "demand");
|
||||||
|
demand_t
|
||||||
|
.insert_one(
|
||||||
|
doc! {
|
||||||
|
"_id": uuid,
|
||||||
|
"destination": destination,
|
||||||
|
"price": price.as_bson()
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
Some(uuid.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Records a supply transaction in the database.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `amount` - The quantity of items supplied.
|
||||||
|
/// * `price` - The price of the supplied items.
|
||||||
|
/// * `origin` - The origin or source of the supplied items.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// Returns a UUID string representing the transaction.
|
||||||
|
pub async fn supply(&self, amount: usize, price: Price, origin: &str) -> String {
|
||||||
|
let db = get_mongo!();
|
||||||
|
|
||||||
|
let mut ses = db.start_session(None).await.unwrap();
|
||||||
|
|
||||||
|
let col: mongodb::Collection<mongodb::bson::Document> =
|
||||||
|
ses.client().database("cdb").collection("supply");
|
||||||
|
|
||||||
|
let mut transactions = vec![];
|
||||||
|
for _ in 0..amount {
|
||||||
|
transactions.push(Transaction::new(
|
||||||
|
&self.item,
|
||||||
|
&self.variant,
|
||||||
|
price.clone(),
|
||||||
|
origin,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
for transaction in &transactions {
|
||||||
|
let r = col
|
||||||
|
.insert_one_with_session(transaction.as_doc(), None, &mut ses)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// batch transaction
|
||||||
|
let ret_uuid = if amount == 1 {
|
||||||
|
transactions.first().unwrap().uuid.clone()
|
||||||
|
} else {
|
||||||
|
let batch =
|
||||||
|
BatchTransaction::new(transactions.iter().map(|x| x.uuid.clone()).collect());
|
||||||
|
let col: mongodb::Collection<mongodb::bson::Document> = ses
|
||||||
|
.client()
|
||||||
|
.database("cdb")
|
||||||
|
.collection("transactions_batch");
|
||||||
|
col.insert_one_with_session(batch.as_doc(), None, &mut ses)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
batch.uuid
|
||||||
|
};
|
||||||
|
|
||||||
|
// todo : transaction overlap cache -> scale
|
||||||
|
|
||||||
|
ses.commit_transaction().await.unwrap();
|
||||||
|
|
||||||
|
ret_uuid
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue