Additional tests
This commit is contained in:
parent
a34f32dc15
commit
7c272d310f
@ -138,7 +138,7 @@ WHERE account_id = $1
|
||||
.await
|
||||
{
|
||||
log::error!("{e}");
|
||||
eprintln!("{e}")
|
||||
dbg!(e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -162,7 +162,7 @@ RETURNING id, name, email, phone, street, city, country, zip, account_id, is_def
|
||||
.await
|
||||
.map_err(|e| {
|
||||
log::error!("{e}");
|
||||
eprintln!("{e}");
|
||||
dbg!(e);
|
||||
Error::CreateAccountAddress.into()
|
||||
})
|
||||
}
|
||||
|
@ -213,6 +213,7 @@ pub struct MultiLoad<'transaction, 'transaction2, 'header, 'condition, T> {
|
||||
pool: &'transaction mut sqlx::Transaction<'transaction2, sqlx::Postgres>,
|
||||
header: &'header str,
|
||||
condition: &'condition str,
|
||||
sort: Option<String>,
|
||||
__phantom: std::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
@ -230,10 +231,16 @@ where
|
||||
pool,
|
||||
header,
|
||||
condition,
|
||||
sort: None,
|
||||
__phantom: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_sorting<S: Into<String>>(mut self, order: S) -> Self {
|
||||
self.sort = Some(order.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub async fn load<'query, Error, Ids>(
|
||||
&mut self,
|
||||
len: usize,
|
||||
@ -257,13 +264,18 @@ where
|
||||
},
|
||||
) {
|
||||
let query: String = self.header.into();
|
||||
let query = ids.iter().enumerate().fold(query, |mut q, (idx, _id)| {
|
||||
let mut query = ids.iter().enumerate().fold(query, |mut q, (idx, _id)| {
|
||||
if idx != 0 {
|
||||
q.push_str(" OR");
|
||||
}
|
||||
q.push_str(&format!(" {} ${}", self.condition, idx + 1));
|
||||
q
|
||||
});
|
||||
if let Some(s) = self.sort.as_deref() {
|
||||
query.push_str("\nORDER BY ");
|
||||
query.push_str(s);
|
||||
query.push_str(" ");
|
||||
}
|
||||
let q = sqlx::query_as_with(
|
||||
query.as_str(),
|
||||
ids.into_iter()
|
||||
|
@ -38,7 +38,7 @@ ORDER BY id DESC
|
||||
.await
|
||||
.map_err(|e| {
|
||||
log::error!("{e:?}");
|
||||
super::Error::from(Error::All)
|
||||
Error::All.into()
|
||||
})
|
||||
}
|
||||
|
||||
@ -102,7 +102,7 @@ WHERE id = $1
|
||||
.await
|
||||
.map_err(|e| {
|
||||
log::error!("{e:?}");
|
||||
super::Error::OrderItem(Error::NotExists)
|
||||
Error::NotExists.into()
|
||||
})
|
||||
}
|
||||
|
||||
@ -128,6 +128,6 @@ ORDER BY id DESC
|
||||
.await
|
||||
.map_err(|e| {
|
||||
log::error!("{e:?}");
|
||||
super::Error::OrderItem(Error::OrderItems)
|
||||
Error::OrderItems.into()
|
||||
})
|
||||
}
|
||||
|
@ -6,6 +6,8 @@ pub enum Error {
|
||||
Create,
|
||||
#[error("Failed to fetch all photo")]
|
||||
All,
|
||||
#[error("Failed to fetch photos for products")]
|
||||
PhotosForProducts,
|
||||
}
|
||||
|
||||
#[derive(actix::Message)]
|
||||
@ -22,6 +24,7 @@ where
|
||||
r#"
|
||||
SELECT id, local_path, file_name, unique_name
|
||||
FROM photos
|
||||
ORDER BY id ASC
|
||||
"#,
|
||||
)
|
||||
.fetch_all(pool)
|
||||
@ -91,20 +94,168 @@ pub(crate) async fn photos_for_products(
|
||||
let res: Vec<model::ProductLinkedPhoto> = MultiLoad::new(
|
||||
pool,
|
||||
r#"
|
||||
SELECT photos.id, photos.local_path, photos.file_name,
|
||||
product_photos.product_id, photos.unique_name FROM photos
|
||||
INNER JOIN product_photos
|
||||
ON photos.id = product_photos.photo_id
|
||||
WHERE
|
||||
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| crate::Error::Photo(Error::All),
|
||||
|e| {
|
||||
log::error!("{}", e);
|
||||
dbg!(e);
|
||||
crate::Error::Photo(Error::PhotosForProducts)
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
log::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!(t);
|
||||
|
||||
test_photo(&mut t, None, None, None).await;
|
||||
|
||||
testx::db_rollback!(t);
|
||||
}
|
||||
|
||||
#[actix::test]
|
||||
async fn all() {
|
||||
testx::db_t!(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!(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
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -6,37 +6,37 @@ pub enum Error {
|
||||
Create,
|
||||
#[error("Failed to load all product photos")]
|
||||
All,
|
||||
#[error("Failed to delete product photo")]
|
||||
Delete,
|
||||
}
|
||||
|
||||
#[derive(actix::Message)]
|
||||
#[rtype(result = "Result<Vec<model::ProductPhoto>>")]
|
||||
pub struct AllProductPhotos;
|
||||
|
||||
crate::db_async_handler!(
|
||||
db_async_handler!(
|
||||
AllProductPhotos,
|
||||
all_product_photos,
|
||||
Vec<model::ProductPhoto>,
|
||||
inner_all_product_photos
|
||||
);
|
||||
|
||||
pub(crate) async fn all_product_photos<'e, E>(
|
||||
pub(crate) async fn all_product_photos(
|
||||
_msg: AllProductPhotos,
|
||||
pool: E,
|
||||
) -> Result<Vec<model::ProductPhoto>>
|
||||
where
|
||||
E: sqlx::Executor<'e, Database = sqlx::Postgres>,
|
||||
{
|
||||
pool: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
) -> Result<Vec<model::ProductPhoto>> {
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
SELECT id, product_id, photo_id
|
||||
FROM product_photos
|
||||
ORDER BY id ASC
|
||||
"#,
|
||||
)
|
||||
.fetch_all(pool)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
log::error!("{e:?}");
|
||||
crate::Error::ProductPhoto(Error::All)
|
||||
Error::All.into()
|
||||
})
|
||||
}
|
||||
|
||||
@ -54,13 +54,10 @@ db_async_handler!(
|
||||
inner_create_product_photo
|
||||
);
|
||||
|
||||
pub(crate) async fn create_product_photo<'e, E>(
|
||||
pub(crate) async fn create_product_photo(
|
||||
msg: CreateProductPhoto,
|
||||
pool: E,
|
||||
) -> Result<model::ProductPhoto>
|
||||
where
|
||||
E: sqlx::Executor<'e, Database = sqlx::Postgres>,
|
||||
{
|
||||
pool: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
) -> Result<model::ProductPhoto> {
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
INSERT INTO product_photos(product_id, photo_id)
|
||||
@ -74,6 +71,131 @@ RETURNING id, product_id, photo_id
|
||||
.await
|
||||
.map_err(|e| {
|
||||
log::error!("{:?}", e);
|
||||
crate::Error::ProductPhoto(Error::Create)
|
||||
Error::Create.into()
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(actix::Message)]
|
||||
#[rtype(result = "Result<Option<model::ProductPhoto>>")]
|
||||
pub struct DeleteProductPhoto {
|
||||
pub id: model::ProductPhotoId,
|
||||
}
|
||||
|
||||
db_async_handler!(
|
||||
DeleteProductPhoto,
|
||||
delete_product_photo,
|
||||
Option<model::ProductPhoto>,
|
||||
inner_delete_product_photo
|
||||
);
|
||||
|
||||
pub(crate) async fn delete_product_photo(
|
||||
msg: DeleteProductPhoto,
|
||||
pool: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
) -> Result<Option<model::ProductPhoto>> {
|
||||
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| {
|
||||
log::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!(t);
|
||||
|
||||
test_product_photo(&mut t).await;
|
||||
|
||||
testx::db_rollback!(t);
|
||||
}
|
||||
|
||||
#[actix::test]
|
||||
async fn delete() {
|
||||
testx::db_t!(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!(t);
|
||||
|
||||
test_product_photo(&mut t).await;
|
||||
|
||||
testx::db_rollback!(t);
|
||||
}
|
||||
}
|
||||
|
@ -133,7 +133,7 @@ RETURNING id,
|
||||
.await
|
||||
.map_err(|e| {
|
||||
log::error!("{e:?}");
|
||||
eprintln!("{e:?}");
|
||||
dbg!(e);
|
||||
crate::Error::Product(Error::Create)
|
||||
})
|
||||
}
|
||||
@ -152,7 +152,7 @@ pub struct UpdateProduct {
|
||||
|
||||
crate::db_async_handler!(UpdateProduct, update_product, Product, inner_update_product);
|
||||
|
||||
pub(crate) async fn update_product<'e, E>(msg: UpdateProduct, pool: E) -> Result<model::Product>
|
||||
pub(crate) async fn update_product<'e, E>(msg: UpdateProduct, pool: E) -> Result<Product>
|
||||
where
|
||||
E: sqlx::Executor<'e, Database = sqlx::Postgres>,
|
||||
{
|
||||
@ -315,3 +315,86 @@ WHERE
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[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>,
|
||||
name: Option<String>,
|
||||
short_description: Option<String>,
|
||||
long_description: Option<String>,
|
||||
category: Option<ProductCategory>,
|
||||
price: Option<u32>,
|
||||
deliver_days_flag: Option<Days>,
|
||||
) -> Product {
|
||||
super::create_product(
|
||||
CreateProduct {
|
||||
name: ProductName::new(name.unwrap_or_else(|| format!("{}", Uuid::new_v4()))),
|
||||
short_description: ProductShortDesc::new(
|
||||
short_description.unwrap_or_else(|| format!("{}", Uuid::new_v4())),
|
||||
),
|
||||
long_description: ProductLongDesc::new(
|
||||
long_description.unwrap_or_else(|| format!("{}", Uuid::new_v4())),
|
||||
),
|
||||
category,
|
||||
price: Price::from_u32(price.unwrap_or(4687)),
|
||||
deliver_days_flag: deliver_days_flag
|
||||
.unwrap_or_else(|| Days(vec![Day::Friday, Day::Sunday])),
|
||||
},
|
||||
t,
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[actix::test]
|
||||
async fn create_product() {
|
||||
testx::db_t!(t);
|
||||
|
||||
test_product(&mut t, None, None, None, None, None, None).await;
|
||||
|
||||
testx::db_rollback!(t);
|
||||
}
|
||||
|
||||
#[actix::test]
|
||||
async fn all() {
|
||||
testx::db_t!(t);
|
||||
|
||||
let p1 = test_product(&mut t, None, None, None, None, None, None).await;
|
||||
let p2 = test_product(&mut t, None, None, None, None, None, None).await;
|
||||
let p3 = test_product(&mut t, None, None, None, None, None, None).await;
|
||||
|
||||
let products = super::all(AllProducts, &mut t).await.unwrap();
|
||||
|
||||
testx::db_rollback!(t);
|
||||
assert_eq!(products, vec![p1, p2, p3]);
|
||||
}
|
||||
|
||||
#[actix::test]
|
||||
async fn find() {
|
||||
testx::db_t!(t);
|
||||
|
||||
let p1 = test_product(&mut t, None, None, None, None, None, None).await;
|
||||
let p2 = test_product(&mut t, None, None, None, None, None, None).await;
|
||||
let p3 = test_product(&mut t, None, None, None, None, None, None).await;
|
||||
|
||||
let product = find_product(FindProduct { product_id: p2.id }, &mut t)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
testx::db_rollback!(t);
|
||||
assert_ne!(product, p1);
|
||||
assert_eq!(product, p2);
|
||||
assert_ne!(product, p3);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
use model::*;
|
||||
use sqlx::PgPool;
|
||||
|
||||
use super::Result;
|
||||
use crate::db_async_handler;
|
||||
@ -34,23 +33,32 @@ pub struct AllShoppingCartItems;
|
||||
db_async_handler!(
|
||||
AllShoppingCartItems,
|
||||
all_shopping_cart_items,
|
||||
Vec<ShoppingCartItem>
|
||||
Vec<ShoppingCartItem>,
|
||||
inner_all_shopping_cart_items
|
||||
);
|
||||
|
||||
pub(crate) async fn all_shopping_cart_items(
|
||||
_msg: AllShoppingCartItems,
|
||||
pool: PgPool,
|
||||
t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
) -> Result<Vec<ShoppingCartItem>> {
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
SELECT id, product_id, shopping_cart_id, quantity, quantity_unit
|
||||
SELECT shopping_cart_items.id,
|
||||
shopping_cart_items.product_id,
|
||||
shopping_cart_items.shopping_cart_id,
|
||||
shopping_cart_items.quantity,
|
||||
shopping_cart_items.quantity_unit
|
||||
FROM shopping_cart_items
|
||||
INNER JOIN shopping_carts
|
||||
ON shopping_cart_items.shopping_cart_id = shopping_carts.id
|
||||
ORDER BY shopping_cart_items.id ASC
|
||||
"#,
|
||||
)
|
||||
.fetch_all(&pool)
|
||||
.fetch_all(t)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
log::error!("{e:?}");
|
||||
dbg!(e);
|
||||
super::Error::ShoppingCartItem(Error::All)
|
||||
})
|
||||
}
|
||||
@ -65,12 +73,13 @@ pub struct AccountShoppingCartItems {
|
||||
db_async_handler!(
|
||||
AccountShoppingCartItems,
|
||||
account_shopping_cart_items,
|
||||
Vec<ShoppingCartItem>
|
||||
Vec<ShoppingCartItem>,
|
||||
inner_account_shopping_cart_items
|
||||
);
|
||||
|
||||
pub(crate) async fn account_shopping_cart_items(
|
||||
msg: AccountShoppingCartItems,
|
||||
pool: PgPool,
|
||||
t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
) -> Result<Vec<ShoppingCartItem>> {
|
||||
match msg.shopping_cart_id {
|
||||
Some(shopping_cart_id) => sqlx::query_as(
|
||||
@ -103,7 +112,7 @@ WHERE shopping_carts.buyer_id = $1
|
||||
)
|
||||
.bind(msg.account_id),
|
||||
}
|
||||
.fetch_all(&pool)
|
||||
.fetch_all(t)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
log::error!("{e:?}");
|
||||
@ -123,12 +132,13 @@ pub struct CreateShoppingCartItem {
|
||||
db_async_handler!(
|
||||
CreateShoppingCartItem,
|
||||
create_shopping_cart_item,
|
||||
ShoppingCartItem
|
||||
ShoppingCartItem,
|
||||
inner_create_shopping_cart_item
|
||||
);
|
||||
|
||||
pub(crate) async fn create_shopping_cart_item(
|
||||
msg: CreateShoppingCartItem,
|
||||
db: PgPool,
|
||||
t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
) -> Result<ShoppingCartItem> {
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
@ -141,10 +151,11 @@ RETURNING id, product_id, shopping_cart_id, quantity, quantity_unit
|
||||
.bind(msg.shopping_cart_id)
|
||||
.bind(msg.quantity)
|
||||
.bind(msg.quantity_unit)
|
||||
.fetch_one(&db)
|
||||
.fetch_one(t)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
log::error!("{e:?}");
|
||||
dbg!(&e);
|
||||
super::Error::ShoppingCartItem(Error::CantCreate)
|
||||
})
|
||||
}
|
||||
@ -162,12 +173,13 @@ pub struct UpdateShoppingCartItem {
|
||||
db_async_handler!(
|
||||
UpdateShoppingCartItem,
|
||||
update_shopping_cart_item,
|
||||
ShoppingCartItem
|
||||
ShoppingCartItem,
|
||||
inner_update_shopping_cart_item
|
||||
);
|
||||
|
||||
pub(crate) async fn update_shopping_cart_item(
|
||||
msg: UpdateShoppingCartItem,
|
||||
db: PgPool,
|
||||
t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
) -> Result<ShoppingCartItem> {
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
@ -182,7 +194,7 @@ RETURNING id, product_id, shopping_cart_id, quantity, quantity_unit
|
||||
.bind(msg.shopping_cart_id)
|
||||
.bind(msg.quantity)
|
||||
.bind(msg.quantity_unit)
|
||||
.fetch_one(&db)
|
||||
.fetch_one(t)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
log::error!("{e:?}");
|
||||
@ -199,12 +211,13 @@ pub struct DeleteShoppingCartItem {
|
||||
db_async_handler!(
|
||||
DeleteShoppingCartItem,
|
||||
delete_shopping_cart_item,
|
||||
Option<ShoppingCartItem>
|
||||
Option<ShoppingCartItem>,
|
||||
inner_delete_shopping_cart_item
|
||||
);
|
||||
|
||||
pub(crate) async fn delete_shopping_cart_item(
|
||||
msg: DeleteShoppingCartItem,
|
||||
db: PgPool,
|
||||
t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
) -> Result<Option<ShoppingCartItem>> {
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
@ -214,7 +227,7 @@ RETURNING id, product_id, shopping_cart_id, quantity, quantity_unit
|
||||
"#,
|
||||
)
|
||||
.bind(msg.id)
|
||||
.fetch_optional(&db)
|
||||
.fetch_optional(t)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
log::error!("{e:?}");
|
||||
@ -231,12 +244,13 @@ pub struct FindShoppingCartItem {
|
||||
db_async_handler!(
|
||||
FindShoppingCartItem,
|
||||
find_shopping_cart_item,
|
||||
ShoppingCartItem
|
||||
ShoppingCartItem,
|
||||
inner_find_shopping_cart_item
|
||||
);
|
||||
|
||||
pub(crate) async fn find_shopping_cart_item(
|
||||
msg: FindShoppingCartItem,
|
||||
db: PgPool,
|
||||
t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
) -> Result<ShoppingCartItem> {
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
@ -246,7 +260,7 @@ WHERE id = $1
|
||||
"#,
|
||||
)
|
||||
.bind(msg.id)
|
||||
.fetch_one(&db)
|
||||
.fetch_one(t)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
log::error!("{e:?}");
|
||||
@ -257,18 +271,19 @@ WHERE id = $1
|
||||
#[derive(actix::Message)]
|
||||
#[rtype(result = "Result<Option<ShoppingCartItem>>")]
|
||||
pub struct ActiveCartItemByProduct {
|
||||
pub product_id: model::ProductId,
|
||||
pub product_id: ProductId,
|
||||
}
|
||||
|
||||
db_async_handler!(
|
||||
ActiveCartItemByProduct,
|
||||
active_cart_item_by_product,
|
||||
Option<ShoppingCartItem>
|
||||
Option<ShoppingCartItem>,
|
||||
inner_active_cart_item_by_product
|
||||
);
|
||||
|
||||
pub(crate) async fn active_cart_item_by_product(
|
||||
msg: ActiveCartItemByProduct,
|
||||
db: PgPool,
|
||||
t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
) -> Result<Option<ShoppingCartItem>> {
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
@ -278,13 +293,16 @@ SELECT shopping_cart_items.id,
|
||||
shopping_cart_items.quantity,
|
||||
shopping_cart_items.quantity_unit
|
||||
FROM shopping_cart_items
|
||||
INNER JOIN shopping_carts ON shopping_cart_items.shopping_cart_id = shopping_carts.id
|
||||
WHERE product_id = $1 AND shopping_carts.state = $2
|
||||
INNER JOIN shopping_carts
|
||||
ON shopping_cart_items.shopping_cart_id = shopping_carts.id
|
||||
WHERE product_id = $1
|
||||
AND shopping_carts.state = $2
|
||||
ORDER BY shopping_cart_items.id ASC
|
||||
"#,
|
||||
)
|
||||
.bind(msg.product_id)
|
||||
.bind(model::ShoppingCartState::Active)
|
||||
.fetch_optional(&db)
|
||||
.fetch_optional(t)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
log::error!("{e:?}");
|
||||
@ -305,20 +323,25 @@ db_async_handler!(
|
||||
inner_cart_items
|
||||
);
|
||||
|
||||
pub(crate) async fn cart_items<'e, E>(msg: CartItems, pool: E) -> Result<Vec<ShoppingCartItem>>
|
||||
where
|
||||
E: sqlx::Executor<'e, Database = sqlx::Postgres>,
|
||||
{
|
||||
pub(crate) async fn cart_items(
|
||||
msg: CartItems,
|
||||
t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
) -> Result<Vec<ShoppingCartItem>> {
|
||||
let shopping_cart_id = msg.shopping_cart_id;
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
SELECT id, product_id, shopping_cart_id, quantity, quantity_unit
|
||||
SELECT id,
|
||||
product_id,
|
||||
shopping_cart_id,
|
||||
quantity,
|
||||
quantity_unit
|
||||
FROM shopping_cart_items
|
||||
WHERE shopping_cart_id = $1
|
||||
ORDER BY shopping_cart_items.id ASC
|
||||
"#,
|
||||
)
|
||||
.bind(msg.shopping_cart_id)
|
||||
.fetch_all(pool)
|
||||
.fetch_all(t)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
log::error!("{e:?}");
|
||||
@ -334,11 +357,16 @@ pub struct RemoveCartItem {
|
||||
pub product_id: Option<ProductId>,
|
||||
}
|
||||
|
||||
db_async_handler!(RemoveCartItem, remove_cart_item, Option<ShoppingCartItem>);
|
||||
db_async_handler!(
|
||||
RemoveCartItem,
|
||||
remove_cart_item,
|
||||
Option<ShoppingCartItem>,
|
||||
inner_remove_cart_item
|
||||
);
|
||||
|
||||
pub(crate) async fn remove_cart_item(
|
||||
msg: RemoveCartItem,
|
||||
pool: PgPool,
|
||||
t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
) -> Result<Option<ShoppingCartItem>> {
|
||||
match (msg.shopping_cart_item_id, msg.product_id) {
|
||||
(Some(shopping_cart_item_id), None) => sqlx::query_as(
|
||||
@ -371,7 +399,7 @@ RETURNING id, product_id, shopping_cart_id, quantity, quantity_unit
|
||||
.bind(product_id),
|
||||
_ => return Err(crate::Error::ShoppingCartItem(Error::NoIdentity)),
|
||||
}
|
||||
.fetch_optional(&pool)
|
||||
.fetch_optional(t)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
log::error!("{e:?}");
|
||||
@ -381,3 +409,280 @@ RETURNING id, product_id, shopping_cart_id, quantity, quantity_unit
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use config::UpdateConfig;
|
||||
use fake::Fake;
|
||||
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_account(
|
||||
t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
login: Option<String>,
|
||||
email: Option<String>,
|
||||
hash: Option<String>,
|
||||
) -> FullAccount {
|
||||
use fake::faker::internet::en;
|
||||
let login: String = login.unwrap_or_else(|| en::Username().fake());
|
||||
let email: String = email.unwrap_or_else(|| en::FreeEmail().fake());
|
||||
let hash: String = hash.unwrap_or_else(|| en::Password(10..20).fake());
|
||||
|
||||
create_account(
|
||||
CreateAccount {
|
||||
email: Email::new(email),
|
||||
login: Login::new(login),
|
||||
pass_hash: PassHash::new(hash),
|
||||
role: Role::Admin,
|
||||
},
|
||||
t,
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
async fn test_shopping_cart(
|
||||
t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
buyer_id: Option<AccountId>,
|
||||
state: ShoppingCartState,
|
||||
) -> ShoppingCart {
|
||||
let buyer_id = match buyer_id {
|
||||
Some(id) => id,
|
||||
_ => test_account(&mut *t, None, None, None).await.id,
|
||||
};
|
||||
|
||||
sqlx::query(
|
||||
r#"
|
||||
UPDATE shopping_carts
|
||||
SET state = 'closed'
|
||||
WHERE buyer_id = $1
|
||||
"#,
|
||||
)
|
||||
.bind(buyer_id)
|
||||
.execute(&mut *t)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let cart = create_shopping_cart(
|
||||
CreateShoppingCart {
|
||||
buyer_id,
|
||||
payment_method: PaymentMethod::PaymentOnTheSpot,
|
||||
},
|
||||
&mut *t,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
update_shopping_cart(
|
||||
UpdateShoppingCart {
|
||||
id: cart.id,
|
||||
buyer_id: cart.buyer_id,
|
||||
payment_method: cart.payment_method,
|
||||
state,
|
||||
checkout_notes: None,
|
||||
},
|
||||
&mut *t,
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
async fn test_shopping_cart_item(
|
||||
t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
shopping_cart_id: Option<ShoppingCartId>,
|
||||
product_id: Option<ProductId>,
|
||||
) -> ShoppingCartItem {
|
||||
let shopping_cart_id = match shopping_cart_id {
|
||||
Some(id) => id,
|
||||
_ => {
|
||||
test_shopping_cart(&mut *t, None, ShoppingCartState::Closed)
|
||||
.await
|
||||
.id
|
||||
}
|
||||
};
|
||||
let product_id = match product_id {
|
||||
Some(id) => id,
|
||||
_ => test_product(&mut *t).await.id,
|
||||
};
|
||||
create_shopping_cart_item(
|
||||
CreateShoppingCartItem {
|
||||
product_id,
|
||||
shopping_cart_id,
|
||||
quantity: Quantity::from_u32(496879),
|
||||
quantity_unit: QuantityUnit::Gram,
|
||||
},
|
||||
t,
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[actix::test]
|
||||
async fn create() {
|
||||
testx::db_t!(t);
|
||||
|
||||
test_shopping_cart_item(&mut t, None, None).await;
|
||||
|
||||
testx::db_rollback!(t);
|
||||
}
|
||||
|
||||
#[actix::test]
|
||||
async fn all() {
|
||||
testx::db_t!(t);
|
||||
|
||||
let account_id = test_account(&mut t, None, None, None).await.id;
|
||||
|
||||
let mut items = Vec::with_capacity(9);
|
||||
|
||||
let cart1 = test_shopping_cart(&mut t, Some(account_id), ShoppingCartState::Closed).await;
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart1.id), None).await);
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart1.id), None).await);
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart1.id), None).await);
|
||||
|
||||
let cart2 = test_shopping_cart(&mut t, Some(account_id), ShoppingCartState::Active).await;
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart2.id), None).await);
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart2.id), None).await);
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart2.id), None).await);
|
||||
|
||||
let cart3 = test_shopping_cart(&mut t, Some(account_id), ShoppingCartState::Closed).await;
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart3.id), None).await);
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart3.id), None).await);
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart3.id), None).await);
|
||||
|
||||
let all = all_shopping_cart_items(AllShoppingCartItems, &mut t)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
testx::db_rollback!(t);
|
||||
assert_eq!(all, items)
|
||||
}
|
||||
|
||||
#[actix::test]
|
||||
async fn account_cart_with_cart_id() {
|
||||
testx::db_t!(t);
|
||||
|
||||
let account_id = test_account(&mut t, None, None, None).await.id;
|
||||
|
||||
let mut items = Vec::with_capacity(9);
|
||||
|
||||
let cart1 = test_shopping_cart(&mut t, Some(account_id), ShoppingCartState::Closed).await;
|
||||
test_shopping_cart_item(&mut t, Some(cart1.id), None).await;
|
||||
test_shopping_cart_item(&mut t, Some(cart1.id), None).await;
|
||||
test_shopping_cart_item(&mut t, Some(cart1.id), None).await;
|
||||
|
||||
let cart2 = test_shopping_cart(&mut t, Some(account_id), ShoppingCartState::Active).await;
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart2.id), None).await);
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart2.id), None).await);
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart2.id), None).await);
|
||||
|
||||
let cart3 = test_shopping_cart(&mut t, Some(account_id), ShoppingCartState::Closed).await;
|
||||
test_shopping_cart_item(&mut t, Some(cart3.id), None).await;
|
||||
test_shopping_cart_item(&mut t, Some(cart3.id), None).await;
|
||||
test_shopping_cart_item(&mut t, Some(cart3.id), None).await;
|
||||
|
||||
let all = account_shopping_cart_items(
|
||||
AccountShoppingCartItems {
|
||||
account_id,
|
||||
shopping_cart_id: Some(cart2.id),
|
||||
},
|
||||
&mut t,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
testx::db_rollback!(t);
|
||||
assert_eq!(all, items)
|
||||
}
|
||||
|
||||
#[actix::test]
|
||||
async fn account_cart_without_cart_id() {
|
||||
testx::db_t!(t);
|
||||
|
||||
let account_id = test_account(&mut t, None, None, None).await.id;
|
||||
|
||||
let mut items = Vec::with_capacity(9);
|
||||
|
||||
let cart1 = test_shopping_cart(&mut t, Some(account_id), ShoppingCartState::Closed).await;
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart1.id), None).await);
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart1.id), None).await);
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart1.id), None).await);
|
||||
|
||||
let cart2 = test_shopping_cart(&mut t, Some(account_id), ShoppingCartState::Active).await;
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart2.id), None).await);
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart2.id), None).await);
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart2.id), None).await);
|
||||
|
||||
let cart3 = test_shopping_cart(&mut t, Some(account_id), ShoppingCartState::Closed).await;
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart3.id), None).await);
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart3.id), None).await);
|
||||
items.push(test_shopping_cart_item(&mut t, Some(cart3.id), None).await);
|
||||
|
||||
let all = account_shopping_cart_items(
|
||||
AccountShoppingCartItems {
|
||||
account_id,
|
||||
shopping_cart_id: None,
|
||||
},
|
||||
&mut t,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
testx::db_rollback!(t);
|
||||
assert_eq!(all, items)
|
||||
}
|
||||
|
||||
#[actix::test]
|
||||
async fn update() {
|
||||
testx::db_t!(t);
|
||||
let account_id = test_account(&mut t, None, None, None).await.id;
|
||||
let cart1 = test_shopping_cart(&mut t, Some(account_id), ShoppingCartState::Closed).await;
|
||||
let item = test_shopping_cart_item(&mut t, Some(cart1.id), None).await;
|
||||
|
||||
let updated = update_shopping_cart_item(
|
||||
UpdateShoppingCartItem {
|
||||
id: item.id,
|
||||
product_id: item.product_id,
|
||||
shopping_cart_id: item.shopping_cart_id,
|
||||
quantity: Quantity::from_u32(987979879),
|
||||
quantity_unit: QuantityUnit::Kilogram,
|
||||
},
|
||||
&mut t,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_ne!(item, updated);
|
||||
assert_eq!(
|
||||
updated,
|
||||
ShoppingCartItem {
|
||||
id: item.id,
|
||||
product_id: item.product_id,
|
||||
shopping_cart_id: item.shopping_cart_id,
|
||||
quantity: Quantity::from_u32(987979879),
|
||||
quantity_unit: QuantityUnit::Kilogram,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -94,11 +94,16 @@ pub struct CreateShoppingCart {
|
||||
pub payment_method: PaymentMethod,
|
||||
}
|
||||
|
||||
db_async_handler!(CreateShoppingCart, create_shopping_cart, ShoppingCart);
|
||||
db_async_handler!(
|
||||
CreateShoppingCart,
|
||||
create_shopping_cart,
|
||||
ShoppingCart,
|
||||
inner_create_shopping_cart
|
||||
);
|
||||
|
||||
pub(crate) async fn create_shopping_cart(
|
||||
msg: CreateShoppingCart,
|
||||
db: PgPool,
|
||||
pool: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
) -> Result<ShoppingCart> {
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
@ -109,10 +114,11 @@ RETURNING id, buyer_id, payment_method, state, checkout_notes
|
||||
)
|
||||
.bind(msg.buyer_id)
|
||||
.bind(msg.payment_method)
|
||||
.fetch_one(&db)
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
log::error!("{e:?}");
|
||||
dbg!(e);
|
||||
super::Error::ShoppingCart(Error::CantCreate)
|
||||
})
|
||||
}
|
||||
@ -127,11 +133,16 @@ pub struct UpdateShoppingCart {
|
||||
pub checkout_notes: Option<String>,
|
||||
}
|
||||
|
||||
db_async_handler!(UpdateShoppingCart, update_shopping_cart, ShoppingCart);
|
||||
db_async_handler!(
|
||||
UpdateShoppingCart,
|
||||
update_shopping_cart,
|
||||
ShoppingCart,
|
||||
inner_update_shopping_cart
|
||||
);
|
||||
|
||||
pub(crate) async fn update_shopping_cart(
|
||||
msg: UpdateShoppingCart,
|
||||
db: PgPool,
|
||||
pool: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
) -> Result<ShoppingCart> {
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
@ -146,7 +157,7 @@ RETURNING id, buyer_id, payment_method, state, checkout_notes
|
||||
.bind(msg.payment_method)
|
||||
.bind(msg.state)
|
||||
.bind(msg.checkout_notes)
|
||||
.fetch_one(&db)
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
log::error!("{e:?}");
|
||||
@ -198,9 +209,17 @@ pub struct FindShoppingCart {
|
||||
pub id: ShoppingCartId,
|
||||
}
|
||||
|
||||
db_async_handler!(FindShoppingCart, find_shopping_cart, ShoppingCart);
|
||||
db_async_handler!(
|
||||
FindShoppingCart,
|
||||
find_shopping_cart,
|
||||
ShoppingCart,
|
||||
inner_find_shopping_cart
|
||||
);
|
||||
|
||||
pub(crate) async fn find_shopping_cart(msg: FindShoppingCart, db: PgPool) -> Result<ShoppingCart> {
|
||||
pub(crate) async fn find_shopping_cart(
|
||||
msg: FindShoppingCart,
|
||||
pool: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
) -> Result<ShoppingCart> {
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
SELECT id, buyer_id, payment_method, state, checkout_notes
|
||||
@ -209,7 +228,7 @@ WHERE id = $1
|
||||
"#,
|
||||
)
|
||||
.bind(msg.id)
|
||||
.fetch_one(&db)
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
log::error!("{e:?}");
|
||||
@ -267,3 +286,177 @@ WHERE buyer_id = $1 AND state = 'active'
|
||||
super::Error::ShoppingCart(Error::NotExists)
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use config::UpdateConfig;
|
||||
use fake::Fake;
|
||||
use model::*;
|
||||
|
||||
pub struct NoOpts;
|
||||
|
||||
impl UpdateConfig for NoOpts {}
|
||||
|
||||
use crate::*;
|
||||
|
||||
async fn test_account(
|
||||
t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
login: Option<String>,
|
||||
email: Option<String>,
|
||||
hash: Option<String>,
|
||||
) -> FullAccount {
|
||||
use fake::faker::internet::en;
|
||||
let login: String = login.unwrap_or_else(|| en::Username().fake());
|
||||
let email: String = email.unwrap_or_else(|| en::FreeEmail().fake());
|
||||
let hash: String = hash.unwrap_or_else(|| en::Password(10..20).fake());
|
||||
|
||||
create_account(
|
||||
CreateAccount {
|
||||
email: Email::new(email),
|
||||
login: Login::new(login),
|
||||
pass_hash: PassHash::new(hash),
|
||||
role: Role::Admin,
|
||||
},
|
||||
t,
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
async fn test_shopping_cart(
|
||||
t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
buyer_id: Option<AccountId>,
|
||||
) -> ShoppingCart {
|
||||
let buyer_id = match buyer_id {
|
||||
Some(id) => id,
|
||||
_ => test_account(&mut *t, None, None, None).await.id,
|
||||
};
|
||||
|
||||
super::create_shopping_cart(
|
||||
CreateShoppingCart {
|
||||
buyer_id,
|
||||
payment_method: PaymentMethod::PaymentOnTheSpot,
|
||||
},
|
||||
t,
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[actix::test]
|
||||
async fn create_shopping_cart() {
|
||||
testx::db_t!(t);
|
||||
|
||||
let account = test_account(&mut t, None, None, None).await;
|
||||
|
||||
let cart = super::create_shopping_cart(
|
||||
CreateShoppingCart {
|
||||
buyer_id: account.id,
|
||||
payment_method: PaymentMethod::PaymentOnTheSpot,
|
||||
},
|
||||
&mut t,
|
||||
)
|
||||
.await;
|
||||
|
||||
testx::db_rollback!(t);
|
||||
assert!(cart.is_ok());
|
||||
}
|
||||
|
||||
#[actix::test]
|
||||
async fn update_shopping_cart() {
|
||||
testx::db_t!(t);
|
||||
|
||||
let account = test_account(&mut t, None, None, None).await;
|
||||
|
||||
let original = test_shopping_cart(&mut t, Some(account.id)).await;
|
||||
|
||||
let cart = super::update_shopping_cart(
|
||||
UpdateShoppingCart {
|
||||
id: original.id,
|
||||
buyer_id: account.id,
|
||||
payment_method: PaymentMethod::PayU,
|
||||
state: ShoppingCartState::Closed,
|
||||
checkout_notes: Some("Foo bar".into()),
|
||||
},
|
||||
&mut t,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
testx::db_rollback!(t);
|
||||
assert_ne!(cart, original);
|
||||
assert_eq!(
|
||||
cart,
|
||||
ShoppingCart {
|
||||
id: original.id,
|
||||
buyer_id: account.id,
|
||||
payment_method: PaymentMethod::PayU,
|
||||
state: ShoppingCartState::Closed,
|
||||
checkout_notes: Some("Foo bar".into())
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[actix::test]
|
||||
async fn without_cart_ensure_shopping_cart() {
|
||||
testx::db_t!(t);
|
||||
|
||||
let account = test_account(&mut t, None, None, None).await;
|
||||
|
||||
let cart = super::ensure_active_shopping_cart(
|
||||
EnsureActiveShoppingCart {
|
||||
buyer_id: account.id,
|
||||
},
|
||||
&mut t,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let id = cart.id;
|
||||
|
||||
testx::db_rollback!(t);
|
||||
assert_eq!(
|
||||
cart,
|
||||
model::ShoppingCart {
|
||||
id,
|
||||
buyer_id: account.id,
|
||||
payment_method: Default::default(),
|
||||
state: ShoppingCartState::Active,
|
||||
checkout_notes: None
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[actix::test]
|
||||
async fn with_inactive_cart_ensure_shopping_cart() {
|
||||
testx::db_t!(t);
|
||||
|
||||
let account = test_account(&mut t, None, None, None).await;
|
||||
|
||||
let original = test_shopping_cart(&mut t, Some(account.id)).await;
|
||||
let _ = super::update_shopping_cart(
|
||||
UpdateShoppingCart {
|
||||
id: original.id,
|
||||
buyer_id: account.id,
|
||||
payment_method: Default::default(),
|
||||
state: ShoppingCartState::Closed,
|
||||
checkout_notes: None,
|
||||
},
|
||||
&mut t,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let cart = super::ensure_active_shopping_cart(
|
||||
EnsureActiveShoppingCart {
|
||||
buyer_id: account.id,
|
||||
},
|
||||
&mut t,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
testx::db_rollback!(t);
|
||||
assert_ne!(original, cart);
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,8 @@ pub enum Error {
|
||||
Delete,
|
||||
#[error("Unable find stock for product")]
|
||||
ProductStock,
|
||||
#[error("Stock does not exists")]
|
||||
NotFound,
|
||||
}
|
||||
|
||||
#[derive(Message)]
|
||||
@ -42,6 +44,35 @@ ORDER BY id ASC
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Message)]
|
||||
#[rtype(result = "Result<model::Stock>")]
|
||||
pub struct FindStock {
|
||||
pub id: StockId,
|
||||
}
|
||||
|
||||
crate::db_async_handler!(FindStock, find_stock, Stock, inner_find_stock);
|
||||
|
||||
async fn find_stock(
|
||||
msg: FindStock,
|
||||
pool: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
) -> Result<Stock> {
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
SELECT id, product_id, quantity, quantity_unit
|
||||
FROM stocks
|
||||
WHERE id = $1
|
||||
"#,
|
||||
)
|
||||
.bind(msg.id)
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
log::error!("{e:?}");
|
||||
dbg!(e);
|
||||
crate::Error::Stock(Error::NotFound)
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Message)]
|
||||
#[rtype(result = "Result<model::Stock>")]
|
||||
pub struct CreateStock {
|
||||
@ -70,7 +101,7 @@ RETURNING id, product_id, quantity, quantity_unit
|
||||
.await
|
||||
.map_err(|e| {
|
||||
log::error!("{e:?}");
|
||||
eprintln!("{e:?}");
|
||||
dbg!(e);
|
||||
crate::Error::Stock(Error::Create)
|
||||
})
|
||||
}
|
||||
@ -93,8 +124,8 @@ async fn update_stock(
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
UPDATE stocks
|
||||
SET product_id = $1 AND
|
||||
quantity = $2
|
||||
SET product_id = $1,
|
||||
quantity = $2,
|
||||
quantity_unit = $3
|
||||
WHERE id = $4
|
||||
RETURNING id, product_id, quantity, quantity_unit
|
||||
@ -165,9 +196,9 @@ async fn product_stock(
|
||||
Ok(MultiLoad::new(
|
||||
pool,
|
||||
r#"
|
||||
SELECT id, product_id, quantity, quantity_unit
|
||||
FROM stocks
|
||||
WHERE
|
||||
SELECT id, product_id, quantity, quantity_unit
|
||||
FROM stocks
|
||||
WHERE
|
||||
"#,
|
||||
" product_id =",
|
||||
)
|
||||
@ -291,10 +322,49 @@ mod tests {
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
// let reloaded = super::Stock
|
||||
let reloaded = super::find_stock(FindStock { id: second.id }, &mut t).await;
|
||||
|
||||
testx::db_rollback!(t);
|
||||
assert_eq!(deleted, Some(second));
|
||||
assert_ne!(deleted, Some(first));
|
||||
assert_eq!(reloaded, Err(crate::Error::Stock(super::Error::NotFound)));
|
||||
}
|
||||
|
||||
#[actix::test]
|
||||
async fn update_stock() {
|
||||
testx::db_t!(t);
|
||||
|
||||
let first = test_stock(&mut t, None, None, None).await;
|
||||
let second = test_stock(&mut t, None, None, None).await;
|
||||
let another_product = test_product(&mut t).await;
|
||||
|
||||
let updated: Stock = super::update_stock(
|
||||
UpdateStock {
|
||||
id: second.id,
|
||||
product_id: another_product.id,
|
||||
quantity: Quantity::from_u32(19191),
|
||||
quantity_unit: QuantityUnit::Gram,
|
||||
},
|
||||
&mut t,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let reloaded = super::find_stock(FindStock { id: second.id }, &mut t)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
testx::db_rollback!(t);
|
||||
assert_eq!(
|
||||
updated,
|
||||
Stock {
|
||||
id: second.id,
|
||||
product_id: another_product.id,
|
||||
quantity: Quantity::from_u32(19191),
|
||||
quantity_unit: QuantityUnit::Gram,
|
||||
}
|
||||
);
|
||||
assert_ne!(updated, second);
|
||||
assert_ne!(updated, first);
|
||||
assert_eq!(reloaded, updated);
|
||||
}
|
||||
}
|
||||
|
2
migrations/20220606123105_drop_unique_active_cart.sql
Normal file
2
migrations/20220606123105_drop_unique_active_cart.sql
Normal file
@ -0,0 +1,2 @@
|
||||
ALTER TABLE shopping_carts
|
||||
DROP CONSTRAINT single_active_cart;
|
@ -362,7 +362,7 @@ impl<'path>
|
||||
.drain_filter(|photo| photo.product_id == id)
|
||||
.map(
|
||||
|ProductLinkedPhoto {
|
||||
id,
|
||||
photo_id: id,
|
||||
local_path: _,
|
||||
file_name,
|
||||
product_id: _,
|
||||
|
@ -224,7 +224,7 @@ impl Default for PaymentMethod {
|
||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(rename_all = "snake_case"))]
|
||||
#[derive(Copy, Clone, Debug, Hash, Display, Deserialize, Serialize)]
|
||||
#[derive(Copy, Clone, Debug, Hash, PartialEq, Display, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ShoppingCartState {
|
||||
Active,
|
||||
@ -279,7 +279,9 @@ impl Default for Audience {
|
||||
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
||||
#[derive(Serialize, Deserialize, Default, Debug, Copy, Clone, Hash, Deref, From)]
|
||||
#[derive(
|
||||
Default, Debug, Copy, Clone, Hash, PartialOrd, PartialEq, Serialize, Deserialize, Deref, From,
|
||||
)]
|
||||
#[serde(transparent)]
|
||||
pub struct Price(NonNegative);
|
||||
|
||||
@ -435,7 +437,7 @@ impl<'de> serde::Deserialize<'de> for Email {
|
||||
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
||||
#[derive(Serialize, Default, Debug, PartialEq, Copy, Clone, Hash, Deref, Display)]
|
||||
#[derive(Default, Debug, PartialEq, PartialOrd, Copy, Clone, Hash, Serialize, Deref, Display)]
|
||||
#[serde(transparent)]
|
||||
pub struct NonNegative(i32);
|
||||
|
||||
@ -533,7 +535,9 @@ impl<'de> serde::Deserialize<'de> for NonNegative {
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||
#[derive(Serialize, Deserialize, Debug, Copy, Clone, Hash, Display, From)]
|
||||
#[derive(
|
||||
Debug, Copy, Clone, Hash, PartialOrd, PartialEq, Serialize, Deserialize, Display, From,
|
||||
)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum Day {
|
||||
Monday = 1 << 0,
|
||||
@ -595,7 +599,7 @@ impl TryFrom<i32> for Day {
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||
#[derive(Serialize, Deserialize, Hash, Debug)]
|
||||
#[derive(Hash, Debug, PartialOrd, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct Days(pub Vec<Day>);
|
||||
|
||||
@ -773,7 +777,7 @@ impl From<FullAccount> for Account {
|
||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
||||
#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq, Hash, Deref, From)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Deref, From)]
|
||||
#[serde(transparent)]
|
||||
pub struct ProductId(RecordId);
|
||||
|
||||
@ -785,7 +789,7 @@ impl Display for ProductId {
|
||||
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Hash, Deref, Display, From)]
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Serialize, Deserialize, Deref, Display, From)]
|
||||
#[serde(transparent)]
|
||||
pub struct ProductName(String);
|
||||
|
||||
@ -801,7 +805,7 @@ impl ProductName {
|
||||
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Hash, Deref, Display, From)]
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Serialize, Deserialize, Deref, Display, From)]
|
||||
#[serde(transparent)]
|
||||
pub struct ProductShortDesc(String);
|
||||
|
||||
@ -817,7 +821,7 @@ impl ProductShortDesc {
|
||||
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Hash, Deref, Display, From)]
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Serialize, Deserialize, Deref, Display, From)]
|
||||
#[serde(transparent)]
|
||||
pub struct ProductLongDesc(String);
|
||||
|
||||
@ -836,7 +840,7 @@ impl ProductLongDesc {
|
||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Hash, Deref, Display, From)]
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Serialize, Deserialize, Deref, Display, From)]
|
||||
#[serde(transparent)]
|
||||
pub struct ProductCategory(String);
|
||||
|
||||
@ -848,7 +852,7 @@ impl ProductCategory {
|
||||
|
||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||
#[cfg_attr(feature = "db", derive(sqlx::FromRow))]
|
||||
#[derive(Serialize, Deserialize, Debug, Hash)]
|
||||
#[derive(Debug, Hash, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Product {
|
||||
pub id: ProductId,
|
||||
pub name: ProductName,
|
||||
@ -973,7 +977,7 @@ pub struct ShoppingCartId(pub RecordId);
|
||||
|
||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||
#[cfg_attr(feature = "db", derive(sqlx::FromRow))]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct ShoppingCart {
|
||||
pub id: ShoppingCartId,
|
||||
pub buyer_id: AccountId,
|
||||
@ -985,12 +989,12 @@ pub struct ShoppingCart {
|
||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
||||
#[derive(Serialize, Deserialize, PartialEq, Copy, Clone, Debug, Deref, Display)]
|
||||
#[derive(PartialEq, Copy, Clone, Debug, Serialize, Deserialize, Deref, Display)]
|
||||
#[serde(transparent)]
|
||||
pub struct ShoppingCartItemId(RecordId);
|
||||
|
||||
#[cfg_attr(feature = "db", derive(sqlx::FromRow))]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct ShoppingCartItem {
|
||||
pub id: ShoppingCartItemId,
|
||||
pub product_id: ProductId,
|
||||
@ -1047,7 +1051,7 @@ impl Token {
|
||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Deref, Display, From)]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Deref, Display, From)]
|
||||
pub struct AccessTokenString(String);
|
||||
|
||||
impl AccessTokenString {
|
||||
@ -1065,7 +1069,7 @@ impl From<RefreshTokenString> for AccessTokenString {
|
||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Deref, Display, From)]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Deref, Display, From)]
|
||||
pub struct RefreshTokenString(String);
|
||||
|
||||
impl From<AccessTokenString> for RefreshTokenString {
|
||||
@ -1083,7 +1087,7 @@ impl RefreshTokenString {
|
||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
||||
#[derive(Serialize, Deserialize, Debug, Hash, Deref, Display, From)]
|
||||
#[derive(Debug, Hash, PartialEq, Serialize, Deserialize, Deref, Display, From)]
|
||||
pub struct LocalPath(String);
|
||||
|
||||
impl LocalPath {
|
||||
@ -1095,7 +1099,7 @@ impl LocalPath {
|
||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
||||
#[derive(Serialize, Deserialize, Debug, Hash, Deref, Display, From)]
|
||||
#[derive(Debug, Hash, PartialEq, Serialize, Deserialize, Deref, Display, From)]
|
||||
pub struct UniqueName(String);
|
||||
|
||||
impl UniqueName {
|
||||
@ -1107,7 +1111,7 @@ impl UniqueName {
|
||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
||||
#[derive(Serialize, Deserialize, Debug, Hash, Deref, Display, From)]
|
||||
#[derive(Debug, Hash, PartialEq, Serialize, Deserialize, Deref, Display, From)]
|
||||
pub struct FileName(String);
|
||||
|
||||
impl FileName {
|
||||
@ -1119,18 +1123,20 @@ impl FileName {
|
||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
||||
#[derive(Serialize, Deserialize, Debug, Copy, Clone, Hash, Deref, Display, From)]
|
||||
#[derive(
|
||||
Debug, Copy, Clone, Hash, PartialOrd, PartialEq, Serialize, Deserialize, Deref, Display, From,
|
||||
)]
|
||||
pub struct PhotoId(RecordId);
|
||||
|
||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
||||
#[derive(Serialize, Deserialize, Debug, Hash, Deref, Display, From)]
|
||||
#[derive(Debug, Hash, Copy, Clone, PartialEq, Serialize, Deserialize, Deref, Display, From)]
|
||||
pub struct ProductPhotoId(RecordId);
|
||||
|
||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||
#[cfg_attr(feature = "db", derive(sqlx::FromRow))]
|
||||
#[derive(Serialize, Deserialize, Debug, Hash)]
|
||||
#[derive(Debug, Hash, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Photo {
|
||||
pub id: PhotoId,
|
||||
pub local_path: LocalPath,
|
||||
@ -1140,9 +1146,9 @@ pub struct Photo {
|
||||
|
||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||
#[cfg_attr(feature = "db", derive(sqlx::FromRow))]
|
||||
#[derive(Serialize, Deserialize, Debug, Hash)]
|
||||
#[derive(Debug, Hash, PartialEq, Serialize, Deserialize)]
|
||||
pub struct ProductLinkedPhoto {
|
||||
pub id: PhotoId,
|
||||
pub photo_id: PhotoId,
|
||||
pub local_path: LocalPath,
|
||||
pub file_name: FileName,
|
||||
pub unique_name: UniqueName,
|
||||
@ -1151,7 +1157,7 @@ pub struct ProductLinkedPhoto {
|
||||
|
||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||
#[cfg_attr(feature = "db", derive(sqlx::FromRow))]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct ProductPhoto {
|
||||
pub id: ProductPhotoId,
|
||||
pub product_id: ProductId,
|
||||
|
Loading…
Reference in New Issue
Block a user