Skip to content

Commit

Permalink
crud cart + added Extension(user_id)
Browse files Browse the repository at this point in the history
  • Loading branch information
Tapo-o-chka committed Jan 4, 2025
1 parent 5f0c410 commit f5c8bc6
Show file tree
Hide file tree
Showing 7 changed files with 283 additions and 12 deletions.
6 changes: 3 additions & 3 deletions src/entities/cart.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ pub struct Model {
pub id: i32,
#[sea_orm(indexed)]
pub user_id: i32,
pub item_id: i32,
pub quantity: i32,
pub product_id: i32,
pub quantity: u32,
}

#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
Expand All @@ -25,7 +25,7 @@ pub enum Relation {
User,
#[sea_orm(
belongs_to = "Product",
from = "crate::entities::cart::Column::ItemId",
from = "crate::entities::cart::Column::ProductId",
to = "crate::entities::product::Column::Id",
on_update = "Cascade",
on_delete = "Cascade"
Expand Down
3 changes: 3 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use crate::routes::{
auth_routes::auth_routes,
category_routes::{category_routes, admin_category_routes},
product_routes::{product_routes, admin_product_routes},
cart_routes::cart_routes,
upload_routes::upload_routes
};

Expand All @@ -34,13 +35,15 @@ async fn main() {
let product_routes = product_routes(shared_db.clone()).await;
let admin_product_routes = admin_product_routes(shared_db.clone()).await;
let upload_routes = upload_routes(shared_db.clone()).await;
let cart_routes = cart_routes(shared_db.clone()).await;

let app = Router::new()
.route("/", get(root))
.nest("/", user_routes)
.nest("/api", category_routes)
.nest("/api", product_routes)
.nest("/api", upload_routes)
.nest("/api", cart_routes)
.nest("/api/admin", admin_category_routes)
.nest("/api/admin", admin_product_routes);

Expand Down
4 changes: 2 additions & 2 deletions src/middleware/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ pub async fn jwt_middleware(mut req: Request<Body>, next: Next) -> impl IntoResp
let token: &str = &auth_header[7..];
match validate_token(token) {
Ok(claims) => {
req.extensions_mut().insert(claims.username.clone());
req.extensions_mut().insert(claims.user_id);
return next.run(req).await;
}
Err(_) => {
Expand Down Expand Up @@ -59,6 +59,6 @@ struct ResponseMessage {

#[derive(Debug, Serialize, Deserialize)]
struct Claims {
username: String,
user_id: i32,
exp: usize,
}
9 changes: 5 additions & 4 deletions src/routes/auth_routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ pub async fn login(
match result {
Ok(Some(model)) => match model.check_hash(&payload.password.clone()) {
Ok(()) => {
let token = generate_token(&*payload.username);
let token = generate_token(model.id);
println!("{:?}", token);
(
StatusCode::OK,
Expand Down Expand Up @@ -148,9 +148,9 @@ pub fn hash_password(password: &str) -> Result<String, argon2::password_hash::Er
Ok(password_hash)
}

fn generate_token(username: &str) -> String {
fn generate_token(user_id: i32) -> String {
let claims = Claims {
username: username.to_string(),
user_id: user_id,
exp: (chrono::Utc::now() + chrono::Duration::days(1)).timestamp() as usize,
};

Expand Down Expand Up @@ -185,10 +185,11 @@ pub struct JWTSend {

#[derive(Debug, Serialize, Deserialize)]
struct Claims {
username: String,
user_id: i32,
exp: usize,
}


#[derive(Debug, Serialize, Deserialize)]
struct ResponseMessage {
message: String,
Expand Down
266 changes: 266 additions & 0 deletions src/routes/cart_routes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
use axum::{
extract::{Extension, Path},
http::StatusCode,
middleware::{self},
response::IntoResponse,
routing::{get, patch},
Json, Router,
};
use sea_orm::{
ActiveModelTrait, ColumnTrait, DatabaseConnection, DbErr, EntityTrait, QueryFilter, Set, TransactionTrait
};
use serde::{Deserialize, Serialize};
use serde_json::json;
use std::sync::Arc;
use tokio::sync::Mutex;

use crate::entities::{cart, product};
use crate::middleware::auth::jwt_middleware;

//ROUTERS
pub async fn cart_routes(db: Arc<Mutex<DatabaseConnection>>) -> Router {
Router::new()
.route("/cart", get(get_cart).post(add_product))
.route("/cart/:id", patch(patch_entry).delete(remove_product))
.layer(middleware::from_fn(jwt_middleware))
.layer(Extension(db))
}

async fn get_cart(
Extension(db): Extension<Arc<Mutex<DatabaseConnection>>>,
Extension(userd_id): Extension<i32>,
) -> impl IntoResponse {
let db = db.lock().await;
match db.begin().await {
Ok(txn) => match cart::Entity::find()
.filter(cart::Column::UserId.eq(userd_id))
.into_json()
.all(&txn)
.await
{
Ok(entries) => (StatusCode::OK, Json(entries)).into_response(),
Err(_) => (
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({
"error": "Internal server error."
})),
)
.into_response(),
},
Err(_) => (
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({
"error": "Internal server error"
})),
)
.into_response(),
}
}

async fn add_product(
Extension(db): Extension<Arc<Mutex<DatabaseConnection>>>,
Extension(userd_id): Extension<i32>,
Json(payload): Json<AddProduct>,
) -> impl IntoResponse {
let db = db.lock().await;
match db.begin().await {
Ok(txn) => match product::Entity::find_by_id(payload.product_id)
.one(&txn)
.await
{
Ok(Some(_)) => {
if payload.quantity > 0 {
let new_entry = cart::ActiveModel {
user_id: Set(userd_id),
product_id: Set(payload.product_id),
quantity: Set(payload.quantity),
..Default::default()
};
match cart::Entity::insert(new_entry).exec(&txn).await {
Ok(_) => {
let _ = txn.commit().await;
(
StatusCode::CREATED,
Json(json!({
"message": "Added successfully"
})),
)
}
Err(_) => {
let _ = txn.rollback().await;
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({
"error": "Internal server error"
})),
)
}
}
} else {
(
StatusCode::BAD_REQUEST,
Json(json!({
"error": "Quantity should be greater than 0"
})),
)
}
}
Ok(None) => (
StatusCode::BAD_REQUEST,
Json(json!({
"error": format!("No product with {} id was found", payload.product_id)
})),
),
Err(_) => (
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({
"error": "Internal server error."
})),
),
},
Err(_) => (
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({
"error": "Internal server error"
})),
),
}
}

async fn remove_product(
Path(id): Path<i32>,
Extension(userd_id): Extension<i32>,
Extension(db): Extension<Arc<Mutex<DatabaseConnection>>>,
) -> impl IntoResponse {
let db = db.lock().await;
match db.begin().await {
Ok(txn) => match cart::Entity::find_by_id(id)
.filter(cart::Column::UserId.eq(userd_id))
.one(&txn)
.await
{
Ok(Some(entry)) => {
let entry: cart::ActiveModel = entry.into();
let result = entry.delete(&txn).await;
match result {
Ok(_) => {
let _ = txn.commit().await;
(
StatusCode::OK,
Json(json!({
"message": "Resource deleted successfully"
})),
)
}
Err(_) => {
let _ = txn.rollback().await;
(
StatusCode::BAD_REQUEST,
Json(json!({
"error": "Failed to delete this resource"
})),
)
}
}
}
Ok(None) => (
StatusCode::BAD_REQUEST,
Json(json!({
"error": format!("No related entry with {} id was found.", id)
})),
),
Err(_) => (
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({
"error": "Internal server error."
})),
),
},
Err(_) => (
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({
"error": "Internal server error"
})),
),
}
}

async fn patch_entry(
Path(id): Path<i32>,
Extension(userd_id): Extension<i32>,
Extension(db): Extension<Arc<Mutex<DatabaseConnection>>>,
Json(payload): Json<PatchCart>,
) -> impl IntoResponse {
let db = db.lock().await;
match db.begin().await {
Ok(txn) => match cart::Entity::find_by_id(id)
.filter(cart::Column::UserId.eq(userd_id))
.one(&txn)
.await
{
Ok(Some(entry)) => {
let mut entry: cart::ActiveModel = entry.into();

let result: Result<(), DbErr> = match payload.quantity {
value if value == 0 => {
entry.delete(&txn).await.map(|_| ())
}
_ => {
entry.quantity = Set(payload.quantity);
entry.update(&txn).await.map(|_| ())
}
};
match result {
Ok(_) => {
let _ = txn.commit().await;
(
StatusCode::OK,
Json(json!({
"message": "Resource patched successfully"
})),
)
}
Err(_) => {
let _ = txn.rollback().await;
(
StatusCode::BAD_REQUEST,
Json(json!({
"error": "Failed to patch this resource"
})),
)
}
}
}
Ok(None) => (
StatusCode::BAD_REQUEST,
Json(json!({
"error": format!("No related entry with {} id was found.", id)
})),
),
Err(_) => (
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({
"error": "Internal server error."
})),
),
},
Err(_) => (
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({
"error": "Internal server error"
})),
),
}
}

//Structs
#[derive(Deserialize)]
struct AddProduct {
product_id: i32,
quantity: u32, //maybe u16 is enough...
}

#[derive(Deserialize)]
struct PatchCart {
quantity: u32
}
3 changes: 2 additions & 1 deletion src/routes/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod auth_routes;
pub mod category_routes;
pub mod upload_routes;
pub mod product_routes;
pub mod product_routes;
pub mod cart_routes;
4 changes: 2 additions & 2 deletions src/routes/product_routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -378,9 +378,9 @@ async fn delete_product(
let product: product::ActiveModel = product.into();
let result = product.delete(&txn).await;
match result {
Ok(new_model) => {
Ok(delete_res) => {
let _ = txn.commit().await;
println!("New model: {:?}", new_model);
println!("New model: {:?}", delete_res);//?????
(
StatusCode::OK,
Json(json!({
Expand Down

0 comments on commit f5c8bc6

Please sign in to comment.