diff options
author | Akshay <[email protected]> | 2020-12-24 05:21:40 +0000 |
---|---|---|
committer | Akshay <[email protected]> | 2020-12-24 05:21:40 +0000 |
commit | 9d2b6ee10ec5359cc91769d430485c8c869ba1a8 (patch) | |
tree | b2d541e575ab6e3f70cb709f156f06afca085881 /backend/src | |
parent | 7c65421328552b08e64df25e224fe9d54d363e6e (diff) |
monorepo
Diffstat (limited to 'backend/src')
-rw-r--r-- | backend/src/bin/server.rs | 94 | ||||
-rw-r--r-- | backend/src/handlers/cart_items.rs | 107 | ||||
-rw-r--r-- | backend/src/handlers/mod.rs | 5 | ||||
-rw-r--r-- | backend/src/handlers/product.rs | 138 | ||||
-rw-r--r-- | backend/src/handlers/rating.rs | 91 | ||||
-rw-r--r-- | backend/src/handlers/smoke.rs | 15 | ||||
-rw-r--r-- | backend/src/handlers/users.rs | 148 | ||||
-rw-r--r-- | backend/src/lib.rs | 10 | ||||
-rw-r--r-- | backend/src/models.rs | 97 | ||||
-rw-r--r-- | backend/src/schema.rs | 61 |
10 files changed, 766 insertions, 0 deletions
diff --git a/backend/src/bin/server.rs b/backend/src/bin/server.rs new file mode 100644 index 0000000..7c67e4f --- /dev/null +++ b/backend/src/bin/server.rs | |||
@@ -0,0 +1,94 @@ | |||
1 | use actix_cors::Cors; | ||
2 | use actix_identity::{CookieIdentityPolicy, IdentityService}; | ||
3 | use actix_web::middleware; | ||
4 | use actix_web::{web, App, HttpServer}; | ||
5 | use diesel::r2d2::{ConnectionManager, Pool}; | ||
6 | use diesel::MysqlConnection; | ||
7 | use furby::handlers::smoke::manual_hello; | ||
8 | use furby::handlers::{cart_items, product, rating, users}; | ||
9 | use rand::Rng; | ||
10 | |||
11 | #[actix_web::main] | ||
12 | async fn main() -> std::io::Result<()> { | ||
13 | pretty_env_logger::init(); | ||
14 | |||
15 | let db_url = env!("DATABASE_URL"); | ||
16 | let manager = ConnectionManager::<MysqlConnection>::new(db_url); | ||
17 | let pool = Pool::builder() | ||
18 | .build(manager) | ||
19 | .expect("Failed to create pool."); | ||
20 | |||
21 | let private_key = rand::thread_rng().gen::<[u8; 32]>(); | ||
22 | HttpServer::new(move || { | ||
23 | App::new() | ||
24 | .wrap(IdentityService::new( | ||
25 | CookieIdentityPolicy::new(&private_key) | ||
26 | .name("user-login") | ||
27 | .domain("127.0.0.1") | ||
28 | .path("/") | ||
29 | .same_site(actix_web::cookie::SameSite::None) | ||
30 | .http_only(true) | ||
31 | .secure(false), | ||
32 | )) | ||
33 | .wrap( | ||
34 | Cors::default() | ||
35 | .allowed_origin("http://127.0.0.1:8000") | ||
36 | .allowed_origin("http://localhost:8000") | ||
37 | .allow_any_method() | ||
38 | .allow_any_header(), | ||
39 | ) | ||
40 | .wrap( | ||
41 | middleware::DefaultHeaders::new() | ||
42 | .header("Access-Control-Allow-Credentials", "true") | ||
43 | .header("Access-Control-Expose-Headers", "set-cookie"), | ||
44 | ) | ||
45 | .wrap(middleware::Logger::default()) | ||
46 | .data(pool.clone()) | ||
47 | .service( | ||
48 | web::scope("/user") | ||
49 | .route("/existing", web::post().to(users::name_exists)) | ||
50 | .route("/login", web::post().to(users::login)) | ||
51 | .route("/{uname}", web::get().to(users::user_details)) | ||
52 | .route("/new", web::post().to(users::new_user)) | ||
53 | .route( | ||
54 | "/change_password", | ||
55 | web::post().to(users::change_password), | ||
56 | ), | ||
57 | ) | ||
58 | .service( | ||
59 | web::scope("/product") | ||
60 | .route("/catalog", web::get().to(product::get_all_products)) | ||
61 | .route("/new", web::post().to(product::new_product)) | ||
62 | .route("/{id}", web::get().to(product::product_details)) | ||
63 | .route( | ||
64 | "/reviews/{id}", | ||
65 | web::get().to(product::get_product_reviews), | ||
66 | ) | ||
67 | .route( | ||
68 | "/update_product/{id}", | ||
69 | web::post().to(product::update_product), | ||
70 | ), | ||
71 | ) | ||
72 | .service( | ||
73 | web::scope("/cart") | ||
74 | .route( | ||
75 | "/items", | ||
76 | web::get().to(cart_items::get_user_cart_items), | ||
77 | ) | ||
78 | .route("/add", web::post().to(cart_items::add_to_cart)) | ||
79 | .route( | ||
80 | "/remove", | ||
81 | web::post().to(cart_items::remove_from_cart), | ||
82 | ), | ||
83 | ) | ||
84 | .service( | ||
85 | web::scope("/rating") | ||
86 | .route("/add", web::post().to(rating::add_rating)) | ||
87 | .route("/remove", web::post().to(rating::remove_rating)), | ||
88 | ) | ||
89 | .route("/hey", web::get().to(manual_hello)) | ||
90 | }) | ||
91 | .bind("127.0.0.1:7878")? | ||
92 | .run() | ||
93 | .await | ||
94 | } | ||
diff --git a/backend/src/handlers/cart_items.rs b/backend/src/handlers/cart_items.rs new file mode 100644 index 0000000..25baaeb --- /dev/null +++ b/backend/src/handlers/cart_items.rs | |||
@@ -0,0 +1,107 @@ | |||
1 | use crate::models::{AddCartItem, CartItem, Customer, Product}; | ||
2 | use crate::schema::product::dsl as prod; | ||
3 | use crate::schema::{cart_items::dsl::*, customer::dsl::*}; | ||
4 | use crate::TPool; | ||
5 | |||
6 | use actix_identity::Identity; | ||
7 | use actix_web::{web, HttpResponse, Responder}; | ||
8 | use diesel::prelude::*; | ||
9 | use log::{error, info}; | ||
10 | use serde::Deserialize; | ||
11 | |||
12 | pub async fn add_to_cart( | ||
13 | cookie: Identity, | ||
14 | item_id: String, | ||
15 | pool: web::Data<TPool>, | ||
16 | ) -> impl Responder { | ||
17 | let item_details = item_id.parse::<i32>().unwrap_or(-1); | ||
18 | info!("Add to cart hit: {:?}", item_details); | ||
19 | info!("[cart] Current user: {:?}", cookie.identity()); | ||
20 | let conn = pool.get().unwrap(); | ||
21 | if let Some(uname) = cookie.identity() { | ||
22 | let selected_user = customer | ||
23 | .filter(username.eq(&uname)) | ||
24 | .limit(1) | ||
25 | .first::<Customer>(&conn) | ||
26 | .expect("Couldn't connect to DB"); | ||
27 | let new_cart_item = AddCartItem { | ||
28 | cart_id: selected_user.id, | ||
29 | product_id: item_details, | ||
30 | }; | ||
31 | info!( | ||
32 | "cart id: {:?}, product id {:?}", | ||
33 | selected_user.id, item_details | ||
34 | ); | ||
35 | diesel::insert_into(cart_items) | ||
36 | .values((cart_id.eq(selected_user.id), product_id.eq(item_details))) | ||
37 | .execute(&conn) | ||
38 | .expect("Coundn't connect to DB"); | ||
39 | HttpResponse::Ok().body("Inserted successfully!") | ||
40 | } else { | ||
41 | error!("Unauthorized add to cart action!"); | ||
42 | return HttpResponse::Unauthorized() | ||
43 | .body("Need to be logged in to add to cart!"); | ||
44 | } | ||
45 | } | ||
46 | |||
47 | pub async fn remove_from_cart( | ||
48 | cookie: Identity, | ||
49 | item_id: String, | ||
50 | pool: web::Data<TPool>, | ||
51 | ) -> impl Responder { | ||
52 | info!("Remove from cart hit: {:?}", item_id); | ||
53 | let item_details = item_id.parse::<i32>().unwrap_or(-1); | ||
54 | let conn = pool.get().unwrap(); | ||
55 | if let Some(uname) = cookie.identity() { | ||
56 | let selected_user = customer | ||
57 | .filter(username.eq(&uname)) | ||
58 | .limit(1) | ||
59 | .first::<Customer>(&conn) | ||
60 | .expect("Couldn't connect to DB"); | ||
61 | |||
62 | diesel::delete( | ||
63 | cart_items | ||
64 | .filter(cart_id.eq(selected_user.id)) | ||
65 | .filter(product_id.eq(item_details)), | ||
66 | ) | ||
67 | .execute(&conn) | ||
68 | .expect("Coundn't connect to DB"); | ||
69 | HttpResponse::Ok().body("Removed successfully!") | ||
70 | } else { | ||
71 | error!("Unauthorized add to cart action!"); | ||
72 | return HttpResponse::Unauthorized() | ||
73 | .body("Need to be logged in to add to cart!"); | ||
74 | } | ||
75 | } | ||
76 | |||
77 | pub async fn get_user_cart_items( | ||
78 | cookie: Identity, | ||
79 | pool: web::Data<TPool>, | ||
80 | ) -> impl Responder { | ||
81 | let conn = pool.get().unwrap(); | ||
82 | if let Some(uname) = cookie.identity() { | ||
83 | let selected_user = customer | ||
84 | .filter(username.eq(&uname)) | ||
85 | .limit(1) | ||
86 | .first::<Customer>(&conn) | ||
87 | .expect("Couldn't connect to DB"); | ||
88 | let user_cart_items = cart_items | ||
89 | .filter(cart_id.eq(selected_user.id)) | ||
90 | .load::<CartItem>(&conn) | ||
91 | .expect("Couldn't connect to DB"); | ||
92 | let cart_products = user_cart_items | ||
93 | .into_iter() | ||
94 | .map(|item| { | ||
95 | prod::product | ||
96 | .filter(prod::id.eq(item.product_id)) | ||
97 | .limit(1) | ||
98 | .first::<Product>(&conn) | ||
99 | .expect("Couldn't connect to db") | ||
100 | }) | ||
101 | .collect::<Vec<_>>(); | ||
102 | return HttpResponse::Ok().json(&cart_products); | ||
103 | } else { | ||
104 | return HttpResponse::Unauthorized() | ||
105 | .body("Need to be logged in to add to cart!"); | ||
106 | } | ||
107 | } | ||
diff --git a/backend/src/handlers/mod.rs b/backend/src/handlers/mod.rs new file mode 100644 index 0000000..9416857 --- /dev/null +++ b/backend/src/handlers/mod.rs | |||
@@ -0,0 +1,5 @@ | |||
1 | pub mod cart_items; | ||
2 | pub mod product; | ||
3 | pub mod rating; | ||
4 | pub mod smoke; | ||
5 | pub mod users; | ||
diff --git a/backend/src/handlers/product.rs b/backend/src/handlers/product.rs new file mode 100644 index 0000000..41a47a0 --- /dev/null +++ b/backend/src/handlers/product.rs | |||
@@ -0,0 +1,138 @@ | |||
1 | use crate::models::{Customer, NewProduct, Product, Rating, UpdateProduct}; | ||
2 | use crate::schema::customer::dsl as cust; | ||
3 | use crate::schema::product::dsl::*; | ||
4 | use crate::schema::rating::dsl as rating; | ||
5 | use crate::TPool; | ||
6 | |||
7 | use actix_web::{web, HttpResponse, Responder}; | ||
8 | use chrono::naive::NaiveDate; | ||
9 | use diesel::prelude::*; | ||
10 | use log::{error, info}; | ||
11 | use serde::{Deserialize, Serialize}; | ||
12 | |||
13 | pub async fn new_product( | ||
14 | pool: web::Data<TPool>, | ||
15 | item: web::Json<NewProduct>, | ||
16 | ) -> impl Responder { | ||
17 | info!("New product hit: {:?}", item.name); | ||
18 | let conn = pool.get().unwrap(); | ||
19 | diesel::insert_into(product) | ||
20 | .values(item.into_inner()) | ||
21 | .execute(&conn) | ||
22 | .expect("Coundn't connect to DB"); | ||
23 | HttpResponse::Ok().body("Inserted successfully!") | ||
24 | } | ||
25 | |||
26 | pub async fn product_details( | ||
27 | pool: web::Data<TPool>, | ||
28 | product_id: web::Path<i32>, | ||
29 | ) -> impl Responder { | ||
30 | let conn = pool.get().unwrap(); | ||
31 | let product_id = product_id.into_inner(); | ||
32 | info!("Fetching product details for {}", product_id); | ||
33 | let selected_product = product | ||
34 | .filter(id.eq(&product_id)) | ||
35 | .limit(1) | ||
36 | .first::<Product>(&conn); | ||
37 | match selected_product { | ||
38 | Ok(m) => { | ||
39 | info!("Found product: {}", product_id); | ||
40 | HttpResponse::Ok().json(m) | ||
41 | } | ||
42 | Err(_) => { | ||
43 | error!("Product not found: {}", product_id); | ||
44 | HttpResponse::NotFound().finish() | ||
45 | } | ||
46 | } | ||
47 | } | ||
48 | |||
49 | pub async fn update_product( | ||
50 | pool: web::Data<TPool>, | ||
51 | product_id: web::Path<i32>, | ||
52 | product_details: web::Json<UpdateProduct>, | ||
53 | ) -> impl Responder { | ||
54 | let conn = pool.get().unwrap(); | ||
55 | let product_id = product_id.into_inner(); | ||
56 | let product_details = product_details.into_inner(); | ||
57 | info!("Updating product: {:?}", product_id); | ||
58 | match diesel::update(product.filter(id.eq(product_id))) | ||
59 | .set(( | ||
60 | name.eq(product_details.name), | ||
61 | kind.eq(product_details.kind), | ||
62 | price.eq(product_details.price), | ||
63 | description.eq(product_details.description), | ||
64 | )) | ||
65 | .execute(&conn) | ||
66 | { | ||
67 | Ok(_) => { | ||
68 | return HttpResponse::Ok().body("Changed product successfully") | ||
69 | } | ||
70 | _ => { | ||
71 | return HttpResponse::InternalServerError() | ||
72 | .body("Unable to update record") | ||
73 | } | ||
74 | } | ||
75 | } | ||
76 | |||
77 | pub async fn get_all_products(pool: web::Data<TPool>) -> impl Responder { | ||
78 | let conn = pool.get().unwrap(); | ||
79 | info!("Generating and returning catalog ..."); | ||
80 | match product.load::<Product>(&conn) { | ||
81 | Ok(products) => return HttpResponse::Ok().json(&products), | ||
82 | Err(_) => { | ||
83 | return HttpResponse::InternalServerError() | ||
84 | .body("Unable to fetch product catalog") | ||
85 | } | ||
86 | } | ||
87 | } | ||
88 | |||
89 | #[derive(Serialize, Deserialize, Debug)] | ||
90 | struct ProductRating { | ||
91 | pub comment_text: Option<String>, | ||
92 | pub comment_date: NaiveDate, | ||
93 | pub product_name: String, | ||
94 | pub customer_name: String, | ||
95 | pub stars: Option<i32>, | ||
96 | } | ||
97 | |||
98 | pub async fn get_product_reviews( | ||
99 | pool: web::Data<TPool>, | ||
100 | product_id: web::Path<i32>, | ||
101 | ) -> impl Responder { | ||
102 | let conn = pool.get().unwrap(); | ||
103 | info!("Fetching product reviews for {}", product_id); | ||
104 | let pid = product_id.into_inner(); | ||
105 | let rating_entries = rating::rating | ||
106 | .filter(rating::product_id.eq(pid)) | ||
107 | .load::<Rating>(&conn) | ||
108 | .expect("Couldn't connect to DB"); | ||
109 | let json_ratings = rating_entries | ||
110 | .into_iter() | ||
111 | .map(move |p| { | ||
112 | let selected_product = product | ||
113 | .filter(id.eq(&p.product_id.unwrap())) | ||
114 | .limit(1) | ||
115 | .first::<Product>(&conn) | ||
116 | .unwrap() | ||
117 | .name | ||
118 | .clone(); | ||
119 | |||
120 | let selected_customer = cust::customer | ||
121 | .filter(cust::id.eq(&p.customer_id.unwrap())) | ||
122 | .limit(1) | ||
123 | .first::<Customer>(&conn) | ||
124 | .unwrap() | ||
125 | .username | ||
126 | .clone(); | ||
127 | |||
128 | ProductRating { | ||
129 | comment_text: p.comment_text, | ||
130 | comment_date: p.comment_date.unwrap(), | ||
131 | product_name: selected_product, | ||
132 | customer_name: selected_customer, | ||
133 | stars: p.stars, | ||
134 | } | ||
135 | }) | ||
136 | .collect::<Vec<_>>(); | ||
137 | return HttpResponse::Ok().json(&json_ratings); | ||
138 | } | ||
diff --git a/backend/src/handlers/rating.rs b/backend/src/handlers/rating.rs new file mode 100644 index 0000000..dfbeb3e --- /dev/null +++ b/backend/src/handlers/rating.rs | |||
@@ -0,0 +1,91 @@ | |||
1 | use crate::models::{AddRating, Customer, Rating}; | ||
2 | use crate::schema::rating::dsl as rating; | ||
3 | use crate::schema::{customer::dsl::*, product::dsl::*}; | ||
4 | use crate::TPool; | ||
5 | |||
6 | use actix_identity::Identity; | ||
7 | use actix_web::{web, HttpResponse, Responder}; | ||
8 | use diesel::prelude::*; | ||
9 | use log::{error, info}; | ||
10 | use serde::Deserialize; | ||
11 | |||
12 | #[derive(Deserialize, Debug)] | ||
13 | pub struct AddRatingJson { | ||
14 | pub comment_text: Option<String>, | ||
15 | pub stars: Option<i32>, | ||
16 | pub product_id: i32, | ||
17 | } | ||
18 | |||
19 | pub async fn add_rating( | ||
20 | cookie: Identity, | ||
21 | rating_details: web::Json<AddRatingJson>, | ||
22 | pool: web::Data<TPool>, | ||
23 | ) -> impl Responder { | ||
24 | info!("Add rating hit: {:?}", rating_details.product_id); | ||
25 | info!("{:?}", cookie.identity()); | ||
26 | let conn = pool.get().unwrap(); | ||
27 | if let Some(uname) = cookie.identity() { | ||
28 | let selected_user = customer | ||
29 | .filter(username.eq(&uname)) | ||
30 | .limit(1) | ||
31 | .first::<Customer>(&conn) | ||
32 | .expect("Couldn't connect to DB"); | ||
33 | let rating_details = rating_details.into_inner(); | ||
34 | let new_rating = AddRating { | ||
35 | comment_text: rating_details.comment_text, | ||
36 | stars: rating_details.stars, | ||
37 | product_id: rating_details.product_id, | ||
38 | customer_id: selected_user.id, | ||
39 | }; | ||
40 | diesel::insert_into(rating::rating) | ||
41 | .values(new_rating) | ||
42 | .execute(&conn) | ||
43 | .expect("Coundn't connect to DB"); | ||
44 | HttpResponse::Ok().body("Inserted rating successfully!") | ||
45 | } else { | ||
46 | error!("Unauthorized add rating action!"); | ||
47 | return HttpResponse::Unauthorized() | ||
48 | .body("Need to be logged in to add rating!"); | ||
49 | } | ||
50 | } | ||
51 | |||
52 | #[derive(Deserialize, Debug)] | ||
53 | pub struct RemoveRating { | ||
54 | rating_id: i32, | ||
55 | } | ||
56 | |||
57 | pub async fn remove_rating( | ||
58 | cookie: Identity, | ||
59 | rating_details: web::Json<RemoveRating>, | ||
60 | pool: web::Data<TPool>, | ||
61 | ) -> impl Responder { | ||
62 | info!("Remove rating hit: {:?}", rating_details.rating_id); | ||
63 | let conn = pool.get().unwrap(); | ||
64 | if let Some(uname) = cookie.identity() { | ||
65 | let selected_user = customer | ||
66 | .filter(username.eq(&uname)) | ||
67 | .limit(1) | ||
68 | .first::<Customer>(&conn) | ||
69 | .expect("Couldn't connect to DB"); | ||
70 | |||
71 | diesel::delete( | ||
72 | rating::rating | ||
73 | .filter(rating::customer_id.eq(selected_user.id)) | ||
74 | .filter(rating::id.eq(rating_details.rating_id)), | ||
75 | ) | ||
76 | .execute(&conn) | ||
77 | .expect("Coundn't connect to DB"); | ||
78 | HttpResponse::Ok().body("Removed successfully!") | ||
79 | } else { | ||
80 | error!("Unauthorized add to cart action!"); | ||
81 | return HttpResponse::Unauthorized() | ||
82 | .body("Need to be logged in to add to cart!"); | ||
83 | } | ||
84 | } | ||
85 | |||
86 | // pub async fn get_product_reviews( | ||
87 | // product: web::Json<GetProductReviews>, | ||
88 | // pool: web::Data<TPool>, | ||
89 | // ) -> impl Responder { | ||
90 | // unimplemented!() | ||
91 | // } | ||
diff --git a/backend/src/handlers/smoke.rs b/backend/src/handlers/smoke.rs new file mode 100644 index 0000000..d0a1038 --- /dev/null +++ b/backend/src/handlers/smoke.rs | |||
@@ -0,0 +1,15 @@ | |||
1 | use actix_web::{get, post, HttpResponse, Responder}; | ||
2 | |||
3 | #[get("/")] | ||
4 | async fn hello() -> impl Responder { | ||
5 | HttpResponse::Ok().body("Hello world!") | ||
6 | } | ||
7 | |||
8 | #[post("/echo")] | ||
9 | async fn echo(req_body: String) -> impl Responder { | ||
10 | HttpResponse::Ok().body(req_body) | ||
11 | } | ||
12 | |||
13 | pub async fn manual_hello() -> impl Responder { | ||
14 | HttpResponse::Ok().body("Hey there!") | ||
15 | } | ||
diff --git a/backend/src/handlers/users.rs b/backend/src/handlers/users.rs new file mode 100644 index 0000000..24fb591 --- /dev/null +++ b/backend/src/handlers/users.rs | |||
@@ -0,0 +1,148 @@ | |||
1 | use crate::models::{Customer, NewCustomer}; | ||
2 | use crate::schema::customer::dsl::*; | ||
3 | use crate::TPool; | ||
4 | |||
5 | use actix_identity::Identity; | ||
6 | use actix_web::{web, HttpResponse, Responder}; | ||
7 | use bcrypt::{hash, verify, DEFAULT_COST}; | ||
8 | use diesel::prelude::*; | ||
9 | use log::{error, info}; | ||
10 | use serde::Deserialize; | ||
11 | |||
12 | pub async fn new_user( | ||
13 | pool: web::Data<TPool>, | ||
14 | item: web::Json<NewCustomer>, | ||
15 | ) -> impl Responder { | ||
16 | info!("Creating ... {:?}", item.username); | ||
17 | let conn = pool.get().unwrap(); | ||
18 | let hashed_item = NewCustomer { | ||
19 | password: hash(&item.password, DEFAULT_COST).unwrap(), | ||
20 | ..(item.into_inner()) | ||
21 | }; | ||
22 | diesel::insert_into(customer) | ||
23 | .values(hashed_item) | ||
24 | .execute(&conn) | ||
25 | .expect("Coundn't connect to DB"); | ||
26 | HttpResponse::Ok().body("Inserted successfully!") | ||
27 | } | ||
28 | |||
29 | pub async fn name_exists( | ||
30 | pool: web::Data<TPool>, | ||
31 | item: String, | ||
32 | ) -> impl Responder { | ||
33 | let conn = pool.get().unwrap(); | ||
34 | info!("target: {:?}", item); | ||
35 | if (customer | ||
36 | .filter(username.eq(&item)) | ||
37 | .limit(1) | ||
38 | .load::<Customer>(&conn) | ||
39 | .expect("Coundn't connect to DB")) | ||
40 | .len() | ||
41 | > 0 | ||
42 | { | ||
43 | HttpResponse::Ok().body("true") | ||
44 | } else { | ||
45 | HttpResponse::Ok().body("false") | ||
46 | } | ||
47 | } | ||
48 | |||
49 | #[derive(Deserialize)] | ||
50 | pub struct Login { | ||
51 | username: String, | ||
52 | password: String, | ||
53 | } | ||
54 | |||
55 | pub async fn login( | ||
56 | pool: web::Data<TPool>, | ||
57 | cookie: Identity, | ||
58 | login_details: web::Json<Login>, | ||
59 | ) -> impl Responder { | ||
60 | info!("Login hit"); | ||
61 | if let Some(uname) = cookie.identity() { | ||
62 | info!("Found existing cookie: {:?}", cookie.identity()); | ||
63 | return HttpResponse::Ok().finish(); | ||
64 | } | ||
65 | let conn = pool.get().unwrap(); | ||
66 | let entered_pass = &login_details.password; | ||
67 | let selected_user = customer | ||
68 | .filter(username.eq(&login_details.username)) | ||
69 | .limit(1) | ||
70 | .first::<Customer>(&conn) | ||
71 | .expect("Couldn't connect to DB"); | ||
72 | let hashed_pass = selected_user.password; | ||
73 | if verify(entered_pass, &hashed_pass).unwrap() { | ||
74 | cookie.remember(login_details.username.clone()); | ||
75 | info!( | ||
76 | "Successful login: {} {}", | ||
77 | selected_user.username, selected_user.email_id | ||
78 | ); | ||
79 | HttpResponse::Ok().finish() | ||
80 | } else { | ||
81 | HttpResponse::Unauthorized().finish() | ||
82 | } | ||
83 | } | ||
84 | |||
85 | pub async fn logout(cookie: Identity) -> impl Responder { | ||
86 | cookie.forget(); | ||
87 | HttpResponse::Found().header("location", "/").finish() | ||
88 | } | ||
89 | |||
90 | pub async fn user_details( | ||
91 | uname: web::Path<String>, | ||
92 | pool: web::Data<TPool>, | ||
93 | ) -> impl Responder { | ||
94 | let conn = pool.get().unwrap(); | ||
95 | let uname = uname.into_inner(); | ||
96 | info!("Fetching info for: \"{}\"", uname); | ||
97 | let selected_user = customer | ||
98 | .filter(username.eq(&uname)) | ||
99 | .limit(1) | ||
100 | .first::<Customer>(&conn); | ||
101 | match selected_user { | ||
102 | Ok(m) => { | ||
103 | info!("Found user: {}", uname); | ||
104 | HttpResponse::Ok().json(m) | ||
105 | } | ||
106 | Err(_) => { | ||
107 | error!("User not found: {}", uname); | ||
108 | HttpResponse::NotFound().finish() | ||
109 | } | ||
110 | } | ||
111 | } | ||
112 | |||
113 | #[derive(Deserialize, Debug)] | ||
114 | pub struct ChangePassword { | ||
115 | old_password: String, | ||
116 | new_password: String, | ||
117 | } | ||
118 | |||
119 | pub async fn change_password( | ||
120 | cookie: Identity, | ||
121 | password_details: web::Json<ChangePassword>, | ||
122 | pool: web::Data<TPool>, | ||
123 | ) -> impl Responder { | ||
124 | info!("Change password request: {:?}", password_details); | ||
125 | let conn = pool.get().unwrap(); | ||
126 | if let Some(uname) = cookie.identity() { | ||
127 | let entered_pass = &password_details.old_password; | ||
128 | let new_password = &password_details.new_password; | ||
129 | let selected_user = customer | ||
130 | .filter(username.eq(&uname)) | ||
131 | .limit(1) | ||
132 | .first::<Customer>(&conn) | ||
133 | .expect("Couldn't connect to DB"); | ||
134 | let hashed_pass = selected_user.password; | ||
135 | if verify(entered_pass, &hashed_pass).unwrap() { | ||
136 | let hashed_new_password = | ||
137 | hash(&new_password, DEFAULT_COST).unwrap(); | ||
138 | diesel::update(customer.filter(id.eq(selected_user.id))) | ||
139 | .set(password.eq(hashed_new_password)) | ||
140 | .execute(&conn) | ||
141 | .unwrap(); | ||
142 | return HttpResponse::Ok().body("Changed password successfully"); | ||
143 | } else { | ||
144 | return HttpResponse::Ok().body("Invalid password"); | ||
145 | } | ||
146 | } | ||
147 | return HttpResponse::Unauthorized().body("Login first"); | ||
148 | } | ||
diff --git a/backend/src/lib.rs b/backend/src/lib.rs new file mode 100644 index 0000000..d956a3f --- /dev/null +++ b/backend/src/lib.rs | |||
@@ -0,0 +1,10 @@ | |||
1 | #[macro_use] | ||
2 | extern crate diesel; | ||
3 | |||
4 | pub mod handlers; | ||
5 | pub mod models; | ||
6 | pub mod schema; | ||
7 | |||
8 | use diesel::r2d2::{self, ConnectionManager}; | ||
9 | use diesel::MysqlConnection; | ||
10 | pub type TPool = r2d2::Pool<ConnectionManager<MysqlConnection>>; | ||
diff --git a/backend/src/models.rs b/backend/src/models.rs new file mode 100644 index 0000000..a104209 --- /dev/null +++ b/backend/src/models.rs | |||
@@ -0,0 +1,97 @@ | |||
1 | use super::schema::{cart_items, customer, product, rating, transaction}; | ||
2 | |||
3 | use chrono::naive::{NaiveDate, NaiveDateTime}; | ||
4 | use diesel::{Insertable, Queryable}; | ||
5 | use serde::{Deserialize, Serialize}; | ||
6 | |||
7 | /* Member */ | ||
8 | #[derive(Queryable, Serialize)] | ||
9 | pub struct Customer { | ||
10 | pub id: i32, | ||
11 | pub username: String, | ||
12 | pub password: String, | ||
13 | pub phone_number: String, | ||
14 | pub email_id: String, | ||
15 | pub address: Option<String>, | ||
16 | } | ||
17 | |||
18 | #[derive(Insertable, Deserialize)] | ||
19 | #[table_name = "customer"] | ||
20 | pub struct NewCustomer { | ||
21 | pub username: String, | ||
22 | pub password: String, | ||
23 | pub phone_number: String, | ||
24 | pub email_id: String, | ||
25 | |||
26 | #[serde(skip_serializing_if = "Option::is_none")] | ||
27 | pub address: Option<String>, | ||
28 | } | ||
29 | |||
30 | /* Product */ | ||
31 | #[derive(Queryable, Serialize)] | ||
32 | pub struct Product { | ||
33 | pub id: i32, | ||
34 | pub name: String, | ||
35 | pub kind: Option<String>, | ||
36 | pub price: f32, | ||
37 | pub description: Option<String>, | ||
38 | } | ||
39 | |||
40 | #[derive(Insertable, Deserialize)] | ||
41 | #[table_name = "product"] | ||
42 | pub struct NewProduct { | ||
43 | pub name: String, | ||
44 | |||
45 | #[serde(skip_serializing_if = "Option::is_none")] | ||
46 | pub kind: Option<String>, | ||
47 | pub price: f32, | ||
48 | |||
49 | #[serde(skip_serializing_if = "Option::is_none")] | ||
50 | pub description: Option<String>, | ||
51 | } | ||
52 | |||
53 | #[derive(Deserialize)] | ||
54 | pub struct UpdateProduct { | ||
55 | pub name: String, | ||
56 | pub kind: Option<String>, | ||
57 | pub price: f32, | ||
58 | pub description: Option<String>, | ||
59 | } | ||
60 | |||
61 | /* Cart Items */ | ||
62 | #[derive(Queryable, Serialize)] | ||
63 | pub struct CartItem { | ||
64 | pub cart_id: i32, | ||
65 | pub product_id: i32, | ||
66 | } | ||
67 | |||
68 | #[derive(Insertable, Deserialize)] | ||
69 | #[table_name = "cart_items"] | ||
70 | pub struct AddCartItem { | ||
71 | pub cart_id: i32, | ||
72 | pub product_id: i32, | ||
73 | } | ||
74 | |||
75 | /* Rating */ | ||
76 | #[derive(Queryable, Serialize)] | ||
77 | pub struct Rating { | ||
78 | pub id: i32, | ||
79 | pub comment_text: Option<String>, | ||
80 | pub comment_date: Option<NaiveDate>, | ||
81 | pub product_id: Option<i32>, | ||
82 | pub customer_id: Option<i32>, | ||
83 | pub stars: Option<i32>, | ||
84 | } | ||
85 | |||
86 | #[derive(Insertable, Deserialize)] | ||
87 | #[table_name = "rating"] | ||
88 | pub struct AddRating { | ||
89 | #[serde(skip_serializing_if = "Option::is_none")] | ||
90 | pub comment_text: Option<String>, | ||
91 | |||
92 | #[serde(skip_serializing_if = "Option::is_none")] | ||
93 | pub stars: Option<i32>, | ||
94 | |||
95 | pub product_id: i32, | ||
96 | pub customer_id: i32, | ||
97 | } | ||
diff --git a/backend/src/schema.rs b/backend/src/schema.rs new file mode 100644 index 0000000..f08221a --- /dev/null +++ b/backend/src/schema.rs | |||
@@ -0,0 +1,61 @@ | |||
1 | table! { | ||
2 | cart_items (cart_id, product_id) { | ||
3 | cart_id -> Integer, | ||
4 | product_id -> Integer, | ||
5 | } | ||
6 | } | ||
7 | |||
8 | table! { | ||
9 | customer (id) { | ||
10 | id -> Integer, | ||
11 | username -> Varchar, | ||
12 | password -> Varchar, | ||
13 | phone_number -> Varchar, | ||
14 | email_id -> Varchar, | ||
15 | address -> Nullable<Text>, | ||
16 | } | ||
17 | } | ||
18 | |||
19 | table! { | ||
20 | product (id) { | ||
21 | id -> Integer, | ||
22 | name -> Varchar, | ||
23 | kind -> Nullable<Varchar>, | ||
24 | price -> Float, | ||
25 | description -> Nullable<Varchar>, | ||
26 | } | ||
27 | } | ||
28 | |||
29 | table! { | ||
30 | rating (id) { | ||
31 | id -> Integer, | ||
32 | comment_text -> Nullable<Text>, | ||
33 | comment_date -> Nullable<Date>, | ||
34 | product_id -> Nullable<Integer>, | ||
35 | customer_id -> Nullable<Integer>, | ||
36 | stars -> Nullable<Integer>, | ||
37 | } | ||
38 | } | ||
39 | |||
40 | table! { | ||
41 | transaction (id) { | ||
42 | id -> Integer, | ||
43 | payment_type -> Varchar, | ||
44 | amount -> Float, | ||
45 | customer_id -> Nullable<Integer>, | ||
46 | } | ||
47 | } | ||
48 | |||
49 | joinable!(cart_items -> customer (cart_id)); | ||
50 | joinable!(cart_items -> product (product_id)); | ||
51 | joinable!(rating -> customer (customer_id)); | ||
52 | joinable!(rating -> product (product_id)); | ||
53 | joinable!(transaction -> customer (customer_id)); | ||
54 | |||
55 | allow_tables_to_appear_in_same_query!( | ||
56 | cart_items, | ||
57 | customer, | ||
58 | product, | ||
59 | rating, | ||
60 | transaction, | ||
61 | ); | ||