use crate::{MultiLoad, Result}; #[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, thiserror::Error)] pub enum Error { #[error("Failed to create photo")] Create, #[error("Failed to fetch all photo")] All, #[error("Failed to fetch photos for products")] PhotosForProducts, } #[derive(actix::Message)] #[rtype(result = "Result>")] pub struct AllPhotos; crate::db_async_handler!(AllPhotos, all_photos, Vec, inner_all_photos); pub(crate) async fn all_photos<'e, E>(_msg: AllPhotos, pool: E) -> Result> where E: sqlx::Executor<'e, Database = sqlx::Postgres>, { sqlx::query_as( r#" SELECT id, local_path, file_name, unique_name FROM photos ORDER BY id ASC "#, ) .fetch_all(pool) .await .map_err(|e| { tracing::error!("{e:?}"); crate::Error::Photo(Error::All) }) } #[derive(actix::Message)] #[rtype(result = "Result")] pub struct CreatePhoto { /// Local FILE path pub local_path: model::LocalPath, /// Only file name, this part should be also included in `local_path` pub file_name: model::FileName, pub unique_name: model::UniqueName, } crate::db_async_handler!(CreatePhoto, create_photo, model::Photo, inner_create_photo); pub(crate) async fn create_photo<'e, E>(msg: CreatePhoto, pool: E) -> Result where E: sqlx::Executor<'e, Database = sqlx::Postgres>, { sqlx::query_as( r#" INSERT INTO photos (file_name, local_path, unique_name) VALUES ($1, $2, $3) RETURNING id, local_path, file_name, unique_name "#, ) .bind(msg.file_name) .bind(msg.local_path) .bind(msg.unique_name) .fetch_one(pool) .await .map_err(|e| { tracing::error!("{e:?}"); crate::Error::Photo(Error::Create) }) } //###################################### // SPECIAL CASES //###################################### #[derive(actix::Message)] #[rtype(result = "Result>")] pub struct PhotosForProducts { pub product_ids: Vec, } crate::db_async_handler!( PhotosForProducts, photos_for_products, Vec, inner_photos_for_products ); pub(crate) async fn photos_for_products( msg: PhotosForProducts, pool: &mut sqlx::Transaction<'_, sqlx::Postgres>, ) -> Result> { tracing::debug!("all product ids {:?}", msg.product_ids); let res: Vec = MultiLoad::new( pool, r#" SELECT photos.id AS photo_id, photos.local_path AS local_path, photos.file_name AS file_name, product_photos.product_id AS product_id, photos.unique_name AS unique_name FROM photos INNER JOIN product_photos ON photos.id = product_photos.photo_id WHERE "#, " product_photos.product_id =", ) .with_sorting("photos.id ASC") .load( msg.product_ids.len(), msg.product_ids.into_iter().map(|id| *id), |e| { tracing::error!("{}", e); dbg!(e); crate::Error::Photo(Error::PhotosForProducts) }, ) .await?; tracing::debug!("product linked photos {:?}", res); Ok(res) } #[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_product_photo( t: &mut sqlx::Transaction<'_, sqlx::Postgres>, product_id: ProductId, ) -> ProductPhoto { let photo = test_photo(&mut *t, None, None, None).await; create_product_photo( CreateProductPhoto { product_id, photo_id: photo.id, }, t, ) .await .unwrap() } async fn test_photo( t: &mut sqlx::Transaction<'_, sqlx::Postgres>, local_path: Option, file_name: Option, unique_name: Option, ) -> Photo { super::create_photo( CreatePhoto { local_path: LocalPath::new( local_path.unwrap_or_else(|| format!("{}", Uuid::new_v4())), ), file_name: FileName::new( file_name.unwrap_or_else(|| format!("{}", Uuid::new_v4())), ), unique_name: UniqueName::new( unique_name.unwrap_or_else(|| format!("{}", Uuid::new_v4())), ), }, t, ) .await .unwrap() } #[actix::test] async fn create_photo() { testx::db_t_ref!(t); test_photo(&mut t, None, None, None).await; testx::db_rollback!(t); } #[actix::test] async fn all() { testx::db_t_ref!(t); let p1 = test_photo(&mut t, None, None, None).await; let p2 = test_photo(&mut t, None, None, None).await; let p3 = test_photo(&mut t, None, None, None).await; let all = all_photos(AllPhotos, &mut t).await.unwrap(); testx::db_rollback!(t); assert_eq!(all, vec![p1, p2, p3]); } #[actix::test] async fn products_photos() { testx::db_t_ref!(t); let product_1 = test_product(&mut t).await; let p1 = test_product_photo(&mut t, product_1.id).await; let p2 = test_product_photo(&mut t, product_1.id).await; let p3 = test_product_photo(&mut t, product_1.id).await; let product_2 = test_product(&mut t).await; let _p4 = test_product_photo(&mut t, product_2.id).await; let _p5 = test_product_photo(&mut t, product_2.id).await; let _p6 = test_product_photo(&mut t, product_2.id).await; let product_3 = test_product(&mut t).await; let p7 = test_product_photo(&mut t, product_3.id).await; let p8 = test_product_photo(&mut t, product_3.id).await; let p9 = test_product_photo(&mut t, product_3.id).await; let mut all = photos_for_products( PhotosForProducts { product_ids: vec![product_1.id, product_3.id], }, &mut t, ) .await .unwrap() .into_iter() .map(|p| p.photo_id) .collect::>(); all.sort_by(|left, right| left.partial_cmp(right).unwrap()); testx::db_rollback!(t); assert_eq!( all, vec![ p1.photo_id, p2.photo_id, p3.photo_id, p7.photo_id, p8.photo_id, p9.photo_id ] ); } }