use crate::{db_async_handler, Result}; #[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")] Delete, } #[derive(actix::Message)] #[rtype(result = "Result>")] pub struct AllProductPhotos; db_async_handler!( AllProductPhotos, all_product_photos, Vec, inner_all_product_photos ); pub(crate) async fn all_product_photos( _msg: AllProductPhotos, pool: &mut sqlx::Transaction<'_, sqlx::Postgres>, ) -> Result> { sqlx::query_as( r#" SELECT id, product_id, photo_id FROM product_photos ORDER BY id ASC "#, ) .fetch_all(pool) .await .map_err(|e| { tracing::error!("{e:?}"); Error::All.into() }) } #[derive(actix::Message)] #[rtype(result = "Result")] pub struct CreateProductPhoto { pub product_id: model::ProductId, pub photo_id: model::PhotoId, } db_async_handler!( CreateProductPhoto, create_product_photo, model::ProductPhoto, inner_create_product_photo ); pub(crate) async fn create_product_photo( msg: CreateProductPhoto, pool: &mut sqlx::Transaction<'_, sqlx::Postgres>, ) -> Result { sqlx::query_as( r#" INSERT INTO product_photos(product_id, photo_id) VALUES ($1, $2) RETURNING id, product_id, photo_id "#, ) .bind(msg.product_id) .bind(msg.photo_id) .fetch_one(pool) .await .map_err(|e| { tracing::error!("{:?}", e); Error::Create.into() }) } #[derive(actix::Message)] #[rtype(result = "Result>")] pub struct DeleteProductPhoto { pub id: model::ProductPhotoId, } db_async_handler!( DeleteProductPhoto, delete_product_photo, Option, inner_delete_product_photo ); pub(crate) async fn delete_product_photo( msg: DeleteProductPhoto, pool: &mut sqlx::Transaction<'_, sqlx::Postgres>, ) -> Result> { sqlx::query_as( r#" DELETE FROM product_photos WHERE id = $1 RETURNING id, product_id, photo_id "#, ) .bind(msg.id) .fetch_optional(pool) .await .map_err(|e| { tracing::error!("{:?}", e); Error::Delete.into() }) } #[cfg(test)] mod tests { use config::UpdateConfig; use model::*; use uuid::Uuid; pub struct NoOpts; impl UpdateConfig for NoOpts {} use crate::*; async fn test_product(t: &mut sqlx::Transaction<'_, sqlx::Postgres>) -> Product { create_product( CreateProduct { name: ProductName::new(format!("{}", Uuid::new_v4())), short_description: ProductShortDesc::new(format!("{}", Uuid::new_v4())), long_description: ProductLongDesc::new(format!("{}", Uuid::new_v4())), category: None, price: Price::from_u32(4687), deliver_days_flag: Days(vec![Day::Friday, Day::Sunday]), }, t, ) .await .unwrap() } async fn test_photo(t: &mut sqlx::Transaction<'_, sqlx::Postgres>) -> Photo { crate::create_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())), }, t, ) .await .unwrap() } async fn test_product_photo(t: &mut sqlx::Transaction<'_, sqlx::Postgres>) -> ProductPhoto { let product = test_product(t).await; let photo = test_photo(t).await; create_product_photo( CreateProductPhoto { product_id: product.id, photo_id: photo.id, }, 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 = delete_product_photo(DeleteProductPhoto { id: p2.id }, &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); } }