bazzar/actors/database_manager/src/stocks.rs

279 lines
6.8 KiB
Rust
Raw Normal View History

2022-04-18 22:07:52 +02:00
use actix::Message;
2022-05-06 11:47:18 +02:00
use model::{ProductId, Quantity, QuantityUnit, Stock, StockId};
2022-05-12 20:33:16 +02:00
use crate::{MultiLoad, Result};
2022-06-04 16:55:29 +02:00
#[derive(Debug, Copy, Clone, PartialEq, serde::Serialize, thiserror::Error)]
pub enum Error {
#[error("Unable to load all stocks")]
All,
#[error("Unable to create stock")]
Create,
#[error("Unable to update stock")]
Update,
#[error("Unable to delete stock")]
Delete,
2022-05-12 20:33:16 +02:00
#[error("Unable find stock for product")]
ProductStock,
}
#[derive(Message)]
#[rtype(result = "Result<Vec<model::Stock>>")]
pub struct AllStocks;
crate::db_async_handler!(AllStocks, all_stocks, Vec<Stock>, inner_all_stocks);
async fn all_stocks(
_msg: AllStocks,
pool: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<Vec<Stock>> {
sqlx::query_as(
r#"
SELECT id, product_id, quantity, quantity_unit
FROM stocks
"#,
)
.fetch_all(pool)
.await
.map_err(|e| {
log::error!("{e:?}");
2022-05-06 11:47:18 +02:00
crate::Error::Stock(Error::All)
})
}
#[derive(Message)]
#[rtype(result = "Result<model::Stock>")]
pub struct CreateStock {
pub product_id: ProductId,
pub quantity: Quantity,
pub quantity_unit: QuantityUnit,
}
crate::db_async_handler!(CreateStock, create_stock, Stock, inner_create_stock);
async fn create_stock(
msg: CreateStock,
pool: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<Stock> {
sqlx::query_as(
r#"
2022-04-19 16:49:30 +02:00
INSERT INTO stocks (product_id, quantity, quantity_unit)
VALUES ($1, $2, $3)
RETURNING id, product_id, quantity, quantity_unit
"#,
)
.bind(msg.product_id)
.bind(msg.quantity)
.bind(msg.quantity_unit)
.fetch_one(pool)
.await
.map_err(|e| {
log::error!("{e:?}");
eprintln!("{e:?}");
2022-05-06 11:47:18 +02:00
crate::Error::Stock(Error::Create)
})
}
#[derive(Message)]
#[rtype(result = "Result<model::Stock>")]
pub struct UpdateStock {
pub id: StockId,
pub product_id: ProductId,
pub quantity: Quantity,
pub quantity_unit: QuantityUnit,
}
crate::db_async_handler!(UpdateStock, update_stock, Stock, inner_update_stock);
async fn update_stock(
msg: UpdateStock,
pool: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<Stock> {
sqlx::query_as(
r#"
UPDATE stocks
SET product_id = $1 AND
quantity = $2
quantity_unit = $3
WHERE id = $4
RETURNING id, product_id, quantity, quantity_unit
"#,
)
.bind(msg.product_id)
.bind(msg.quantity)
.bind(msg.quantity_unit)
.bind(msg.id)
.fetch_one(pool)
.await
.map_err(|e| {
log::error!("{e:?}");
2022-05-06 11:47:18 +02:00
crate::Error::Stock(Error::Update)
})
}
#[derive(Message)]
#[rtype(result = "Result<Option<model::Stock>>")]
pub struct DeleteStock {
pub stock_id: StockId,
}
crate::db_async_handler!(
DeleteStock,
delete_stock,
Option<model::Stock>,
inner_delete_stock
);
async fn delete_stock(
msg: DeleteStock,
pool: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<Option<Stock>> {
sqlx::query_as(
r#"
DELETE FROM stocks
WHERE id = $1
RETURNING id, product_id, quantity, quantity_unit
"#,
)
.bind(msg.stock_id)
.fetch_optional(pool)
.await
.map_err(|e| {
log::error!("{e:?}");
2022-05-06 11:47:18 +02:00
crate::Error::Stock(Error::Delete)
})
}
2022-05-12 20:33:16 +02:00
#[derive(Message)]
#[rtype(result = "Result<Vec<model::Stock>>")]
pub struct ProductsStock {
pub product_ids: Vec<ProductId>,
}
crate::db_async_handler!(
ProductsStock,
product_stock,
Vec<model::Stock>,
inner_product_stock
);
async fn product_stock(
msg: ProductsStock,
pool: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<Vec<Stock>> {
2022-05-12 20:33:16 +02:00
Ok(MultiLoad::new(
pool,
r#"
SELECT id, product_id, quantity, quantity_unit
FROM stocks
WHERE
"#,
" product_id =",
)
.load(
msg.product_ids.len(),
msg.product_ids.into_iter().map(|id| *id),
|_e| crate::Error::Stock(Error::ProductStock),
)
.await?)
}
#[cfg(test)]
mod tests {
use config::UpdateConfig;
use fake::faker::lorem::en as lorem;
use fake::Fake;
use model::*;
use uuid::Uuid;
pub struct NoOpts;
impl UpdateConfig for NoOpts {}
use crate::*;
async fn test_product(pool: &mut sqlx::Transaction<'_, sqlx::Postgres>) -> Product {
create_product(
CreateProduct {
name: ProductName::new(format!("db stocks test product {}", Uuid::new_v4())),
short_description: ProductShortDesc::new(lorem::Paragraph(1..2).fake::<String>()),
long_description: ProductLongDesc::new(lorem::Paragraph(4..5).fake::<String>()),
category: None,
price: Price::from_u32(12321),
deliver_days_flag: Days(vec![Day::Friday, Day::Sunday]),
},
pool,
)
.await
.unwrap()
}
async fn test_stock(
pool: &mut sqlx::Transaction<'_, sqlx::Postgres>,
product_id: Option<ProductId>,
quantity: Option<Quantity>,
quantity_unit: Option<QuantityUnit>,
) -> Stock {
let product_id = match product_id {
Some(id) => id,
_ => test_product(&mut *pool).await.id,
};
let quantity = quantity.unwrap_or_else(|| Quantity::from_u32(345));
let quantity_unit = quantity_unit.unwrap_or_else(|| QuantityUnit::Piece);
super::create_stock(
CreateStock {
product_id,
quantity_unit,
quantity,
},
&mut *pool,
)
.await
.unwrap()
}
#[actix::test]
async fn create_stock() {
let config = config::default_load(&mut NoOpts);
config
.lock()
.database_mut()
.set_url("postgres://postgres@localhost/bazzar_test");
let db = Database::build(config).await;
let pool = db.pool();
let mut t = pool.begin().await.unwrap();
test_stock(&mut t, None, None, None).await;
t.rollback().await.unwrap();
}
#[actix::test]
async fn products_stock() {
let config = config::default_load(&mut NoOpts);
config
.lock()
.database_mut()
.set_url("postgres://postgres@localhost/bazzar_test");
let db = Database::build(config).await;
let pool = db.pool();
let mut t = pool.begin().await.unwrap();
let first = test_stock(&mut t, None, None, None).await;
let second = test_stock(&mut t, None, None, None).await;
let stocks: Vec<Stock> = super::product_stock(
ProductsStock {
product_ids: vec![first.product_id, second.product_id],
},
&mut t,
)
.await
.unwrap();
t.rollback().await.unwrap();
assert_eq!(stocks, vec![first, second]);
}
}