Add create order
This commit is contained in:
parent
e7446e7df2
commit
75a68a317c
@ -1,7 +1,9 @@
|
||||
use sqlx::PgPool;
|
||||
|
||||
use super::Result;
|
||||
use crate::database::Database;
|
||||
use crate::database::{
|
||||
create_order_item, shopping_cart_set_state, CreateOrderItem, Database, ShoppingCartSetState,
|
||||
};
|
||||
use crate::db_async_handler;
|
||||
use crate::model::*;
|
||||
|
||||
@ -41,12 +43,22 @@ FROM account_orders
|
||||
})
|
||||
}
|
||||
|
||||
pub mod create_order {
|
||||
use crate::model::{ProductId, Quantity, QuantityUnit};
|
||||
|
||||
pub struct OrderItem {
|
||||
pub product_id: ProductId,
|
||||
pub quantity: Quantity,
|
||||
pub quantity_unit: QuantityUnit,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(actix::Message)]
|
||||
#[rtype(result = "Result<AccountOrder>")]
|
||||
pub struct CreateAccountOrder {
|
||||
pub buyer_id: AccountId,
|
||||
pub status: OrderStatus,
|
||||
pub order_id: Option<OrderId>,
|
||||
pub items: Vec<create_order::OrderItem>,
|
||||
pub shopping_cart_id: ShoppingCartId,
|
||||
}
|
||||
|
||||
db_async_handler!(CreateAccountOrder, create_account_order, AccountOrder);
|
||||
@ -55,22 +67,64 @@ pub(crate) async fn create_account_order(
|
||||
msg: CreateAccountOrder,
|
||||
db: PgPool,
|
||||
) -> Result<AccountOrder> {
|
||||
sqlx::query_as(
|
||||
let mut t = db.begin().await?;
|
||||
|
||||
let order: AccountOrder = match sqlx::query_as(
|
||||
r#"
|
||||
INSERT INTO account_orders (buyer_id, status, order_id)
|
||||
INSERT INTO account_orders (buyer_id, status)
|
||||
VALUES ($1, $2, $3)
|
||||
RETURNING id, buyer_id, status, order_id
|
||||
RETURNING id, buyer_id, status
|
||||
"#,
|
||||
)
|
||||
.bind(msg.buyer_id)
|
||||
.bind(msg.status)
|
||||
.bind(msg.order_id)
|
||||
.fetch_one(&db)
|
||||
.bind(OrderStatus::Confirmed)
|
||||
.fetch_one(&mut t)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
{
|
||||
Ok(order) => order,
|
||||
Err(e) => {
|
||||
log::error!("{e:?}");
|
||||
t.rollback().await.ok();
|
||||
return Err(super::Error::AccountOrder(Error::CantCreate));
|
||||
}
|
||||
};
|
||||
for item in msg.items {
|
||||
if let Err(e) = create_order_item(
|
||||
CreateOrderItem {
|
||||
product_id: item.product_id,
|
||||
order_id: order.id,
|
||||
quantity: item.quantity,
|
||||
quantity_unit: item.quantity_unit,
|
||||
},
|
||||
&mut t,
|
||||
)
|
||||
.await
|
||||
{
|
||||
log::error!("{e:?}");
|
||||
|
||||
t.rollback().await.ok();
|
||||
return Err(super::Error::AccountOrder(Error::CantCreate));
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(e) = shopping_cart_set_state(
|
||||
ShoppingCartSetState {
|
||||
id: msg.shopping_cart_id,
|
||||
state: ShoppingCartState::Closed,
|
||||
},
|
||||
&mut t,
|
||||
)
|
||||
.await
|
||||
{
|
||||
log::error!("{e:?}");
|
||||
super::Error::AccountOrder(Error::CantCreate)
|
||||
})
|
||||
|
||||
t.rollback().await.ok();
|
||||
return Err(super::Error::AccountOrder(Error::CantCreate));
|
||||
};
|
||||
|
||||
t.commit().await.ok();
|
||||
|
||||
Ok(order)
|
||||
}
|
||||
|
||||
#[derive(actix::Message)]
|
||||
|
@ -26,7 +26,7 @@ db_async_handler!(AllOrderItems, all_order_items, 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
|
||||
SELECT id, product_id, order_id, quantity, quantity_unit
|
||||
FROM order_items
|
||||
"#,
|
||||
)
|
||||
@ -41,23 +41,34 @@ FROM order_items
|
||||
#[derive(actix::Message)]
|
||||
#[rtype(result = "Result<OrderItem>")]
|
||||
pub struct CreateOrderItem {
|
||||
pub buyer_id: AccountId,
|
||||
pub status: OrderStatus,
|
||||
pub product_id: ProductId,
|
||||
pub order_id: AccountOrderId,
|
||||
pub quantity: Quantity,
|
||||
pub quantity_unit: QuantityUnit,
|
||||
}
|
||||
|
||||
db_async_handler!(CreateOrderItem, create_order_item, OrderItem);
|
||||
db_async_handler!(CreateOrderItem, inner_create_order_item, OrderItem);
|
||||
|
||||
pub(crate) async fn create_order_item(msg: CreateOrderItem, db: PgPool) -> Result<OrderItem> {
|
||||
async fn inner_create_order_item(msg: CreateOrderItem, db: PgPool) -> Result<OrderItem> {
|
||||
create_order_item(msg, &db).await
|
||||
}
|
||||
|
||||
pub(crate) async fn create_order_item<'e, E>(msg: CreateOrderItem, db: E) -> Result<OrderItem>
|
||||
where
|
||||
E: sqlx::Executor<'e, Database = sqlx::Postgres>,
|
||||
{
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
INSERT INTO order_items (buyer_id, status)
|
||||
VALUES ($1, $2)
|
||||
RETURNING id, buyer_id, status
|
||||
INSERT INTO order_items (product_id, order_id, quantity, quantity_unit)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
RETURNING id, product_id, order_id, quantity, quantity_unit
|
||||
"#,
|
||||
)
|
||||
.bind(msg.buyer_id)
|
||||
.bind(msg.status)
|
||||
.fetch_one(&db)
|
||||
.bind(msg.product_id)
|
||||
.bind(msg.order_id)
|
||||
.bind(msg.quantity)
|
||||
.bind(msg.quantity_unit)
|
||||
.fetch_one(db)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
log::error!("{e:?}");
|
||||
@ -76,7 +87,7 @@ db_async_handler!(FindOrderItem, find_order_item, OrderItem);
|
||||
pub(crate) async fn find_order_item(msg: FindOrderItem, db: PgPool) -> Result<OrderItem> {
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
SELECT id, buyer_id, status
|
||||
SELECT id, product_id, order_id, quantity, quantity_unit
|
||||
FROM order_items
|
||||
WHERE id = $1
|
||||
"#,
|
||||
|
@ -60,6 +60,7 @@ FROM shopping_cart_items
|
||||
#[rtype(result = "Result<Vec<ShoppingCartItem>>")]
|
||||
pub struct AccountShoppingCartItems {
|
||||
pub account_id: AccountId,
|
||||
pub shopping_cart_id: Option<ShoppingCartId>,
|
||||
}
|
||||
|
||||
db_async_handler!(
|
||||
@ -72,8 +73,24 @@ pub(crate) async fn account_shopping_cart_items(
|
||||
msg: AccountShoppingCartItems,
|
||||
pool: PgPool,
|
||||
) -> Result<Vec<ShoppingCartItem>> {
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
match msg.shopping_cart_id {
|
||||
Some(shopping_cart_id) => sqlx::query_as(
|
||||
r#"
|
||||
SELECT shopping_cart_items.id as id,
|
||||
shopping_cart_items.product_id as product_id,
|
||||
shopping_cart_items.shopping_cart_id as shopping_cart_id,
|
||||
shopping_cart_items.quantity as quantity,
|
||||
shopping_cart_items.quantity_unit as quantity_unit
|
||||
FROM shopping_cart_items
|
||||
LEFT JOIN shopping_carts
|
||||
ON shopping_carts.id = shopping_cart_id
|
||||
WHERE shopping_carts.buyer_id = $1 AND shopping_carts.id = $2
|
||||
"#,
|
||||
)
|
||||
.bind(msg.account_id)
|
||||
.bind(shopping_cart_id),
|
||||
None => sqlx::query_as(
|
||||
r#"
|
||||
SELECT shopping_cart_items.id as id,
|
||||
shopping_cart_items.product_id as product_id,
|
||||
shopping_cart_items.shopping_cart_id as shopping_cart_id,
|
||||
@ -84,8 +101,9 @@ LEFT JOIN shopping_carts
|
||||
ON shopping_carts.id = shopping_cart_id
|
||||
WHERE shopping_carts.buyer_id = $1
|
||||
"#,
|
||||
)
|
||||
.bind(msg.account_id)
|
||||
)
|
||||
.bind(msg.account_id),
|
||||
}
|
||||
.fetch_all(&pool)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
|
@ -153,6 +153,51 @@ RETURNING id, buyer_id, payment_method, state
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(actix::Message)]
|
||||
#[rtype(result = "Result<ShoppingCart>")]
|
||||
pub struct ShoppingCartSetState {
|
||||
pub id: ShoppingCartId,
|
||||
pub state: ShoppingCartState,
|
||||
}
|
||||
|
||||
db_async_handler!(
|
||||
ShoppingCartSetState,
|
||||
inner_shopping_cart_set_state,
|
||||
ShoppingCart
|
||||
);
|
||||
|
||||
async fn inner_shopping_cart_set_state(
|
||||
msg: ShoppingCartSetState,
|
||||
pool: PgPool,
|
||||
) -> Result<ShoppingCart> {
|
||||
shopping_cart_set_state(msg, &pool).await
|
||||
}
|
||||
|
||||
pub(crate) async fn shopping_cart_set_state<'e, E>(
|
||||
msg: ShoppingCartSetState,
|
||||
pool: E,
|
||||
) -> Result<ShoppingCart>
|
||||
where
|
||||
E: sqlx::Executor<'e, Database = sqlx::Postgres>,
|
||||
{
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
UPDATE shopping_carts
|
||||
SET state = $2
|
||||
WHERE id = $1
|
||||
RETURNING id, buyer_id, payment_method, state
|
||||
"#,
|
||||
)
|
||||
.bind(msg.id)
|
||||
.bind(msg.state)
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
log::error!("{e:?}");
|
||||
super::Error::ShoppingCart(Error::CantUpdate(msg.id))
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(actix::Message)]
|
||||
#[rtype(result = "Result<ShoppingCart>")]
|
||||
pub struct FindShoppingCart {
|
||||
|
@ -1,9 +1,9 @@
|
||||
use actix::Addr;
|
||||
use actix_web::Message;
|
||||
use sqlx_core::postgres::PgPool;
|
||||
use actix::Message;
|
||||
|
||||
use crate::database::{Database, SharedDatabase, self};
|
||||
use crate::model::{AccountOrder, OrderStatus, ShoppingCartId};
|
||||
use crate::database::{self, SharedDatabase};
|
||||
use crate::model::{
|
||||
AccountId, AccountOrder, OrderStatus, ShoppingCart, ShoppingCartId, ShoppingCartItem,
|
||||
};
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! order_async_handler {
|
||||
@ -21,7 +21,14 @@ macro_rules! order_async_handler {
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {}
|
||||
pub enum Error {
|
||||
#[error("Database actor failed")]
|
||||
DatabaseInternal,
|
||||
#[error("Shopping cart does not exists")]
|
||||
ShoppingCart,
|
||||
#[error("Failed to create account order")]
|
||||
CreateAccountOrder,
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
@ -42,11 +49,73 @@ impl OrderManager {
|
||||
#[derive(Message, Debug)]
|
||||
#[rtype(result = "Result<AccountOrder>")]
|
||||
pub struct CreateOrder {
|
||||
pub account_id: AccountId,
|
||||
pub shopping_cart_id: ShoppingCartId,
|
||||
}
|
||||
|
||||
order_async_handler!(CreateOrder, create_order, AccountOrder);
|
||||
|
||||
pub(crate) async fn create_order(msg: CreateOrder, db: SharedDatabase) -> Result<AccountOrder> {
|
||||
let cart = match db.send(database)
|
||||
let cart: ShoppingCart = match db
|
||||
.send(database::FindShoppingCart {
|
||||
id: msg.shopping_cart_id,
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(Ok(cart)) => cart,
|
||||
Ok(Err(e)) => {
|
||||
log::error!("{e}");
|
||||
return Err(Error::ShoppingCart);
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("{e:?}");
|
||||
return Err(Error::DatabaseInternal);
|
||||
}
|
||||
};
|
||||
let items: Vec<ShoppingCartItem> = match db
|
||||
.send(database::AccountShoppingCartItems {
|
||||
account_id: cart.buyer_id,
|
||||
shopping_cart_id: Some(cart.id),
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(Ok(items)) => items,
|
||||
Ok(Err(e)) => {
|
||||
log::error!("{e}");
|
||||
return Err(Error::ShoppingCart);
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("{e:?}");
|
||||
return Err(Error::DatabaseInternal);
|
||||
}
|
||||
};
|
||||
let order = match db
|
||||
.send(database::CreateAccountOrder {
|
||||
shopping_cart_id: cart.id,
|
||||
buyer_id: msg.account_id,
|
||||
items: items
|
||||
.into_iter()
|
||||
.map(|item| database::create_order::OrderItem {
|
||||
product_id: item.product_id,
|
||||
quantity: item.quantity,
|
||||
quantity_unit: item.quantity_unit,
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(Ok(order)) => order,
|
||||
Ok(Err(e)) => {
|
||||
log::error!("{e}");
|
||||
return Err(Error::CreateAccountOrder);
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("{e:?}");
|
||||
return Err(Error::DatabaseInternal);
|
||||
}
|
||||
};
|
||||
|
||||
Ok(order)
|
||||
}
|
||||
|
||||
pub fn change(current: OrderStatus, next: OrderStatus) -> Option<OrderStatus> {
|
||||
|
@ -1 +1 @@
|
||||
use crate::model::OrderStatus;
|
||||
|
||||
|
@ -13,7 +13,7 @@ use jemallocator::Jemalloc;
|
||||
use password_hash::SaltString;
|
||||
use validator::{validate_email, validate_length};
|
||||
|
||||
use crate::actors::{database, token_manager};
|
||||
use crate::actors::{database, order_manager, token_manager};
|
||||
use crate::logic::encrypt_password;
|
||||
use crate::model::{Email, Login, PassHash, Password, Role};
|
||||
|
||||
@ -172,6 +172,7 @@ async fn server(opts: ServerOpts) -> Result<()> {
|
||||
let config = Arc::new(Config::load());
|
||||
let db = database::Database::build(&opts.db_url()).await?.start();
|
||||
let token_manager = token_manager::TokenManager::new(db.clone()).start();
|
||||
let order_manager = order_manager::OrderManager::new(db.clone()).start();
|
||||
|
||||
HttpServer::new(move || {
|
||||
App::new()
|
||||
@ -185,6 +186,7 @@ async fn server(opts: ServerOpts) -> Result<()> {
|
||||
.app_data(Data::new(config.clone()))
|
||||
.app_data(Data::new(db.clone()))
|
||||
.app_data(Data::new(token_manager.clone()))
|
||||
.app_data(Data::new(order_manager.clone()))
|
||||
.configure(routes::configure)
|
||||
// .default_service(web::to(HttpResponse::Ok))
|
||||
})
|
||||
|
@ -390,7 +390,7 @@ impl PartialEq<PasswordConfirmation> for Password {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(sqlx::Type, Serialize, Deserialize, Copy, Clone, Deref, Display, From)]
|
||||
#[derive(sqlx::Type, Serialize, Deserialize, Copy, Clone, Debug, Deref, Display, From)]
|
||||
#[sqlx(transparent)]
|
||||
#[serde(transparent)]
|
||||
pub struct AccountId(RecordId);
|
||||
@ -488,7 +488,7 @@ pub struct Stock {
|
||||
pub quantity_unit: QuantityUnit,
|
||||
}
|
||||
|
||||
#[derive(sqlx::Type, Serialize, Deserialize, Deref)]
|
||||
#[derive(sqlx::Type, Serialize, Deserialize, Copy, Clone, Debug, Deref)]
|
||||
#[sqlx(transparent)]
|
||||
#[serde(transparent)]
|
||||
pub struct AccountOrderId(RecordId);
|
||||
@ -506,7 +506,7 @@ pub struct AccountOrder {
|
||||
pub order_id: Option<OrderId>,
|
||||
}
|
||||
|
||||
#[derive(sqlx::Type, Serialize, Deserialize, Copy, Clone, Deref)]
|
||||
#[derive(sqlx::Type, Serialize, Deserialize, Copy, Clone, Debug, Deref)]
|
||||
#[sqlx(transparent)]
|
||||
#[serde(transparent)]
|
||||
pub struct OrderItemId(pub RecordId);
|
||||
@ -515,12 +515,12 @@ pub struct OrderItemId(pub RecordId);
|
||||
pub struct OrderItem {
|
||||
pub id: OrderItemId,
|
||||
pub product_id: ProductId,
|
||||
pub order_id: OrderItemId,
|
||||
pub order_id: AccountOrderId,
|
||||
pub quantity: Quantity,
|
||||
pub quantity_unit: QuantityUnit,
|
||||
}
|
||||
|
||||
#[derive(sqlx::Type, Serialize, Deserialize, Copy, Clone, Deref, Display, Debug)]
|
||||
#[derive(sqlx::Type, Serialize, Deserialize, Copy, Clone, Debug, Deref, Display)]
|
||||
#[sqlx(transparent)]
|
||||
#[serde(transparent)]
|
||||
pub struct ShoppingCartId(pub RecordId);
|
||||
@ -533,7 +533,7 @@ pub struct ShoppingCart {
|
||||
pub state: ShoppingCartState,
|
||||
}
|
||||
|
||||
#[derive(sqlx::Type, Serialize, Deserialize, Copy, Clone, Deref, Display, Debug)]
|
||||
#[derive(sqlx::Type, Serialize, Deserialize, Copy, Clone, Debug, Deref, Display)]
|
||||
#[sqlx(transparent)]
|
||||
#[serde(transparent)]
|
||||
pub struct ShoppingCartItemId(RecordId);
|
||||
|
@ -104,14 +104,14 @@ impl Responder for Error {
|
||||
msg: format!("{}", self),
|
||||
}),
|
||||
},
|
||||
Error::Public(PublicError::ApiV1(V1Error::AddItem | V1Error::RemoveItem)) => {
|
||||
HttpResponse::BadRequest()
|
||||
.content_type("application/json")
|
||||
.json(ReqFailure {
|
||||
success: false,
|
||||
msg: format!("{}", self),
|
||||
})
|
||||
}
|
||||
Error::Public(PublicError::ApiV1(
|
||||
V1Error::AddItem | V1Error::RemoveItem | V1Error::AddOrder,
|
||||
)) => HttpResponse::BadRequest()
|
||||
.content_type("application/json")
|
||||
.json(ReqFailure {
|
||||
success: false,
|
||||
msg: format!("{}", self),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,9 @@ pub enum Error {
|
||||
RemoveItem,
|
||||
#[error("Failed to add shopping cart item")]
|
||||
AddItem,
|
||||
|
||||
#[error("Failed to create order")]
|
||||
AddOrder,
|
||||
}
|
||||
|
||||
pub(crate) fn configure(config: &mut ServiceConfig) {
|
||||
|
@ -7,14 +7,15 @@ use crate::actors::cart_manager;
|
||||
use crate::actors::cart_manager::CartManager;
|
||||
use crate::database::Database;
|
||||
use crate::model::{
|
||||
AccountId, ProductId, Quantity, QuantityUnit, ShoppingCart, ShoppingCartItem,
|
||||
AccountId, ProductId, Quantity, QuantityUnit, ShoppingCart, ShoppingCartId, ShoppingCartItem,
|
||||
ShoppingCartItemId,
|
||||
};
|
||||
use crate::order_manager::OrderManager;
|
||||
use crate::routes::public::api_v1::ShoppingCartError;
|
||||
use crate::routes::public::Error as PublicError;
|
||||
use crate::routes::{RequireUser, Result};
|
||||
use crate::token_manager::TokenManager;
|
||||
use crate::{database, routes};
|
||||
use crate::{database, order_manager, routes};
|
||||
|
||||
#[get("/shopping-cart")]
|
||||
async fn shopping_cart(
|
||||
@ -68,6 +69,7 @@ async fn shopping_cart_items(
|
||||
match db
|
||||
.send(database::AccountShoppingCartItems {
|
||||
account_id: cart.buyer_id,
|
||||
shopping_cart_id: Some(cart.id),
|
||||
})
|
||||
.await
|
||||
{
|
||||
@ -186,7 +188,41 @@ async fn delete_cart_item(
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn create_order() {}
|
||||
#[derive(serde::Deserialize)]
|
||||
pub struct CreateOrderInput {
|
||||
pub shopping_cart_id: ShoppingCartId,
|
||||
}
|
||||
|
||||
#[post("/order")]
|
||||
pub(crate) async fn create_order(
|
||||
Json(payload): Json<CreateOrderInput>,
|
||||
tm: Data<Addr<TokenManager>>,
|
||||
credentials: BearerAuth,
|
||||
om: Data<Addr<OrderManager>>,
|
||||
) -> routes::Result<HttpResponse> {
|
||||
let (token, _) = credentials.require_user(tm.into_inner()).await?;
|
||||
let order = match om
|
||||
.send(order_manager::CreateOrder {
|
||||
account_id: AccountId::from(token.subject),
|
||||
shopping_cart_id: payload.shopping_cart_id,
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(Ok(order)) => order,
|
||||
Ok(Err(e)) => {
|
||||
log::error!("{e}");
|
||||
return Err(routes::Error::Public(PublicError::ApiV1(
|
||||
super::Error::AddOrder,
|
||||
)));
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("{e}");
|
||||
return Err(routes::Error::Public(PublicError::DatabaseConnection));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(HttpResponse::Created().json(order))
|
||||
}
|
||||
|
||||
pub(crate) fn configure(config: &mut ServiceConfig) {
|
||||
config.service(scope("")
|
||||
@ -195,5 +231,6 @@ pub(crate) fn configure(config: &mut ServiceConfig) {
|
||||
.scope("customer_id role subject audience expiration_time not_before_time issued_at_time"))
|
||||
.service(shopping_cart)
|
||||
.service(shopping_cart_items)
|
||||
.service(delete_cart_item));
|
||||
.service(delete_cart_item)
|
||||
.service(create_order));
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user