Additional tests

This commit is contained in:
Adrian Woźniak 2022-06-06 15:09:13 +02:00
parent a34f32dc15
commit 7c272d310f
No known key found for this signature in database
GPG Key ID: 0012845A89C7352B
12 changed files with 1050 additions and 106 deletions

View File

@ -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()
})
}

View File

@ -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()

View File

@ -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()
})
}

View File

@ -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
]
);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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,
}
);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -0,0 +1,2 @@
ALTER TABLE shopping_carts
DROP CONSTRAINT single_active_cart;

View File

@ -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: _,

View File

@ -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,