bazzar/crates/database_manager/src/photos.rs

262 lines
7.1 KiB
Rust

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<Vec<model::Photo>>")]
pub struct AllPhotos;
crate::db_async_handler!(AllPhotos, all_photos, Vec<model::Photo>, inner_all_photos);
pub(crate) async fn all_photos<'e, E>(_msg: AllPhotos, pool: E) -> Result<Vec<model::Photo>>
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<model::Photo>")]
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<model::Photo>
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<Vec<model::ProductLinkedPhoto>>")]
pub struct PhotosForProducts {
pub product_ids: Vec<model::ProductId>,
}
crate::db_async_handler!(
PhotosForProducts,
photos_for_products,
Vec<model::ProductLinkedPhoto>,
inner_photos_for_products
);
pub(crate) async fn photos_for_products(
msg: PhotosForProducts,
pool: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<Vec<model::ProductLinkedPhoto>> {
tracing::debug!("all product ids {:?}", msg.product_ids);
let res: Vec<model::ProductLinkedPhoto> = 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<String>,
file_name: Option<String>,
unique_name: Option<String>,
) -> 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::<Vec<PhotoId>>();
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
]
);
}
}