aboutsummaryrefslogtreecommitdiff
path: root/backend/src
diff options
context:
space:
mode:
Diffstat (limited to 'backend/src')
-rw-r--r--backend/src/bin/server.rs94
-rw-r--r--backend/src/handlers/cart_items.rs107
-rw-r--r--backend/src/handlers/mod.rs5
-rw-r--r--backend/src/handlers/product.rs138
-rw-r--r--backend/src/handlers/rating.rs91
-rw-r--r--backend/src/handlers/smoke.rs15
-rw-r--r--backend/src/handlers/users.rs148
-rw-r--r--backend/src/lib.rs10
-rw-r--r--backend/src/models.rs97
-rw-r--r--backend/src/schema.rs61
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 @@
1use actix_cors::Cors;
2use actix_identity::{CookieIdentityPolicy, IdentityService};
3use actix_web::middleware;
4use actix_web::{web, App, HttpServer};
5use diesel::r2d2::{ConnectionManager, Pool};
6use diesel::MysqlConnection;
7use furby::handlers::smoke::manual_hello;
8use furby::handlers::{cart_items, product, rating, users};
9use rand::Rng;
10
11#[actix_web::main]
12async 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 @@
1use crate::models::{AddCartItem, CartItem, Customer, Product};
2use crate::schema::product::dsl as prod;
3use crate::schema::{cart_items::dsl::*, customer::dsl::*};
4use crate::TPool;
5
6use actix_identity::Identity;
7use actix_web::{web, HttpResponse, Responder};
8use diesel::prelude::*;
9use log::{error, info};
10use serde::Deserialize;
11
12pub 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
47pub 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
77pub 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 @@
1pub mod cart_items;
2pub mod product;
3pub mod rating;
4pub mod smoke;
5pub 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 @@
1use crate::models::{Customer, NewProduct, Product, Rating, UpdateProduct};
2use crate::schema::customer::dsl as cust;
3use crate::schema::product::dsl::*;
4use crate::schema::rating::dsl as rating;
5use crate::TPool;
6
7use actix_web::{web, HttpResponse, Responder};
8use chrono::naive::NaiveDate;
9use diesel::prelude::*;
10use log::{error, info};
11use serde::{Deserialize, Serialize};
12
13pub 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
26pub 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
49pub 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
77pub 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)]
90struct 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
98pub 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 @@
1use crate::models::{AddRating, Customer, Rating};
2use crate::schema::rating::dsl as rating;
3use crate::schema::{customer::dsl::*, product::dsl::*};
4use crate::TPool;
5
6use actix_identity::Identity;
7use actix_web::{web, HttpResponse, Responder};
8use diesel::prelude::*;
9use log::{error, info};
10use serde::Deserialize;
11
12#[derive(Deserialize, Debug)]
13pub struct AddRatingJson {
14 pub comment_text: Option<String>,
15 pub stars: Option<i32>,
16 pub product_id: i32,
17}
18
19pub 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)]
53pub struct RemoveRating {
54 rating_id: i32,
55}
56
57pub 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 @@
1use actix_web::{get, post, HttpResponse, Responder};
2
3#[get("/")]
4async fn hello() -> impl Responder {
5 HttpResponse::Ok().body("Hello world!")
6}
7
8#[post("/echo")]
9async fn echo(req_body: String) -> impl Responder {
10 HttpResponse::Ok().body(req_body)
11}
12
13pub 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 @@
1use crate::models::{Customer, NewCustomer};
2use crate::schema::customer::dsl::*;
3use crate::TPool;
4
5use actix_identity::Identity;
6use actix_web::{web, HttpResponse, Responder};
7use bcrypt::{hash, verify, DEFAULT_COST};
8use diesel::prelude::*;
9use log::{error, info};
10use serde::Deserialize;
11
12pub 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
29pub 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)]
50pub struct Login {
51 username: String,
52 password: String,
53}
54
55pub 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
85pub async fn logout(cookie: Identity) -> impl Responder {
86 cookie.forget();
87 HttpResponse::Found().header("location", "/").finish()
88}
89
90pub 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)]
114pub struct ChangePassword {
115 old_password: String,
116 new_password: String,
117}
118
119pub 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]
2extern crate diesel;
3
4pub mod handlers;
5pub mod models;
6pub mod schema;
7
8use diesel::r2d2::{self, ConnectionManager};
9use diesel::MysqlConnection;
10pub 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 @@
1use super::schema::{cart_items, customer, product, rating, transaction};
2
3use chrono::naive::{NaiveDate, NaiveDateTime};
4use diesel::{Insertable, Queryable};
5use serde::{Deserialize, Serialize};
6
7/* Member */
8#[derive(Queryable, Serialize)]
9pub 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"]
20pub 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)]
32pub 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"]
42pub 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)]
54pub 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)]
63pub struct CartItem {
64 pub cart_id: i32,
65 pub product_id: i32,
66}
67
68#[derive(Insertable, Deserialize)]
69#[table_name = "cart_items"]
70pub struct AddCartItem {
71 pub cart_id: i32,
72 pub product_id: i32,
73}
74
75/* Rating */
76#[derive(Queryable, Serialize)]
77pub 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"]
88pub 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 @@
1table! {
2 cart_items (cart_id, product_id) {
3 cart_id -> Integer,
4 product_id -> Integer,
5 }
6}
7
8table! {
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
19table! {
20 product (id) {
21 id -> Integer,
22 name -> Varchar,
23 kind -> Nullable<Varchar>,
24 price -> Float,
25 description -> Nullable<Varchar>,
26 }
27}
28
29table! {
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
40table! {
41 transaction (id) {
42 id -> Integer,
43 payment_type -> Varchar,
44 amount -> Float,
45 customer_id -> Nullable<Integer>,
46 }
47}
48
49joinable!(cart_items -> customer (cart_id));
50joinable!(cart_items -> product (product_id));
51joinable!(rating -> customer (customer_id));
52joinable!(rating -> product (product_id));
53joinable!(transaction -> customer (customer_id));
54
55allow_tables_to_appear_in_same_query!(
56 cart_items,
57 customer,
58 product,
59 rating,
60 transaction,
61);