Logic for add item

This commit is contained in:
eraden 2022-04-16 18:57:37 +02:00
parent 5d757e84d5
commit 39d65f5e12
15 changed files with 260 additions and 86 deletions

15
Cargo.lock generated
View File

@ -963,6 +963,8 @@ dependencies = [
"chrono",
"derive_more",
"dotenv",
"futures",
"futures-util",
"gumdrop",
"log",
"parking_lot 0.12.0",
@ -974,6 +976,7 @@ dependencies = [
"sqlx",
"tera",
"thiserror",
"tokio 1.17.0",
"toml",
"tracing",
"uuid",
@ -3636,9 +3639,21 @@ dependencies = [
"pin-project-lite 0.2.8",
"signal-hook-registry",
"socket2 0.4.4",
"tokio-macros",
"winapi 0.3.9",
]
[[package]]
name = "tokio-macros"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tokio-rustls"
version = "0.22.0"

View File

@ -47,3 +47,7 @@ parking_lot = { version = "0.12.0" }
password-hash = { version = "0.4.0", features = ["alloc"] }
argon2 = { version = "0.4.0", features = ["parallel", "password-hash"] }
rand_core = { version = "0.6", features = ["std"] }
tokio = { version = "1.17.0", features = ["full"] }
futures = { version = "0.3.21" }
futures-util = { version = "0.3.21" }

View File

@ -0,0 +1,82 @@
use crate::database::Database;
use crate::model::{
AccountId, ProductId, Quantity, QuantityUnit, ShoppingCartItem, ShoppingCartState,
};
use crate::{cart_async_handler, database};
use actix::{Actor, Addr, Context, Handler, Message, ResponseActFuture, WrapFuture};
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("System can't ensure shopping cart existence")]
ShoppingCartFailed,
#[error("Shopping cart is not available for unknown reason")]
CartNotAvailable,
#[error("Failed to add item to cart")]
CantAddItem,
#[error("{0}")]
Db(#[from] database::Error),
}
pub type Result<T> = std::result::Result<T, Error>;
pub struct CartManager {
db: Addr<Database>,
}
impl Actor for CartManager {
type Context = Context<Self>;
}
impl CartManager {
pub fn new(db: Addr<Database>) -> Self {
Self { db }
}
}
#[derive(Message)]
#[rtype(result = "Result<ShoppingCartItem>")]
pub struct AddItem {
pub buyer_id: AccountId,
pub product_id: ProductId,
pub quantity: Quantity,
pub quantity_unit: QuantityUnit,
}
cart_async_handler!(AddItem, add_item, ShoppingCartItem);
async fn add_item(msg: AddItem, db: Addr<Database>) -> Result<ShoppingCartItem> {
match db.send(database::EnsureActiveShoppingCart { buyer_id: msg.buyer_id }).await {
Ok(Ok(_)) => {}
_ => return Err(Error::ShoppingCartFailed),
};
let cart = match db
.send(database::AccountShoppingCarts {
account_id: msg.buyer_id,
state: Some(ShoppingCartState::Active),
})
.await
.map(|res| match res {
Ok(mut v) if !v.is_empty() => Ok(v.remove(0)),
Err(e) => return Err(Error::Db(e)),
_ => Err(Error::CartNotAvailable),
}) {
Ok(Ok(cart)) => cart,
Ok(Err(e)) => {
log::error!("{e:?}");
return Err(e);
}
_ => return Err(Error::CartNotAvailable),
};
match db
.send(database::CreateShoppingCartItem {
product_id: msg.product_id,
shopping_cart_id: cart.id,
quantity: msg.quantity,
quantity_unit: msg.quantity_unit,
})
.await
{
Ok(res) => res.map_err(Into::into),
_ => Err(Error::CantAddItem),
}
}

View File

@ -9,27 +9,13 @@ pub use shopping_cart_items::*;
pub use shopping_carts::*;
pub use stocks::*;
mod account_orders;
mod accounts;
mod order_items;
mod products;
mod shopping_cart_items;
mod shopping_carts;
mod stocks;
#[macro_export]
macro_rules! async_handler {
($msg: ty, $async: ident, $res: ty) => {
impl Handler<$msg> for Database {
type Result = ResponseActFuture<Self, Result<$res>>;
fn handle(&mut self, msg: $msg, _ctx: &mut Self::Context) -> Self::Result {
let pool = self.pool.clone();
Box::pin(async { $async(msg, pool).await }.into_actor(self))
}
}
};
}
pub mod account_orders;
pub mod accounts;
pub mod order_items;
pub mod products;
pub mod shopping_cart_items;
pub mod shopping_carts;
pub mod stocks;
#[derive(Debug, thiserror::Error)]
pub enum Error {

View File

@ -1,4 +1,4 @@
use crate::async_handler;
use crate::db_async_handler;
use actix::{Handler, ResponseActFuture, WrapFuture};
use sqlx::PgPool;
@ -23,9 +23,12 @@ pub enum Error {
#[rtype(result = "Result<Vec<AccountOrder>>")]
pub struct AllAccountOrders;
async_handler!(AllAccountOrders, all_account_orders, Vec<AccountOrder>);
db_async_handler!(AllAccountOrders, all_account_orders, Vec<AccountOrder>);
pub async fn all_account_orders(_msg: AllAccountOrders, pool: PgPool) -> Result<Vec<AccountOrder>> {
pub(crate) async fn all_account_orders(
_msg: AllAccountOrders,
pool: PgPool,
) -> Result<Vec<AccountOrder>> {
sqlx::query_as(
r#"
SELECT id, buyer_id, status
@ -47,9 +50,12 @@ pub struct CreateAccountOrder {
pub status: OrderStatus,
}
async_handler!(CreateAccountOrder, create_account_order, AccountOrder);
db_async_handler!(CreateAccountOrder, create_account_order, AccountOrder);
async fn create_account_order(msg: CreateAccountOrder, db: PgPool) -> Result<AccountOrder> {
pub(crate) async fn create_account_order(
msg: CreateAccountOrder,
db: PgPool,
) -> Result<AccountOrder> {
sqlx::query_as(
r#"
INSERT INTO account_orders (buyer_id, status)
@ -73,9 +79,9 @@ pub struct FindAccountOrder {
pub id: AccountOrderId,
}
async_handler!(FindAccountOrder, find_account_order, AccountOrder);
db_async_handler!(FindAccountOrder, find_account_order, AccountOrder);
async fn find_account_order(msg: FindAccountOrder, db: PgPool) -> Result<AccountOrder> {
pub(crate) async fn find_account_order(msg: FindAccountOrder, db: PgPool) -> Result<AccountOrder> {
sqlx::query_as(
r#"
SELECT id, buyer_id, status

View File

@ -1,4 +1,4 @@
use crate::async_handler;
use crate::db_async_handler;
use actix::{Handler, ResponseActFuture, WrapFuture};
use sqlx::PgPool;
@ -23,9 +23,9 @@ pub enum Error {
#[rtype(result = "Result<Vec<FullAccount>>")]
pub struct AllAccounts;
async_handler!(AllAccounts, all_accounts, Vec<FullAccount>);
db_async_handler!(AllAccounts, all_accounts, Vec<FullAccount>);
pub async fn all_accounts(_msg: AllAccounts, pool: PgPool) -> Result<Vec<FullAccount>> {
pub(crate) async fn all_accounts(_msg: AllAccounts, pool: PgPool) -> Result<Vec<FullAccount>> {
sqlx::query_as(
r#"
SELECT id, email, login, pass_hash, role
@ -49,9 +49,9 @@ pub struct CreateAccount {
pub role: Role,
}
async_handler!(CreateAccount, create_account, FullAccount);
db_async_handler!(CreateAccount, create_account, FullAccount);
async fn create_account(msg: CreateAccount, db: PgPool) -> Result<FullAccount> {
pub(crate) async fn create_account(msg: CreateAccount, db: PgPool) -> Result<FullAccount> {
sqlx::query_as(
r#"
INSERT INTO accounts (login, email, role, pass_hash)
@ -77,9 +77,9 @@ pub struct FindAccount {
pub account_id: AccountId,
}
async_handler!(FindAccount, find_account, FullAccount);
db_async_handler!(FindAccount, find_account, FullAccount);
async fn find_account(msg: FindAccount, db: PgPool) -> Result<FullAccount> {
pub(crate) async fn find_account(msg: FindAccount, db: PgPool) -> Result<FullAccount> {
sqlx::query_as(
r#"
SELECT id, email, login, pass_hash, role
@ -103,9 +103,9 @@ pub struct AccountByIdentity {
pub email: Option<Email>,
}
async_handler!(AccountByIdentity, account_by_identity, FullAccount);
db_async_handler!(AccountByIdentity, account_by_identity, FullAccount);
async fn account_by_identity(msg: AccountByIdentity, db: PgPool) -> Result<FullAccount> {
pub(crate) async fn account_by_identity(msg: AccountByIdentity, db: PgPool) -> Result<FullAccount> {
match (msg.login, msg.email) {
(Some(login), None) => sqlx::query_as(
r#"

View File

@ -1,4 +1,4 @@
use crate::async_handler;
use crate::db_async_handler;
use actix::{Handler, ResponseActFuture, WrapFuture};
use sqlx::PgPool;
@ -23,9 +23,9 @@ pub enum Error {
#[rtype(result = "Result<Vec<OrderItem>>")]
pub struct AllOrderItems;
async_handler!(AllOrderItems, all_order_items, Vec<OrderItem>);
db_async_handler!(AllOrderItems, all_order_items, Vec<OrderItem>);
pub async fn all_order_items(_msg: AllOrderItems, pool: PgPool) -> Result<Vec<OrderItem>> {
pub(crate) async fn all_order_items(_msg: AllOrderItems, pool: PgPool) -> Result<Vec<OrderItem>> {
sqlx::query_as(
r#"
SELECT id, buyer_id, status
@ -47,9 +47,9 @@ pub struct CreateOrderItem {
pub status: OrderStatus,
}
async_handler!(CreateOrderItem, create_order_item, OrderItem);
db_async_handler!(CreateOrderItem, create_order_item, OrderItem);
async fn create_order_item(msg: CreateOrderItem, db: PgPool) -> Result<OrderItem> {
pub(crate) async fn create_order_item(msg: CreateOrderItem, db: PgPool) -> Result<OrderItem> {
sqlx::query_as(
r#"
INSERT INTO order_items (buyer_id, status)
@ -73,9 +73,9 @@ pub struct FindOrderItem {
pub id: OrderItemId,
}
async_handler!(FindOrderItem, find_order_item, OrderItem);
db_async_handler!(FindOrderItem, find_order_item, OrderItem);
async fn find_order_item(msg: FindOrderItem, db: PgPool) -> Result<OrderItem> {
pub(crate) async fn find_order_item(msg: FindOrderItem, db: PgPool) -> Result<OrderItem> {
sqlx::query_as(
r#"
SELECT id, buyer_id, status

View File

@ -25,9 +25,9 @@ pub enum Error {
#[rtype(result = "Result<Vec<model::Product>>")]
pub struct AllProducts;
crate::async_handler!(AllProducts, all, Vec<Product>);
crate::db_async_handler!(AllProducts, all, Vec<Product>);
async fn all(_msg: AllProducts, pool: PgPool) -> Result<Vec<model::Product>> {
pub(crate) async fn all(_msg: AllProducts, pool: PgPool) -> Result<Vec<model::Product>> {
sqlx::query_as(
r#"
SELECT id,
@ -59,9 +59,9 @@ pub struct CreateProduct {
pub price_minor: PriceMinor,
}
crate::async_handler!(CreateProduct, create_product, Product);
crate::db_async_handler!(CreateProduct, create_product, Product);
async fn create_product(msg: CreateProduct, pool: PgPool) -> Result<model::Product> {
pub(crate) async fn create_product(msg: CreateProduct, pool: PgPool) -> Result<model::Product> {
sqlx::query_as(
r#"
INSERT INTO products (name, short_description, long_description, category, price_major, price_minor)
@ -101,9 +101,9 @@ pub struct UpdateProduct {
pub price_minor: PriceMinor,
}
crate::async_handler!(UpdateProduct, update_product, Product);
crate::db_async_handler!(UpdateProduct, update_product, Product);
async fn update_product(msg: UpdateProduct, pool: PgPool) -> Result<model::Product> {
pub(crate) async fn update_product(msg: UpdateProduct, pool: PgPool) -> Result<model::Product> {
sqlx::query_as(
r#"
UPDATE products
@ -144,9 +144,9 @@ pub struct DeleteProduct {
pub product_id: ProductId,
}
crate::async_handler!(DeleteProduct, delete_product, Option<model::Product>);
crate::db_async_handler!(DeleteProduct, delete_product, Option<model::Product>);
async fn delete_product(msg: DeleteProduct, pool: PgPool) -> Result<Option<Product>> {
pub(crate) async fn delete_product(msg: DeleteProduct, pool: PgPool) -> Result<Option<Product>> {
sqlx::query_as(
r#"
DELETE FROM products

View File

@ -1,4 +1,4 @@
use crate::async_handler;
use crate::db_async_handler;
use actix::{Handler, ResponseActFuture, WrapFuture};
use sqlx::PgPool;
@ -27,9 +27,9 @@ pub enum Error {
#[rtype(result = "Result<Vec<ShoppingCartItem>>")]
pub struct AllShoppingCartItems;
async_handler!(AllShoppingCartItems, all_shopping_cart_items, Vec<ShoppingCartItem>);
db_async_handler!(AllShoppingCartItems, all_shopping_cart_items, Vec<ShoppingCartItem>);
pub async fn all_shopping_cart_items(
pub(crate) async fn all_shopping_cart_items(
_msg: AllShoppingCartItems,
pool: PgPool,
) -> Result<Vec<ShoppingCartItem>> {
@ -53,9 +53,9 @@ pub struct AccountShoppingCartItems {
pub account_id: AccountId,
}
async_handler!(AccountShoppingCartItems, account_shopping_cart_items, Vec<ShoppingCartItem>);
db_async_handler!(AccountShoppingCartItems, account_shopping_cart_items, Vec<ShoppingCartItem>);
pub async fn account_shopping_cart_items(
pub(crate) async fn account_shopping_cart_items(
msg: AccountShoppingCartItems,
pool: PgPool,
) -> Result<Vec<ShoppingCartItem>> {
@ -84,9 +84,9 @@ pub struct CreateShoppingCartItem {
pub quantity_unit: QuantityUnit,
}
async_handler!(CreateShoppingCartItem, create_shopping_cart_item, ShoppingCartItem);
db_async_handler!(CreateShoppingCartItem, create_shopping_cart_item, ShoppingCartItem);
async fn create_shopping_cart_item(
pub(crate) async fn create_shopping_cart_item(
msg: CreateShoppingCartItem,
db: PgPool,
) -> Result<ShoppingCartItem> {
@ -119,9 +119,9 @@ pub struct UpdateShoppingCartItem {
pub quantity_unit: QuantityUnit,
}
async_handler!(UpdateShoppingCartItem, update_shopping_cart_item, ShoppingCartItem);
db_async_handler!(UpdateShoppingCartItem, update_shopping_cart_item, ShoppingCartItem);
async fn update_shopping_cart_item(
pub(crate) async fn update_shopping_cart_item(
msg: UpdateShoppingCartItem,
db: PgPool,
) -> Result<ShoppingCartItem> {
@ -152,9 +152,9 @@ pub struct FindShoppingCartItem {
pub id: ShoppingCartItemId,
}
async_handler!(FindShoppingCartItem, find_shopping_cart_item, ShoppingCartItem);
db_async_handler!(FindShoppingCartItem, find_shopping_cart_item, ShoppingCartItem);
async fn find_shopping_cart_item(
pub(crate) async fn find_shopping_cart_item(
msg: FindShoppingCartItem,
db: PgPool,
) -> Result<ShoppingCartItem> {
@ -180,9 +180,9 @@ pub struct CartItems {
pub shopping_cart_id: ShoppingCartId,
}
async_handler!(CartItems, cart_items, Vec<ShoppingCartItem>);
db_async_handler!(CartItems, cart_items, Vec<ShoppingCartItem>);
async fn cart_items(msg: CartItems, pool: PgPool) -> Result<Vec<ShoppingCartItem>> {
pub(crate) async fn cart_items(msg: CartItems, pool: PgPool) -> Result<Vec<ShoppingCartItem>> {
let shopping_cart_id = msg.shopping_cart_id;
sqlx::query_as(
r#"

View File

@ -1,4 +1,4 @@
use crate::async_handler;
use crate::db_async_handler;
use actix::{Handler, ResponseActFuture, WrapFuture};
use sqlx::PgPool;
@ -25,9 +25,12 @@ pub enum Error {
#[rtype(result = "Result<Vec<ShoppingCart>>")]
pub struct AllShoppingCarts;
async_handler!(AllShoppingCarts, all_shopping_carts, Vec<ShoppingCart>);
db_async_handler!(AllShoppingCarts, all_shopping_carts, Vec<ShoppingCart>);
pub async fn all_shopping_carts(_msg: AllShoppingCarts, pool: PgPool) -> Result<Vec<ShoppingCart>> {
pub(crate) async fn all_shopping_carts(
_msg: AllShoppingCarts,
pool: PgPool,
) -> Result<Vec<ShoppingCart>> {
sqlx::query_as(
r#"
SELECT id, buyer_id, payment_method, state
@ -46,14 +49,26 @@ FROM shopping_carts
#[rtype(result = "Result<Vec<ShoppingCart>>")]
pub struct AccountShoppingCarts {
pub account_id: AccountId,
pub state: Option<ShoppingCartState>,
}
async_handler!(AccountShoppingCarts, account_shopping_carts, Vec<ShoppingCart>);
db_async_handler!(AccountShoppingCarts, account_shopping_carts, Vec<ShoppingCart>);
pub async fn account_shopping_carts(
pub(crate) async fn account_shopping_carts(
msg: AccountShoppingCarts,
pool: PgPool,
) -> Result<Vec<ShoppingCart>> {
if let Some(state) = msg.state {
sqlx::query_as(
r#"
SELECT id, buyer_id, payment_method, state
FROM shopping_carts
WHERE buyer_id = $1 AND state = $2
"#,
)
.bind(msg.account_id)
.bind(state)
} else {
sqlx::query_as(
r#"
SELECT id, buyer_id, payment_method, state
@ -62,6 +77,7 @@ WHERE buyer_id = $1
"#,
)
.bind(msg.account_id)
}
.fetch_all(&pool)
.await
.map_err(|e| {
@ -77,9 +93,12 @@ pub struct CreateShoppingCart {
pub payment_method: PaymentMethod,
}
async_handler!(CreateShoppingCart, create_shopping_cart, ShoppingCart);
db_async_handler!(CreateShoppingCart, create_shopping_cart, ShoppingCart);
async fn create_shopping_cart(msg: CreateShoppingCart, db: PgPool) -> Result<ShoppingCart> {
pub(crate) async fn create_shopping_cart(
msg: CreateShoppingCart,
db: PgPool,
) -> Result<ShoppingCart> {
sqlx::query_as(
r#"
INSERT INTO shopping_carts (buyer_id, payment_method)
@ -106,9 +125,12 @@ pub struct UpdateShoppingCart {
pub state: ShoppingCartState,
}
async_handler!(UpdateShoppingCart, update_shopping_cart, ShoppingCart);
db_async_handler!(UpdateShoppingCart, update_shopping_cart, ShoppingCart);
async fn update_shopping_cart(msg: UpdateShoppingCart, db: PgPool) -> Result<ShoppingCart> {
pub(crate) async fn update_shopping_cart(
msg: UpdateShoppingCart,
db: PgPool,
) -> Result<ShoppingCart> {
sqlx::query_as(
r#"
UPDATE shopping_carts
@ -135,9 +157,9 @@ pub struct FindShoppingCart {
pub id: ShoppingCartId,
}
async_handler!(FindShoppingCart, find_shopping_cart, ShoppingCart);
db_async_handler!(FindShoppingCart, find_shopping_cart, ShoppingCart);
async fn find_shopping_cart(msg: FindShoppingCart, db: PgPool) -> Result<ShoppingCart> {
pub(crate) async fn find_shopping_cart(msg: FindShoppingCart, db: PgPool) -> Result<ShoppingCart> {
sqlx::query_as(
r#"
SELECT id, buyer_id, payment_method, state
@ -153,3 +175,33 @@ WHERE id = $1
super::Error::ShoppingCart(Error::NotExists)
})
}
#[derive(actix::Message)]
#[rtype(result = "Result<Option<ShoppingCart>>")]
pub struct EnsureActiveShoppingCart {
pub buyer_id: AccountId,
}
db_async_handler!(EnsureActiveShoppingCart, ensure_active_shopping_cart, Option<ShoppingCart>);
pub(crate) async fn ensure_active_shopping_cart(
msg: EnsureActiveShoppingCart,
pool: PgPool,
) -> Result<Option<ShoppingCart>> {
sqlx::query_as(
r#"
INSERT INTO shopping_carts (buyer_id, state)
VALUES ($1, 'active')
ON CONFLICT
DO NOTHING
RETURNING id, buyer_id, payment_method, state;
"#,
)
.bind(msg.buyer_id)
.fetch_optional(&pool)
.await
.map_err(|e| {
log::error!("{e:?}");
super::Error::ShoppingCart(Error::NotExists)
})
}

View File

@ -22,7 +22,7 @@ pub enum Error {
#[rtype(result = "Result<Vec<model::Stock>>")]
pub struct AllStocks;
crate::async_handler!(AllStocks, all_stocks, Vec<Stock>);
crate::db_async_handler!(AllStocks, all_stocks, Vec<Stock>);
async fn all_stocks(_msg: AllStocks, pool: PgPool) -> Result<Vec<model::Stock>> {
sqlx::query_as(
@ -47,7 +47,7 @@ pub struct CreateStock {
pub quantity_unit: QuantityUnit,
}
crate::async_handler!(CreateStock, create_stock, Stock);
crate::db_async_handler!(CreateStock, create_stock, Stock);
async fn create_stock(msg: CreateStock, pool: PgPool) -> Result<model::Stock> {
sqlx::query_as(
@ -77,7 +77,7 @@ pub struct UpdateStock {
pub quantity_unit: QuantityUnit,
}
crate::async_handler!(UpdateStock, update_stock, Stock);
crate::db_async_handler!(UpdateStock, update_stock, Stock);
async fn update_stock(msg: UpdateStock, pool: PgPool) -> Result<model::Stock> {
sqlx::query_as(
@ -108,7 +108,7 @@ pub struct DeleteStock {
pub stock_id: StockId,
}
crate::async_handler!(DeleteStock, delete_stock, Option<model::Stock>);
crate::db_async_handler!(DeleteStock, delete_stock, Option<model::Stock>);
async fn delete_stock(msg: DeleteStock, pool: PgPool) -> Result<Option<Stock>> {
sqlx::query_as(

View File

@ -1 +1,2 @@
pub mod cart_manager;
pub mod database;

View File

@ -18,6 +18,7 @@ pub mod actors;
pub mod logic;
pub mod model;
pub mod routes;
mod utils;
trait ResolveDbUrl {
fn own_db_url(&self) -> Option<String>;

View File

@ -198,10 +198,10 @@ impl PartialEq<PasswordConfirmation> for Password {
}
}
#[derive(sqlx::Type, Serialize, Deserialize, Deref)]
#[derive(sqlx::Type, Serialize, Deserialize, Copy, Clone, Deref, Display)]
#[sqlx(transparent)]
#[serde(transparent)]
pub struct AccountId(pub RecordId);
pub struct AccountId(RecordId);
#[derive(sqlx::FromRow, Serialize, Deserialize)]
pub struct FullAccount {

27
api/src/utils.rs Normal file
View File

@ -0,0 +1,27 @@
#[macro_export]
macro_rules! db_async_handler {
($msg: ty, $async: ident, $res: ty) => {
impl Handler<$msg> for Database {
type Result = ResponseActFuture<Self, Result<$res>>;
fn handle(&mut self, msg: $msg, _ctx: &mut Self::Context) -> Self::Result {
let pool = self.pool.clone();
Box::pin(async { $async(msg, pool).await }.into_actor(self))
}
}
};
}
#[macro_export]
macro_rules! cart_async_handler {
($msg: ty, $async: ident, $res: ty) => {
impl Handler<$msg> for CartManager {
type Result = ResponseActFuture<Self, Result<$res>>;
fn handle(&mut self, msg: $msg, _ctx: &mut Self::Context) -> Self::Result {
let db = self.db.clone();
Box::pin(async { $async(msg, db).await }.into_actor(self))
}
}
};
}