From e426fd5f31b0a37e60ed49dc0616b8e8db305f80 Mon Sep 17 00:00:00 2001 From: eraden Date: Thu, 10 Nov 2022 06:52:48 +0100 Subject: [PATCH] Migrate product photo db --- crates/stock_manager/src/db/product_photos.rs | 191 ++++++++++++++++++ crates/stock_manager/src/db/products.rs | 8 +- 2 files changed, 195 insertions(+), 4 deletions(-) diff --git a/crates/stock_manager/src/db/product_photos.rs b/crates/stock_manager/src/db/product_photos.rs index e633e09..3bf6172 100644 --- a/crates/stock_manager/src/db/product_photos.rs +++ b/crates/stock_manager/src/db/product_photos.rs @@ -1 +1,192 @@ +use db_utils::PgT; use model::v2::*; + +use crate::db::products::AllProducts; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, thiserror::Error)] +pub enum Error { + #[error("Failed to attach photo to product")] + Create, + #[error("Failed to load all product photos")] + All, + #[error("Failed to delete product photo {0:?}")] + Delete(ProductPhotoId), +} + +pub type Result = std::result::Result; + +#[derive(Debug)] +pub struct AllProductPhotos { + pub limit: Option, + pub offset: Option, +} + +impl AllProductPhotos { + pub async fn run(self, pool: &mut PgT<'_>) -> Result> { + sqlx::query_as( + r#" +SELECT id, product_variant_id, photo_id +FROM product_photos +ORDER BY id ASC +LIMIT $1 OFFSET $2 +"#, + ) + .bind(self.limit) + .bind(self.offset) + .fetch_all(pool) + .await + .map_err(|e| { + tracing::error!("{e:?}"); + Error::All + }) + } +} + +#[derive(Debug)] +pub struct CreateProductPhoto { + pub product_variant_id: ProductVariantId, + pub photo_id: PhotoId, +} + +impl CreateProductPhoto { + pub async fn run(self, pool: &mut PgT<'_>) -> Result { + sqlx::query_as( + r#" +INSERT INTO product_photos(product_variant_id, photo_id) +VALUES ($1, $2) +RETURNING id, product_variant_id, photo_id + "#, + ) + .bind(self.product_variant_id) + .bind(self.photo_id) + .fetch_one(pool) + .await + .map_err(|e| { + tracing::error!("{:?}", e); + Error::Create + }) + } +} + +#[derive(Debug)] +pub struct DeleteProductPhoto { + pub id: ProductPhotoId, +} + +impl DeleteProductPhoto { + pub async fn run(self, pool: &mut PgT<'_>) -> Result> { + sqlx::query_as( + r#" +DELETE FROM product_photos +WHERE id = $1 +RETURNING id, product_variant_id, photo_id + "#, + ) + .bind(self.id) + .fetch_optional(pool) + .await + .map_err(|e| { + tracing::error!("{:?}", e); + Error::Delete(self.id) + }) + } +} + +#[cfg(test)] +mod tests { + use config::UpdateConfig; + use model::v2::*; + use model::Day; + use uuid::Uuid; + + use crate::db::product_variants::CreateProductVariant; + + pub struct NoOpts; + + impl UpdateConfig for NoOpts {} + + use super::*; + use crate::db::products::*; + + 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("ajs9d8ua9sdu9ahsd98has"), + long_description: ProductLongDesc::new("hja89sdy9yha9sdy98ayusd9hya9sy8dh"), + 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(t: &mut PgT<'_>) -> ProductPhoto { + let product = test_product(t).await; + let product_variant = test_product_variant(product.id, t).await; + let photo = test_photo(t).await; + CreateProductPhoto { + product_variant_id: product_variant.id, + photo_id: photo.id, + } + .run(t) + .await + .unwrap() + } + + #[actix::test] + async fn create_photo() { + testx::db_t_ref!(t); + + test_product_photo(&mut t).await; + + testx::db_rollback!(t); + } + + #[actix::test] + async fn delete() { + testx::db_t_ref!(t); + + let p1 = test_product_photo(&mut t).await; + let p2 = test_product_photo(&mut t).await; + let p3 = test_product_photo(&mut t).await; + + let deleted = DeleteProductPhoto { id: p2.id }.run(&mut t).await.unwrap(); + + testx::db_rollback!(t); + assert_ne!(deleted, Some(p1)); + assert_eq!(deleted, Some(p2)); + assert_ne!(deleted, Some(p3)); + } + + #[actix::test] + async fn create() { + testx::db_t_ref!(t); + + test_product_photo(&mut t).await; + + testx::db_rollback!(t); + } +} diff --git a/crates/stock_manager/src/db/products.rs b/crates/stock_manager/src/db/products.rs index 0bf5195..cb13afd 100644 --- a/crates/stock_manager/src/db/products.rs +++ b/crates/stock_manager/src/db/products.rs @@ -28,7 +28,7 @@ pub struct AllProducts { } impl AllProducts { - pub async fn run<'e, E>(self, pool: E) -> Result> + pub async fn run<'e, E>(self, pool: E) -> Result> where E: sqlx::Executor<'e, Database = sqlx::Postgres>, { @@ -60,7 +60,7 @@ pub struct FindProduct { } impl FindProduct { - pub async fn run<'e, E>(self, pool: E) -> Result + pub async fn run<'e, E>(self, pool: E) -> Result where E: sqlx::Executor<'e, Database = sqlx::Postgres>, { @@ -92,7 +92,7 @@ pub struct CreateProduct { } impl CreateProduct { - pub async fn run<'e, E>(self, pool: E) -> Result + pub async fn run<'e, E>(self, pool: E) -> Result where E: sqlx::Executor<'e, Database = sqlx::Postgres>, { @@ -264,7 +264,7 @@ WHERE #[cfg(test)] mod tests { use config::UpdateConfig; - use model::*; + use model::v2::*; use uuid::Uuid; pub struct NoOpts;