WIP: Place order for guess and account.
This commit is contained in:
parent
28e9736562
commit
c1c97061eb
@ -90,7 +90,7 @@ impl actix::Actor for AccountManager {
|
||||
|
||||
pub struct MeResult {
|
||||
pub account: FullAccount,
|
||||
pub addresses: Vec<model::Address>,
|
||||
pub addresses: Vec<model::AccountAddress>,
|
||||
}
|
||||
|
||||
#[derive(actix::Message, Debug)]
|
||||
|
@ -9,7 +9,7 @@ pub enum Error {
|
||||
}
|
||||
|
||||
#[derive(actix::Message)]
|
||||
#[rtype(result = "Result<Vec<model::Address>>")]
|
||||
#[rtype(result = "Result<Vec<model::AccountAddress>>")]
|
||||
pub struct AccountAddresses {
|
||||
pub account_id: model::AccountId,
|
||||
}
|
||||
@ -17,17 +17,17 @@ pub struct AccountAddresses {
|
||||
db_async_handler!(
|
||||
AccountAddresses,
|
||||
account_addresses,
|
||||
Vec<model::Address>,
|
||||
Vec<model::AccountAddress>,
|
||||
inner_account_addresses
|
||||
);
|
||||
|
||||
pub(crate) async fn account_addresses(
|
||||
msg: AccountAddresses,
|
||||
pool: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
) -> Result<Vec<model::Address>> {
|
||||
) -> Result<Vec<model::AccountAddress>> {
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
SELECT id, name, email, street, city, country, zip, account_id
|
||||
SELECT id, name, email, street, city, country, zip, account_id, is_default
|
||||
FROM account_addresses
|
||||
WHERE account_id = $1
|
||||
"#,
|
||||
@ -39,7 +39,37 @@ WHERE account_id = $1
|
||||
}
|
||||
|
||||
#[derive(actix::Message)]
|
||||
#[rtype(result = "Result<model::Address>")]
|
||||
#[rtype(result = "Result<model::AccountAddress>")]
|
||||
pub struct DefaultAccountAddress {
|
||||
pub account_id: model::AccountId,
|
||||
}
|
||||
|
||||
db_async_handler!(
|
||||
DefaultAccountAddress,
|
||||
default_account_address,
|
||||
model::AccountAddress,
|
||||
inner_default_account_address
|
||||
);
|
||||
|
||||
pub(crate) async fn default_account_address(
|
||||
msg: DefaultAccountAddress,
|
||||
pool: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
) -> Result<model::AccountAddress> {
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
SELECT id, name, email, street, city, country, zip, account_id, is_default
|
||||
FROM account_addresses
|
||||
WHERE account_id = $1 AND is_default
|
||||
"#,
|
||||
)
|
||||
.bind(msg.account_id)
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
.map_err(|_| Error::AccountAddresses.into())
|
||||
}
|
||||
|
||||
#[derive(actix::Message)]
|
||||
#[rtype(result = "Result<model::AccountAddress>")]
|
||||
pub struct CreateAccountAddress {
|
||||
pub name: model::Name,
|
||||
pub email: model::Email,
|
||||
@ -47,25 +77,41 @@ pub struct CreateAccountAddress {
|
||||
pub city: model::City,
|
||||
pub country: model::Country,
|
||||
pub zip: model::Zip,
|
||||
pub account_id: model::AccountId,
|
||||
pub account_id: Option<model::AccountId>,
|
||||
pub is_default: bool,
|
||||
}
|
||||
|
||||
db_async_handler!(
|
||||
CreateAccountAddress,
|
||||
create_address,
|
||||
model::Address,
|
||||
model::AccountAddress,
|
||||
inner_create_address
|
||||
);
|
||||
|
||||
pub(crate) async fn create_address(
|
||||
msg: CreateAccountAddress,
|
||||
pool: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
) -> Result<model::Address> {
|
||||
) -> Result<model::AccountAddress> {
|
||||
if msg.is_default {
|
||||
if let Err(e) = sqlx::query(
|
||||
r#"
|
||||
UPDATE account_addresses
|
||||
SET is_default = FALSE
|
||||
WHERE account_id = $1
|
||||
"#,
|
||||
)
|
||||
.bind(msg.account_id)
|
||||
.fetch_all(&mut *pool)
|
||||
.await
|
||||
{
|
||||
log::error!("{}", e);
|
||||
}
|
||||
}
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
INSERT INTO account_addresses ( name, email, street, city, country, zip, account_id )
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
RETURNING id, name, email, street, city, country, zip, account_id
|
||||
RETURNING id, name, email, street, city, country, zip, account_id, is_default
|
||||
"#,
|
||||
)
|
||||
.bind(msg.name)
|
||||
@ -81,7 +127,7 @@ RETURNING id, name, email, street, city, country, zip, account_id
|
||||
}
|
||||
|
||||
#[derive(actix::Message)]
|
||||
#[rtype(result = "Result<model::Address>")]
|
||||
#[rtype(result = "Result<model::AccountAddress>")]
|
||||
pub struct UpdateAccountAddress {
|
||||
pub id: model::AddressId,
|
||||
pub name: model::Name,
|
||||
@ -91,25 +137,26 @@ pub struct UpdateAccountAddress {
|
||||
pub country: model::Country,
|
||||
pub zip: model::Zip,
|
||||
pub account_id: model::AccountId,
|
||||
pub is_default: bool,
|
||||
}
|
||||
|
||||
db_async_handler!(
|
||||
UpdateAccountAddress,
|
||||
update_account_address,
|
||||
model::Address,
|
||||
model::AccountAddress,
|
||||
inner_update_account_address
|
||||
);
|
||||
|
||||
pub(crate) async fn update_account_address(
|
||||
msg: UpdateAccountAddress,
|
||||
pool: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
) -> Result<model::Address> {
|
||||
) -> Result<model::AccountAddress> {
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
UPDATE account_addresses
|
||||
SET name = $2, email = $3, street = $4, city = $5, country = $6, zip = $7, account_id = $8
|
||||
SET name = $2, email = $3, street = $4, city = $5, country = $6, zip = $7, account_id = $8, is_default = $9
|
||||
WHERE id = $1
|
||||
RETURNING id, name, email, street, city, country, zip, account_id
|
||||
RETURNING id, name, email, street, city, country, zip, account_id, is_default
|
||||
"#,
|
||||
)
|
||||
.bind(msg.id)
|
||||
@ -120,6 +167,7 @@ RETURNING id, name, email, street, city, country, zip, account_id
|
||||
.bind(msg.country)
|
||||
.bind(msg.zip)
|
||||
.bind(msg.account_id)
|
||||
.bind(msg.is_default)
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
.map_err(|_| Error::CreateAccountAddress.into())
|
@ -3,10 +3,11 @@ use config::SharedAppConfig;
|
||||
use sqlx::PgPool;
|
||||
use sqlx_core::arguments::Arguments;
|
||||
|
||||
pub use crate::account_orders::*;
|
||||
pub use crate::account_addresses::*;
|
||||
pub use crate::accounts::*;
|
||||
pub use crate::addresses::*;
|
||||
pub use crate::order_addresses::*;
|
||||
pub use crate::order_items::*;
|
||||
pub use crate::orders::*;
|
||||
pub use crate::photos::*;
|
||||
pub use crate::product_photos::*;
|
||||
pub use crate::products::*;
|
||||
@ -15,10 +16,11 @@ pub use crate::shopping_carts::*;
|
||||
pub use crate::stocks::*;
|
||||
pub use crate::tokens::*;
|
||||
|
||||
pub mod account_orders;
|
||||
pub mod account_addresses;
|
||||
pub mod accounts;
|
||||
pub mod addresses;
|
||||
pub mod order_addresses;
|
||||
pub mod order_items;
|
||||
pub mod orders;
|
||||
pub mod photos;
|
||||
pub mod product_photos;
|
||||
pub mod products;
|
||||
@ -128,7 +130,7 @@ pub enum Error {
|
||||
#[error("{0}")]
|
||||
Account(#[from] accounts::Error),
|
||||
#[error("{0}")]
|
||||
AccountOrder(#[from] account_orders::Error),
|
||||
AccountOrder(#[from] orders::Error),
|
||||
#[error("{0}")]
|
||||
Product(#[from] products::Error),
|
||||
#[error("{0}")]
|
||||
@ -146,7 +148,9 @@ pub enum Error {
|
||||
#[error("{0}")]
|
||||
ProductPhoto(#[from] product_photos::Error),
|
||||
#[error("{0}")]
|
||||
AccountAddress(#[from] addresses::Error),
|
||||
AccountAddress(#[from] account_addresses::Error),
|
||||
#[error("{0}")]
|
||||
OrderAddress(#[from] order_addresses::Error),
|
||||
#[error("Failed to start or finish transaction")]
|
||||
TransactionFailed,
|
||||
}
|
||||
|
130
actors/database_manager/src/order_addresses.rs
Normal file
130
actors/database_manager/src/order_addresses.rs
Normal file
@ -0,0 +1,130 @@
|
||||
use crate::{db_async_handler, Result};
|
||||
|
||||
#[derive(Debug, Copy, Clone, serde::Serialize, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("Can't load account addresses")]
|
||||
OrderAddress,
|
||||
#[error("Failed to save account address")]
|
||||
CreateOrderAddress,
|
||||
}
|
||||
|
||||
#[derive(actix::Message)]
|
||||
#[rtype(result = "Result<model::OrderAddress>")]
|
||||
pub struct OrderAddress {
|
||||
pub order_id: model::OrderId,
|
||||
}
|
||||
|
||||
db_async_handler!(
|
||||
OrderAddress,
|
||||
order_address,
|
||||
model::OrderAddress,
|
||||
inner_order_address
|
||||
);
|
||||
|
||||
pub(crate) async fn order_address(
|
||||
msg: OrderAddress,
|
||||
pool: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
) -> Result<model::OrderAddress> {
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
SELECT
|
||||
order_addresses.id,
|
||||
order_addresses.name,
|
||||
order_addresses.email,
|
||||
order_addresses.street,
|
||||
order_addresses.city,
|
||||
order_addresses.country,
|
||||
order_addresses.zip
|
||||
FROM order_addresses
|
||||
INNER JOIN orders ON orders.address_id = order_addresses.id
|
||||
WHERE orders.id = $1
|
||||
"#,
|
||||
)
|
||||
.bind(msg.order_id)
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
.map_err(|_| Error::OrderAddress.into())
|
||||
}
|
||||
|
||||
#[derive(actix::Message)]
|
||||
#[rtype(result = "Result<model::OrderAddress>")]
|
||||
pub struct CreateOrderAddress {
|
||||
pub name: model::Name,
|
||||
pub email: model::Email,
|
||||
pub street: model::Street,
|
||||
pub city: model::City,
|
||||
pub country: model::Country,
|
||||
pub zip: model::Zip,
|
||||
}
|
||||
|
||||
db_async_handler!(
|
||||
CreateOrderAddress,
|
||||
create_order_address,
|
||||
model::OrderAddress,
|
||||
inner_create_order_address
|
||||
);
|
||||
|
||||
pub(crate) async fn create_order_address(
|
||||
msg: CreateOrderAddress,
|
||||
pool: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
) -> Result<model::OrderAddress> {
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
INSERT INTO order_addresses ( name, email, street, city, country, zip )
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
RETURNING id, name, email, street, city, country, zip
|
||||
"#,
|
||||
)
|
||||
.bind(msg.name)
|
||||
.bind(msg.email)
|
||||
.bind(msg.street)
|
||||
.bind(msg.city)
|
||||
.bind(msg.country)
|
||||
.bind(msg.zip)
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
.map_err(|_| Error::CreateOrderAddress.into())
|
||||
}
|
||||
|
||||
#[derive(actix::Message)]
|
||||
#[rtype(result = "Result<model::OrderAddress>")]
|
||||
pub struct UpdateOrderAddress {
|
||||
pub id: model::OrderAddressId,
|
||||
pub name: model::Name,
|
||||
pub email: model::Email,
|
||||
pub street: model::Street,
|
||||
pub city: model::City,
|
||||
pub country: model::Country,
|
||||
pub zip: model::Zip,
|
||||
}
|
||||
|
||||
db_async_handler!(
|
||||
UpdateOrderAddress,
|
||||
update_account_address,
|
||||
model::OrderAddress,
|
||||
inner_update_account_address
|
||||
);
|
||||
|
||||
pub(crate) async fn update_account_address(
|
||||
msg: UpdateOrderAddress,
|
||||
pool: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
) -> Result<model::OrderAddress> {
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
UPDATE order_addresses
|
||||
SET name = $2, email = $3, street = $4, city = $5, country = $6, zip = $7
|
||||
WHERE id = $1
|
||||
RETURNING id, name, email, street, city, country, zip
|
||||
"#,
|
||||
)
|
||||
.bind(msg.id)
|
||||
.bind(msg.name)
|
||||
.bind(msg.email)
|
||||
.bind(msg.street)
|
||||
.bind(msg.city)
|
||||
.bind(msg.country)
|
||||
.bind(msg.zip)
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
.map_err(|_| Error::CreateOrderAddress.into())
|
||||
}
|
@ -47,7 +47,7 @@ ORDER BY id DESC
|
||||
#[rtype(result = "Result<OrderItem>")]
|
||||
pub struct CreateOrderItem {
|
||||
pub product_id: ProductId,
|
||||
pub order_id: AccountOrderId,
|
||||
pub order_id: OrderId,
|
||||
pub quantity: Quantity,
|
||||
pub quantity_unit: QuantityUnit,
|
||||
}
|
||||
@ -109,7 +109,7 @@ WHERE id = $1
|
||||
#[derive(actix::Message)]
|
||||
#[rtype(result = "Result<Vec<OrderItem>>")]
|
||||
pub struct OrderItems {
|
||||
pub order_id: model::AccountOrderId,
|
||||
pub order_id: model::OrderId,
|
||||
}
|
||||
|
||||
db_async_handler!(OrderItems, order_items, Vec<OrderItem>);
|
||||
|
@ -21,19 +21,16 @@ pub enum Error {
|
||||
}
|
||||
|
||||
#[derive(actix::Message)]
|
||||
#[rtype(result = "Result<Vec<AccountOrder>>")]
|
||||
#[rtype(result = "Result<Vec<Order>>")]
|
||||
pub struct AllAccountOrders;
|
||||
|
||||
db_async_handler!(AllAccountOrders, all_account_orders, Vec<AccountOrder>);
|
||||
db_async_handler!(AllAccountOrders, all_orders, Vec<Order>);
|
||||
|
||||
pub(crate) async fn all_account_orders(
|
||||
_msg: AllAccountOrders,
|
||||
pool: PgPool,
|
||||
) -> Result<Vec<AccountOrder>> {
|
||||
pub(crate) async fn all_orders(_msg: AllAccountOrders, pool: PgPool) -> Result<Vec<Order>> {
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
SELECT id, buyer_id, status, order_ext_id, service_order_id, checkout_notes
|
||||
FROM account_orders
|
||||
SELECT id, buyer_id, status, order_ext_id, service_order_id, checkout_notes, address_id
|
||||
FROM orders
|
||||
ORDER BY id DESC
|
||||
"#,
|
||||
)
|
||||
@ -56,7 +53,7 @@ pub mod create_order {
|
||||
}
|
||||
|
||||
#[derive(actix::Message)]
|
||||
#[rtype(result = "Result<AccountOrder>")]
|
||||
#[rtype(result = "Result<Order>")]
|
||||
pub struct CreateAccountOrder {
|
||||
pub buyer_id: AccountId,
|
||||
pub items: Vec<create_order::OrderItem>,
|
||||
@ -67,19 +64,19 @@ pub struct CreateAccountOrder {
|
||||
db_async_handler!(
|
||||
CreateAccountOrder,
|
||||
create_account_order,
|
||||
AccountOrder,
|
||||
Order,
|
||||
inner_create_account_order
|
||||
);
|
||||
|
||||
pub(crate) async fn create_account_order(
|
||||
msg: CreateAccountOrder,
|
||||
t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
) -> Result<AccountOrder> {
|
||||
let order: AccountOrder = match sqlx::query_as(
|
||||
) -> Result<Order> {
|
||||
let order: Order = match sqlx::query_as(
|
||||
r#"
|
||||
INSERT INTO account_orders (buyer_id, status)
|
||||
INSERT INTO orders (buyer_id, status)
|
||||
VALUES ($1, $2, $3)
|
||||
RETURNING id, buyer_id, status, order_ext_id, service_order_id, checkout_notes
|
||||
RETURNING id, buyer_id, status, order_ext_id, service_order_id, checkout_notes, address_id
|
||||
"#,
|
||||
)
|
||||
.bind(msg.buyer_id)
|
||||
@ -130,26 +127,23 @@ RETURNING id, buyer_id, status, order_ext_id, service_order_id, checkout_notes
|
||||
}
|
||||
|
||||
#[derive(actix::Message)]
|
||||
#[rtype(result = "Result<AccountOrder>")]
|
||||
#[rtype(result = "Result<Order>")]
|
||||
pub struct UpdateAccountOrder {
|
||||
pub id: AccountOrderId,
|
||||
pub id: OrderId,
|
||||
pub buyer_id: AccountId,
|
||||
pub status: OrderStatus,
|
||||
pub order_id: Option<OrderId>,
|
||||
pub order_id: Option<ExtOrderId>,
|
||||
}
|
||||
|
||||
db_async_handler!(UpdateAccountOrder, update_account_order, AccountOrder);
|
||||
db_async_handler!(UpdateAccountOrder, update_account_order, Order);
|
||||
|
||||
pub(crate) async fn update_account_order(
|
||||
msg: UpdateAccountOrder,
|
||||
db: PgPool,
|
||||
) -> Result<AccountOrder> {
|
||||
pub(crate) async fn update_account_order(msg: UpdateAccountOrder, db: PgPool) -> Result<Order> {
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
UPDATE account_orders
|
||||
UPDATE orders
|
||||
SET buyer_id = $2 AND status = $3 AND order_id = $4
|
||||
WHERE id = $1
|
||||
RETURNING id, buyer_id, status, order_ext_id, service_order_id, checkout_notes
|
||||
RETURNING id, buyer_id, status, order_ext_id, service_order_id, checkout_notes, address_id
|
||||
"#,
|
||||
)
|
||||
.bind(msg.id)
|
||||
@ -165,28 +159,24 @@ RETURNING id, buyer_id, status, order_ext_id, service_order_id, checkout_notes
|
||||
}
|
||||
|
||||
#[derive(actix::Message)]
|
||||
#[rtype(result = "Result<AccountOrder>")]
|
||||
#[rtype(result = "Result<Order>")]
|
||||
pub struct UpdateAccountOrderByExt {
|
||||
pub order_ext_id: String,
|
||||
pub status: OrderStatus,
|
||||
}
|
||||
|
||||
db_async_handler!(
|
||||
UpdateAccountOrderByExt,
|
||||
update_account_order_by_ext,
|
||||
AccountOrder
|
||||
);
|
||||
db_async_handler!(UpdateAccountOrderByExt, update_account_order_by_ext, Order);
|
||||
|
||||
pub(crate) async fn update_account_order_by_ext(
|
||||
msg: UpdateAccountOrderByExt,
|
||||
db: PgPool,
|
||||
) -> Result<AccountOrder> {
|
||||
) -> Result<Order> {
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
UPDATE account_orders
|
||||
UPDATE orders
|
||||
SET status = $2
|
||||
WHERE order_ext_id = $1
|
||||
RETURNING id, buyer_id, status, order_ext_id, service_order_id, checkout_notes
|
||||
RETURNING id, buyer_id, status, order_ext_id, service_order_id, checkout_notes, address_id
|
||||
"#,
|
||||
)
|
||||
.bind(msg.order_ext_id)
|
||||
@ -200,18 +190,18 @@ RETURNING id, buyer_id, status, order_ext_id, service_order_id, checkout_notes
|
||||
}
|
||||
|
||||
#[derive(actix::Message)]
|
||||
#[rtype(result = "Result<AccountOrder>")]
|
||||
#[rtype(result = "Result<Order>")]
|
||||
pub struct FindAccountOrder {
|
||||
pub id: AccountOrderId,
|
||||
pub id: OrderId,
|
||||
}
|
||||
|
||||
db_async_handler!(FindAccountOrder, find_account_order, AccountOrder);
|
||||
db_async_handler!(FindAccountOrder, find_account_order, Order);
|
||||
|
||||
pub(crate) async fn find_account_order(msg: FindAccountOrder, db: PgPool) -> Result<AccountOrder> {
|
||||
pub(crate) async fn find_account_order(msg: FindAccountOrder, db: PgPool) -> Result<Order> {
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
SELECT id, buyer_id, status, order_ext_id, service_order_id, checkout_notes
|
||||
FROM account_orders
|
||||
SELECT id, buyer_id, status, order_ext_id, service_order_id, checkout_notes, address_id
|
||||
FROM orders
|
||||
WHERE id = $1
|
||||
"#,
|
||||
)
|
||||
@ -225,24 +215,21 @@ WHERE id = $1
|
||||
}
|
||||
|
||||
#[derive(actix::Message)]
|
||||
#[rtype(result = "Result<AccountOrder>")]
|
||||
#[rtype(result = "Result<Order>")]
|
||||
pub struct SetOrderServiceId {
|
||||
pub id: AccountOrderId,
|
||||
pub id: OrderId,
|
||||
pub service_order_id: String,
|
||||
}
|
||||
|
||||
db_async_handler!(SetOrderServiceId, set_order_service_id, AccountOrder);
|
||||
db_async_handler!(SetOrderServiceId, set_order_service_id, Order);
|
||||
|
||||
pub(crate) async fn set_order_service_id(
|
||||
msg: SetOrderServiceId,
|
||||
db: PgPool,
|
||||
) -> Result<AccountOrder> {
|
||||
pub(crate) async fn set_order_service_id(msg: SetOrderServiceId, db: PgPool) -> Result<Order> {
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
UPDATE account_orders
|
||||
UPDATE orders
|
||||
SET service_order_id = $2
|
||||
WHERE id = $1
|
||||
RETURNING id, buyer_id, status, order_ext_id, service_order_id, checkout_notes
|
||||
RETURNING id, buyer_id, status, order_ext_id, service_order_id, checkout_notes, address_id
|
||||
"#,
|
||||
)
|
||||
.bind(msg.id)
|
@ -1,7 +1,7 @@
|
||||
use actix::Message;
|
||||
use config::SharedAppConfig;
|
||||
use database_manager::{query_db, SharedDatabase};
|
||||
use model::{AccountId, AccountOrder, OrderStatus, ShoppingCart, ShoppingCartId, ShoppingCartItem};
|
||||
use model::{AccountId, Order, OrderStatus, ShoppingCart, ShoppingCartItem};
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! order_async_handler {
|
||||
@ -19,6 +19,27 @@ macro_rules! order_async_handler {
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! query_order {
|
||||
($order_manager: expr, $msg: expr, $fail: expr) => {
|
||||
$crate::query_order!($order_manager, $msg, $fail, $fail)
|
||||
};
|
||||
|
||||
($order_manager: expr, $msg: expr, $db_fail: expr, $act_fail: expr) => {
|
||||
match $order_manager.send($msg).await {
|
||||
Ok(Ok(r)) => Ok(r),
|
||||
Ok(Err(e)) => {
|
||||
log::error!("{e}");
|
||||
Err($db_fail)
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("{e:?}");
|
||||
Err($act_fail)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, serde::Serialize, thiserror::Error)]
|
||||
#[serde(rename_all = "kebab-case", tag = "order")]
|
||||
pub enum Error {
|
||||
@ -28,6 +49,12 @@ pub enum Error {
|
||||
ShoppingCart,
|
||||
#[error("Failed to create account order")]
|
||||
CreateAccountOrder,
|
||||
#[error("Account does not have address")]
|
||||
NoAddress,
|
||||
#[error("Invalid account address")]
|
||||
InvalidAccountAddress,
|
||||
#[error("Invalid order address")]
|
||||
InvalidOrderAddress,
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
@ -47,24 +74,34 @@ impl OrderManager {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Message, Debug)]
|
||||
#[rtype(result = "Result<AccountOrder>")]
|
||||
pub struct CreateOrder {
|
||||
pub account_id: AccountId,
|
||||
pub shopping_cart_id: ShoppingCartId,
|
||||
#[derive(Debug)]
|
||||
pub struct CreateOrderAddress {
|
||||
pub name: model::Name,
|
||||
pub email: model::Email,
|
||||
pub street: model::Street,
|
||||
pub city: model::City,
|
||||
pub country: model::Country,
|
||||
pub zip: model::Zip,
|
||||
}
|
||||
|
||||
order_async_handler!(CreateOrder, create_order, AccountOrder);
|
||||
#[derive(Message, Debug)]
|
||||
#[rtype(result = "Result<Order>")]
|
||||
pub struct CreateAccountOrder {
|
||||
pub account_id: AccountId,
|
||||
pub create_address: Option<CreateOrderAddress>,
|
||||
}
|
||||
|
||||
pub(crate) async fn create_order(
|
||||
msg: CreateOrder,
|
||||
order_async_handler!(CreateAccountOrder, create_account_order, Order);
|
||||
|
||||
pub(crate) async fn create_account_order(
|
||||
msg: CreateAccountOrder,
|
||||
db: SharedDatabase,
|
||||
_config: SharedAppConfig,
|
||||
) -> Result<AccountOrder> {
|
||||
) -> Result<Order> {
|
||||
let cart: ShoppingCart = query_db!(
|
||||
db,
|
||||
database_manager::FindShoppingCart {
|
||||
id: msg.shopping_cart_id,
|
||||
database_manager::EnsureActiveShoppingCart {
|
||||
buyer_id: msg.account_id
|
||||
},
|
||||
Error::ShoppingCart,
|
||||
Error::DatabaseInternal
|
||||
@ -78,6 +115,44 @@ pub(crate) async fn create_order(
|
||||
Error::ShoppingCart,
|
||||
Error::DatabaseInternal
|
||||
);
|
||||
let address: model::AccountAddress = if let Some(input) = msg.create_address {
|
||||
query_db!(
|
||||
db,
|
||||
database_manager::CreateAccountAddress {
|
||||
name: input.name,
|
||||
email: input.email,
|
||||
street: input.street,
|
||||
city: input.city,
|
||||
country: input.country,
|
||||
zip: input.zip,
|
||||
account_id: None,
|
||||
is_default: true,
|
||||
},
|
||||
Error::InvalidAccountAddress
|
||||
)
|
||||
} else {
|
||||
query_db!(
|
||||
db,
|
||||
database_manager::DefaultAccountAddress {
|
||||
account_id: cart.buyer_id
|
||||
},
|
||||
Error::NoAddress
|
||||
)
|
||||
};
|
||||
|
||||
query_db!(
|
||||
db,
|
||||
database_manager::CreateOrderAddress {
|
||||
name: address.name,
|
||||
email: address.email,
|
||||
street: address.street,
|
||||
city: address.city,
|
||||
country: address.country,
|
||||
zip: address.zip,
|
||||
},
|
||||
Error::InvalidOrderAddress
|
||||
);
|
||||
|
||||
let order = query_db!(
|
||||
db,
|
||||
database_manager::CreateAccountOrder {
|
||||
|
@ -151,7 +151,7 @@ impl From<Product> for pay_u::Product {
|
||||
}
|
||||
|
||||
pub struct CreatePaymentResult {
|
||||
pub order: model::AccountOrder,
|
||||
pub order: model::Order,
|
||||
pub items: Vec<model::OrderItem>,
|
||||
pub redirect_uri: String,
|
||||
}
|
||||
@ -215,7 +215,7 @@ pub(crate) async fn request_payment(
|
||||
Error::UnavailableShoppingCart
|
||||
);
|
||||
|
||||
let db_order: model::AccountOrder = query_db!(
|
||||
let db_order: model::Order = query_db!(
|
||||
db,
|
||||
database_manager::CreateAccountOrder {
|
||||
buyer_id: msg.buyer_id,
|
||||
|
@ -4,7 +4,7 @@ use actix_web::{get, patch, post, HttpResponse};
|
||||
use actix_web_httpauth::extractors::bearer::BearerAuth;
|
||||
use config::SharedAppConfig;
|
||||
use database_manager::Database;
|
||||
use model::{AccountId, AccountState, Address, Encrypt, PasswordConfirmation};
|
||||
use model::{AccountAddress, AccountId, AccountState, Encrypt, PasswordConfirmation};
|
||||
use token_manager::TokenManager;
|
||||
|
||||
use crate::routes::admin::Error;
|
||||
@ -112,7 +112,7 @@ pub async fn create_account(
|
||||
role: payload.role,
|
||||
}
|
||||
);
|
||||
let addresses: Vec<Address> = admin_send_db!(
|
||||
let addresses: Vec<AccountAddress> = admin_send_db!(
|
||||
db,
|
||||
database_manager::AccountAddresses {
|
||||
account_id: account.id
|
||||
|
@ -3,7 +3,7 @@ use actix_web::get;
|
||||
use actix_web::web::{Data, Json, ServiceConfig};
|
||||
use actix_web_httpauth::extractors::bearer::BearerAuth;
|
||||
use database_manager::Database;
|
||||
use model::api::AccountOrders;
|
||||
use model::api::Orders;
|
||||
use token_manager::TokenManager;
|
||||
|
||||
use crate::routes::RequireUser;
|
||||
@ -14,10 +14,10 @@ async fn orders(
|
||||
credentials: BearerAuth,
|
||||
tm: Data<Addr<TokenManager>>,
|
||||
db: Data<Addr<Database>>,
|
||||
) -> routes::Result<Json<AccountOrders>> {
|
||||
) -> routes::Result<Json<Orders>> {
|
||||
credentials.require_admin(tm.into_inner()).await?;
|
||||
|
||||
let orders: Vec<model::AccountOrder> = admin_send_db!(&db, database_manager::AllAccountOrders);
|
||||
let orders: Vec<model::Order> = admin_send_db!(&db, database_manager::AllAccountOrders);
|
||||
let items: Vec<model::OrderItem> = admin_send_db!(db, database_manager::AllOrderItems);
|
||||
|
||||
Ok(Json((orders, items).into()))
|
||||
|
@ -6,6 +6,7 @@ use actix_web_httpauth::extractors::bearer::BearerAuth;
|
||||
use cart_manager::{query_cart, CartManager};
|
||||
use database_manager::{query_db, Database};
|
||||
use model::api;
|
||||
use order_manager::{query_order, OrderManager};
|
||||
use payment_manager::{query_pay, PaymentManager};
|
||||
use token_manager::TokenManager;
|
||||
|
||||
@ -234,6 +235,7 @@ pub(crate) async fn create_order(
|
||||
tm: Data<Addr<TokenManager>>,
|
||||
credentials: BearerAuth,
|
||||
payment: Data<Addr<PaymentManager>>,
|
||||
order: Data<Addr<OrderManager>>,
|
||||
) -> routes::Result<HttpResponse> {
|
||||
let account_id = credentials
|
||||
.require_user(tm.into_inner())
|
||||
@ -248,6 +250,7 @@ pub(crate) async fn create_order(
|
||||
language,
|
||||
charge_client,
|
||||
currency,
|
||||
address,
|
||||
} = payload;
|
||||
let ip = match req.peer_addr() {
|
||||
Some(ip) => ip,
|
||||
@ -272,6 +275,32 @@ pub(crate) async fn create_order(
|
||||
routes::Error::Public(PublicError::DatabaseConnection)
|
||||
);
|
||||
|
||||
query_order!(
|
||||
order,
|
||||
order_manager::CreateAccountOrder {
|
||||
account_id,
|
||||
create_address: address.map(
|
||||
|model::api::CreateOrderAddress {
|
||||
name,
|
||||
email,
|
||||
street,
|
||||
city,
|
||||
country,
|
||||
zip,
|
||||
}| order_manager::CreateOrderAddress {
|
||||
name,
|
||||
email,
|
||||
street,
|
||||
city,
|
||||
country,
|
||||
zip,
|
||||
},
|
||||
),
|
||||
},
|
||||
PublicError::PlaceOrder,
|
||||
PublicError::DatabaseConnection
|
||||
)?;
|
||||
|
||||
Ok(HttpResponse::SeeOther()
|
||||
.append_header(("Location", redirect_uri.as_str()))
|
||||
.body(format!(
|
||||
|
@ -48,6 +48,8 @@ macro_rules! public_send_db {
|
||||
pub enum Error {
|
||||
#[error("{0}")]
|
||||
ApiV1(#[from] api_v1::Error),
|
||||
#[error("Failed to place order")]
|
||||
PlaceOrder,
|
||||
#[error("Internal server error")]
|
||||
DatabaseConnection,
|
||||
#[error("{0}")]
|
||||
|
19
migrations/20220523090806_change_orders.sql
Normal file
19
migrations/20220523090806_change_orders.sql
Normal file
@ -0,0 +1,19 @@
|
||||
ALTER TABLE account_orders
|
||||
ALTER COLUMN buyer_id DROP NOT NULL;
|
||||
|
||||
CREATE TABLE order_addresses
|
||||
(
|
||||
id serial not null primary key unique,
|
||||
name text not null,
|
||||
email text not null,
|
||||
street text not null,
|
||||
city text not null,
|
||||
country text not null,
|
||||
zip text not null
|
||||
);
|
||||
|
||||
ALTER TABLE account_orders
|
||||
ADD COLUMN address_id INT REFERENCES order_addresses (id);
|
||||
|
||||
ALTER TABLE account_orders
|
||||
RENAME TO orders;
|
@ -0,0 +1,2 @@
|
||||
ALTER TABLE account_addresses
|
||||
ADD COLUMN is_default BOOLEAN NOT NULL DEFAULT false;
|
@ -28,10 +28,10 @@ pub struct Account {
|
||||
pub role: Role,
|
||||
pub customer_id: uuid::Uuid,
|
||||
pub state: AccountState,
|
||||
pub addresses: Vec<Address>,
|
||||
pub addresses: Vec<AccountAddress>,
|
||||
}
|
||||
|
||||
impl From<(FullAccount, Vec<crate::Address>)> for Account {
|
||||
impl From<(FullAccount, Vec<crate::AccountAddress>)> for Account {
|
||||
fn from(
|
||||
(
|
||||
FullAccount {
|
||||
@ -44,7 +44,7 @@ impl From<(FullAccount, Vec<crate::Address>)> for Account {
|
||||
state,
|
||||
},
|
||||
addresses,
|
||||
): (FullAccount, Vec<crate::Address>),
|
||||
): (FullAccount, Vec<crate::AccountAddress>),
|
||||
) -> Self {
|
||||
Self {
|
||||
id,
|
||||
@ -59,7 +59,7 @@ impl From<(FullAccount, Vec<crate::Address>)> for Account {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Address {
|
||||
pub struct AccountAddress {
|
||||
pub id: AddressId,
|
||||
pub name: Name,
|
||||
pub email: Email,
|
||||
@ -68,11 +68,12 @@ pub struct Address {
|
||||
pub country: Country,
|
||||
pub zip: Zip,
|
||||
pub account_id: AccountId,
|
||||
pub is_default: bool,
|
||||
}
|
||||
|
||||
impl From<crate::Address> for Address {
|
||||
impl From<crate::AccountAddress> for AccountAddress {
|
||||
fn from(
|
||||
crate::Address {
|
||||
crate::AccountAddress {
|
||||
id,
|
||||
name,
|
||||
email,
|
||||
@ -81,7 +82,8 @@ impl From<crate::Address> for Address {
|
||||
country,
|
||||
zip,
|
||||
account_id,
|
||||
}: crate::Address,
|
||||
is_default,
|
||||
}: crate::AccountAddress,
|
||||
) -> Self {
|
||||
Self {
|
||||
id,
|
||||
@ -92,6 +94,7 @@ impl From<crate::Address> for Address {
|
||||
country,
|
||||
zip,
|
||||
account_id,
|
||||
is_default,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -99,15 +102,15 @@ impl From<crate::Address> for Address {
|
||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(transparent)]
|
||||
pub struct AccountOrders(pub Vec<AccountOrder>);
|
||||
pub struct Orders(pub Vec<Order>);
|
||||
|
||||
impl From<(Vec<crate::AccountOrder>, Vec<crate::OrderItem>)> for AccountOrders {
|
||||
fn from((orders, mut items): (Vec<crate::AccountOrder>, Vec<crate::OrderItem>)) -> Self {
|
||||
impl From<(Vec<crate::Order>, Vec<crate::OrderItem>)> for Orders {
|
||||
fn from((orders, mut items): (Vec<crate::Order>, Vec<crate::OrderItem>)) -> Self {
|
||||
Self(
|
||||
orders
|
||||
.into_iter()
|
||||
.map(
|
||||
|crate::AccountOrder {
|
||||
|crate::Order {
|
||||
id,
|
||||
buyer_id,
|
||||
status,
|
||||
@ -115,14 +118,16 @@ impl From<(Vec<crate::AccountOrder>, Vec<crate::OrderItem>)> for AccountOrders {
|
||||
order_ext_id: _,
|
||||
service_order_id: _,
|
||||
checkout_notes,
|
||||
address_id,
|
||||
}| {
|
||||
AccountOrder {
|
||||
Order {
|
||||
id,
|
||||
buyer_id,
|
||||
status,
|
||||
order_id,
|
||||
items: items.drain_filter(|item| item.order_id == id).collect(),
|
||||
checkout_notes,
|
||||
address_id,
|
||||
}
|
||||
},
|
||||
)
|
||||
@ -131,10 +136,10 @@ impl From<(Vec<crate::AccountOrder>, Vec<crate::OrderItem>)> for AccountOrders {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(crate::AccountOrder, Vec<crate::OrderItem>)> for AccountOrder {
|
||||
impl From<(crate::Order, Vec<crate::OrderItem>)> for Order {
|
||||
fn from(
|
||||
(
|
||||
crate::AccountOrder {
|
||||
crate::Order {
|
||||
id,
|
||||
buyer_id,
|
||||
status,
|
||||
@ -142,30 +147,33 @@ impl From<(crate::AccountOrder, Vec<crate::OrderItem>)> for AccountOrder {
|
||||
order_ext_id: _,
|
||||
service_order_id: _,
|
||||
checkout_notes,
|
||||
address_id,
|
||||
},
|
||||
mut items,
|
||||
): (crate::AccountOrder, Vec<crate::OrderItem>),
|
||||
): (crate::Order, Vec<crate::OrderItem>),
|
||||
) -> Self {
|
||||
AccountOrder {
|
||||
Order {
|
||||
id,
|
||||
buyer_id,
|
||||
status,
|
||||
order_id,
|
||||
items: items.drain_filter(|item| item.order_id == id).collect(),
|
||||
checkout_notes,
|
||||
address_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct AccountOrder {
|
||||
pub id: crate::AccountOrderId,
|
||||
pub struct Order {
|
||||
pub id: crate::OrderId,
|
||||
pub buyer_id: crate::AccountId,
|
||||
pub status: crate::OrderStatus,
|
||||
pub order_id: Option<crate::OrderId>,
|
||||
pub order_id: Option<crate::ExtOrderId>,
|
||||
pub items: Vec<crate::OrderItem>,
|
||||
pub checkout_notes: Option<String>,
|
||||
pub address_id: OrderAddressId,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||
@ -436,6 +444,7 @@ pub struct CreateOrderInput {
|
||||
pub charge_client: bool,
|
||||
/// User currency
|
||||
pub currency: String,
|
||||
pub address: Option<CreateOrderAddress>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
@ -482,6 +491,27 @@ pub struct SearchRequest {
|
||||
pub lang: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct CreateOrderAddress {
|
||||
pub name: Name,
|
||||
pub email: Email,
|
||||
pub street: Street,
|
||||
pub city: City,
|
||||
pub country: Country,
|
||||
pub zip: Zip,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct UpdateOrderAddress {
|
||||
pub id: OrderAddressId,
|
||||
pub name: Name,
|
||||
pub email: Email,
|
||||
pub street: Street,
|
||||
pub city: City,
|
||||
pub country: Country,
|
||||
pub zip: Zip,
|
||||
}
|
||||
|
||||
pub mod admin {
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
@ -342,7 +342,7 @@ impl Login {
|
||||
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
||||
#[derive(Serialize, Debug, Clone, Deref, DerefMut, From, Display)]
|
||||
#[derive(Serialize, Debug, Clone, Default, Deref, DerefMut, From, Display)]
|
||||
#[serde(transparent)]
|
||||
pub struct Email(String);
|
||||
|
||||
@ -843,42 +843,51 @@ pub struct Stock {
|
||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Display, Deref)]
|
||||
#[serde(transparent)]
|
||||
pub struct AccountOrderId(RecordId);
|
||||
pub struct OrderAddressId(RecordId);
|
||||
|
||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Display, Deref)]
|
||||
#[serde(transparent)]
|
||||
pub struct OrderId(RecordId);
|
||||
|
||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Display, Deref)]
|
||||
#[serde(transparent)]
|
||||
pub struct OrderId(String);
|
||||
pub struct ExtOrderId(String);
|
||||
|
||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||
#[cfg_attr(feature = "db", derive(sqlx::FromRow))]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct AccountOrder {
|
||||
pub id: AccountOrderId,
|
||||
pub struct Order {
|
||||
pub id: OrderId,
|
||||
pub buyer_id: AccountId,
|
||||
pub status: OrderStatus,
|
||||
pub order_id: Option<OrderId>,
|
||||
pub order_id: Option<ExtOrderId>,
|
||||
pub order_ext_id: uuid::Uuid,
|
||||
pub service_order_id: Option<String>,
|
||||
pub checkout_notes: Option<String>,
|
||||
pub address_id: OrderAddressId,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||
#[cfg_attr(feature = "db", derive(sqlx::FromRow))]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct PublicAccountOrder {
|
||||
pub id: AccountOrderId,
|
||||
pub id: OrderId,
|
||||
pub buyer_id: AccountId,
|
||||
pub status: OrderStatus,
|
||||
pub order_id: Option<OrderId>,
|
||||
pub order_id: Option<ExtOrderId>,
|
||||
pub checkout_notes: String,
|
||||
pub address_id: OrderAddressId,
|
||||
}
|
||||
|
||||
impl From<AccountOrder> for PublicAccountOrder {
|
||||
impl From<Order> for PublicAccountOrder {
|
||||
fn from(
|
||||
AccountOrder {
|
||||
Order {
|
||||
id,
|
||||
buyer_id,
|
||||
status,
|
||||
@ -886,7 +895,8 @@ impl From<AccountOrder> for PublicAccountOrder {
|
||||
order_ext_id: _,
|
||||
service_order_id: _,
|
||||
checkout_notes,
|
||||
}: AccountOrder,
|
||||
address_id,
|
||||
}: Order,
|
||||
) -> Self {
|
||||
Self {
|
||||
id,
|
||||
@ -894,6 +904,7 @@ impl From<AccountOrder> for PublicAccountOrder {
|
||||
status,
|
||||
order_id,
|
||||
checkout_notes: checkout_notes.unwrap_or_default(),
|
||||
address_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -910,7 +921,7 @@ pub struct OrderItemId(pub RecordId);
|
||||
pub struct OrderItem {
|
||||
pub id: OrderItemId,
|
||||
pub product_id: ProductId,
|
||||
pub order_id: AccountOrderId,
|
||||
pub order_id: OrderId,
|
||||
pub quantity: Quantity,
|
||||
pub quantity_unit: QuantityUnit,
|
||||
}
|
||||
@ -1128,7 +1139,7 @@ pub struct AddressId(RecordId);
|
||||
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Deref, DerefMut, From, Display)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default, Deref, DerefMut, From, Display)]
|
||||
#[serde(transparent)]
|
||||
pub struct Name(String);
|
||||
|
||||
@ -1140,7 +1151,7 @@ impl Name {
|
||||
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Deref, DerefMut, From, Display)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default, Deref, DerefMut, From, Display)]
|
||||
#[serde(transparent)]
|
||||
pub struct Street(String);
|
||||
|
||||
@ -1152,7 +1163,7 @@ impl Street {
|
||||
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Deref, DerefMut, From, Display)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default, Deref, DerefMut, From, Display)]
|
||||
#[serde(transparent)]
|
||||
pub struct City(String);
|
||||
|
||||
@ -1164,7 +1175,7 @@ impl City {
|
||||
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Deref, DerefMut, From, Display)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default, Deref, DerefMut, From, Display)]
|
||||
#[serde(transparent)]
|
||||
pub struct Country(String);
|
||||
|
||||
@ -1176,7 +1187,7 @@ impl Country {
|
||||
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Deref, DerefMut, From, Display)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default, Deref, DerefMut, From, Display)]
|
||||
#[serde(transparent)]
|
||||
pub struct Zip(String);
|
||||
|
||||
@ -1189,7 +1200,7 @@ impl Zip {
|
||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||
#[cfg_attr(feature = "db", derive(sqlx::FromRow))]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Address {
|
||||
pub struct AccountAddress {
|
||||
pub id: AddressId,
|
||||
pub name: Name,
|
||||
pub email: Email,
|
||||
@ -1198,4 +1209,18 @@ pub struct Address {
|
||||
pub country: Country,
|
||||
pub zip: Zip,
|
||||
pub account_id: AccountId,
|
||||
pub is_default: bool,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||
#[cfg_attr(feature = "db", derive(sqlx::FromRow))]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct OrderAddress {
|
||||
pub id: OrderAddressId,
|
||||
pub name: Name,
|
||||
pub email: Email,
|
||||
pub street: Street,
|
||||
pub city: City,
|
||||
pub country: Country,
|
||||
pub zip: Zip,
|
||||
}
|
||||
|
@ -117,3 +117,24 @@ pub async fn update_cart(
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn place_order(access_token: AccessTokenString) -> NetRes<String> {
|
||||
let input = model::api::CreateOrderInput {
|
||||
email: "".to_string(),
|
||||
phone: "".to_string(),
|
||||
first_name: "".to_string(),
|
||||
last_name: "".to_string(),
|
||||
language: "".to_string(),
|
||||
charge_client: false,
|
||||
currency: "".to_string(),
|
||||
address: None,
|
||||
};
|
||||
perform(
|
||||
Request::new("/api/v1/order")
|
||||
.method(Method::Post)
|
||||
.header(Header::bearer(access_token.as_str()))
|
||||
.json(&input)
|
||||
.map_err(NetRes::Http)?,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use seed::prelude::*;
|
||||
use seed::*;
|
||||
|
||||
@ -7,11 +9,28 @@ use crate::NetRes;
|
||||
#[derive(Debug)]
|
||||
pub enum CheckoutMsg {
|
||||
ProductsFetched(NetRes<model::api::Products>),
|
||||
AddressNameChanged(String),
|
||||
AddressEmailChanged(String),
|
||||
AddressStreetChanged(String),
|
||||
AddressCityChanged(String),
|
||||
AddressCountryChanged(String),
|
||||
AddressZipChanged(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct AddressForm {
|
||||
pub name: model::Name,
|
||||
pub email: model::Email,
|
||||
pub street: model::Street,
|
||||
pub city: model::City,
|
||||
pub country: model::Country,
|
||||
pub zip: model::Zip,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CheckoutPage {
|
||||
pub products: Products,
|
||||
pub address: AddressForm,
|
||||
}
|
||||
|
||||
pub fn init(_url: Url, orders: &mut impl Orders<crate::Msg>) -> CheckoutPage {
|
||||
@ -22,6 +41,7 @@ pub fn init(_url: Url, orders: &mut impl Orders<crate::Msg>) -> CheckoutPage {
|
||||
});
|
||||
CheckoutPage {
|
||||
products: Default::default(),
|
||||
address: AddressForm::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -38,6 +58,26 @@ pub fn update(msg: CheckoutMsg, model: &mut CheckoutPage, _orders: &mut impl Ord
|
||||
CheckoutMsg::ProductsFetched(NetRes::Http(e)) => {
|
||||
seed::error!("fetch product http", e);
|
||||
}
|
||||
CheckoutMsg::AddressNameChanged(value) => {
|
||||
model.address.name = model::Name::new(value);
|
||||
}
|
||||
CheckoutMsg::AddressEmailChanged(value) => {
|
||||
if let Ok(value) = model::Email::from_str(&value) {
|
||||
model.address.email = value;
|
||||
}
|
||||
}
|
||||
CheckoutMsg::AddressStreetChanged(value) => {
|
||||
model.address.street = model::Street::new(value);
|
||||
}
|
||||
CheckoutMsg::AddressCityChanged(value) => {
|
||||
model.address.city = model::City::new(value);
|
||||
}
|
||||
CheckoutMsg::AddressCountryChanged(value) => {
|
||||
model.address.country = model::Country::new(value);
|
||||
}
|
||||
CheckoutMsg::AddressZipChanged(value) => {
|
||||
model.address.zip = model::Zip::new(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -182,7 +222,8 @@ mod right_side {
|
||||
use seed::prelude::*;
|
||||
use seed::*;
|
||||
|
||||
use crate::pages::public::checkout::CheckoutPage;
|
||||
use crate::pages::public::checkout::{CheckoutMsg, CheckoutPage};
|
||||
use crate::pages::public::sign_up::RegisterMsg;
|
||||
use crate::shopping_cart::CartMsg;
|
||||
use crate::Msg;
|
||||
|
||||
@ -204,11 +245,15 @@ mod right_side {
|
||||
]
|
||||
}
|
||||
|
||||
fn contact(model: &crate::Model, _page: &CheckoutPage) -> Node<Msg> {
|
||||
if model.shared.me.is_some() {
|
||||
// TODO: Display user addresses
|
||||
return empty![];
|
||||
fn contact(model: &crate::Model, page: &CheckoutPage) -> Node<Msg> {
|
||||
match &model.shared.me {
|
||||
Some(me) if me.addresses.is_empty() => contact_form(model, page),
|
||||
Some(_me) => empty![],
|
||||
None => contact_form(model, page),
|
||||
}
|
||||
}
|
||||
|
||||
fn contact_form(model: &crate::Model, _page: &CheckoutPage) -> Node<Msg> {
|
||||
div![
|
||||
C!["w-full mx-auto rounded-lg bg-white border border-gray-200 p-3 text-gray-800 font-light mb-6"],
|
||||
form![
|
||||
@ -219,19 +264,13 @@ mod right_side {
|
||||
div![
|
||||
C!["mb-3"],
|
||||
div![
|
||||
input![
|
||||
C!["w-full px-3 py-2 mb-1 border border-gray-200 rounded-md focus:outline-none focus:border-indigo-500 transition-colors"],
|
||||
attrs![At::Id => "client-name", At::Placeholder => model.i18n.t("Name")]
|
||||
]
|
||||
address_input(model, "client-name", "text", "Name", CheckoutMsg::AddressNameChanged),
|
||||
],
|
||||
],
|
||||
div![
|
||||
C!["mb-3"],
|
||||
div![
|
||||
input![
|
||||
C!["w-full px-3 py-2 mb-1 border border-gray-200 rounded-md focus:outline-none focus:border-indigo-500 transition-colors"],
|
||||
attrs![At::Id => "client-email", At::Type => "email", At::Placeholder => model.i18n.t("E-Mail")]
|
||||
]
|
||||
address_input(model, "client-email", "email", "E-Mail", CheckoutMsg::AddressEmailChanged),
|
||||
],
|
||||
],
|
||||
div![
|
||||
@ -240,36 +279,47 @@ mod right_side {
|
||||
],
|
||||
div![
|
||||
C!["mb-3"],
|
||||
input![
|
||||
C!["w-full px-3 py-2 mb-1 border border-gray-200 rounded-md focus:outline-none focus:border-indigo-500 transition-colors"],
|
||||
attrs![At::Id => "client-street", At::Placeholder => model.i18n.t("Street")]
|
||||
]
|
||||
address_input(model, "client-street", "text", "Street", CheckoutMsg::AddressStreetChanged),
|
||||
],
|
||||
div![
|
||||
C!["mb-3"],
|
||||
input![
|
||||
C!["w-full px-3 py-2 mb-1 border border-gray-200 rounded-md focus:outline-none focus:border-indigo-500 transition-colors"],
|
||||
attrs![At::Id => "client-city", At::Placeholder => model.i18n.t("City")]
|
||||
]
|
||||
address_input(model, "client-city", "text", "City", CheckoutMsg::AddressCityChanged),
|
||||
],
|
||||
div![
|
||||
C!["mb-3 inline-block w-1/2 pr-1"],
|
||||
input![
|
||||
C!["w-full px-3 py-2 mb-1 border border-gray-200 rounded-md focus:outline-none focus:border-indigo-500 transition-colors"],
|
||||
attrs![At::Id => "client-country", At::Placeholder => model.i18n.t("Country")]
|
||||
]
|
||||
address_input(model, "client-country", "text", "Country", CheckoutMsg::AddressCountryChanged),
|
||||
],
|
||||
div![
|
||||
C!["mb-3 inline-block -mx-1 pl-1 w-1/2"],
|
||||
input![
|
||||
C!["w-full px-3 py-2 mb-1 border border-gray-200 rounded-md focus:outline-none focus:border-indigo-500 transition-colors"],
|
||||
attrs![At::Id => "client-zip", At::Placeholder => model.i18n.t("Zip")]
|
||||
]
|
||||
address_input(model, "client-zip", "text", "Zip", CheckoutMsg::AddressZipChanged),
|
||||
],
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
fn address_input<F>(
|
||||
model: &crate::Model,
|
||||
id: &str,
|
||||
ty: &str,
|
||||
label: &'static str,
|
||||
msg: F,
|
||||
) -> Node<Msg>
|
||||
where
|
||||
F: Clone + 'static + Fn(String) -> CheckoutMsg,
|
||||
{
|
||||
let handler = ev(Ev::Change, move |ev| {
|
||||
ev.prevent_default();
|
||||
let target = ev.target()?;
|
||||
let input = seed::to_input(&target);
|
||||
Some(crate::Msg::from(msg(input.value())))
|
||||
});
|
||||
input![
|
||||
C!["w-full px-3 py-2 mb-1 border border-gray-200 rounded-md focus:outline-none focus:border-indigo-500 transition-colors"],
|
||||
attrs![At::Id => id, At::Type => ty, At::Placeholder => model.i18n.t(label), At::Required => true],
|
||||
handler,
|
||||
]
|
||||
}
|
||||
|
||||
fn pay_now(model: &crate::Model) -> Node<Msg> {
|
||||
div![
|
||||
button![
|
||||
@ -292,13 +342,7 @@ mod right_side {
|
||||
}
|
||||
|
||||
fn pay_u(model: &crate::Model) -> Node<Msg> {
|
||||
payment_input(
|
||||
model,
|
||||
model::PaymentMethod::PayU,
|
||||
"pay_u",
|
||||
"PayU",
|
||||
pay_u_icon(),
|
||||
)
|
||||
payment_input(model, PaymentMethod::PayU, "pay_u", "PayU", pay_u_icon())
|
||||
}
|
||||
|
||||
fn pay_u_icon() -> Node<Msg> {
|
||||
@ -329,7 +373,7 @@ mod right_side {
|
||||
fn pay_on_spot(model: &crate::Model) -> Node<Msg> {
|
||||
payment_input(
|
||||
model,
|
||||
model::PaymentMethod::PaymentOnTheSpot,
|
||||
PaymentMethod::PaymentOnTheSpot,
|
||||
"pay_on_spot",
|
||||
"Pay on spot",
|
||||
pay_in_spot_icon(),
|
||||
@ -397,7 +441,7 @@ mod right_side {
|
||||
|
||||
fn payment_input(
|
||||
model: &crate::Model,
|
||||
method: model::PaymentMethod,
|
||||
method: PaymentMethod,
|
||||
name: &str,
|
||||
label: &'static str,
|
||||
icon: Node<Msg>,
|
||||
|
@ -56,7 +56,6 @@ pub fn update(msg: RegisterMsg, model: &mut SignUpPage, orders: &mut impl Orders
|
||||
let email = model.email.clone();
|
||||
let login = model.login.clone();
|
||||
let password = model.password.clone();
|
||||
let password_confirmation = model.password_confirmation.clone();
|
||||
|
||||
orders.perform_cmd(async move {
|
||||
crate::Msg::Public(
|
||||
@ -65,7 +64,6 @@ pub fn update(msg: RegisterMsg, model: &mut SignUpPage, orders: &mut impl Orders
|
||||
email,
|
||||
login,
|
||||
password,
|
||||
password_confirmation,
|
||||
})
|
||||
.await,
|
||||
)
|
||||
@ -84,16 +82,15 @@ pub fn update(msg: RegisterMsg, model: &mut SignUpPage, orders: &mut impl Orders
|
||||
|
||||
pub fn view(model: &crate::Model, page: &SignUpPage) -> Node<crate::Msg> {
|
||||
let home = Urls::new(&model.url).home();
|
||||
let logo = model
|
||||
.logo
|
||||
.as_deref()
|
||||
.map(|src| {
|
||||
let logo = model.logo.as_deref().map_or_else(
|
||||
|| a![attrs![At::Href => home], "Logo"],
|
||||
|src| {
|
||||
a![
|
||||
attrs![At::Href => home],
|
||||
img![attrs![At::Src => src], C!["m-auto"]]
|
||||
]
|
||||
})
|
||||
.unwrap_or_else(|| a![attrs![At::Href => home], "Logo"]);
|
||||
},
|
||||
);
|
||||
|
||||
let content = div![
|
||||
C!["relative flex flex-col justify-center overflow-hidden"],
|
||||
@ -129,51 +126,54 @@ fn sign_up_form(model: &crate::Model, _page: &SignUpPage) -> Node<RegisterMsg> {
|
||||
RegisterMsg::Submit
|
||||
}),
|
||||
div![
|
||||
label![attrs!["for" => "email"], C!["block text-sm text-indigo-800"], model.i18n.t("E-Mail")],
|
||||
label![attrs![At::For => "email"], C!["block text-sm text-indigo-800"], model.i18n.t("E-Mail")],
|
||||
input![
|
||||
attrs!["type" => "email", "id" => "email"],
|
||||
attrs![At::Type => "email", At::Id => "email"],
|
||||
C!["block w-full px-4 py-2 mt-2 text-indigo-700 bg-white border rounded-md focus:border-indigo-400 focus:ring-indigo-300 focus:outline-none focus:ring focus:ring-opacity-40"],
|
||||
ev(Ev::Change, |ev| {
|
||||
ev.stop_propagation();
|
||||
ev.target().map(|target| seed::to_input(&target).value()).map(RegisterMsg::EmailChanged)
|
||||
})
|
||||
}),
|
||||
]
|
||||
],
|
||||
div![
|
||||
label![attrs!["for" => "login"], C!["block text-sm text-indigo-800"], model.i18n.t("Login")],
|
||||
label![attrs![At::For => "login"], C!["block text-sm text-indigo-800"], model.i18n.t("Login")],
|
||||
input![
|
||||
attrs!["type" => "text", "id" => "login"],
|
||||
attrs![At::Type => "text", At::Id => "login"],
|
||||
C!["block w-full px-4 py-2 mt-2 text-indigo-700 bg-white border rounded-md focus:border-indigo-400 focus:ring-indigo-300 focus:outline-none focus:ring focus:ring-opacity-40"],
|
||||
ev(Ev::Change, |ev| {
|
||||
ev.stop_propagation();
|
||||
ev.target().map(|target| seed::to_input(&target).value()).map(RegisterMsg::LoginChanged)
|
||||
})
|
||||
}),
|
||||
]
|
||||
],
|
||||
div![
|
||||
C!["mt-4"],
|
||||
div![
|
||||
label![attrs!["for" => "password"], C!["block text-sm text-indigo-800"], model.i18n.t("Password")],
|
||||
input![attrs!["type" => "password", "id" => "password"], C!["block w-full px-4 py-2 mt-2 text-indigo-700 bg-white border rounded-md focus:border-indigo-400 focus:ring-indigo-300 focus:outline-none focus:ring focus:ring-opacity-40"]],
|
||||
label![attrs![At::For => "password"], C!["block text-sm text-indigo-800"], model.i18n.t("Password")],
|
||||
input![
|
||||
attrs![At::Type => "password", At::Id => "password"],
|
||||
C!["block w-full px-4 py-2 mt-2 text-indigo-700 bg-white border rounded-md focus:border-indigo-400 focus:ring-indigo-300 focus:outline-none focus:ring focus:ring-opacity-40"],
|
||||
ev(Ev::Change, |ev| {
|
||||
ev.stop_propagation();
|
||||
ev.target().map(|target| seed::to_input(&target).value()).map(RegisterMsg::PasswordChanged)
|
||||
})
|
||||
]
|
||||
],
|
||||
div![
|
||||
label![
|
||||
attrs!["for" => "password-confirmation"],
|
||||
attrs![At::For => "password-confirmation"],
|
||||
C!["block text-sm text-indigo-800"],
|
||||
model.i18n.t("Password confirmation")
|
||||
],
|
||||
input![
|
||||
attrs!["type" => "password", "id" => "password-confirmation"],
|
||||
C!["block w-full px-4 py-2 mt-2 text-indigo-700 bg-white border rounded-md focus:border-indigo-400 focus:ring-indigo-300 focus:outline-none focus:ring focus:ring-opacity-40"]
|
||||
],
|
||||
attrs![At::Type => "password", At::Id => "password-confirmation"],
|
||||
C!["block w-full px-4 py-2 mt-2 text-indigo-700 bg-white border rounded-md focus:border-indigo-400 focus:ring-indigo-300 focus:outline-none focus:ring focus:ring-opacity-40"],
|
||||
ev(Ev::Change, |ev| {
|
||||
ev.stop_propagation();
|
||||
ev.target().map(|target| seed::to_input(&target).value()).map(RegisterMsg::PasswordConfirmationChanged)
|
||||
})
|
||||
}),
|
||||
]
|
||||
],
|
||||
div![
|
||||
C!["mt-6"],
|
||||
|
Loading…
Reference in New Issue
Block a user