use std::collections::HashMap; use channels::stocks::{detailed_product, detailed_products}; use channels::AsyncClient; use config::SharedAppConfig; use db_utils::PgT; use model::v2::{DetailedProduct, DetailedProductVariant, Product, ProductVariant}; use model::Limit; use crate::db::{Database, PhotosForProductVariants, ProductVariantsStock, ProductsVariants}; pub async fn detailed_product( _input: detailed_product::Input, _db: Database, _mqtt: AsyncClient, _config: SharedAppConfig, ) -> detailed_product::Output { todo!() } pub async fn detailed_products( input: detailed_products::Input, db: Database, _mqtt: AsyncClient, _config: SharedAppConfig, ) -> detailed_products::Output { let mut t = match db.pool().begin().await { Err(e) => { tracing::error!("{}", e); return Err(detailed_products::Error::InternalServerError); } Ok(t) => t, }; let res = inner_detailed_products(input, &mut t, Some(_mqtt), Some(_config)).await; t.commit().await.ok(); res } async fn inner_detailed_products( input: detailed_products::Input, t: &mut PgT<'_>, _mqtt: Option, _config: Option, ) -> detailed_products::Output { let dbm = crate::db::AllProducts { limit: input.limit, offset: input.offset, }; let products = match dbm.run(&mut *t).await { Ok(products) => products, Err(e) => { tracing::error!("{}", e); return Err(detailed_products::Error::Products); } }; let dbm = ProductsVariants { product_ids: products.iter().map(|p| p.id).collect(), limit: Some(Limit::from_u32(products.len() as u32 * 10)), offset: Some(0.into()), }; let variants = match dbm.run(&mut *t).await { Ok(variants) => variants, Err(e) => { tracing::error!("{}", e); return Err(detailed_products::Error::ProductVariants( products.into_iter().map(|p| p.id).collect(), )); } }; let dbm = ProductVariantsStock { product_variant_ids: variants.iter().map(|p| p.id).collect(), }; let stocks = match dbm.run(&mut *t).await { Ok(stocks) => stocks, Err(e) => { tracing::error!("{}", e); return Err(detailed_products::Error::VariantStocks( variants.into_iter().map(|p| p.id).collect(), )); } }; let dbm = PhotosForProductVariants { product_variant_ids: variants.iter().map(|p| p.id).collect(), }; let photos = match dbm.run(t).await { Ok(photos) => photos, Err(e) => { tracing::error!("{}", e); return Err(detailed_products::Error::VariantPhotos( variants.into_iter().map(|p| p.id).collect(), )); } }; let mut variants = { let len = variants.len(); variants .into_iter() .fold(HashMap::with_capacity(len), |mut h, variant| { h.entry(variant.product_id) .or_insert_with(|| Vec::with_capacity(10)) .push(variant); h }) }; let mut stocks = { let len = stocks.len(); stocks .into_iter() .fold(HashMap::with_capacity(len), |mut h, stock| { h.entry(stock.product_variant_id) .or_insert_with(|| Vec::with_capacity(10)) .push(stock); h }) }; let mut photos = photos .into_iter() .fold(HashMap::with_capacity(stocks.len()), |mut h, photo| { h.entry(photo.product_variant_id) .or_insert_with(|| Vec::with_capacity(10)) .push(photo); h }); let products = products .into_iter() .map( |Product { id, name, category, deliver_days_flag, }| DetailedProduct { id, name, category, deliver_days_flag, variants: variants .remove(&id) .unwrap_or(vec![]) .into_iter() .map( |ProductVariant { id, product_id: _, name, short_description, long_description, price, }| DetailedProductVariant { id, name, short_description, long_description, price, stocks: stocks.remove(&id).unwrap_or_default(), photos: photos.remove(&id).unwrap_or_default(), }, ) .collect(), }, ) .collect(); Ok(detailed_products::Details { products }) } #[cfg(test)] mod tests { use channels::stocks::detailed_products; use config::UpdateConfig; use db_utils::PgT; use model::v2::*; use uuid::Uuid; use crate::actions::load::inner_detailed_products; use crate::db::*; pub struct NoOpts; impl UpdateConfig for NoOpts {} async fn test_product(t: &mut PgT<'_>) -> Product { CreateProduct { name: ProductName::new(format!("{}", Uuid::new_v4())), category: None, deliver_days_flag: Days(vec![Day::Friday, Day::Sunday]), } .run(t) .await .unwrap() } async fn test_product_variant(product_id: ProductId, t: &mut PgT<'_>) -> ProductVariant { CreateProductVariant { product_id, name: ProductName::new(format!("{}", Uuid::new_v4())), short_description: ProductShortDesc::new(format!("{}", Uuid::new_v4())), long_description: ProductLongDesc::new(format!("{}", Uuid::new_v4())), price: Default::default(), } .run(t) .await .unwrap() } async fn test_photo(t: &mut PgT<'_>) -> Photo { CreatePhoto { local_path: LocalPath::new(format!("{}", Uuid::new_v4())), file_name: FileName::new(format!("{}", Uuid::new_v4())), unique_name: UniqueName::new(format!("{}", Uuid::new_v4())), } .run(t) .await .unwrap() } async fn test_product_photo( product_variant_id: ProductVariantId, photo_id: PhotoId, t: &mut PgT<'_>, ) -> ProductPhoto { CreateProductPhoto { product_variant_id, photo_id, } .run(t) .await .unwrap() } async fn n_test_photo( n: usize, product_variant_id: ProductVariantId, t: &mut PgT<'_>, ) -> Vec<(Photo, ProductPhoto)> { let mut res = Vec::with_capacity(n); for _ in 0..n { let photo = test_photo(t).await; let product_photo = test_product_photo(product_variant_id, photo.id, t).await; res.push((photo, product_photo)); } res } async fn test_stock(product_variant_id: ProductVariantId, pool: &mut PgT<'_>) -> Stock { let quantity = Quantity::from_u32(345); let quantity_unit = QuantityUnit::Piece; CreateStock { product_variant_id, quantity_unit, quantity, } .run(&mut *pool) .await .unwrap() } async fn n_test_variant( variant_count: usize, product_id: ProductId, t: &mut PgT<'_>, ) -> Vec<(ProductVariant, Stock, Vec<(Photo, ProductPhoto)>)> { let mut variants = Vec::with_capacity(variant_count); for _ in 0..variant_count { let variant = test_product_variant(product_id, t).await; let stock = test_stock(variant.id, t).await; let photos = n_test_photo(3, variant.id, t).await; variants.push((variant, stock, photos)); } variants } #[tokio::test] async fn load_details() { testx::db_t_ref!(t); let product_1 = test_product(&mut t).await; let _variants_1 = n_test_variant(3, product_1.id, &mut t).await; let product_2 = test_product(&mut t).await; let _variants_2 = n_test_variant(5, product_2.id, &mut t).await; let product_3 = test_product(&mut t).await; let _variants_2 = n_test_variant(2, product_3.id, &mut t).await; let res = inner_detailed_products( detailed_products::Input { limit: Limit::from_u32(2000), offset: Offset::from_u32(0), }, &mut t, None, None, ) .await; testx::db_rollback!(t); let mut res = res.unwrap(); assert_eq!(res.products.len(), 3); let product = res.products.remove(0); assert_eq!(product.variants.len(), 3); for variant in product.variants { assert_eq!(variant.photos.len(), 3); assert_eq!(variant.stocks.len(), 1); } let product = res.products.remove(0); assert_eq!(product.variants.len(), 5); for variant in product.variants { assert_eq!(variant.photos.len(), 3); assert_eq!(variant.stocks.len(), 1); } let product = res.products.remove(0); assert_eq!(product.variants.len(), 2); for variant in product.variants { assert_eq!(variant.photos.len(), 3); assert_eq!(variant.stocks.len(), 1); } } }