From 8014def1a8da3397d78d1162f9e1b8c3f22d0322 Mon Sep 17 00:00:00 2001 From: Akshay Date: Sat, 26 Dec 2020 10:51:46 +0530 Subject: add transactions and quantities - backend exposes endpoints to perform transactions - frontend introduces a new page to "checkout" cart --- .../2020-12-25-041256_cart_quantity/down.sql | 4 + .../2020-12-25-041256_cart_quantity/up.sql | 3 + .../2020-12-25-150728_transaction_date/down.sql | 4 + .../2020-12-25-150728_transaction_date/up.sql | 4 + backend/src/bin/server.rs | 18 ++- backend/src/handlers/cart_items.rs | 131 ++++++++++++++++++--- backend/src/handlers/mod.rs | 1 + backend/src/handlers/transaction.rs | 74 ++++++++++++ backend/src/handlers/users.rs | 4 +- backend/src/models.rs | 28 +++++ backend/src/schema.rs | 2 + 11 files changed, 253 insertions(+), 20 deletions(-) create mode 100644 backend/migrations/2020-12-25-041256_cart_quantity/down.sql create mode 100644 backend/migrations/2020-12-25-041256_cart_quantity/up.sql create mode 100644 backend/migrations/2020-12-25-150728_transaction_date/down.sql create mode 100644 backend/migrations/2020-12-25-150728_transaction_date/up.sql create mode 100644 backend/src/handlers/transaction.rs (limited to 'backend') diff --git a/backend/migrations/2020-12-25-041256_cart_quantity/down.sql b/backend/migrations/2020-12-25-041256_cart_quantity/down.sql new file mode 100644 index 0000000..94aec7a --- /dev/null +++ b/backend/migrations/2020-12-25-041256_cart_quantity/down.sql @@ -0,0 +1,4 @@ +-- This file should undo anything in `up.sql` + +alter table cart_items +drop column quantity; diff --git a/backend/migrations/2020-12-25-041256_cart_quantity/up.sql b/backend/migrations/2020-12-25-041256_cart_quantity/up.sql new file mode 100644 index 0000000..314c11c --- /dev/null +++ b/backend/migrations/2020-12-25-041256_cart_quantity/up.sql @@ -0,0 +1,3 @@ +-- Your SQL goes here +alter table cart_items +add quantity integer default 1; diff --git a/backend/migrations/2020-12-25-150728_transaction_date/down.sql b/backend/migrations/2020-12-25-150728_transaction_date/down.sql new file mode 100644 index 0000000..18fe306 --- /dev/null +++ b/backend/migrations/2020-12-25-150728_transaction_date/down.sql @@ -0,0 +1,4 @@ +-- This file should undo anything in `up.sql` + +alter table transaction +drop column order_date; diff --git a/backend/migrations/2020-12-25-150728_transaction_date/up.sql b/backend/migrations/2020-12-25-150728_transaction_date/up.sql new file mode 100644 index 0000000..76f9820 --- /dev/null +++ b/backend/migrations/2020-12-25-150728_transaction_date/up.sql @@ -0,0 +1,4 @@ +-- Your SQL goes here + +alter table transaction +add order_date date not null default curdate(); diff --git a/backend/src/bin/server.rs b/backend/src/bin/server.rs index 7c67e4f..310914e 100644 --- a/backend/src/bin/server.rs +++ b/backend/src/bin/server.rs @@ -5,7 +5,7 @@ use actix_web::{web, App, HttpServer}; use diesel::r2d2::{ConnectionManager, Pool}; use diesel::MysqlConnection; use furby::handlers::smoke::manual_hello; -use furby::handlers::{cart_items, product, rating, users}; +use furby::handlers::{cart_items, product, rating, transaction, users}; use rand::Rng; #[actix_web::main] @@ -48,6 +48,7 @@ async fn main() -> std::io::Result<()> { web::scope("/user") .route("/existing", web::post().to(users::name_exists)) .route("/login", web::post().to(users::login)) + .route("/logout", web::post().to(users::logout)) .route("/{uname}", web::get().to(users::user_details)) .route("/new", web::post().to(users::new_user)) .route( @@ -75,6 +76,10 @@ async fn main() -> std::io::Result<()> { "/items", web::get().to(cart_items::get_user_cart_items), ) + .route( + "/total", + web::get().to(cart_items::get_user_cart_total), + ) .route("/add", web::post().to(cart_items::add_to_cart)) .route( "/remove", @@ -86,6 +91,17 @@ async fn main() -> std::io::Result<()> { .route("/add", web::post().to(rating::add_rating)) .route("/remove", web::post().to(rating::remove_rating)), ) + .service( + web::scope("/transaction") + .route( + "/checkout", + web::post().to(transaction::checkout_cart), + ) + .route( + "/list", + web::get().to(transaction::list_transactions), + ), + ) .route("/hey", web::get().to(manual_hello)) }) .bind("127.0.0.1:7878")? diff --git a/backend/src/handlers/cart_items.rs b/backend/src/handlers/cart_items.rs index 25baaeb..e17f4c4 100644 --- a/backend/src/handlers/cart_items.rs +++ b/backend/src/handlers/cart_items.rs @@ -7,7 +7,7 @@ use actix_identity::Identity; use actix_web::{web, HttpResponse, Responder}; use diesel::prelude::*; use log::{error, info}; -use serde::Deserialize; +use serde::Serialize; pub async fn add_to_cart( cookie: Identity, @@ -27,16 +27,41 @@ pub async fn add_to_cart( let new_cart_item = AddCartItem { cart_id: selected_user.id, product_id: item_details, + quantity: Some(1), }; info!( "cart id: {:?}, product id {:?}", selected_user.id, item_details ); - diesel::insert_into(cart_items) - .values((cart_id.eq(selected_user.id), product_id.eq(item_details))) - .execute(&conn) - .expect("Coundn't connect to DB"); - HttpResponse::Ok().body("Inserted successfully!") + let current_entry = cart_items + .filter(cart_id.eq(selected_user.id)) + .filter(product_id.eq(item_details)) + .limit(1) + .first::(&conn); + match current_entry { + Ok(v) => { + info!("Item already present in cart, increasing quantity."); + let old_quantity = v.quantity.unwrap_or(1); + diesel::update( + cart_items + .filter(cart_id.eq(selected_user.id)) + .filter(product_id.eq(item_details)), + ) + .set(quantity.eq(old_quantity + 1)) + .execute(&conn) + .unwrap(); + return HttpResponse::Ok() + .body("Updated quantity successfully!"); + } + Err(_) => { + info!("Item not present, adding to cart."); + diesel::insert_into(cart_items) + .values(new_cart_item) + .execute(&conn) + .expect("Couldn't connect to DB"); + HttpResponse::Ok().body("Inserted successfully!") + } + } } else { error!("Unauthorized add to cart action!"); return HttpResponse::Unauthorized() @@ -58,15 +83,44 @@ pub async fn remove_from_cart( .limit(1) .first::(&conn) .expect("Couldn't connect to DB"); - - diesel::delete( - cart_items - .filter(cart_id.eq(selected_user.id)) - .filter(product_id.eq(item_details)), - ) - .execute(&conn) - .expect("Coundn't connect to DB"); - HttpResponse::Ok().body("Removed successfully!") + let current_entry = cart_items + .filter(cart_id.eq(selected_user.id)) + .filter(product_id.eq(item_details)) + .limit(1) + .first::(&conn); + match current_entry { + Ok(v) => { + info!("Item already present in cart, increasing quantity."); + let old_quantity = v.quantity.unwrap_or(1); + if old_quantity == 1 { + diesel::delete( + cart_items + .filter(cart_id.eq(selected_user.id)) + .filter(product_id.eq(item_details)), + ) + .execute(&conn) + .expect("Coundn't connect to DB"); + } else { + diesel::update( + cart_items + .filter(cart_id.eq(selected_user.id)) + .filter(product_id.eq(item_details)), + ) + .set(quantity.eq(old_quantity - 1)) + .execute(&conn) + .unwrap(); + return HttpResponse::Ok() + .body("Updated quantity successfully!"); + } + return HttpResponse::Ok() + .body("Updated quantity successfully!"); + } + Err(_) => { + info!("Item not present."); + return HttpResponse::InternalServerError() + .body("Item not found!"); + } + } } else { error!("Unauthorized add to cart action!"); return HttpResponse::Unauthorized() @@ -74,6 +128,12 @@ pub async fn remove_from_cart( } } +#[derive(Serialize)] +struct UserCartItem { + product_item: Product, + quantity: i32, +} + pub async fn get_user_cart_items( cookie: Identity, pool: web::Data, @@ -92,11 +152,15 @@ pub async fn get_user_cart_items( let cart_products = user_cart_items .into_iter() .map(|item| { - prod::product + let p = prod::product .filter(prod::id.eq(item.product_id)) .limit(1) .first::(&conn) - .expect("Couldn't connect to db") + .expect("Couldn't connect to db"); + UserCartItem { + product_item: p, + quantity: item.quantity.unwrap_or(1), + } }) .collect::>(); return HttpResponse::Ok().json(&cart_products); @@ -105,3 +169,36 @@ pub async fn get_user_cart_items( .body("Need to be logged in to add to cart!"); } } + +pub async fn get_user_cart_total( + cookie: Identity, + pool: web::Data, +) -> impl Responder { + let conn = pool.get().unwrap(); + if let Some(uname) = cookie.identity() { + let selected_user = customer + .filter(username.eq(&uname)) + .limit(1) + .first::(&conn) + .expect("Couldn't connect to DB"); + let user_cart_items = cart_items + .filter(cart_id.eq(selected_user.id)) + .load::(&conn) + .expect("Couldn't connect to DB"); + let cart_total: f32 = user_cart_items + .into_iter() + .map(|item| { + let p = prod::product + .filter(prod::id.eq(item.product_id)) + .limit(1) + .first::(&conn) + .expect("Couldn't connect to db"); + return p.price * item.quantity.unwrap_or(1) as f32; + }) + .sum(); + return HttpResponse::Ok().json(&cart_total); + } else { + return HttpResponse::Unauthorized() + .body("Need to be logged in to add to cart!"); + } +} diff --git a/backend/src/handlers/mod.rs b/backend/src/handlers/mod.rs index 9416857..e4ecb3a 100644 --- a/backend/src/handlers/mod.rs +++ b/backend/src/handlers/mod.rs @@ -2,4 +2,5 @@ pub mod cart_items; pub mod product; pub mod rating; pub mod smoke; +pub mod transaction; pub mod users; diff --git a/backend/src/handlers/transaction.rs b/backend/src/handlers/transaction.rs new file mode 100644 index 0000000..1e87312 --- /dev/null +++ b/backend/src/handlers/transaction.rs @@ -0,0 +1,74 @@ +use crate::models::{AddTransaction, CartItem, Customer, Product, Transaction}; +use crate::schema::cart_items::dsl::*; +use crate::schema::customer::dsl::*; +use crate::schema::product::dsl as prod; +use crate::schema::transaction::dsl::*; +use crate::TPool; + +use actix_identity::Identity; +use actix_web::{web, HttpResponse, Responder}; +use diesel::prelude::*; +use log::{error, info}; + +pub async fn checkout_cart( + pool: web::Data, + pmt_kind: String, + cookie: Identity, +) -> impl Responder { + let conn = pool.get().unwrap(); + info!("Checkout cart for user: {:?}", cookie.identity()); + if let Some(uname) = cookie.identity() { + let selected_user = customer + .filter(username.eq(&uname)) + .limit(1) + .first::(&conn) + .expect("Couldn't connect to DB"); + let user_cart_items = cart_items + .filter(cart_id.eq(selected_user.id)) + .load::(&conn) + .expect("Couldn't connect to DB"); + let cart_total = user_cart_items.into_iter().fold(0., |acc, item| { + let item_price = prod::product + .filter(prod::id.eq(item.product_id)) + .limit(1) + .first::(&conn) + .unwrap() + .price; + acc + item.quantity.unwrap_or(1) as f32 * item_price + }); + let transaction_entry = AddTransaction { + customer_id: Some(selected_user.id), + amount: cart_total, + payment_type: pmt_kind, + }; + diesel::insert_into(transaction) + .values(transaction_entry) + .execute(&conn) + .expect("Coundn't connect to DB"); + return HttpResponse::Ok().body("Transaction performed successfully"); + } else { + return HttpResponse::Unauthorized().body("Login first"); + } +} + +pub async fn list_transactions( + pool: web::Data, + cookie: Identity, +) -> impl Responder { + let conn = pool.get().unwrap(); + if let Some(uname) = cookie.identity() { + let selected_user = customer + .filter(username.eq(&uname)) + .limit(1) + .first::(&conn) + .expect("Couldn't connect to DB"); + let user_transactions = transaction + .filter(customer_id.eq(selected_user.id)) + .load::(&conn) + .expect("Couldn't connect to DB"); + return HttpResponse::Ok().json(&user_transactions); + } else { + return HttpResponse::Unauthorized() + .body("Need to be logged in to add to cart!"); + } +} diff --git a/backend/src/handlers/users.rs b/backend/src/handlers/users.rs index 24fb591..a043c1f 100644 --- a/backend/src/handlers/users.rs +++ b/backend/src/handlers/users.rs @@ -58,7 +58,7 @@ pub async fn login( login_details: web::Json, ) -> impl Responder { info!("Login hit"); - if let Some(uname) = cookie.identity() { + if cookie.identity().is_some() { info!("Found existing cookie: {:?}", cookie.identity()); return HttpResponse::Ok().finish(); } @@ -84,7 +84,7 @@ pub async fn login( pub async fn logout(cookie: Identity) -> impl Responder { cookie.forget(); - HttpResponse::Found().header("location", "/").finish() + HttpResponse::Ok().body("Successful logout.") } pub async fn user_details( diff --git a/backend/src/models.rs b/backend/src/models.rs index a104209..bf531ad 100644 --- a/backend/src/models.rs +++ b/backend/src/models.rs @@ -35,6 +35,8 @@ pub struct Product { pub kind: Option, pub price: f32, pub description: Option, + pub src: Option, + pub ios_src: Option, } #[derive(Insertable, Deserialize)] @@ -48,6 +50,12 @@ pub struct NewProduct { #[serde(skip_serializing_if = "Option::is_none")] pub description: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub src: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub ios_src: Option, } #[derive(Deserialize)] @@ -63,6 +71,7 @@ pub struct UpdateProduct { pub struct CartItem { pub cart_id: i32, pub product_id: i32, + pub quantity: Option, } #[derive(Insertable, Deserialize)] @@ -70,6 +79,7 @@ pub struct CartItem { pub struct AddCartItem { pub cart_id: i32, pub product_id: i32, + pub quantity: Option, } /* Rating */ @@ -95,3 +105,21 @@ pub struct AddRating { pub product_id: i32, pub customer_id: i32, } + +/* Transaction */ +#[derive(Queryable, Serialize)] +pub struct Transaction { + pub id: i32, + pub payment_type: String, + pub amount: f32, + pub customer_id: Option, + pub order_date: NaiveDate, +} + +#[derive(Insertable, Deserialize)] +#[table_name = "transaction"] +pub struct AddTransaction { + pub payment_type: String, + pub amount: f32, + pub customer_id: Option, +} diff --git a/backend/src/schema.rs b/backend/src/schema.rs index f08221a..1419bc0 100644 --- a/backend/src/schema.rs +++ b/backend/src/schema.rs @@ -2,6 +2,7 @@ table! { cart_items (cart_id, product_id) { cart_id -> Integer, product_id -> Integer, + quantity -> Nullable, } } @@ -43,6 +44,7 @@ table! { payment_type -> Varchar, amount -> Float, customer_id -> Nullable, + order_date -> Date, } } -- cgit v1.2.3