This commit is contained in:
eraden 2022-11-10 07:35:05 +01:00
parent e426fd5f31
commit 6a4c38593f
2 changed files with 266 additions and 2 deletions

View File

@ -1 +1,264 @@
use db_utils::PgT;
use model::v2::*; use model::v2::*;
#[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,
}
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug)]
pub struct AllPhotos {
pub limit: i32,
pub offset: i32,
}
impl AllPhotos {
pub async fn run(self, pool: &mut PgT<'_>) -> Result<Vec<Photo>> {
sqlx::query_as(
r#"
SELECT id, local_path, file_name, unique_name
FROM 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 CreatePhoto {
/// Local FILE path
pub local_path: LocalPath,
/// Only file name, this part should be also included in `local_path`
pub file_name: FileName,
pub unique_name: UniqueName,
}
impl CreatePhoto {
pub async fn run(self, pool: &mut PgT<'_>) -> Result<Photo> {
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(self.file_name)
.bind(self.local_path)
.bind(self.unique_name)
.fetch_one(pool)
.await
.map_err(|e| {
tracing::error!("{e:?}");
Error::Create
})
}
}
//######################################
// SPECIAL CASES
//######################################
#[derive(Debug)]
pub struct PhotosForProducts {
pub product_ids: Vec<ProductId>,
}
impl PhotosForProducts {
pub async fn run(self, pool: &mut PgT<'_>) -> Result<Vec<model::ProductLinkedPhoto>> {
tracing::debug!("all product ids {:?}", self.product_ids);
let res: Vec<model::ProductLinkedPhoto> = db_utils::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
INNER JOIN product_variants
ON product_variants.id = product_photos.product_variant_id
INNER JOIN products
ON product_variants.product_id = products.id
WHERE
"#,
" products.id =",
)
.with_sorting("photos.id ASC")
.allow_over_max()
.with_size(1000)
.load(
self.product_ids.len(),
self.product_ids.into_iter().map(|id| *id),
|e| {
tracing::error!("{}", e);
dbg!(e);
Error::PhotosForProducts
},
)
.await?;
tracing::debug!("product linked photos {:?}", res);
Ok(res)
}
}
#[cfg(test)]
mod tests {
use config::UpdateConfig;
use model::v2::*;
use model::Day;
use uuid::Uuid;
pub struct NoOpts;
impl UpdateConfig for NoOpts {}
use super::*;
use crate::db::product_photos::*;
use crate::db::product_variants::*;
use crate::db::products::*;
async fn test_product(t: &mut sqlx::Transaction<'_, sqlx::Postgres>) -> 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_photo(
t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
product_variant_id: ProductVariantId,
) -> ProductPhoto {
let photo = test_photo(&mut *t, None, None, None).await;
CreateProductPhoto {
product_variant_id,
photo_id: photo.id,
}
.run(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 {
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())),
),
}
.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()
}
#[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 product_variant_1 = test_product_variant(product_1.id, &mut t).await;
let p1 = test_product_photo(&mut t, product_variant_1.id).await;
let p2 = test_product_photo(&mut t, product_variant_1.id).await;
let p3 = test_product_photo(&mut t, product_variant_1.id).await;
let product_2 = test_product(&mut t).await;
let product_variant_2 = test_product_variant(product_2.id, &mut t).await;
let _p4 = test_product_photo(&mut t, product_variant_2.id).await;
let _p5 = test_product_photo(&mut t, product_variant_2.id).await;
let _p6 = test_product_photo(&mut t, product_variant_2.id).await;
let product_3 = test_product(&mut t).await;
let product_variant_3 = test_product_variant(product_3.id, &mut t).await;
let p7 = test_product_photo(&mut t, product_variant_3.id).await;
let p8 = test_product_photo(&mut t, product_variant_3.id).await;
let p9 = test_product_photo(&mut t, product_variant_3.id).await;
let mut all = PhotosForProducts {
product_ids: vec![product_1.id, product_3.id],
}
.run(&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
]
);
}
}

View File

@ -106,6 +106,7 @@ mod tests {
impl UpdateConfig for NoOpts {} impl UpdateConfig for NoOpts {}
use super::*; use super::*;
use crate::db::photos::*;
use crate::db::products::*; use crate::db::products::*;
async fn test_product(t: &mut PgT<'_>) -> Product { async fn test_product(t: &mut PgT<'_>) -> Product {
@ -123,8 +124,8 @@ mod tests {
CreateProductVariant { CreateProductVariant {
product_id, product_id,
name: ProductName::new(format!("{}", Uuid::new_v4())), name: ProductName::new(format!("{}", Uuid::new_v4())),
short_description: ProductShortDesc::new("ajs9d8ua9sdu9ahsd98has"), short_description: ProductShortDesc::new(format!("{}", Uuid::new_v4())),
long_description: ProductLongDesc::new("hja89sdy9yha9sdy98ayusd9hya9sy8dh"), long_description: ProductLongDesc::new(format!("{}", Uuid::new_v4())),
price: Default::default(), price: Default::default(),
} }
.run(t) .run(t)