Additional tests
This commit is contained in:
parent
a34f32dc15
commit
7c272d310f
@ -138,7 +138,7 @@ WHERE account_id = $1
|
|||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
log::error!("{e}");
|
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
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
log::error!("{e}");
|
log::error!("{e}");
|
||||||
eprintln!("{e}");
|
dbg!(e);
|
||||||
Error::CreateAccountAddress.into()
|
Error::CreateAccountAddress.into()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -213,6 +213,7 @@ pub struct MultiLoad<'transaction, 'transaction2, 'header, 'condition, T> {
|
|||||||
pool: &'transaction mut sqlx::Transaction<'transaction2, sqlx::Postgres>,
|
pool: &'transaction mut sqlx::Transaction<'transaction2, sqlx::Postgres>,
|
||||||
header: &'header str,
|
header: &'header str,
|
||||||
condition: &'condition str,
|
condition: &'condition str,
|
||||||
|
sort: Option<String>,
|
||||||
__phantom: std::marker::PhantomData<T>,
|
__phantom: std::marker::PhantomData<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,10 +231,16 @@ where
|
|||||||
pool,
|
pool,
|
||||||
header,
|
header,
|
||||||
condition,
|
condition,
|
||||||
|
sort: None,
|
||||||
__phantom: Default::default(),
|
__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>(
|
pub async fn load<'query, Error, Ids>(
|
||||||
&mut self,
|
&mut self,
|
||||||
len: usize,
|
len: usize,
|
||||||
@ -257,13 +264,18 @@ where
|
|||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
let query: String = self.header.into();
|
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 {
|
if idx != 0 {
|
||||||
q.push_str(" OR");
|
q.push_str(" OR");
|
||||||
}
|
}
|
||||||
q.push_str(&format!(" {} ${}", self.condition, idx + 1));
|
q.push_str(&format!(" {} ${}", self.condition, idx + 1));
|
||||||
q
|
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(
|
let q = sqlx::query_as_with(
|
||||||
query.as_str(),
|
query.as_str(),
|
||||||
ids.into_iter()
|
ids.into_iter()
|
||||||
|
@ -38,7 +38,7 @@ ORDER BY id DESC
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
log::error!("{e:?}");
|
log::error!("{e:?}");
|
||||||
super::Error::from(Error::All)
|
Error::All.into()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,7 +102,7 @@ WHERE id = $1
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
log::error!("{e:?}");
|
log::error!("{e:?}");
|
||||||
super::Error::OrderItem(Error::NotExists)
|
Error::NotExists.into()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,6 +128,6 @@ ORDER BY id DESC
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
log::error!("{e:?}");
|
log::error!("{e:?}");
|
||||||
super::Error::OrderItem(Error::OrderItems)
|
Error::OrderItems.into()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,8 @@ pub enum Error {
|
|||||||
Create,
|
Create,
|
||||||
#[error("Failed to fetch all photo")]
|
#[error("Failed to fetch all photo")]
|
||||||
All,
|
All,
|
||||||
|
#[error("Failed to fetch photos for products")]
|
||||||
|
PhotosForProducts,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(actix::Message)]
|
#[derive(actix::Message)]
|
||||||
@ -22,6 +24,7 @@ where
|
|||||||
r#"
|
r#"
|
||||||
SELECT id, local_path, file_name, unique_name
|
SELECT id, local_path, file_name, unique_name
|
||||||
FROM photos
|
FROM photos
|
||||||
|
ORDER BY id ASC
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
.fetch_all(pool)
|
.fetch_all(pool)
|
||||||
@ -91,20 +94,168 @@ pub(crate) async fn photos_for_products(
|
|||||||
let res: Vec<model::ProductLinkedPhoto> = MultiLoad::new(
|
let res: Vec<model::ProductLinkedPhoto> = MultiLoad::new(
|
||||||
pool,
|
pool,
|
||||||
r#"
|
r#"
|
||||||
SELECT photos.id, photos.local_path, photos.file_name,
|
SELECT photos.id AS photo_id,
|
||||||
product_photos.product_id, photos.unique_name FROM photos
|
photos.local_path AS local_path,
|
||||||
INNER JOIN product_photos
|
photos.file_name AS file_name,
|
||||||
ON photos.id = product_photos.photo_id
|
product_photos.product_id AS product_id,
|
||||||
WHERE
|
photos.unique_name AS unique_name
|
||||||
|
FROM photos
|
||||||
|
INNER JOIN product_photos
|
||||||
|
ON photos.id = product_photos.photo_id
|
||||||
|
WHERE
|
||||||
"#,
|
"#,
|
||||||
" product_photos.product_id =",
|
" product_photos.product_id =",
|
||||||
)
|
)
|
||||||
|
.with_sorting("photos.id ASC")
|
||||||
.load(
|
.load(
|
||||||
msg.product_ids.len(),
|
msg.product_ids.len(),
|
||||||
msg.product_ids.into_iter().map(|id| *id),
|
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?;
|
.await?;
|
||||||
log::debug!("product linked photos {:?}", res);
|
log::debug!("product linked photos {:?}", res);
|
||||||
Ok(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,
|
Create,
|
||||||
#[error("Failed to load all product photos")]
|
#[error("Failed to load all product photos")]
|
||||||
All,
|
All,
|
||||||
|
#[error("Failed to delete product photo")]
|
||||||
|
Delete,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(actix::Message)]
|
#[derive(actix::Message)]
|
||||||
#[rtype(result = "Result<Vec<model::ProductPhoto>>")]
|
#[rtype(result = "Result<Vec<model::ProductPhoto>>")]
|
||||||
pub struct AllProductPhotos;
|
pub struct AllProductPhotos;
|
||||||
|
|
||||||
crate::db_async_handler!(
|
db_async_handler!(
|
||||||
AllProductPhotos,
|
AllProductPhotos,
|
||||||
all_product_photos,
|
all_product_photos,
|
||||||
Vec<model::ProductPhoto>,
|
Vec<model::ProductPhoto>,
|
||||||
inner_all_product_photos
|
inner_all_product_photos
|
||||||
);
|
);
|
||||||
|
|
||||||
pub(crate) async fn all_product_photos<'e, E>(
|
pub(crate) async fn all_product_photos(
|
||||||
_msg: AllProductPhotos,
|
_msg: AllProductPhotos,
|
||||||
pool: E,
|
pool: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||||
) -> Result<Vec<model::ProductPhoto>>
|
) -> Result<Vec<model::ProductPhoto>> {
|
||||||
where
|
|
||||||
E: sqlx::Executor<'e, Database = sqlx::Postgres>,
|
|
||||||
{
|
|
||||||
sqlx::query_as(
|
sqlx::query_as(
|
||||||
r#"
|
r#"
|
||||||
SELECT id, product_id, photo_id
|
SELECT id, product_id, photo_id
|
||||||
FROM product_photos
|
FROM product_photos
|
||||||
|
ORDER BY id ASC
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
.fetch_all(pool)
|
.fetch_all(pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
log::error!("{e:?}");
|
log::error!("{e:?}");
|
||||||
crate::Error::ProductPhoto(Error::All)
|
Error::All.into()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,13 +54,10 @@ db_async_handler!(
|
|||||||
inner_create_product_photo
|
inner_create_product_photo
|
||||||
);
|
);
|
||||||
|
|
||||||
pub(crate) async fn create_product_photo<'e, E>(
|
pub(crate) async fn create_product_photo(
|
||||||
msg: CreateProductPhoto,
|
msg: CreateProductPhoto,
|
||||||
pool: E,
|
pool: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||||
) -> Result<model::ProductPhoto>
|
) -> Result<model::ProductPhoto> {
|
||||||
where
|
|
||||||
E: sqlx::Executor<'e, Database = sqlx::Postgres>,
|
|
||||||
{
|
|
||||||
sqlx::query_as(
|
sqlx::query_as(
|
||||||
r#"
|
r#"
|
||||||
INSERT INTO product_photos(product_id, photo_id)
|
INSERT INTO product_photos(product_id, photo_id)
|
||||||
@ -74,6 +71,131 @@ RETURNING id, product_id, photo_id
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
log::error!("{:?}", 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
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
log::error!("{e:?}");
|
log::error!("{e:?}");
|
||||||
eprintln!("{e:?}");
|
dbg!(e);
|
||||||
crate::Error::Product(Error::Create)
|
crate::Error::Product(Error::Create)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -152,7 +152,7 @@ pub struct UpdateProduct {
|
|||||||
|
|
||||||
crate::db_async_handler!(UpdateProduct, update_product, Product, inner_update_product);
|
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
|
where
|
||||||
E: sqlx::Executor<'e, Database = sqlx::Postgres>,
|
E: sqlx::Executor<'e, Database = sqlx::Postgres>,
|
||||||
{
|
{
|
||||||
@ -315,3 +315,86 @@ WHERE
|
|||||||
)
|
)
|
||||||
.await
|
.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 model::*;
|
||||||
use sqlx::PgPool;
|
|
||||||
|
|
||||||
use super::Result;
|
use super::Result;
|
||||||
use crate::db_async_handler;
|
use crate::db_async_handler;
|
||||||
@ -34,23 +33,32 @@ pub struct AllShoppingCartItems;
|
|||||||
db_async_handler!(
|
db_async_handler!(
|
||||||
AllShoppingCartItems,
|
AllShoppingCartItems,
|
||||||
all_shopping_cart_items,
|
all_shopping_cart_items,
|
||||||
Vec<ShoppingCartItem>
|
Vec<ShoppingCartItem>,
|
||||||
|
inner_all_shopping_cart_items
|
||||||
);
|
);
|
||||||
|
|
||||||
pub(crate) async fn all_shopping_cart_items(
|
pub(crate) async fn all_shopping_cart_items(
|
||||||
_msg: AllShoppingCartItems,
|
_msg: AllShoppingCartItems,
|
||||||
pool: PgPool,
|
t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||||
) -> Result<Vec<ShoppingCartItem>> {
|
) -> Result<Vec<ShoppingCartItem>> {
|
||||||
sqlx::query_as(
|
sqlx::query_as(
|
||||||
r#"
|
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
|
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
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
log::error!("{e:?}");
|
log::error!("{e:?}");
|
||||||
|
dbg!(e);
|
||||||
super::Error::ShoppingCartItem(Error::All)
|
super::Error::ShoppingCartItem(Error::All)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -65,12 +73,13 @@ pub struct AccountShoppingCartItems {
|
|||||||
db_async_handler!(
|
db_async_handler!(
|
||||||
AccountShoppingCartItems,
|
AccountShoppingCartItems,
|
||||||
account_shopping_cart_items,
|
account_shopping_cart_items,
|
||||||
Vec<ShoppingCartItem>
|
Vec<ShoppingCartItem>,
|
||||||
|
inner_account_shopping_cart_items
|
||||||
);
|
);
|
||||||
|
|
||||||
pub(crate) async fn account_shopping_cart_items(
|
pub(crate) async fn account_shopping_cart_items(
|
||||||
msg: AccountShoppingCartItems,
|
msg: AccountShoppingCartItems,
|
||||||
pool: PgPool,
|
t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||||
) -> Result<Vec<ShoppingCartItem>> {
|
) -> Result<Vec<ShoppingCartItem>> {
|
||||||
match msg.shopping_cart_id {
|
match msg.shopping_cart_id {
|
||||||
Some(shopping_cart_id) => sqlx::query_as(
|
Some(shopping_cart_id) => sqlx::query_as(
|
||||||
@ -103,7 +112,7 @@ WHERE shopping_carts.buyer_id = $1
|
|||||||
)
|
)
|
||||||
.bind(msg.account_id),
|
.bind(msg.account_id),
|
||||||
}
|
}
|
||||||
.fetch_all(&pool)
|
.fetch_all(t)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
log::error!("{e:?}");
|
log::error!("{e:?}");
|
||||||
@ -123,12 +132,13 @@ pub struct CreateShoppingCartItem {
|
|||||||
db_async_handler!(
|
db_async_handler!(
|
||||||
CreateShoppingCartItem,
|
CreateShoppingCartItem,
|
||||||
create_shopping_cart_item,
|
create_shopping_cart_item,
|
||||||
ShoppingCartItem
|
ShoppingCartItem,
|
||||||
|
inner_create_shopping_cart_item
|
||||||
);
|
);
|
||||||
|
|
||||||
pub(crate) async fn create_shopping_cart_item(
|
pub(crate) async fn create_shopping_cart_item(
|
||||||
msg: CreateShoppingCartItem,
|
msg: CreateShoppingCartItem,
|
||||||
db: PgPool,
|
t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||||
) -> Result<ShoppingCartItem> {
|
) -> Result<ShoppingCartItem> {
|
||||||
sqlx::query_as(
|
sqlx::query_as(
|
||||||
r#"
|
r#"
|
||||||
@ -141,10 +151,11 @@ RETURNING id, product_id, shopping_cart_id, quantity, quantity_unit
|
|||||||
.bind(msg.shopping_cart_id)
|
.bind(msg.shopping_cart_id)
|
||||||
.bind(msg.quantity)
|
.bind(msg.quantity)
|
||||||
.bind(msg.quantity_unit)
|
.bind(msg.quantity_unit)
|
||||||
.fetch_one(&db)
|
.fetch_one(t)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
log::error!("{e:?}");
|
log::error!("{e:?}");
|
||||||
|
dbg!(&e);
|
||||||
super::Error::ShoppingCartItem(Error::CantCreate)
|
super::Error::ShoppingCartItem(Error::CantCreate)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -162,12 +173,13 @@ pub struct UpdateShoppingCartItem {
|
|||||||
db_async_handler!(
|
db_async_handler!(
|
||||||
UpdateShoppingCartItem,
|
UpdateShoppingCartItem,
|
||||||
update_shopping_cart_item,
|
update_shopping_cart_item,
|
||||||
ShoppingCartItem
|
ShoppingCartItem,
|
||||||
|
inner_update_shopping_cart_item
|
||||||
);
|
);
|
||||||
|
|
||||||
pub(crate) async fn update_shopping_cart_item(
|
pub(crate) async fn update_shopping_cart_item(
|
||||||
msg: UpdateShoppingCartItem,
|
msg: UpdateShoppingCartItem,
|
||||||
db: PgPool,
|
t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||||
) -> Result<ShoppingCartItem> {
|
) -> Result<ShoppingCartItem> {
|
||||||
sqlx::query_as(
|
sqlx::query_as(
|
||||||
r#"
|
r#"
|
||||||
@ -182,7 +194,7 @@ RETURNING id, product_id, shopping_cart_id, quantity, quantity_unit
|
|||||||
.bind(msg.shopping_cart_id)
|
.bind(msg.shopping_cart_id)
|
||||||
.bind(msg.quantity)
|
.bind(msg.quantity)
|
||||||
.bind(msg.quantity_unit)
|
.bind(msg.quantity_unit)
|
||||||
.fetch_one(&db)
|
.fetch_one(t)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
log::error!("{e:?}");
|
log::error!("{e:?}");
|
||||||
@ -199,12 +211,13 @@ pub struct DeleteShoppingCartItem {
|
|||||||
db_async_handler!(
|
db_async_handler!(
|
||||||
DeleteShoppingCartItem,
|
DeleteShoppingCartItem,
|
||||||
delete_shopping_cart_item,
|
delete_shopping_cart_item,
|
||||||
Option<ShoppingCartItem>
|
Option<ShoppingCartItem>,
|
||||||
|
inner_delete_shopping_cart_item
|
||||||
);
|
);
|
||||||
|
|
||||||
pub(crate) async fn delete_shopping_cart_item(
|
pub(crate) async fn delete_shopping_cart_item(
|
||||||
msg: DeleteShoppingCartItem,
|
msg: DeleteShoppingCartItem,
|
||||||
db: PgPool,
|
t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||||
) -> Result<Option<ShoppingCartItem>> {
|
) -> Result<Option<ShoppingCartItem>> {
|
||||||
sqlx::query_as(
|
sqlx::query_as(
|
||||||
r#"
|
r#"
|
||||||
@ -214,7 +227,7 @@ RETURNING id, product_id, shopping_cart_id, quantity, quantity_unit
|
|||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
.bind(msg.id)
|
.bind(msg.id)
|
||||||
.fetch_optional(&db)
|
.fetch_optional(t)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
log::error!("{e:?}");
|
log::error!("{e:?}");
|
||||||
@ -231,12 +244,13 @@ pub struct FindShoppingCartItem {
|
|||||||
db_async_handler!(
|
db_async_handler!(
|
||||||
FindShoppingCartItem,
|
FindShoppingCartItem,
|
||||||
find_shopping_cart_item,
|
find_shopping_cart_item,
|
||||||
ShoppingCartItem
|
ShoppingCartItem,
|
||||||
|
inner_find_shopping_cart_item
|
||||||
);
|
);
|
||||||
|
|
||||||
pub(crate) async fn find_shopping_cart_item(
|
pub(crate) async fn find_shopping_cart_item(
|
||||||
msg: FindShoppingCartItem,
|
msg: FindShoppingCartItem,
|
||||||
db: PgPool,
|
t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||||
) -> Result<ShoppingCartItem> {
|
) -> Result<ShoppingCartItem> {
|
||||||
sqlx::query_as(
|
sqlx::query_as(
|
||||||
r#"
|
r#"
|
||||||
@ -246,7 +260,7 @@ WHERE id = $1
|
|||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
.bind(msg.id)
|
.bind(msg.id)
|
||||||
.fetch_one(&db)
|
.fetch_one(t)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
log::error!("{e:?}");
|
log::error!("{e:?}");
|
||||||
@ -257,18 +271,19 @@ WHERE id = $1
|
|||||||
#[derive(actix::Message)]
|
#[derive(actix::Message)]
|
||||||
#[rtype(result = "Result<Option<ShoppingCartItem>>")]
|
#[rtype(result = "Result<Option<ShoppingCartItem>>")]
|
||||||
pub struct ActiveCartItemByProduct {
|
pub struct ActiveCartItemByProduct {
|
||||||
pub product_id: model::ProductId,
|
pub product_id: ProductId,
|
||||||
}
|
}
|
||||||
|
|
||||||
db_async_handler!(
|
db_async_handler!(
|
||||||
ActiveCartItemByProduct,
|
ActiveCartItemByProduct,
|
||||||
active_cart_item_by_product,
|
active_cart_item_by_product,
|
||||||
Option<ShoppingCartItem>
|
Option<ShoppingCartItem>,
|
||||||
|
inner_active_cart_item_by_product
|
||||||
);
|
);
|
||||||
|
|
||||||
pub(crate) async fn active_cart_item_by_product(
|
pub(crate) async fn active_cart_item_by_product(
|
||||||
msg: ActiveCartItemByProduct,
|
msg: ActiveCartItemByProduct,
|
||||||
db: PgPool,
|
t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||||
) -> Result<Option<ShoppingCartItem>> {
|
) -> Result<Option<ShoppingCartItem>> {
|
||||||
sqlx::query_as(
|
sqlx::query_as(
|
||||||
r#"
|
r#"
|
||||||
@ -278,13 +293,16 @@ SELECT shopping_cart_items.id,
|
|||||||
shopping_cart_items.quantity,
|
shopping_cart_items.quantity,
|
||||||
shopping_cart_items.quantity_unit
|
shopping_cart_items.quantity_unit
|
||||||
FROM shopping_cart_items
|
FROM shopping_cart_items
|
||||||
INNER JOIN shopping_carts ON shopping_cart_items.shopping_cart_id = shopping_carts.id
|
INNER JOIN shopping_carts
|
||||||
WHERE product_id = $1 AND shopping_carts.state = $2
|
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(msg.product_id)
|
||||||
.bind(model::ShoppingCartState::Active)
|
.bind(model::ShoppingCartState::Active)
|
||||||
.fetch_optional(&db)
|
.fetch_optional(t)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
log::error!("{e:?}");
|
log::error!("{e:?}");
|
||||||
@ -305,20 +323,25 @@ db_async_handler!(
|
|||||||
inner_cart_items
|
inner_cart_items
|
||||||
);
|
);
|
||||||
|
|
||||||
pub(crate) async fn cart_items<'e, E>(msg: CartItems, pool: E) -> Result<Vec<ShoppingCartItem>>
|
pub(crate) async fn cart_items(
|
||||||
where
|
msg: CartItems,
|
||||||
E: sqlx::Executor<'e, Database = sqlx::Postgres>,
|
t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||||
{
|
) -> Result<Vec<ShoppingCartItem>> {
|
||||||
let shopping_cart_id = msg.shopping_cart_id;
|
let shopping_cart_id = msg.shopping_cart_id;
|
||||||
sqlx::query_as(
|
sqlx::query_as(
|
||||||
r#"
|
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
|
FROM shopping_cart_items
|
||||||
WHERE shopping_cart_id = $1
|
WHERE shopping_cart_id = $1
|
||||||
|
ORDER BY shopping_cart_items.id ASC
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
.bind(msg.shopping_cart_id)
|
.bind(msg.shopping_cart_id)
|
||||||
.fetch_all(pool)
|
.fetch_all(t)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
log::error!("{e:?}");
|
log::error!("{e:?}");
|
||||||
@ -334,11 +357,16 @@ pub struct RemoveCartItem {
|
|||||||
pub product_id: Option<ProductId>,
|
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(
|
pub(crate) async fn remove_cart_item(
|
||||||
msg: RemoveCartItem,
|
msg: RemoveCartItem,
|
||||||
pool: PgPool,
|
t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||||
) -> Result<Option<ShoppingCartItem>> {
|
) -> Result<Option<ShoppingCartItem>> {
|
||||||
match (msg.shopping_cart_item_id, msg.product_id) {
|
match (msg.shopping_cart_item_id, msg.product_id) {
|
||||||
(Some(shopping_cart_item_id), None) => sqlx::query_as(
|
(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),
|
.bind(product_id),
|
||||||
_ => return Err(crate::Error::ShoppingCartItem(Error::NoIdentity)),
|
_ => return Err(crate::Error::ShoppingCartItem(Error::NoIdentity)),
|
||||||
}
|
}
|
||||||
.fetch_optional(&pool)
|
.fetch_optional(t)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
log::error!("{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,
|
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(
|
pub(crate) async fn create_shopping_cart(
|
||||||
msg: CreateShoppingCart,
|
msg: CreateShoppingCart,
|
||||||
db: PgPool,
|
pool: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||||
) -> Result<ShoppingCart> {
|
) -> Result<ShoppingCart> {
|
||||||
sqlx::query_as(
|
sqlx::query_as(
|
||||||
r#"
|
r#"
|
||||||
@ -109,10 +114,11 @@ RETURNING id, buyer_id, payment_method, state, checkout_notes
|
|||||||
)
|
)
|
||||||
.bind(msg.buyer_id)
|
.bind(msg.buyer_id)
|
||||||
.bind(msg.payment_method)
|
.bind(msg.payment_method)
|
||||||
.fetch_one(&db)
|
.fetch_one(pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
log::error!("{e:?}");
|
log::error!("{e:?}");
|
||||||
|
dbg!(e);
|
||||||
super::Error::ShoppingCart(Error::CantCreate)
|
super::Error::ShoppingCart(Error::CantCreate)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -127,11 +133,16 @@ pub struct UpdateShoppingCart {
|
|||||||
pub checkout_notes: Option<String>,
|
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(
|
pub(crate) async fn update_shopping_cart(
|
||||||
msg: UpdateShoppingCart,
|
msg: UpdateShoppingCart,
|
||||||
db: PgPool,
|
pool: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||||
) -> Result<ShoppingCart> {
|
) -> Result<ShoppingCart> {
|
||||||
sqlx::query_as(
|
sqlx::query_as(
|
||||||
r#"
|
r#"
|
||||||
@ -146,7 +157,7 @@ RETURNING id, buyer_id, payment_method, state, checkout_notes
|
|||||||
.bind(msg.payment_method)
|
.bind(msg.payment_method)
|
||||||
.bind(msg.state)
|
.bind(msg.state)
|
||||||
.bind(msg.checkout_notes)
|
.bind(msg.checkout_notes)
|
||||||
.fetch_one(&db)
|
.fetch_one(pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
log::error!("{e:?}");
|
log::error!("{e:?}");
|
||||||
@ -198,9 +209,17 @@ pub struct FindShoppingCart {
|
|||||||
pub id: ShoppingCartId,
|
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(
|
sqlx::query_as(
|
||||||
r#"
|
r#"
|
||||||
SELECT id, buyer_id, payment_method, state, checkout_notes
|
SELECT id, buyer_id, payment_method, state, checkout_notes
|
||||||
@ -209,7 +228,7 @@ WHERE id = $1
|
|||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
.bind(msg.id)
|
.bind(msg.id)
|
||||||
.fetch_one(&db)
|
.fetch_one(pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
log::error!("{e:?}");
|
log::error!("{e:?}");
|
||||||
@ -267,3 +286,177 @@ WHERE buyer_id = $1 AND state = 'active'
|
|||||||
super::Error::ShoppingCart(Error::NotExists)
|
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,
|
Delete,
|
||||||
#[error("Unable find stock for product")]
|
#[error("Unable find stock for product")]
|
||||||
ProductStock,
|
ProductStock,
|
||||||
|
#[error("Stock does not exists")]
|
||||||
|
NotFound,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Message)]
|
#[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)]
|
#[derive(Message)]
|
||||||
#[rtype(result = "Result<model::Stock>")]
|
#[rtype(result = "Result<model::Stock>")]
|
||||||
pub struct CreateStock {
|
pub struct CreateStock {
|
||||||
@ -70,7 +101,7 @@ RETURNING id, product_id, quantity, quantity_unit
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
log::error!("{e:?}");
|
log::error!("{e:?}");
|
||||||
eprintln!("{e:?}");
|
dbg!(e);
|
||||||
crate::Error::Stock(Error::Create)
|
crate::Error::Stock(Error::Create)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -93,8 +124,8 @@ async fn update_stock(
|
|||||||
sqlx::query_as(
|
sqlx::query_as(
|
||||||
r#"
|
r#"
|
||||||
UPDATE stocks
|
UPDATE stocks
|
||||||
SET product_id = $1 AND
|
SET product_id = $1,
|
||||||
quantity = $2
|
quantity = $2,
|
||||||
quantity_unit = $3
|
quantity_unit = $3
|
||||||
WHERE id = $4
|
WHERE id = $4
|
||||||
RETURNING id, product_id, quantity, quantity_unit
|
RETURNING id, product_id, quantity, quantity_unit
|
||||||
@ -165,9 +196,9 @@ async fn product_stock(
|
|||||||
Ok(MultiLoad::new(
|
Ok(MultiLoad::new(
|
||||||
pool,
|
pool,
|
||||||
r#"
|
r#"
|
||||||
SELECT id, product_id, quantity, quantity_unit
|
SELECT id, product_id, quantity, quantity_unit
|
||||||
FROM stocks
|
FROM stocks
|
||||||
WHERE
|
WHERE
|
||||||
"#,
|
"#,
|
||||||
" product_id =",
|
" product_id =",
|
||||||
)
|
)
|
||||||
@ -291,10 +322,49 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
// let reloaded = super::Stock
|
let reloaded = super::find_stock(FindStock { id: second.id }, &mut t).await;
|
||||||
|
|
||||||
testx::db_rollback!(t);
|
testx::db_rollback!(t);
|
||||||
assert_eq!(deleted, Some(second));
|
assert_eq!(deleted, Some(second));
|
||||||
assert_ne!(deleted, Some(first));
|
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)
|
.drain_filter(|photo| photo.product_id == id)
|
||||||
.map(
|
.map(
|
||||||
|ProductLinkedPhoto {
|
|ProductLinkedPhoto {
|
||||||
id,
|
photo_id: id,
|
||||||
local_path: _,
|
local_path: _,
|
||||||
file_name,
|
file_name,
|
||||||
product_id: _,
|
product_id: _,
|
||||||
|
@ -224,7 +224,7 @@ impl Default for PaymentMethod {
|
|||||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||||
#[cfg_attr(feature = "db", sqlx(rename_all = "snake_case"))]
|
#[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")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum ShoppingCartState {
|
pub enum ShoppingCartState {
|
||||||
Active,
|
Active,
|
||||||
@ -279,7 +279,9 @@ impl Default for Audience {
|
|||||||
|
|
||||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
#[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)]
|
#[serde(transparent)]
|
||||||
pub struct Price(NonNegative);
|
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", derive(sqlx::Type))]
|
||||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
#[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)]
|
#[serde(transparent)]
|
||||||
pub struct NonNegative(i32);
|
pub struct NonNegative(i32);
|
||||||
|
|
||||||
@ -533,7 +535,9 @@ impl<'de> serde::Deserialize<'de> for NonNegative {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
#[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")]
|
#[serde(rename_all = "lowercase")]
|
||||||
pub enum Day {
|
pub enum Day {
|
||||||
Monday = 1 << 0,
|
Monday = 1 << 0,
|
||||||
@ -595,7 +599,7 @@ impl TryFrom<i32> for Day {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||||
#[derive(Serialize, Deserialize, Hash, Debug)]
|
#[derive(Hash, Debug, PartialOrd, PartialEq, Serialize, Deserialize)]
|
||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
pub struct Days(pub Vec<Day>);
|
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 = "dummy", derive(fake::Dummy))]
|
||||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
#[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)]
|
#[serde(transparent)]
|
||||||
pub struct ProductId(RecordId);
|
pub struct ProductId(RecordId);
|
||||||
|
|
||||||
@ -785,7 +789,7 @@ impl Display for ProductId {
|
|||||||
|
|
||||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
#[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)]
|
#[serde(transparent)]
|
||||||
pub struct ProductName(String);
|
pub struct ProductName(String);
|
||||||
|
|
||||||
@ -801,7 +805,7 @@ impl ProductName {
|
|||||||
|
|
||||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
#[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)]
|
#[serde(transparent)]
|
||||||
pub struct ProductShortDesc(String);
|
pub struct ProductShortDesc(String);
|
||||||
|
|
||||||
@ -817,7 +821,7 @@ impl ProductShortDesc {
|
|||||||
|
|
||||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
#[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)]
|
#[serde(transparent)]
|
||||||
pub struct ProductLongDesc(String);
|
pub struct ProductLongDesc(String);
|
||||||
|
|
||||||
@ -836,7 +840,7 @@ impl ProductLongDesc {
|
|||||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
#[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)]
|
#[serde(transparent)]
|
||||||
pub struct ProductCategory(String);
|
pub struct ProductCategory(String);
|
||||||
|
|
||||||
@ -848,7 +852,7 @@ impl ProductCategory {
|
|||||||
|
|
||||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||||
#[cfg_attr(feature = "db", derive(sqlx::FromRow))]
|
#[cfg_attr(feature = "db", derive(sqlx::FromRow))]
|
||||||
#[derive(Serialize, Deserialize, Debug, Hash)]
|
#[derive(Debug, Hash, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct Product {
|
pub struct Product {
|
||||||
pub id: ProductId,
|
pub id: ProductId,
|
||||||
pub name: ProductName,
|
pub name: ProductName,
|
||||||
@ -973,7 +977,7 @@ pub struct ShoppingCartId(pub RecordId);
|
|||||||
|
|
||||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||||
#[cfg_attr(feature = "db", derive(sqlx::FromRow))]
|
#[cfg_attr(feature = "db", derive(sqlx::FromRow))]
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct ShoppingCart {
|
pub struct ShoppingCart {
|
||||||
pub id: ShoppingCartId,
|
pub id: ShoppingCartId,
|
||||||
pub buyer_id: AccountId,
|
pub buyer_id: AccountId,
|
||||||
@ -985,12 +989,12 @@ pub struct ShoppingCart {
|
|||||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
#[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)]
|
#[serde(transparent)]
|
||||||
pub struct ShoppingCartItemId(RecordId);
|
pub struct ShoppingCartItemId(RecordId);
|
||||||
|
|
||||||
#[cfg_attr(feature = "db", derive(sqlx::FromRow))]
|
#[cfg_attr(feature = "db", derive(sqlx::FromRow))]
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct ShoppingCartItem {
|
pub struct ShoppingCartItem {
|
||||||
pub id: ShoppingCartItemId,
|
pub id: ShoppingCartItemId,
|
||||||
pub product_id: ProductId,
|
pub product_id: ProductId,
|
||||||
@ -1047,7 +1051,7 @@ impl Token {
|
|||||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
#[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);
|
pub struct AccessTokenString(String);
|
||||||
|
|
||||||
impl AccessTokenString {
|
impl AccessTokenString {
|
||||||
@ -1065,7 +1069,7 @@ impl From<RefreshTokenString> for AccessTokenString {
|
|||||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
#[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);
|
pub struct RefreshTokenString(String);
|
||||||
|
|
||||||
impl From<AccessTokenString> for RefreshTokenString {
|
impl From<AccessTokenString> for RefreshTokenString {
|
||||||
@ -1083,7 +1087,7 @@ impl RefreshTokenString {
|
|||||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
#[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);
|
pub struct LocalPath(String);
|
||||||
|
|
||||||
impl LocalPath {
|
impl LocalPath {
|
||||||
@ -1095,7 +1099,7 @@ impl LocalPath {
|
|||||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
#[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);
|
pub struct UniqueName(String);
|
||||||
|
|
||||||
impl UniqueName {
|
impl UniqueName {
|
||||||
@ -1107,7 +1111,7 @@ impl UniqueName {
|
|||||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
#[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);
|
pub struct FileName(String);
|
||||||
|
|
||||||
impl FileName {
|
impl FileName {
|
||||||
@ -1119,18 +1123,20 @@ impl FileName {
|
|||||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
#[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);
|
pub struct PhotoId(RecordId);
|
||||||
|
|
||||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
#[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);
|
pub struct ProductPhotoId(RecordId);
|
||||||
|
|
||||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||||
#[cfg_attr(feature = "db", derive(sqlx::FromRow))]
|
#[cfg_attr(feature = "db", derive(sqlx::FromRow))]
|
||||||
#[derive(Serialize, Deserialize, Debug, Hash)]
|
#[derive(Debug, Hash, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct Photo {
|
pub struct Photo {
|
||||||
pub id: PhotoId,
|
pub id: PhotoId,
|
||||||
pub local_path: LocalPath,
|
pub local_path: LocalPath,
|
||||||
@ -1140,9 +1146,9 @@ pub struct Photo {
|
|||||||
|
|
||||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||||
#[cfg_attr(feature = "db", derive(sqlx::FromRow))]
|
#[cfg_attr(feature = "db", derive(sqlx::FromRow))]
|
||||||
#[derive(Serialize, Deserialize, Debug, Hash)]
|
#[derive(Debug, Hash, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct ProductLinkedPhoto {
|
pub struct ProductLinkedPhoto {
|
||||||
pub id: PhotoId,
|
pub photo_id: PhotoId,
|
||||||
pub local_path: LocalPath,
|
pub local_path: LocalPath,
|
||||||
pub file_name: FileName,
|
pub file_name: FileName,
|
||||||
pub unique_name: UniqueName,
|
pub unique_name: UniqueName,
|
||||||
@ -1151,7 +1157,7 @@ pub struct ProductLinkedPhoto {
|
|||||||
|
|
||||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||||
#[cfg_attr(feature = "db", derive(sqlx::FromRow))]
|
#[cfg_attr(feature = "db", derive(sqlx::FromRow))]
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct ProductPhoto {
|
pub struct ProductPhoto {
|
||||||
pub id: ProductPhotoId,
|
pub id: ProductPhotoId,
|
||||||
pub product_id: ProductId,
|
pub product_id: ProductId,
|
||||||
|
Loading…
Reference in New Issue
Block a user