diff --git a/Cargo.lock b/Cargo.lock index 4b2bae5..1af1dfa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -103,7 +103,7 @@ dependencies = [ "mime", "percent-encoding", "pin-project-lite", - "rand", + "rand 0.8.5", "sha1", "smallvec", "tracing", @@ -347,7 +347,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b88d82667eca772c4aa12f0f1348b3ae643424c8876448f3f7bd5787032e234c" dependencies = [ - "autocfg", + "autocfg 1.1.0", ] [[package]] @@ -367,6 +367,15 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "autocfg" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dde43e75fd43e8a1bf86103336bc699aa8d17ad1be60c76c0bdfd4828e19b78" +dependencies = [ + "autocfg 1.1.0", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -585,6 +594,15 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +dependencies = [ + "bitflags", +] + [[package]] name = "codespan-reporting" version = "0.11.1" @@ -649,7 +667,7 @@ dependencies = [ "base64", "hmac", "percent-encoding", - "rand", + "rand 0.8.5", "sha2", "subtle", "time 0.3.16", @@ -732,7 +750,7 @@ version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f916dfc5d356b0ed9dae65f1db9fc9770aa2851d2662b988ccf4fe3516e86348" dependencies = [ - "autocfg", + "autocfg 1.1.0", "cfg-if 1.0.0", "crossbeam-utils", "memoffset", @@ -866,7 +884,7 @@ dependencies = [ "itertools", "model", "pretty_env_logger", - "rand", + "rand 0.8.5", "rumqttc", "serde", "sqlx", @@ -1090,10 +1108,23 @@ dependencies = [ "chrono", "dummy", "http", - "rand", + "rand 0.8.5", "uuid 1.2.1", ] +[[package]] +name = "fakeit" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "853c484c6a2a2a7e7d5f0d7863b4a64b90875b60cea1ccab68cc4da21c785656" +dependencies = [ + "chrono", + "libmath", + "rand 0.6.5", + "simplerand", + "uuid 0.8.2", +] + [[package]] name = "fastrand" version = "1.8.0" @@ -1264,6 +1295,12 @@ dependencies = [ "uuid 1.2.1", ] +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + [[package]] name = "fuchsia-zircon" version = "0.3.3" @@ -1726,7 +1763,7 @@ version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" dependencies = [ - "autocfg", + "autocfg 1.1.0", "hashbrown", "serde", ] @@ -1909,6 +1946,15 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "libmath" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfd3416934a853ae80d5c3b006f632dfcbaf320300c5167e88a469e9ac214502" +dependencies = [ + "rand 0.3.23", +] + [[package]] name = "libz-sys" version = "1.1.8" @@ -1960,7 +2006,7 @@ version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" dependencies = [ - "autocfg", + "autocfg 1.1.0", "scopeguard", ] @@ -2019,7 +2065,7 @@ version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" dependencies = [ - "autocfg", + "autocfg 1.1.0", ] [[package]] @@ -2105,8 +2151,8 @@ dependencies = [ "derive_more", "fake", "password-hash", - "rand", - "rand_core", + "rand 0.8.5", + "rand_core 0.6.4", "serde", "sqlx", "sqlx-core", @@ -2205,7 +2251,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" dependencies = [ - "autocfg", + "autocfg 1.1.0", "num-integer", "num-traits", ] @@ -2216,7 +2262,7 @@ version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ - "autocfg", + "autocfg 1.1.0", "num-traits", ] @@ -2226,7 +2272,7 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ - "autocfg", + "autocfg 1.1.0", ] [[package]] @@ -2303,7 +2349,7 @@ version = "0.9.77" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b03b84c3b2d099b81f0953422b4d4ad58761589d0229b5506356afca05a3670a" dependencies = [ - "autocfg", + "autocfg 1.1.0", "cc", "libc", "pkg-config", @@ -2325,7 +2371,7 @@ dependencies = [ "lazy_static", "percent-encoding", "pin-project", - "rand", + "rand 0.8.5", "thiserror", ] @@ -2393,7 +2439,7 @@ dependencies = [ "once_cell", "opentelemetry_api", "percent-encoding", - "rand", + "rand 0.8.5", "thiserror", ] @@ -2442,7 +2488,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" dependencies = [ "base64ct", - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -2626,6 +2672,48 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c" +dependencies = [ + "libc", + "rand 0.4.6", +] + +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +dependencies = [ + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi 0.3.9", +] + +[[package]] +name = "rand" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" +dependencies = [ + "autocfg 0.1.8", + "libc", + "rand_chacha 0.1.1", + "rand_core 0.4.2", + "rand_hc", + "rand_isaac", + "rand_jitter", + "rand_os", + "rand_pcg", + "rand_xorshift", + "winapi 0.3.9", +] + [[package]] name = "rand" version = "0.8.5" @@ -2633,8 +2721,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" +dependencies = [ + "autocfg 0.1.8", + "rand_core 0.3.1", ] [[package]] @@ -2644,9 +2742,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", ] +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + [[package]] name = "rand_core" version = "0.6.4" @@ -2656,13 +2769,75 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rand_hc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rand_isaac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rand_jitter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" +dependencies = [ + "libc", + "rand_core 0.4.2", + "winapi 0.3.9", +] + +[[package]] +name = "rand_os" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" +dependencies = [ + "cloudabi", + "fuchsia-cprng", + "libc", + "rand_core 0.4.2", + "rdrand", + "winapi 0.3.9", +] + +[[package]] +name = "rand_pcg" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" +dependencies = [ + "autocfg 0.1.8", + "rand_core 0.4.2", +] + +[[package]] +name = "rand_xorshift" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" +dependencies = [ + "rand_core 0.3.1", +] + [[package]] name = "rayon" version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" dependencies = [ - "autocfg", + "autocfg 1.1.0", "crossbeam-deque", "either", "rayon-core", @@ -2680,6 +2855,15 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + [[package]] name = "redox_syscall" version = "0.2.16" @@ -3014,7 +3198,7 @@ dependencies = [ "gloo-utils", "indexmap", "js-sys", - "rand", + "rand 0.8.5", "serde", "serde_json", "uuid 1.2.1", @@ -3171,13 +3355,22 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62ac7f900db32bf3fd12e0117dd3dc4da74bc52ebaac97f39668446d89694803" +[[package]] +name = "simplerand" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70455a0a21cb984df0f86033ca19f37cced497deb779603e7fa7b032f5b8897b" +dependencies = [ + "lazy_static", +] + [[package]] name = "slab" version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" dependencies = [ - "autocfg", + "autocfg 1.1.0", ] [[package]] @@ -3297,7 +3490,7 @@ dependencies = [ "once_cell", "paste", "percent-encoding", - "rand", + "rand 0.8.5", "rust_decimal", "rustls", "rustls-pemfile 1.0.1", @@ -3365,7 +3558,7 @@ dependencies = [ "db-utils", "derive_more", "dotenv", - "fake", + "fakeit", "futures 0.3.25", "insta", "model", @@ -3431,7 +3624,7 @@ dependencies = [ "humantime 2.1.0", "opentelemetry 0.17.0", "pin-project", - "rand", + "rand 0.8.5", "serde", "static_assertions", "tarpc-plugins", @@ -3636,7 +3829,7 @@ dependencies = [ "parking_lot", "password-hash", "pretty_env_logger", - "rand_core", + "rand_core 0.6.4", "rumqttc", "serde", "sha2", @@ -3653,7 +3846,7 @@ version = "1.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" dependencies = [ - "autocfg", + "autocfg 1.1.0", "bytes", "libc", "memchr", diff --git a/crates/channels/src/stocks/mod.rs b/crates/channels/src/stocks/mod.rs index 132fadc..32febe4 100644 --- a/crates/channels/src/stocks/mod.rs +++ b/crates/channels/src/stocks/mod.rs @@ -36,8 +36,18 @@ pub enum Error { DeleteProduct(ProductId), #[error("Failed to create variant of product {0:?}")] CreateProductVariant(ProductId), + #[error("Failed to update variant {0:?}")] + UpdateProductVariant(ProductVariantId), #[error("Failed to create stock of variant {0:?}")] CreateVariantStock(ProductVariantId), + #[error("Failed to add photo to variant {0:?}")] + AddProductPhoto(ProductVariantId), + #[error("Failed to delete photo for variant {0:?}")] + DeleteProductPhoto(ProductVariantId), + #[error("Failed to create stock for variant {0:?}")] + CreateProductStock(ProductVariantId), + #[error("Failed to update stock for variant {0:?}")] + UpdateProductStock(ProductVariantId), } pub mod rpc { diff --git a/crates/channels/src/stocks/product_photo.rs b/crates/channels/src/stocks/product_photo.rs index 1313783..203da5e 100644 --- a/crates/channels/src/stocks/product_photo.rs +++ b/crates/channels/src/stocks/product_photo.rs @@ -5,6 +5,7 @@ pub mod add_product_photo { #[derive(Debug, serde::Serialize, serde::Deserialize)] pub struct Input { + pub product_variant_id: ProductVariantId, pub local_path: LocalPath, pub file_name: FileName, pub unique_name: UniqueName, diff --git a/crates/channels/src/stocks/product_variant.rs b/crates/channels/src/stocks/product_variant.rs index 91a16ea..873d379 100644 --- a/crates/channels/src/stocks/product_variant.rs +++ b/crates/channels/src/stocks/product_variant.rs @@ -1,3 +1,8 @@ +use model::v2::{ProductVariant, ProductVariantId}; +use rumqttc::QoS; + +use crate::AsyncClient; + pub mod create_product_variant { use model::v2::*; @@ -29,7 +34,7 @@ pub mod update_product_variant { pub struct Input { pub id: ProductVariantId, pub product_id: ProductId, - pub name: ProductName, + pub name: ProductVariantName, pub short_description: ProductShortDesc, pub long_description: ProductLongDesc, pub price: Price, @@ -62,3 +67,57 @@ pub mod delete_product_variant { pub type Output = Result; } + +pub enum Topic { + ProductVariantCreated, + ProductVariantUpdated, + ProductVariantDeleted, +} + +impl Topic { + pub fn to_str(&self) -> &str { + match self { + Topic::ProductVariantCreated => "product-variant/created", + Topic::ProductVariantUpdated => "product-variant/updated", + Topic::ProductVariantDeleted => "product-variant/deleted", + } + } +} + +impl Into for Topic { + fn into(self) -> String { + self.to_str().into() + } +} + +impl AsyncClient { + pub async fn emit_product_variant_created(&self, product: &ProductVariant) { + self.publish_or_log( + Topic::ProductVariantCreated, + QoS::AtLeastOnce, + true, + product, + ) + .await + } + + pub async fn emit_product_variant_updated(&self, product: &ProductVariant) { + self.publish_or_log( + Topic::ProductVariantUpdated, + QoS::AtLeastOnce, + true, + product, + ) + .await + } + + pub async fn emit_product_variant_deleted(&self, product: &ProductVariantId) { + self.publish_or_log( + Topic::ProductVariantDeleted, + QoS::AtLeastOnce, + true, + product, + ) + .await + } +} diff --git a/crates/stock_manager/Cargo.toml b/crates/stock_manager/Cargo.toml index 9b7299e..64b7d2a 100644 --- a/crates/stock_manager/Cargo.toml +++ b/crates/stock_manager/Cargo.toml @@ -32,6 +32,6 @@ tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } uuid = { version = "1.2.1", features = ['v4'] } [dev-dependencies] -fake = { version = "2.5.0" } testx = { path = "../testx" } insta = { version = "1.21.0" } +fakeit = { version = "1.1.1" } diff --git a/crates/stock_manager/src/actions/load.rs b/crates/stock_manager/src/actions/load.rs index bb80518..f58044e 100644 --- a/crates/stock_manager/src/actions/load.rs +++ b/crates/stock_manager/src/actions/load.rs @@ -106,7 +106,7 @@ async fn inner_detailed_products( let stocks = match dbm.run(&mut *t).await { Ok(stocks) => stocks, Err(e) => { - tracing::error!("{}", e); + tracing::warn!("{}", e); return Err(Error::VariantStocks( variants.into_iter().map(|p| p.id).collect(), )); @@ -118,7 +118,7 @@ async fn inner_detailed_products( let photos = match dbm.run(t).await { Ok(photos) => photos, Err(e) => { - tracing::error!("{}", e); + tracing::warn!("{}", e); return Err(Error::VariantPhotos( variants.into_iter().map(|p| p.id).collect(), )); diff --git a/crates/stock_manager/src/actions/mod.rs b/crates/stock_manager/src/actions/mod.rs index ee11529..7e02b33 100644 --- a/crates/stock_manager/src/actions/mod.rs +++ b/crates/stock_manager/src/actions/mod.rs @@ -15,7 +15,7 @@ macro_rules! begin_t { ($db: ident, $err: expr) => { match $db.pool().begin().await { Err(e) => { - tracing::error!("{}", e); + tracing::warn!("{}", e); return Err($err); } Ok(t) => t, @@ -29,7 +29,7 @@ macro_rules! dbm_run { match $dbm.run($t).await { Ok(res) => res, Err(e) => { - tracing::error!("{}", e); + tracing::warn!("{}", e); return Err($err); } } diff --git a/crates/stock_manager/src/actions/product.rs b/crates/stock_manager/src/actions/product.rs index f2babfd..e831dd4 100644 --- a/crates/stock_manager/src/actions/product.rs +++ b/crates/stock_manager/src/actions/product.rs @@ -18,7 +18,7 @@ pub async fn create_product( match inner_create_product(input, &mut t).await { Ok(res) => { if let Err(e) = t.commit().await { - tracing::error!("{}", e); + tracing::warn!("{}", e); Err(Error::InternalServerError) } else { mqtt.emit_product_created(&res.product).await; @@ -26,7 +26,7 @@ pub async fn create_product( } } Err(e) => { - tracing::error!("{}", e); + tracing::warn!("{}", e); t.rollback().await.ok(); Err(e) } @@ -47,7 +47,7 @@ async fn inner_create_product( let product = match dbm.run(&mut *t).await { Ok(product) => product, Err(e) => { - tracing::error!("{}", e); + tracing::warn!("{}", e); return Err(Error::CreateProduct); } }; @@ -115,7 +115,7 @@ pub async fn update_product( match res { Ok(res) => { t.commit().await.map_err(|e| { - tracing::error!("{}", e); + tracing::warn!("{}", e); Error::InternalServerError })?; @@ -126,7 +126,7 @@ pub async fn update_product( Err(e) => { tracing::warn!("{}", e); t.rollback().await.map_err(|e| { - tracing::error!("{}", e); + tracing::warn!("{}", e); Error::InternalServerError })?; Err(e) @@ -167,16 +167,16 @@ pub async fn delete_product( match res { Ok((product_id, _maybe_product)) => { if let Err(e) = t.commit().await { - tracing::error!("{}", e); + tracing::warn!("{}", e); return Err(Error::InternalServerError); } mqtt.emit_product_deleted(&product_id).await; Ok(delete_product::Details { product_id }) } Err(e) => { - tracing::error!("{}", e); + tracing::warn!("{}", e); t.rollback().await.map_err(|e| { - tracing::error!("{}", e); + tracing::warn!("{}", e); Error::InternalServerError })?; Err(Error::DeleteProduct(product_id)) diff --git a/crates/stock_manager/src/actions/product_photo.rs b/crates/stock_manager/src/actions/product_photo.rs index f805025..ec4f84b 100644 --- a/crates/stock_manager/src/actions/product_photo.rs +++ b/crates/stock_manager/src/actions/product_photo.rs @@ -1,23 +1,225 @@ -use channels::stocks::{add_product_photo, delete_product_photo}; +use channels::stocks::{add_product_photo, delete_product_photo, Error}; use channels::AsyncClient; use config::SharedAppConfig; +use db_utils::PgT; +use crate::begin_t; use crate::db::Database; pub async fn add_product_photo( - _input: add_product_photo::Input, - _db: Database, + input: add_product_photo::Input, + db: Database, _mqtt: AsyncClient, _config: SharedAppConfig, ) -> add_product_photo::Output { - todo!() + let id = input.product_variant_id; + let mut t = begin_t!(db, Error::InternalServerError); + let res = inner_add_product_photo(input, &mut t).await; + + match res { + Ok(details) => { + if let Err(e) = t.commit().await { + tracing::warn!("{}", e); + Err(Error::AddProductPhoto(id)) + } else { + Ok(details) + } + } + Err(e) => { + t.rollback().await.ok(); + Err(e) + } + } +} + +async fn inner_add_product_photo( + input: add_product_photo::Input, + t: &mut PgT<'_>, +) -> add_product_photo::Output { + let dbm = crate::db::CreatePhoto { + local_path: input.local_path, + file_name: input.file_name, + unique_name: input.unique_name, + }; + let photo = match dbm.run(&mut *t).await { + Ok(res) => res, + Err(e) => { + tracing::warn!("{}", e); + return Err(Error::AddProductPhoto(input.product_variant_id)); + } + }; + + let dbm = crate::db::CreateProductPhoto { + product_variant_id: input.product_variant_id, + photo_id: photo.id, + }; + match dbm.run(&mut *t).await { + Ok(res) => Ok(add_product_photo::Details { + photo_id: res.id, + product_variant_id: res.product_variant_id, + local_path: photo.local_path, + file_name: photo.file_name, + unique_name: photo.unique_name, + }), + Err(e) => { + tracing::warn!("{}", e); + Err(Error::AddProductPhoto(input.product_variant_id)) + } + } } pub async fn delete_product_photo( - _input: delete_product_photo::Input, - _db: Database, + input: delete_product_photo::Input, + db: Database, _mqtt: AsyncClient, _config: SharedAppConfig, ) -> delete_product_photo::Output { - todo!() + let id = input.product_variant_id; + let mut t = begin_t!(db, Error::InternalServerError); + let res = inner_delete_product_photo(input, &mut t).await; + + match res { + Ok(details) => { + if let Err(e) = t.commit().await { + tracing::warn!("{}", e); + Err(Error::DeleteProductPhoto(id)) + } else { + Ok(details) + } + } + Err(e) => { + t.rollback().await.ok(); + Err(e) + } + } +} + +async fn inner_delete_product_photo( + input: delete_product_photo::Input, + t: &mut PgT<'_>, +) -> delete_product_photo::Output { + let dbm = crate::db::DeleteProductPhoto { id: input.photo_id }; + + match dbm.run(t).await { + Ok(_details) => Ok(delete_product_photo::Details { + photo_id: input.photo_id, + product_variant_id: input.product_variant_id, + }), + Err(e) => { + tracing::warn!("{}", e); + Err(Error::DeleteProductPhoto(input.product_variant_id)) + } + } +} + +#[cfg(test)] +mod tests { + use channels::stocks::{add_product_photo, delete_product_photo}; + use config::UpdateConfig; + use db_utils::PgT; + use model::v2::*; + + use crate::actions::product_photo::{inner_add_product_photo, inner_delete_product_photo}; + use crate::db::Database; + + struct NoOpts; + impl UpdateConfig for NoOpts {} + + async fn test_product(t: &mut PgT<'_>) -> Product { + crate::db::CreateProduct { + name: ProductName::new(format!("{}", uuid::Uuid::new_v4())), + category: None, + deliver_days_flag: Days(vec![]), + } + .run(t) + .await + .unwrap() + } + + async fn test_product_variant(product_id: ProductId, t: &mut PgT<'_>) -> ProductVariant { + crate::db::CreateProductVariant { + product_id, + name: ProductVariantName::new(format!("{}", uuid::Uuid::new_v4())), + short_description: ProductShortDesc::new(fakeit::words::sentence(4)), + long_description: ProductLongDesc::new(fakeit::words::sentence(16)), + price: Price::from_u32(650), + } + .run(t) + .await + .unwrap() + } + + async fn test_photo(t: &mut PgT<'_>) -> Photo { + crate::db::CreatePhoto { + local_path: LocalPath::new(format!("{}", uuid::Uuid::new_v4())), + file_name: FileName::new(format!("{}", uuid::Uuid::new_v4())), + unique_name: UniqueName::new(format!("{}", uuid::Uuid::new_v4())), + } + .run(t) + .await + .unwrap() + } + + async fn test_product_photo( + product_variant_id: ProductVariantId, + photo_id: PhotoId, + t: &mut PgT<'_>, + ) -> ProductPhoto { + crate::db::CreateProductPhoto { + product_variant_id, + photo_id, + } + .run(t) + .await + .unwrap() + } + + #[tokio::test] + async fn add_product_photo() { + testx::db_t_ref!(t); + + let product = test_product(&mut t).await; + let variant = test_product_variant(product.id, &mut t).await; + + let res = inner_add_product_photo( + add_product_photo::Input { + product_variant_id: variant.id, + local_path: LocalPath::new(format!("{}", uuid::Uuid::new_v4())), + file_name: FileName::new(format!("{}", uuid::Uuid::new_v4())), + unique_name: UniqueName::new(format!("{}", uuid::Uuid::new_v4())), + }, + &mut t, + ) + .await; + + testx::db_rollback!(t); + + let _res = res.unwrap(); + } + + #[tokio::test] + async fn delete_product_photo() { + testx::db_t_ref!(t); + + let product = test_product(&mut t).await; + let variant = test_product_variant(product.id, &mut t).await; + let photo = test_photo(&mut t).await; + let product_photo = test_product_photo(variant.id, photo.id, &mut t).await; + + let res = inner_delete_product_photo( + delete_product_photo::Input { + photo_id: product_photo.id, + product_variant_id: variant.id, + }, + &mut t, + ) + .await; + + testx::db_rollback!(t); + + let res = res.unwrap(); + + assert_eq!(res.photo_id, product_photo.id); + assert_eq!(res.product_variant_id, variant.id); + } } diff --git a/crates/stock_manager/src/actions/product_stock.rs b/crates/stock_manager/src/actions/product_stock.rs index 0792894..acb8bab 100644 --- a/crates/stock_manager/src/actions/product_stock.rs +++ b/crates/stock_manager/src/actions/product_stock.rs @@ -1,23 +1,203 @@ -use channels::stocks::{create_product_stock, update_product_stock}; +use channels::stocks::{create_product_stock, update_product_stock, Error}; use channels::AsyncClient; use config::SharedAppConfig; +use db_utils::PgT; +use crate::begin_t; use crate::db::Database; pub async fn create_product_stock( - _input: create_product_stock::Input, - _db: Database, + input: create_product_stock::Input, + db: Database, _mqtt: AsyncClient, _config: SharedAppConfig, ) -> create_product_stock::Output { - todo!() + let product_variant_id = input.product_variant_id; + let mut t = begin_t!(db, Error::InternalServerError); + let res = inner_create_product_stock(input, &mut t).await; + match res { + Ok(details) => { + if let Err(e) = t.commit().await { + tracing::warn!("{}", e); + Err(Error::CreateProductStock(product_variant_id)) + } else { + Ok(details) + } + } + Err(e) => { + if let Err(e) = t.rollback().await { + tracing::warn!("{}", e); + } + Err(e) + } + } +} + +async fn inner_create_product_stock( + input: create_product_stock::Input, + t: &mut PgT<'_>, +) -> create_product_stock::Output { + let dbm = crate::db::CreateStock { + product_variant_id: input.product_variant_id, + quantity: input.quantity, + quantity_unit: input.quantity_unit, + }; + match dbm.run(t).await { + Ok(product_stock) => Ok(create_product_stock::Details { product_stock }), + Err(e) => { + tracing::warn!("{}", e); + Err(Error::CreateVariantStock(input.product_variant_id)) + } + } } pub async fn update_product_stock( - _input: update_product_stock::Input, - _db: Database, + input: update_product_stock::Input, + db: Database, _mqtt: AsyncClient, _config: SharedAppConfig, ) -> update_product_stock::Output { - todo!() + let product_variant_id = input.product_variant_id; + let mut t = begin_t!(db, Error::InternalServerError); + let res = inner_update_product_stock(input, &mut t).await; + match res { + Ok(details) => { + if let Err(e) = t.commit().await { + tracing::warn!("{}", e); + Err(Error::UpdateProductStock(product_variant_id)) + } else { + Ok(details) + } + } + Err(e) => { + if let Err(e) = t.rollback().await { + tracing::warn!("{}", e); + } + Err(e) + } + } +} + +async fn inner_update_product_stock( + input: update_product_stock::Input, + t: &mut PgT<'_>, +) -> update_product_stock::Output { + let dbm = crate::db::UpdateStock { + id: input.id, + product_variant_id: input.product_variant_id, + quantity: input.quantity, + quantity_unit: input.quantity_unit, + }; + match dbm.run(t).await { + Ok(product_stock) => Ok(update_product_stock::Details { product_stock }), + Err(e) => { + tracing::warn!("{}", e); + Err(Error::CreateVariantStock(input.product_variant_id)) + } + } +} + +#[cfg(test)] +mod tests { + use channels::stocks::{ + add_product_photo, create_product_stock, delete_product_photo, update_product_stock, + }; + use config::UpdateConfig; + use db_utils::PgT; + use model::v2::*; + + use crate::actions::product_stock::{inner_create_product_stock, inner_update_product_stock}; + use crate::db::Database; + + struct NoOpts; + impl UpdateConfig for NoOpts {} + + async fn test_product(t: &mut PgT<'_>) -> Product { + crate::db::CreateProduct { + name: ProductName::new(format!("{}", uuid::Uuid::new_v4())), + category: None, + deliver_days_flag: Days(vec![]), + } + .run(t) + .await + .unwrap() + } + + async fn test_product_variant(product_id: ProductId, t: &mut PgT<'_>) -> ProductVariant { + crate::db::CreateProductVariant { + product_id, + name: ProductVariantName::new(format!("{}", uuid::Uuid::new_v4())), + short_description: ProductShortDesc::new(fakeit::words::sentence(4)), + long_description: ProductLongDesc::new(fakeit::words::sentence(16)), + price: Price::from_u32(650), + } + .run(t) + .await + .unwrap() + } + + async fn test_stock(product_variant_id: ProductVariantId, t: &mut PgT<'_>) -> Stock { + crate::db::CreateStock { + product_variant_id, + quantity: Quantity::from_u32(1056), + quantity_unit: QuantityUnit::Gram, + } + .run(t) + .await + .unwrap() + } + + #[tokio::test] + async fn create_stock() { + testx::db_t_ref!(t); + + let product = test_product(&mut t).await; + let variant = test_product_variant(product.id, &mut t).await; + + let res = inner_create_product_stock( + create_product_stock::Input { + product_variant_id: variant.id, + quantity: Quantity::from_u32(4684), + quantity_unit: QuantityUnit::Gram, + }, + &mut t, + ) + .await; + + testx::db_rollback!(t); + + let res = res.unwrap(); + } + + #[tokio::test] + async fn delete_stock() { + testx::db_t_ref!(t); + + let product = test_product(&mut t).await; + let variant = test_product_variant(product.id, &mut t).await; + let stock = test_stock(variant.id, &mut t).await; + + let quantity = Quantity::from_u32(6699); + let quantity_unit = QuantityUnit::Kilogram; + + let res = inner_update_product_stock( + update_product_stock::Input { + id: stock.id, + product_variant_id: variant.id, + quantity, + quantity_unit, + }, + &mut t, + ) + .await; + + testx::db_rollback!(t); + + let res = res.unwrap(); + + assert_eq!(res.product_stock.id, stock.id); + assert_eq!(res.product_stock.product_variant_id, variant.id); + assert_eq!(res.product_stock.quantity, quantity); + assert_eq!(res.product_stock.quantity_unit, quantity_unit); + } } diff --git a/crates/stock_manager/src/actions/product_variant.rs b/crates/stock_manager/src/actions/product_variant.rs index 4654384..7e3e13c 100644 --- a/crates/stock_manager/src/actions/product_variant.rs +++ b/crates/stock_manager/src/actions/product_variant.rs @@ -11,21 +11,24 @@ use crate::db::Database; pub async fn create_product_variant( input: create_product_variant::Input, db: Database, - _mqtt: AsyncClient, + mqtt: AsyncClient, _config: SharedAppConfig, ) -> create_product_variant::Output { let mut t = begin_t!(db, Error::InternalServerError); match inner_create_product_variant(input, &mut t).await { - Ok(res) => { + Ok(details) => { if let Err(e) = t.commit().await { - tracing::error!("{}", e); - return Err(Error::InternalServerError); + tracing::warn!("{}", e); + Err(Error::InternalServerError) + } else { + mqtt.emit_product_variant_created(&details.product_variant) + .await; + Ok(details) } - Ok(res) } Err(e) => { t.rollback().await.map_err(|e| { - tracing::error!("{}", e); + tracing::warn!("{}", e); Error::InternalServerError })?; Err(e) @@ -49,38 +52,117 @@ async fn inner_create_product_variant( product_variant: variant, }), Err(e) => { - tracing::error!("{}", e); + tracing::warn!("{}", e); Err(Error::CreateProductVariant(input.product_id)) } } } pub async fn update_product_variant( - _input: update_product_variant::Input, - _db: Database, - _mqtt: AsyncClient, + input: update_product_variant::Input, + db: Database, + mqtt: AsyncClient, _config: SharedAppConfig, ) -> update_product_variant::Output { - todo!() + let id = input.id; + let mut t = begin_t!(db, Error::InternalServerError); + match inner_update_product_variant(input, &mut t).await { + Ok(details) => { + if let Err(e) = t.commit().await { + tracing::warn!("{}", e); + Err(Error::UpdateProductVariant(id)) + } else { + mqtt.emit_product_variant_updated(&details.product_variant) + .await; + Ok(details) + } + } + Err(e) => { + t.rollback().await.ok(); + Err(e) + } + } +} + +async fn inner_update_product_variant( + input: update_product_variant::Input, + t: &mut PgT<'_>, +) -> update_product_variant::Output { + let dbm = crate::db::UpdateProductVariant { + product_variant_id: input.id, + product_id: input.product_id, + name: input.name, + short_description: input.short_description, + long_description: input.long_description, + price: input.price, + }; + match dbm.run(t).await { + Ok(product_variant) => Ok(update_product_variant::Details { product_variant }), + Err(e) => { + tracing::warn!("{}", e); + Err(Error::UpdateProductVariant(input.id)) + } + } } pub async fn delete_product_variant( - _input: delete_product_variant::Input, - _db: Database, - _mqtt: AsyncClient, + input: delete_product_variant::Input, + db: Database, + mqtt: AsyncClient, _config: SharedAppConfig, ) -> delete_product_variant::Output { - todo!() + let id = input.product_variant_id; + let mut t = begin_t!(db, Error::InternalServerError); + match inner_delete_product_variant(input, &mut t).await { + Ok(details) => { + if let Err(e) = t.commit().await { + tracing::warn!("{}", e); + Err(Error::UpdateProductVariant(id)) + } else { + mqtt.emit_product_variant_deleted(&details.product_variant_id) + .await; + Ok(details) + } + } + Err(e) => { + t.rollback().await.ok(); + Err(e) + } + } +} + +async fn inner_delete_product_variant( + input: delete_product_variant::Input, + t: &mut PgT<'_>, +) -> delete_product_variant::Output { + let dbm = crate::db::DeleteProductVariant { + product_variant_id: input.product_variant_id, + }; + + match dbm.run(t).await { + Ok(_product_variant) => Ok(delete_product_variant::Details { + product_id: input.product_id, + product_variant_id: input.product_variant_id, + }), + Err(e) => { + tracing::warn!("{}", e); + Err(Error::UpdateProductVariant(input.product_variant_id)) + } + } } #[cfg(test)] mod test { - use channels::stocks::create_product_variant; + use channels::stocks::{ + create_product_variant, delete_product_variant, update_product_variant, + }; use config::UpdateConfig; use db_utils::PgT; use model::v2::*; - use crate::actions::product_variant::inner_create_product_variant; + use crate::actions::product_variant::{ + inner_create_product_variant, inner_delete_product_variant, inner_update_product_variant, + }; use crate::db::Database; struct NoOpts; @@ -97,6 +179,22 @@ mod test { .unwrap() } + async fn test_product_variant(product_id: ProductId, t: &mut PgT<'_>) -> ProductVariant { + inner_create_product_variant( + create_product_variant::Input { + product_id, + name: ProductVariantName::new(format!("{}", uuid::Uuid::new_v4())), + short_description: ProductShortDesc::new(fakeit::words::sentence(4)), + long_description: ProductLongDesc::new(fakeit::words::sentence(16)), + price: Price::from_u32(650), + }, + t, + ) + .await + .unwrap() + .product_variant + } + #[tokio::test] async fn create_product_variant() { testx::db_t_ref!(t); @@ -129,4 +227,70 @@ mod test { assert_eq!(variant.long_description, long_description); assert_eq!(variant.price, price); } + + #[tokio::test] + async fn update_product_variant() { + testx::db_t_ref!(t); + + let product = test_product(&mut t).await; + let variant = test_product_variant(product.id, &mut t).await; + + let id = variant.id; + let product_id = product.id; + let name = ProductVariantName::new(format!("{}", uuid::Uuid::new_v4())); + let short_description = ProductShortDesc::new(format!("{}", uuid::Uuid::new_v4())); + let long_description = ProductLongDesc::new(format!("{}", uuid::Uuid::new_v4())); + let price = Price::from_u32(234234); + + let res = inner_update_product_variant( + update_product_variant::Input { + id, + product_id, + name: name.clone(), + short_description: short_description.clone(), + long_description: long_description.clone(), + price, + }, + &mut t, + ) + .await; + + testx::db_rollback!(t); + + let res = res.unwrap(); + + let expected = ProductVariant { + id, + product_id, + name, + short_description, + long_description, + price, + }; + assert_eq!(res.product_variant, expected); + } + + #[tokio::test] + async fn delete_product_variant() { + testx::db_t_ref!(t); + + let product = test_product(&mut t).await; + let variant = test_product_variant(product.id, &mut t).await; + + let res = inner_delete_product_variant( + delete_product_variant::Input { + product_id: product.id, + product_variant_id: variant.id, + }, + &mut t, + ) + .await; + + testx::db_rollback!(t); + + let res = res.unwrap(); + + assert_eq!(res.product_id, product.id); + assert_eq!(res.product_variant_id, variant.id); + } } diff --git a/crates/stock_manager/src/db/photos.rs b/crates/stock_manager/src/db/photos.rs index f376ad6..50663c9 100644 --- a/crates/stock_manager/src/db/photos.rs +++ b/crates/stock_manager/src/db/photos.rs @@ -34,7 +34,7 @@ LIMIT $1 OFFSET $2 .fetch_all(t) .await .map_err(|e| { - tracing::error!("{e:?}"); + tracing::warn!("{e:?}"); Error::All }) } @@ -64,7 +64,7 @@ RETURNING id, local_path, file_name, unique_name .fetch_one(pool) .await .map_err(|e| { - tracing::error!("{e:?}"); + tracing::warn!("{e:?}"); Error::Create }) } @@ -104,7 +104,7 @@ WHERE self.product_variant_ids.len(), self.product_variant_ids.into_iter().map(|id| *id), |e| { - tracing::error!("{}", e); + tracing::warn!("{}", e); dbg!(e); Error::PhotosForProductVariants }, diff --git a/crates/stock_manager/src/db/product_photos.rs b/crates/stock_manager/src/db/product_photos.rs index 16addb2..047ef5d 100644 --- a/crates/stock_manager/src/db/product_photos.rs +++ b/crates/stock_manager/src/db/product_photos.rs @@ -34,7 +34,7 @@ LIMIT $1 OFFSET $2 .fetch_all(pool) .await .map_err(|e| { - tracing::error!("{e:?}"); + tracing::warn!("{e:?}"); Error::All }) } @@ -60,7 +60,7 @@ RETURNING id, product_variant_id, photo_id .fetch_one(pool) .await .map_err(|e| { - tracing::error!("{:?}", e); + tracing::warn!("{:?}", e); Error::Create }) } @@ -84,7 +84,7 @@ RETURNING id, product_variant_id, photo_id .fetch_optional(pool) .await .map_err(|e| { - tracing::error!("{:?}", e); + tracing::warn!("{:?}", e); Error::Delete(self.id) }) } diff --git a/crates/stock_manager/src/db/product_variants.rs b/crates/stock_manager/src/db/product_variants.rs index 1781f12..99262da 100644 --- a/crates/stock_manager/src/db/product_variants.rs +++ b/crates/stock_manager/src/db/product_variants.rs @@ -49,7 +49,52 @@ RETURNING id, .fetch_one(pool) .await .map_err(|e| { - tracing::error!("{}", e); + tracing::warn!("{}", e); + dbg!(e); + Error::CreateProductVariant + }) + } +} + +#[derive(Debug)] +pub struct UpdateProductVariant { + pub product_variant_id: ProductVariantId, + pub product_id: ProductId, + pub name: ProductVariantName, + pub short_description: ProductShortDesc, + pub long_description: ProductLongDesc, + pub price: Price, +} + +impl UpdateProductVariant { + pub async fn run(self, pool: &mut PgT<'_>) -> Result { + sqlx::query_as( + r#" +UPDATE product_variants +SET product_id = $2, + name = $3, + short_description = $4, + long_description = $5, + price = $6 +WHERE id = $1 +RETURNING id, + product_id, + name, + short_description, + long_description, + price + "#, + ) + .bind(self.product_variant_id) + .bind(self.product_id) + .bind(self.name) + .bind(self.short_description) + .bind(self.long_description) + .bind(self.price) + .fetch_one(pool) + .await + .map_err(|e| { + tracing::warn!("{}", e); dbg!(e); Error::CreateProductVariant }) @@ -91,7 +136,7 @@ WHERE self.product_ids.len(), self.product_ids.iter().copied().map(|id| *id), |e| { - tracing::error!("{}", e); + tracing::warn!("{}", e); Error::ProductsVariants(self.product_ids.clone()) }, ) diff --git a/crates/stock_manager/src/db/products.rs b/crates/stock_manager/src/db/products.rs index b3c912b..b45b326 100644 --- a/crates/stock_manager/src/db/products.rs +++ b/crates/stock_manager/src/db/products.rs @@ -49,7 +49,7 @@ LIMIT $1 OFFSET $2 .fetch_all(pool) .await .map_err(|e| { - tracing::error!("{e:?}"); + tracing::warn!("{e:?}"); Error::All }) } @@ -79,7 +79,7 @@ WHERE id = $1 .fetch_one(pool) .await .map_err(|e| { - tracing::error!("{e:?}"); + tracing::warn!("{e:?}"); Error::Single(self.product_id) }) } @@ -113,7 +113,7 @@ RETURNING id, .fetch_one(pool) .await .map_err(|e| { - tracing::error!("{e:?}"); + tracing::warn!("{e:?}"); dbg!(e); Error::Create }) @@ -153,7 +153,7 @@ RETURNING id, .fetch_one(pool) .await .map_err(|e| { - tracing::error!("{e:?}"); + tracing::warn!("{e:?}"); dbg!(e); Error::Update(self.id) }) @@ -184,7 +184,7 @@ RETURNING id, .fetch_optional(pool) .await .map_err(|e| { - tracing::error!("{e:?}"); + tracing::warn!("{e:?}"); eprintln!("{e:?}"); Error::Delete(self.product_id) }) @@ -219,7 +219,7 @@ LIMIT $2 OFFSET $3 .fetch_all(t) .await .map_err(|e| { - tracing::error!("{e:?}"); + tracing::warn!("{e:?}"); Error::ShoppingCartProducts(self.shopping_cart_id) }) } @@ -253,7 +253,7 @@ WHERE self.product_ids.len(), self.product_ids.into_iter().map(|id| *id), |e| { - tracing::error!("{e:?}"); + tracing::warn!("{e:?}"); Error::FindProducts }, ) diff --git a/crates/stock_manager/src/db/stocks.rs b/crates/stock_manager/src/db/stocks.rs index 640d4a1..237094a 100644 --- a/crates/stock_manager/src/db/stocks.rs +++ b/crates/stock_manager/src/db/stocks.rs @@ -41,7 +41,7 @@ LIMIT $1 OFFSET $2 .fetch_all(pool) .await .map_err(|e| { - tracing::error!("{e:?}"); + tracing::warn!("{e:?}"); Error::All }) } @@ -65,7 +65,7 @@ WHERE id = $1 .fetch_one(pool) .await .map_err(|e| { - tracing::error!("{e:?}"); + tracing::warn!("{e:?}"); dbg!(e); Error::NotFound(self.id) }) @@ -94,7 +94,7 @@ RETURNING id, product_variant_id, quantity, quantity_unit .fetch_one(pool) .await .map_err(|e| { - tracing::error!("{e:?}"); + tracing::warn!("{e:?}"); dbg!(e); Error::Create }) @@ -128,7 +128,7 @@ RETURNING id, product_variant_id, quantity, quantity_unit .fetch_one(pool) .await .map_err(|e| { - tracing::error!("{e:?}"); + tracing::warn!("{e:?}"); Error::Update(self.id) }) } @@ -155,7 +155,7 @@ RETURNING id, product_variant_id, quantity, quantity_unit .fetch_optional(pool) .await .map_err(|e| { - tracing::error!("{e:?}"); + tracing::warn!("{e:?}"); Error::Delete(self.stock_id) }) } diff --git a/crates/stock_manager/src/mqtt.rs b/crates/stock_manager/src/mqtt.rs index e6cdbae..811ff4f 100644 --- a/crates/stock_manager/src/mqtt.rs +++ b/crates/stock_manager/src/mqtt.rs @@ -19,7 +19,7 @@ pub async fn start(config: SharedAppConfig, _db: Database) -> channels::AsyncCli Ok(Event::Incoming(_incoming)) => {} Ok(Event::Outgoing(_outgoing)) => {} Err(e) => { - tracing::error!("{}", e); + tracing::warn!("{}", e); } } }