Photos
This commit is contained in:
parent
e426fd5f31
commit
6a4c38593f
@ -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
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user