Fix order, add non negative constructor

This commit is contained in:
eraden 2022-04-16 07:31:19 +02:00
parent 5354d909b3
commit 388bfd634a
8 changed files with 199 additions and 21 deletions

View File

@ -3,11 +3,13 @@ use sqlx::PgPool;
pub use account_orders::*; pub use account_orders::*;
pub use accounts::*; pub use accounts::*;
pub use order_items::*;
pub use products::*; pub use products::*;
pub use stocks::*; pub use stocks::*;
mod account_orders; mod account_orders;
mod accounts; mod accounts;
mod order_items;
mod products; mod products;
mod stocks; mod stocks;
@ -37,6 +39,8 @@ pub enum Error {
Product(products::Error), Product(products::Error),
#[error("{0}")] #[error("{0}")]
Stock(stocks::Error), Stock(stocks::Error),
#[error("{0}")]
OrderItem(order_items::Error),
} }
pub type Result<T> = std::result::Result<T, Error>; pub type Result<T> = std::result::Result<T, Error>;

View File

@ -9,13 +9,13 @@ use super::Result;
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum Error { pub enum Error {
#[error("Can't create account")] #[error("Can't create account order")]
CantCreate, CantCreate,
#[error("Can't find account does to lack of identity")] #[error("Can't find account order does to lack of identity")]
NoIdentity, NoIdentity,
#[error("Account order does not exists")] #[error("Account order does not exists")]
NotExists, NotExists,
#[error("Failed to load all accounts")] #[error("Failed to load all account orders")]
All, All,
} }

View File

@ -0,0 +1,93 @@
use crate::async_handler;
use actix::{Handler, ResponseActFuture, WrapFuture};
use sqlx::PgPool;
use crate::database::Database;
use crate::model::*;
use super::Result;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Can't create order item")]
CantCreate,
#[error("Can't find order item does to lack of identity")]
NoIdentity,
#[error("Order item does not exists")]
NotExists,
#[error("Failed to load all order items")]
All,
}
#[derive(actix::Message)]
#[rtype(result = "Result<Vec<OrderItem>>")]
pub struct AllOrderItems;
async_handler!(AllOrderItems, all_order_items, Vec<OrderItem>);
pub async fn all_order_items(_msg: AllOrderItems, pool: PgPool) -> Result<Vec<OrderItem>> {
sqlx::query_as(
r#"
SELECT id, buyer_id, status
FROM order_items
"#,
)
.fetch_all(&pool)
.await
.map_err(|e| {
log::error!("{e:?}");
super::Error::OrderItem(Error::All)
})
}
#[derive(actix::Message)]
#[rtype(result = "Result<OrderItem>")]
pub struct CreateOrderItem {
pub buyer_id: AccountId,
pub status: OrderStatus,
}
async_handler!(CreateOrderItem, create_order_item, OrderItem);
async fn create_order_item(msg: CreateOrderItem, db: PgPool) -> Result<OrderItem> {
sqlx::query_as(
r#"
INSERT INTO order_items (buyer_id, status)
VALUES ($1, $2)
RETURNING id, buyer_id, status
"#,
)
.bind(msg.buyer_id)
.bind(msg.status)
.fetch_one(&db)
.await
.map_err(|e| {
log::error!("{e:?}");
super::Error::OrderItem(Error::CantCreate)
})
}
#[derive(actix::Message)]
#[rtype(result = "Result<OrderItem>")]
pub struct FindOrderItem {
pub id: OrderItemId,
}
async_handler!(FindOrderItem, find_order_item, OrderItem);
async fn find_order_item(msg: FindOrderItem, db: PgPool) -> Result<OrderItem> {
sqlx::query_as(
r#"
SELECT id, buyer_id, status
FROM order_items
WHERE id = $1
"#,
)
.bind(msg.id)
.fetch_one(&db)
.await
.map_err(|e| {
log::error!("{e:?}");
super::Error::OrderItem(Error::NotExists)
})
}

View File

