diff --git a/Cargo.lock b/Cargo.lock index 228830b..404b6a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/api/Cargo.toml b/api/Cargo.toml index a5ca5c0..12920a6 100644 --- a/api/Cargo.toml +++ b/api/Cargo.toml @@ -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" } diff --git a/api/src/actors/cart_manager.rs b/api/src/actors/cart_manager.rs new file mode 100644 index 0000000..d324eda --- /dev/null +++ b/api/src/actors/cart_manager.rs @@ -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 = std::result::Result; + +pub struct CartManager { + db: Addr, +} + +impl Actor for CartManager { + type Context = Context; +} + +impl CartManager { + pub fn new(db: Addr) -> Self { + Self { db } + } +} + +#[derive(Message)] +#[rtype(result = "Result")] +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) -> Result { + 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), + } +} diff --git a/api/src/actors/database.rs b/api/src/actors/database.rs index db994d2..bcfcd0e 100644 --- a/api/src/actors/database.rs +++ b/api/src/actors/database.rs @@ -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>; - - 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 { diff --git a/api/src/actors/database/account_orders.rs b/api/src/actors/database/account_orders.rs index 42c143f..7809f0f 100644 --- a/api/src/actors/database/account_orders.rs +++ b/api/src/actors/database/account_orders.rs @@ -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>")] pub struct AllAccountOrders; -async_handler!(AllAccountOrders, all_account_orders, Vec); +db_async_handler!(AllAccountOrders, all_account_orders, Vec); -pub async fn all_account_orders(_msg: AllAccountOrders, pool: PgPool) -> Result> { +pub(crate) async fn all_account_orders( + _msg: AllAccountOrders, + pool: PgPool, +) -> Result> { 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 { +pub(crate) async fn create_account_order( + msg: CreateAccountOrder, + db: PgPool, +) -> Result { 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 { +pub(crate) async fn find_account_order(msg: FindAccountOrder, db: PgPool) -> Result { sqlx::query_as( r#" SELECT id, buyer_id, status diff --git a/api/src/actors/database/accounts.rs b/api/src/actors/database/accounts.rs index 2578416..b0e0726 100644 --- a/api/src/actors/database/accounts.rs +++ b/api/src/actors/database/accounts.rs @@ -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>")] pub struct AllAccounts; -async_handler!(AllAccounts, all_accounts, Vec); +db_async_handler!(AllAccounts, all_accounts, Vec); -pub async fn all_accounts(_msg: AllAccounts, pool: PgPool) -> Result> { +pub(crate) async fn all_accounts(_msg: AllAccounts, pool: PgPool) -> Result> { 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 { +pub(crate) async fn create_account(msg: CreateAccount, db: PgPool) -> Result { 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 { +pub(crate) async fn find_account(msg: FindAccount, db: PgPool) -> Result { sqlx::query_as( r#" SELECT id, email, login, pass_hash, role @@ -103,9 +103,9 @@ pub struct AccountByIdentity { pub email: Option, } -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 { +pub(crate) async fn account_by_identity(msg: AccountByIdentity, db: PgPool) -> Result { match (msg.login, msg.email) { (Some(login), None) => sqlx::query_as( r#" diff --git a/api/src/actors/database/order_items.rs b/api/src/actors/database/order_items.rs index cf2a93a..f60d7b8 100644 --- a/api/src/actors/database/order_items.rs +++ b/api/src/actors/database/order_items.rs @@ -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>")] pub struct AllOrderItems; -async_handler!(AllOrderItems, all_order_items, Vec); +db_async_handler!(AllOrderItems, all_order_items, Vec); -pub async fn all_order_items(_msg: AllOrderItems, pool: PgPool) -> Result> { +pub(crate) async fn all_order_items(_msg: AllOrderItems, pool: PgPool) -> Result> { 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 { +pub(crate) async fn create_order_item(msg: CreateOrderItem, db: PgPool) -> Result { 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 { +pub(crate) async fn find_order_item(msg: FindOrderItem, db: PgPool) -> Result { sqlx::query_as( r#" SELECT id, buyer_id, status diff --git a/api/src/actors/database/products.rs b/api/src/actors/database/products.rs index 22e5aea..d192ca8 100644 --- a/api/src/actors/database/products.rs +++ b/api/src/actors/database/products.rs @@ -25,9 +25,9 @@ pub enum Error { #[rtype(result = "Result>")] pub struct AllProducts; -crate::async_handler!(AllProducts, all, Vec); +crate::db_async_handler!(AllProducts, all, Vec); -async fn all(_msg: AllProducts, pool: PgPool) -> Result> { +pub(crate) async fn all(_msg: AllProducts, pool: PgPool) -> Result> { 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 { +pub(crate) async fn create_product(msg: CreateProduct, pool: PgPool) -> Result { 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 { +pub(crate) async fn update_product(msg: UpdateProduct, pool: PgPool) -> Result { sqlx::query_as( r#" UPDATE products @@ -144,9 +144,9 @@ pub struct DeleteProduct { pub product_id: ProductId, } -crate::async_handler!(DeleteProduct, delete_product, Option); +crate::db_async_handler!(DeleteProduct, delete_product, Option); -async fn delete_product(msg: DeleteProduct, pool: PgPool) -> Result> { +pub(crate) async fn delete_product(msg: DeleteProduct, pool: PgPool) -> Result> { sqlx::query_as( r#" DELETE FROM products diff --git a/api/src/actors/database/shopping_cart_items.rs b/api/src/actors/database/shopping_cart_items.rs index 874cc8d..b66b00f 100644 --- a/api/src/actors/database/shopping_cart_items.rs +++ b/api/src/actors/database/shopping_cart_items.rs @@ -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>")] pub struct AllShoppingCartItems; -async_handler!(AllShoppingCartItems, all_shopping_cart_items, Vec); +db_async_handler!(AllShoppingCartItems, all_shopping_cart_items, Vec); -pub async fn all_shopping_cart_items( +pub(crate) async fn all_shopping_cart_items( _msg: AllShoppingCartItems, pool: PgPool, ) -> Result> { @@ -53,9 +53,9 @@ pub struct AccountShoppingCartItems { pub account_id: AccountId, } -async_handler!(AccountShoppingCartItems, account_shopping_cart_items, Vec); +db_async_handler!(AccountShoppingCartItems, account_shopping_cart_items, Vec); -pub async fn account_shopping_cart_items( +pub(crate) async fn account_shopping_cart_items( msg: AccountShoppingCartItems, pool: PgPool, ) -> Result> { @@ -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 { @@ -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 { @@ -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 { @@ -180,9 +180,9 @@ pub struct CartItems { pub shopping_cart_id: ShoppingCartId, } -async_handler!(CartItems, cart_items, Vec); +db_async_handler!(CartItems, cart_items, Vec); -async fn cart_items(msg: CartItems, pool: PgPool) -> Result> { +pub(crate) async fn cart_items(msg: CartItems, pool: PgPool) -> Result> { let shopping_cart_id = msg.shopping_cart_id; sqlx::query_as( r#" diff --git a/api/src/actors/database/shopping_carts.rs b/api/src/actors/database/shopping_carts.rs index af11388..1bbc716 100644 --- a/api/src/actors/database/shopping_carts.rs +++ b/api/src/actors/database/shopping_carts.rs @@ -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>")] pub struct AllShoppingCarts; -async_handler!(AllShoppingCarts, all_shopping_carts, Vec); +db_async_handler!(AllShoppingCarts, all_shopping_carts, Vec); -pub async fn all_shopping_carts(_msg: AllShoppingCarts, pool: PgPool) -> Result> { +pub(crate) async fn all_shopping_carts( + _msg: AllShoppingCarts, + pool: PgPool, +) -> Result> { sqlx::query_as( r#" SELECT id, buyer_id, payment_method, state @@ -46,22 +49,35 @@ FROM shopping_carts #[rtype(result = "Result>")] pub struct AccountShoppingCarts { pub account_id: AccountId, + pub state: Option, } -async_handler!(AccountShoppingCarts, account_shopping_carts, Vec); +db_async_handler!(AccountShoppingCarts, account_shopping_carts, Vec); -pub async fn account_shopping_carts( +pub(crate) async fn account_shopping_carts( msg: AccountShoppingCarts, pool: PgPool, ) -> Result> { - sqlx::query_as( - r#" + 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 FROM shopping_carts WHERE buyer_id = $1 "#, - ) - .bind(msg.account_id) + ) + .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 { +pub(crate) async fn create_shopping_cart( + msg: CreateShoppingCart, + db: PgPool, +) -> Result { 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 { +pub(crate) async fn update_shopping_cart( + msg: UpdateShoppingCart, + db: PgPool, +) -> Result { 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 { +pub(crate) async fn find_shopping_cart(msg: FindShoppingCart, db: PgPool) -> Result { 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>")] +pub struct EnsureActiveShoppingCart { + pub buyer_id: AccountId, +} + +db_async_handler!(EnsureActiveShoppingCart, ensure_active_shopping_cart, Option); + +pub(crate) async fn ensure_active_shopping_cart( + msg: EnsureActiveShoppingCart, + pool: PgPool, +) -> Result> { + 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) + }) +} diff --git a/api/src/actors/database/stocks.rs b/api/src/actors/database/stocks.rs index 0862d9b..5c24deb 100644 --- a/api/src/actors/database/stocks.rs +++ b/api/src/actors/database/stocks.rs @@ -22,7 +22,7 @@ pub enum Error { #[rtype(result = "Result>")] pub struct AllStocks; -crate::async_handler!(AllStocks, all_stocks, Vec); +crate::db_async_handler!(AllStocks, all_stocks, Vec); async fn all_stocks(_msg: AllStocks, pool: PgPool) -> Result> { 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 { 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 { sqlx::query_as( @@ -108,7 +108,7 @@ pub struct DeleteStock { pub stock_id: StockId, } -crate::async_handler!(DeleteStock, delete_stock, Option); +crate::db_async_handler!(DeleteStock, delete_stock, Option); async fn delete_stock(msg: DeleteStock, pool: PgPool) -> Result> { sqlx::query_as( diff --git a/api/src/actors/mod.rs b/api/src/actors/mod.rs index 8fd0a6b..cefa5ef 100644 --- a/api/src/actors/mod.rs +++ b/api/src/actors/mod.rs @@ -1 +1,2 @@ +pub mod cart_manager; pub mod database; diff --git a/api/src/main.rs b/api/src/main.rs index 52f3250..d4a7271 100644 --- a/api/src/main.rs +++ b/api/src/main.rs @@ -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; diff --git a/api/src/model.rs b/api/src/model.rs index 71bebd8..0069dd0 100644 --- a/api/src/model.rs +++ b/api/src/model.rs @@ -198,10 +198,10 @@ impl PartialEq 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 { diff --git a/api/src/utils.rs b/api/src/utils.rs new file mode 100644 index 0000000..9aa8994 --- /dev/null +++ b/api/src/utils.rs @@ -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>; + + 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>; + + 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)) + } + } + }; +}