From 9e665f8710599849458beb738c56a4ff30d404d0 Mon Sep 17 00:00:00 2001 From: nicekid1 <86746988+nicekid1@users.noreply.github.com> Date: Sun, 5 Jan 2025 11:35:36 +0330 Subject: [PATCH] Define DTOs and refactor cart module --- migrations/20250105063054-create-cart.js | 62 ++++++++++ src/cart/cart.controller.ts | 46 +++++-- src/cart/cart.module.ts | 11 +- src/cart/cart.service.ts | 149 +++++++++++------------ src/cart/dto/add-to-cart.dto.ts | 13 ++ src/cart/dto/update-cart.dto.ts | 8 ++ src/cart/entities/cart.entity.ts | 13 ++ 7 files changed, 211 insertions(+), 91 deletions(-) create mode 100644 migrations/20250105063054-create-cart.js create mode 100644 src/cart/dto/add-to-cart.dto.ts create mode 100644 src/cart/dto/update-cart.dto.ts diff --git a/migrations/20250105063054-create-cart.js b/migrations/20250105063054-create-cart.js new file mode 100644 index 0000000..2b84cb7 --- /dev/null +++ b/migrations/20250105063054-create-cart.js @@ -0,0 +1,62 @@ +'use strict'; + +module.exports = { + up: async (queryInterface, Sequelize) => { + await queryInterface.createTable('Carts', { + id: { + type: Sequelize.INTEGER, + allowNull: false, + autoIncrement: true, + primaryKey: true, + }, + userId: { + type: Sequelize.INTEGER, + allowNull: false, + references: { + model: 'Users', + key: 'id', + }, + onDelete: 'CASCADE', + }, + productId: { + type: Sequelize.INTEGER, + allowNull: false, + references: { + model: 'Products', + key: 'id', + }, + onDelete: 'CASCADE', + }, + quantity: { + type: Sequelize.INTEGER, + allowNull: false, + }, + productPrice: { + type: Sequelize.DECIMAL(10, 2), + allowNull: false, + }, + totalPrice: { + type: Sequelize.DECIMAL(10, 2), + allowNull: false, + }, + productName: { + type: Sequelize.STRING, + allowNull: false, + }, + createdAt: { + type: Sequelize.DATE, + allowNull: false, + defaultValue: Sequelize.NOW, + }, + updatedAt: { + type: Sequelize.DATE, + allowNull: false, + defaultValue: Sequelize.NOW, + }, + }); + }, + + down: async (queryInterface, Sequelize) => { + await queryInterface.dropTable('Carts'); + }, +}; diff --git a/src/cart/cart.controller.ts b/src/cart/cart.controller.ts index d4fe1eb..44a320f 100644 --- a/src/cart/cart.controller.ts +++ b/src/cart/cart.controller.ts @@ -1,26 +1,46 @@ -import { Controller, Get, Post, Body, Patch, Param, Delete } from "@nestjs/common"; +import { Controller, Get, Post, Patch, Delete, Body, Param, UseGuards, Request } from "@nestjs/common"; import { CartService } from "./cart.service"; +import { JwtAuthGuard } from "src/guard/auth.guard"; +import { AddToCartDto } from "./dto/add-to-cart.dto"; +import { UpdateCartDto } from "./dto/update-cart.dto"; import { Cart } from "./entities/cart.entity"; @Controller("cart") export class CartController { constructor(private readonly cartService: CartService) {} + + @UseGuards(JwtAuthGuard) @Post() - async addToCart(@Body() body: { userId: number; productId: number; quantity: number }): Promise<{ message: string; cartItem: Cart }> { - const { userId, productId, quantity } = body; - const result = await this.cartService.addToCart(userId, productId, quantity); - return result; + async addToCart(@Body() addToCartDto: AddToCartDto, @Request() req: any): Promise<{ message: string; cartItem: Cart }> { + const userId = req.user.id; + return this.cartService.addToCart({ ...addToCartDto, userId }); } - @Get(":userId") - async getUserCart(@Param("userId") userId: number) { + + @UseGuards(JwtAuthGuard) + @Get() + async getUserCart(@Request() req: any): Promise<{ cartItems: Cart[]; totalPrice: number }> { + const userId = req.user.id; return this.cartService.getUserCart(userId); } - @Delete(":userId/:productId") - async removeFromCart( - @Param("userId") userId: number, - @Param("productId") productId: number, - ): Promise<{ message: string }> { - return this.cartService.removeFromCart(userId, productId); + @UseGuards(JwtAuthGuard) + @Patch(":productId") + async updateCart(@Param("productId") productId: number, @Body() updateCartDto: UpdateCartDto, @Request() req: any): Promise<{ message: string; updatedCart: Cart }> { + const userId = req.user.id; + const updatedCart = await this.cartService.updateCart(userId, productId, updateCartDto.quantity); + return { + message: "Cart updated successfully", + updatedCart, + }; + } + + @UseGuards(JwtAuthGuard) + @Delete(":productId") + async removeFromCart(@Param("productId") productId: number, @Request() req: any): Promise<{ message: string }> { + const userId = req.user.id; + await this.cartService.removeFromCart(userId, productId); + return { + message: "Product removed from cart successfully", + }; } } diff --git a/src/cart/cart.module.ts b/src/cart/cart.module.ts index 9560b97..bbca002 100644 --- a/src/cart/cart.module.ts +++ b/src/cart/cart.module.ts @@ -5,10 +5,17 @@ import { Cart } from "./entities/cart.entity"; import { SequelizeModule } from "@nestjs/sequelize"; import { User } from "src/users/entities/user.entity"; import { Product } from "src/products/entities/product.entity"; +import { JwtModule } from "@nestjs/jwt"; +import { JwtAuthGuard } from "src/guard/auth.guard"; @Module({ - imports: [SequelizeModule.forFeature([Cart,User,Product])], + imports: [SequelizeModule.forFeature([Cart,User,Product]), + JwtModule.register({ + secret: process.env.JWT_SECRET, + signOptions: { expiresIn: '1h' }, + }) +], controllers: [CartController], - providers: [CartService], + providers: [CartService,JwtAuthGuard], }) export class CartModule {} diff --git a/src/cart/cart.service.ts b/src/cart/cart.service.ts index 577431b..8fab957 100644 --- a/src/cart/cart.service.ts +++ b/src/cart/cart.service.ts @@ -1,101 +1,98 @@ -import { Injectable } from "@nestjs/common"; +import { Injectable, HttpException, HttpStatus } from "@nestjs/common"; import { InjectModel } from "@nestjs/sequelize"; import { Cart } from "./entities/cart.entity"; -import { HttpException, HttpStatus } from "@nestjs/common"; import { CartResponse } from "./cart.response"; import { User } from "src/users/entities/user.entity"; import { Product } from "src/products/entities/product.entity"; +import { console } from "inspector"; @Injectable() export class CartService { constructor( @InjectModel(Cart) private readonly cartModel: typeof Cart, - @InjectModel(User) private readonly userModel: typeof User, @InjectModel(Product) private readonly productModel: typeof Product, ) {} + + // Add product to cart + async addToCart(addToCartDto: { userId: number; productId: number; quantity: number }): Promise<{ message: string; cartItem: Cart }> { + const { userId, productId, quantity } = addToCartDto; + + if (!userId || !productId || !quantity) { + throw new HttpException("Missing required parameters: userId, productId, and quantity are required.", HttpStatus.BAD_REQUEST); + } + + const product = await this.productModel.findByPk(productId); + if (!product) { + throw new HttpException("Product not found!", HttpStatus.NOT_FOUND); + } + + const existingCartItem = await this.cartModel.findOne({ + where: { userId, productId }, + }); - async addToCart(userId: number, productId: number, quantity: number): Promise { - try { - if (!userId || !productId || !quantity) { - throw new HttpException("Missing required parameters: userId, productId, and quantity are required.", HttpStatus.BAD_REQUEST); - } - - const user = await this.userModel.findByPk(userId); - if (!user) { - throw new HttpException("User not found!", HttpStatus.NOT_FOUND); - } - - const product = await this.productModel.findByPk(productId); - if (!product) { - throw new HttpException("Product not found!", HttpStatus.NOT_FOUND); - } - - const existingCartItem = await this.cartModel.findOne({ - where: { userId, productId }, - }); - - if (existingCartItem) { - existingCartItem.quantity += Number(quantity); - await existingCartItem.save(); - return { - message: "Product quantity updated in cart successfully!", - cartItem: existingCartItem, - }; - } - - const newCartItem = await this.cartModel.create({ userId, productId, quantity }); + if (existingCartItem) { + existingCartItem.quantity += Number(quantity); + existingCartItem.totalPrice = existingCartItem.quantity * existingCartItem.productPrice; + await existingCartItem.save(); return { - message: "Product added to cart successfully!", - cartItem: newCartItem, + message: "Product quantity updated in cart successfully!", + cartItem: existingCartItem, }; - } catch (error) { - // Enhanced error handling - if (error instanceof HttpException) { - throw error; - } - throw new HttpException(`An error occurred while adding the product to the cart: ${error.message}`, HttpStatus.INTERNAL_SERVER_ERROR); } + + const newCartItem = await this.cartModel.create({ userId, productId, quantity, productPrice: product.price, totalPrice: product.price * quantity, productName: product.name }); + + return { + message: "Product added to cart successfully!", + cartItem: newCartItem, + }; + } + + // Get user's cart + async getUserCart(userId: number): Promise<{ cartItems: Cart[]; totalPrice: number }> { + const cartItems = await this.cartModel.findAll({ + where: { userId }, + include: [ + { + model: Product, + attributes: ["id", "name", "price", "description", "imageUrl"], + }, + ], + }); + + if (!cartItems || cartItems.length === 0) { + throw new HttpException("No cart items found for the specified user.", HttpStatus.NOT_FOUND); + } + + const totalPrice = cartItems.reduce((sum, item) => sum + Number(item.totalPrice), 0); + + return { cartItems, totalPrice }; } - async getUserCart(userId: number): Promise { - try { - const cartItems = await this.cartModel.findAll({ - where: { userId }, - include: ["product"], - }); - if (!cartItems || cartItems.length === 0) { - throw new HttpException("No cart items found for the specified user.", HttpStatus.NOT_FOUND); - } - - return cartItems; - } catch (error) { - if (error instanceof HttpException) { - throw error; - } - - throw new HttpException("An error occurred while retrieving the cart.", HttpStatus.INTERNAL_SERVER_ERROR); + + // Update cart item quantity + async updateCart(userId: number, productId: number, quantity: number): Promise { + const cartItem = await this.cartModel.findOne({ where: { userId, productId } }); + + if (!cartItem) { + throw new HttpException("Product not found in the cart.", HttpStatus.NOT_FOUND); } + + cartItem.quantity = quantity; + cartItem.totalPrice = cartItem.quantity * cartItem.productPrice; + await cartItem.save(); + return cartItem; } + // Remove product from cart async removeFromCart(userId: number, productId: number): Promise<{ message: string }> { - try { - const cartItem = await this.cartModel.findOne({ where: { userId, productId } }); - - if (!cartItem) { - throw new HttpException('Product not found in the cart.', HttpStatus.NOT_FOUND); - } - - await cartItem.destroy(); - return { message: 'Item deleted from your cart successfully.' }; - } catch (error) { - if (error instanceof HttpException) { - throw error; - } - - throw new HttpException( - 'An error occurred while removing the product from the cart.', - HttpStatus.INTERNAL_SERVER_ERROR, - ); + const cartItem = await this.cartModel.findOne({ where: { userId, productId } }); + + if (!cartItem) { + throw new HttpException("Product not found in the cart.", HttpStatus.NOT_FOUND); } + + await cartItem.destroy(); + return { message: "Item deleted from your cart successfully." }; } } diff --git a/src/cart/dto/add-to-cart.dto.ts b/src/cart/dto/add-to-cart.dto.ts new file mode 100644 index 0000000..11bd45b --- /dev/null +++ b/src/cart/dto/add-to-cart.dto.ts @@ -0,0 +1,13 @@ +// add-to-cart.dto.ts +import { IsInt, IsNotEmpty, IsNumber, min, Min } from 'class-validator'; + +export class AddToCartDto { + @IsNumber() + @IsNotEmpty() + productId: number; + + @IsInt() + @IsNotEmpty() + @Min(0) + quantity: number; +} diff --git a/src/cart/dto/update-cart.dto.ts b/src/cart/dto/update-cart.dto.ts new file mode 100644 index 0000000..1cab8bb --- /dev/null +++ b/src/cart/dto/update-cart.dto.ts @@ -0,0 +1,8 @@ +// update-cart.dto.ts +import { IsInt, Min } from 'class-validator'; + +export class UpdateCartDto { + @IsInt() + @Min(1) + quantity: number; +} diff --git a/src/cart/entities/cart.entity.ts b/src/cart/entities/cart.entity.ts index 1a8e400..c4bfd00 100644 --- a/src/cart/entities/cart.entity.ts +++ b/src/cart/entities/cart.entity.ts @@ -29,4 +29,17 @@ export class Cart extends Model { allowNull: false, }) productPrice: number; + + @Column({ + type: DataType.DECIMAL(10, 2), + allowNull: false, + }) + totalPrice: number; + + @Column({ + type: DataType.STRING, + allowNull: false, + }) + productName: string; + }