@ -8,13 +8,13 @@ use crate::{database, model};
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum Error { pub enum Error {
#[error("Unable to load all products")] #[error("Unable to load all stocks")]
All, All,
#[error("Unable to create product")] #[error("Unable to create stock")]
Create, Create,
#[error("Unable to update product")] #[error("Unable to update stock")]
Update, Update,
#[error("Unable to delete product")] #[error("Unable to delete stock")]
Delete, Delete,
} }
@ -22,9 +22,9 @@ pub enum Error {
#[rtype(result = "Result<Vec<model::Stock>>")] #[rtype(result = "Result<Vec<model::Stock>>")]
pub struct AllStocks; pub struct AllStocks;
crate::async_handler!(AllStocks, all, Vec<Stock>); crate::async_handler!(AllStocks, all_stocks, Vec<Stock>);
async fn all(_msg: AllStocks, pool: PgPool) -> Result<Vec<model::Stock>> { async fn all_stocks(_msg: AllStocks, pool: PgPool) -> Result<Vec<model::Stock>> {
sqlx::query_as( sqlx::query_as(
r#" r#"
SELECT id, product_id, quantity, quantity_unit SELECT id, product_id, quantity, quantity_unit
@ -47,9 +47,9 @@ pub struct CreateStock {
pub quantity_unit: QuantityUnit, pub quantity_unit: QuantityUnit,
} }
crate::async_handler!(CreateStock, create_product, Stock); crate::async_handler!(CreateStock, create_stock, Stock);
async fn create_product(msg: CreateStock, pool: PgPool) -> Result<model::Stock> { async fn create_stock(msg: CreateStock, pool: PgPool) -> Result<model::Stock> {
sqlx::query_as( sqlx::query_as(
r#" r#"
INSERT INTO stocks (product_id, quantity) INSERT INTO stocks (product_id, quantity)
@ -77,9 +77,9 @@ pub struct UpdateStock {
pub quantity_unit: QuantityUnit, pub quantity_unit: QuantityUnit,
} }
crate::async_handler!(UpdateStock, update_product, Stock); crate::async_handler!(UpdateStock, update_stock, Stock);
async fn update_product(msg: UpdateStock, pool: PgPool) -> Result<model::Stock> { async fn update_stock(msg: UpdateStock, pool: PgPool) -> Result<model::Stock> {
sqlx::query_as( sqlx::query_as(
r#" r#"
UPDATE stocks UPDATE stocks
@ -108,9 +108,9 @@ pub struct DeleteStock {
pub stock_id: StockId, pub stock_id: StockId,
} }
crate::async_handler!(DeleteStock, delete_product, Option<model::Stock>); crate::async_handler!(DeleteStock, delete_stock, Option<model::Stock>);
async fn delete_product(msg: DeleteStock, pool: PgPool) -> Result<Option<Stock>> { async fn delete_stock(msg: DeleteStock, pool: PgPool) -> Result<Option<Stock>> {
sqlx::query_as( sqlx::query_as(
r#" r#"
DELETE FROM stocks DELETE FROM stocks

View File

@ -5,7 +5,7 @@ use actix_session::{storage::RedisActorSessionStore, SessionMiddleware};
use actix_web::cookie::Key; use actix_web::cookie::Key;
use actix_web::middleware::Logger; use actix_web::middleware::Logger;
use actix_web::web::Data; use actix_web::web::Data;
use actix_web::{web, App, HttpResponse, HttpServer}; use actix_web::{App, HttpServer};
use gumdrop::Options; use gumdrop::Options;
use password_hash::SaltString; use password_hash::SaltString;
use validator::{validate_email, validate_length}; use validator::{validate_email, validate_length};

View File

@ -4,6 +4,12 @@ use derive_more::{Deref, Display};
use serde::de::{Error, Visitor}; use serde::de::{Error, Visitor};
use serde::{Deserialize, Deserializer, Serialize}; use serde::{Deserialize, Deserializer, Serialize};
#[derive(Debug, thiserror::Error)]
pub enum TransformError {
#[error("Given value is below minimal value")]
BelowMinimal,
}
pub type RecordId = i32; pub type RecordId = i32;
#[derive(sqlx::Type, Copy, Clone, Debug, Display, Deserialize, Serialize)] #[derive(sqlx::Type, Copy, Clone, Debug, Display, Deserialize, Serialize)]
@ -41,6 +47,13 @@ pub enum QuantityUnit {
Unit, Unit,
} }
#[derive(sqlx::Type, Copy, Clone, Debug, Display, Deserialize, Serialize)]
#[sqlx(rename_all = "lowercase")]
pub enum PaymentMethod {
PayU,
PaymentOnTheSpot,
}
#[derive(sqlx::Type, Serialize, Deserialize, Deref)] #[derive(sqlx::Type, Serialize, Deserialize, Deref)]
#[sqlx(transparent)] #[sqlx(transparent)]
#[serde(transparent)] #[serde(transparent)]
@ -56,6 +69,22 @@ pub struct PriceMinor(NonNegative);
#[serde(transparent)] #[serde(transparent)]
pub struct Quantity(NonNegative); pub struct Quantity(NonNegative);
impl TryFrom<NonNegative> for Quantity {
type Error = TransformError;
fn try_from(value: NonNegative) -> Result<Self, Self::Error> {
Ok(Self(value))
}
}
impl TryFrom<i32> for Quantity {
type Error = TransformError;
fn try_from(value: i32) -> Result<Self, Self::Error> {
Ok(Self(value.try_into()?))
}
}
#[derive(sqlx::Type, Deserialize, Serialize, Deref, Debug)] #[derive(sqlx::Type, Deserialize, Serialize, Deref, Debug)]
#[sqlx(transparent)] #[sqlx(transparent)]
#[serde(transparent)] #[serde(transparent)]
@ -98,7 +127,19 @@ impl<'de> serde::Deserialize<'de> for Email {
#[derive(sqlx::Type, Serialize, Deref)] #[derive(sqlx::Type, Serialize, Deref)]
#[sqlx(transparent)] #[sqlx(transparent)]
#[serde(transparent)] #[serde(transparent)]
pub struct NonNegative(pub i32); pub struct NonNegative(i32);
impl TryFrom<i32> for NonNegative {
type Error = TransformError;
fn try_from(value: i32) -> Result<Self, Self::Error> {
if value < 0 {
return Err(TransformError::BelowMinimal);
} else {
Ok(Self(value))
}
}
}
impl<'de> serde::Deserialize<'de> for NonNegative { impl<'de> serde::Deserialize<'de> for NonNegative {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
@ -227,7 +268,7 @@ pub struct Stock {
pub quantity_unit: QuantityUnit, pub quantity_unit: QuantityUnit,
} }
#[derive(sqlx::Type, Serialize, Deserialize)] #[derive(sqlx::Type, Serialize, Deserialize, Deref)]
#[sqlx(transparent)] #[sqlx(transparent)]
#[serde(transparent)] #[serde(transparent)]
pub struct AccountOrderId(pub RecordId); pub struct AccountOrderId(pub RecordId);
@ -238,3 +279,43 @@ pub struct AccountOrder {
pub buyer_id: AccountId, pub buyer_id: AccountId,
pub status: OrderStatus, pub status: OrderStatus,
} }
#[derive(sqlx::Type, Serialize, Deserialize, Deref)]
#[sqlx(transparent)]
#[serde(transparent)]
pub struct OrderItemId(pub RecordId);
#[derive(sqlx::FromRow, Serialize, Deserialize)]
pub struct OrderItem {
pub id: OrderItemId,
pub product_id: ProductId,
pub order_id: OrderItemId,
pub quantity: Quantity,
pub quantity_unit: QuantityUnit,
}
#[derive(sqlx::Type, Serialize, Deserialize, Deref)]
#[sqlx(transparent)]
#[serde(transparent)]
pub struct ShoppingCartId(pub RecordId);
#[derive(sqlx::FromRow, Serialize, Deserialize)]
pub struct ShoppingCart {
pub id: ShoppingCartId,
pub buyer_id: AccountId,
pub payment_method: PaymentMethod,
}
#[derive(sqlx::Type, Serialize, Deserialize, Deref)]
#[sqlx(transparent)]
#[serde(transparent)]
pub struct ShoppingCartItemId(pub RecordId);
#[derive(sqlx::FromRow, Serialize, Deserialize)]
pub struct ShoppingCartItem {
pub id: ShoppingCartId,
pub product_id: ProductId,
pub shopping_cart_id: ShoppingCartId,
pub quantity: Quantity,
pub quantity_unit: QuantityUnit,
}

View File

@ -68,7 +68,7 @@ CREATE TABLE order_items
CONSTRAINT positive_quantity check ( quantity >= 0 ) CONSTRAINT positive_quantity check ( quantity >= 0 )
); );
CREATE TABLE statistics CREATE TABLE "statistics"
( (
id serial not null primary key, id serial not null primary key,
url varchar not null, url varchar not null,

View File

@ -1,5 +1,5 @@
CREATE TYPE "PaymentMethod" AS ENUM ( CREATE TYPE "PaymentMethod" AS ENUM (
'payu', 'pay_u',
'payment_on_the_spot' 'payment_on_the_spot'
); );
@ -14,7 +14,7 @@ CREATE TABLE shopping_cart_items
( (
id serial not null primary key, id serial not null primary key,
product_id int references products (id) not null, product_id int references products (id) not null,
order_id int references shopping_carts (id), shopping_cart_id int references shopping_carts (id),
quantity int not null default 0, quantity int not null default 0,
quantity_unit "QuantityUnit" not null, quantity_unit "QuantityUnit" not null,
CONSTRAINT positive_quantity check ( quantity >= 0 ) CONSTRAINT positive_quantity check ( quantity >= 0 )