diff --git a/api/src/actors/database.rs b/api/src/actors/database.rs index 378ef60..d3735d5 100644 --- a/api/src/actors/database.rs +++ b/api/src/actors/database.rs @@ -3,11 +3,13 @@ use sqlx::PgPool; pub use account_orders::*; pub use accounts::*; +pub use order_items::*; pub use products::*; pub use stocks::*; mod account_orders; mod accounts; +mod order_items; mod products; mod stocks; @@ -37,6 +39,8 @@ pub enum Error { Product(products::Error), #[error("{0}")] Stock(stocks::Error), + #[error("{0}")] + OrderItem(order_items::Error), } pub type Result = std::result::Result; diff --git a/api/src/actors/database/account_orders.rs b/api/src/actors/database/account_orders.rs index 53ba9ef..42c143f 100644 --- a/api/src/actors/database/account_orders.rs +++ b/api/src/actors/database/account_orders.rs @@ -9,13 +9,13 @@ use super::Result; #[derive(Debug, thiserror::Error)] pub enum Error { - #[error("Can't create account")] + #[error("Can't create account order")] CantCreate, - #[error("Can't find account does to lack of identity")] + #[error("Can't find account order does to lack of identity")] NoIdentity, #[error("Account order does not exists")] NotExists, - #[error("Failed to load all accounts")] + #[error("Failed to load all account orders")] All, } diff --git a/api/src/actors/database/order_items.rs b/api/src/actors/database/order_items.rs new file mode 100644 index 0000000..cf2a93a --- /dev/null +++ b/api/src/actors/database/order_items.rs @@ -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>")] +pub struct AllOrderItems; + +async_handler!(AllOrderItems, all_order_items, Vec); + +pub async fn all_order_items(_msg: AllOrderItems, pool: PgPool) -> Result> { + 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")] +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 { + 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")] +pub struct FindOrderItem { + pub id: OrderItemId, +} + +async_handler!(FindOrderItem, find_order_item, OrderItem); + +async fn find_order_item(msg: FindOrderItem, db: PgPool) -> Result { + 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) + }) +} diff --git a/api/src/actors/database/stocks.rs b/api/src/actors/database/stocks.rs index bb1307b..0862d9b 100644 --- a/api/src/actors/database/stocks.rs +++ b/api/src/actors/database/stocks.rs @@ -8,13 +8,13 @@ use crate::{database, model}; #[derive(Debug, thiserror::Error)] pub enum Error { - #[error("Unable to load all products")] + #[error("Unable to load all stocks")] All, - #[error("Unable to create product")] + #[error("Unable to create stock")] Create, - #[error("Unable to update product")] + #[error("Unable to update stock")] Update, - #[error("Unable to delete product")] + #[error("Unable to delete stock")] Delete, } @@ -22,9 +22,9 @@ pub enum Error { #[rtype(result = "Result>")] pub struct AllStocks; -crate::async_handler!(AllStocks, all, Vec); +crate::async_handler!(AllStocks, all_stocks, Vec); -async fn all(_msg: AllStocks, pool: PgPool) -> Result> { +async fn all_stocks(_msg: AllStocks, pool: PgPool) -> Result> { sqlx::query_as( r#" SELECT id, product_id, quantity, quantity_unit @@ -47,9 +47,9 @@ pub struct CreateStock { 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 { +async fn create_stock(msg: CreateStock, pool: PgPool) -> Result { sqlx::query_as( r#" INSERT INTO stocks (product_id, quantity) @@ -77,9 +77,9 @@ pub struct UpdateStock { 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 { +async fn update_stock(msg: UpdateStock, pool: PgPool) -> Result { sqlx::query_as( r#" UPDATE stocks @@ -108,9 +108,9 @@ pub struct DeleteStock { pub stock_id: StockId, } -crate::async_handler!(DeleteStock, delete_product, Option); +crate::async_handler!(DeleteStock, delete_stock, Option); -async fn delete_product(msg: DeleteStock, pool: PgPool) -> Result> { +async fn delete_stock(msg: DeleteStock, pool: PgPool) -> Result> { sqlx::query_as( r#" DELETE FROM stocks diff --git a/api/src/main.rs b/api/src/main.rs index 4be3e72..ce639a3 100644 --- a/api/src/main.rs +++ b/api/src/main.rs @@ -5,7 +5,7 @@ use actix_session::{storage::RedisActorSessionStore, SessionMiddleware}; use actix_web::cookie::Key; use actix_web::middleware::Logger; use actix_web::web::Data; -use actix_web::{web, App, HttpResponse, HttpServer}; +use actix_web::{App, HttpServer}; use gumdrop::Options; use password_hash::SaltString; use validator::{validate_email, validate_length}; diff --git a/api/src/model.rs b/api/src/model.rs index 20e48c4..a102a2b 100644 --- a/api/src/model.rs +++ b/api/src/model.rs @@ -4,6 +4,12 @@ use derive_more::{Deref, Display}; use serde::de::{Error, Visitor}; use serde::{Deserialize, Deserializer, Serialize}; +#[derive(Debug, thiserror::Error)] +pub enum TransformError { + #[error("Given value is below minimal value")] + BelowMinimal, +} + pub type RecordId = i32; #[derive(sqlx::Type, Copy, Clone, Debug, Display, Deserialize, Serialize)] @@ -41,6 +47,13 @@ pub enum QuantityUnit { 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)] #[sqlx(transparent)] #[serde(transparent)] @@ -56,6 +69,22 @@ pub struct PriceMinor(NonNegative); #[serde(transparent)] pub struct Quantity(NonNegative); +impl TryFrom for Quantity { + type Error = TransformError; + + fn try_from(value: NonNegative) -> Result { + Ok(Self(value)) + } +} + +impl TryFrom for Quantity { + type Error = TransformError; + + fn try_from(value: i32) -> Result { + Ok(Self(value.try_into()?)) + } +} + #[derive(sqlx::Type, Deserialize, Serialize, Deref, Debug)] #[sqlx(transparent)] #[serde(transparent)] @@ -98,7 +127,19 @@ impl<'de> serde::Deserialize<'de> for Email { #[derive(sqlx::Type, Serialize, Deref)] #[sqlx(transparent)] #[serde(transparent)] -pub struct NonNegative(pub i32); +pub struct NonNegative(i32); + +impl TryFrom for NonNegative { + type Error = TransformError; + + fn try_from(value: i32) -> Result { + if value < 0 { + return Err(TransformError::BelowMinimal); + } else { + Ok(Self(value)) + } + } +} impl<'de> serde::Deserialize<'de> for NonNegative { fn deserialize(deserializer: D) -> Result @@ -227,7 +268,7 @@ pub struct Stock { pub quantity_unit: QuantityUnit, } -#[derive(sqlx::Type, Serialize, Deserialize)] +#[derive(sqlx::Type, Serialize, Deserialize, Deref)] #[sqlx(transparent)] #[serde(transparent)] pub struct AccountOrderId(pub RecordId); @@ -238,3 +279,43 @@ pub struct AccountOrder { pub buyer_id: AccountId, 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, +} diff --git a/db/migrate/202204131841_init.sql b/db/migrate/202204131841_init.sql index 0dc6d13..abd51f0 100644 --- a/db/migrate/202204131841_init.sql +++ b/db/migrate/202204131841_init.sql @@ -68,7 +68,7 @@ CREATE TABLE order_items CONSTRAINT positive_quantity check ( quantity >= 0 ) ); -CREATE TABLE statistics +CREATE TABLE "statistics" ( id serial not null primary key, url varchar not null, diff --git a/db/migrate/202204160624_create_shopping_cart.sql b/db/migrate/202204160624_create_shopping_cart.sql index c9844fe..349c193 100644 --- a/db/migrate/202204160624_create_shopping_cart.sql +++ b/db/migrate/202204160624_create_shopping_cart.sql @@ -1,5 +1,5 @@ CREATE TYPE "PaymentMethod" AS ENUM ( - 'payu', + 'pay_u', 'payment_on_the_spot' ); @@ -14,7 +14,7 @@ CREATE TABLE shopping_cart_items ( id serial not null primary key, 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_unit "QuantityUnit" not null, CONSTRAINT positive_quantity check ( quantity >= 0 )