Compare commits

...

2 Commits

Author SHA1 Message Date
nicekid1 9e665f8710 Define DTOs and refactor cart module 2 months ago
nicekid1 47910e1ac6 Enhance cart models 2 months ago
  1. 62
      migrations/20250105063054-create-cart.js
  2. 46
      src/cart/cart.controller.ts
  3. 11
      src/cart/cart.module.ts
  4. 149
      src/cart/cart.service.ts
  5. 13
      src/cart/dto/add-to-cart.dto.ts
  6. 8
      src/cart/dto/update-cart.dto.ts
  7. 23
      src/cart/entities/cart.entity.ts

@ -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');
},
};

@ -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 { 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"; import { Cart } from "./entities/cart.entity";
@Controller("cart") @Controller("cart")
export class CartController { export class CartController {
constructor(private readonly cartService: CartService) {} constructor(private readonly cartService: CartService) {}
@UseGuards(JwtAuthGuard)
@Post() @Post()
async addToCart(@Body() body: { userId: number; productId: number; quantity: number }): Promise<{ message: string; cartItem: Cart }> { async addToCart(@Body() addToCartDto: AddToCartDto, @Request() req: any): Promise<{ message: string; cartItem: Cart }> {
const { userId, productId, quantity } = body; const userId = req.user.id;
const result = await this.cartService.addToCart(userId, productId, quantity); return this.cartService.addToCart({ ...addToCartDto, userId });
return result;
} }
@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); return this.cartService.getUserCart(userId);
} }
@Delete(":userId/:productId") @UseGuards(JwtAuthGuard)
async removeFromCart( @Patch(":productId")
@Param("userId") userId: number, async updateCart(@Param("productId") productId: number, @Body() updateCartDto: UpdateCartDto, @Request() req: any): Promise<{ message: string; updatedCart: Cart }> {
@Param("productId") productId: number, const userId = req.user.id;
): Promise<{ message: string }> { const updatedCart = await this.cartService.updateCart(userId, productId, updateCartDto.quantity);
return this.cartService.removeFromCart(userId, productId); 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",
};
} }
} }

@ -5,10 +5,17 @@ import { Cart } from "./entities/cart.entity";
import { SequelizeModule } from "@nestjs/sequelize"; import { SequelizeModule } from "@nestjs/sequelize";
import { User } from "src/users/entities/user.entity"; import { User } from "src/users/entities/user.entity";
import { Product } from "src/products/entities/product.entity"; import { Product } from "src/products/entities/product.entity";
import { JwtModule } from "@nestjs/jwt";
import { JwtAuthGuard } from "src/guard/auth.guard";
@Module({ @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], controllers: [CartController],
providers: [CartService], providers: [CartService,JwtAuthGuard],
}) })
export class CartModule {} export class CartModule {}

@ -1,101 +1,98 @@
import { Injectable } from "@nestjs/common"; import { Injectable, HttpException, HttpStatus } from "@nestjs/common";
import { InjectModel } from "@nestjs/sequelize"; import { InjectModel } from "@nestjs/sequelize";
import { Cart } from "./entities/cart.entity"; import { Cart } from "./entities/cart.entity";
import { HttpException, HttpStatus } from "@nestjs/common";
import { CartResponse } from "./cart.response"; import { CartResponse } from "./cart.response";
import { User } from "src/users/entities/user.entity"; import { User } from "src/users/entities/user.entity";
import { Product } from "src/products/entities/product.entity"; import { Product } from "src/products/entities/product.entity";
import { console } from "inspector";
@Injectable() @Injectable()
export class CartService { export class CartService {
constructor( constructor(
@InjectModel(Cart) private readonly cartModel: typeof Cart, @InjectModel(Cart) private readonly cartModel: typeof Cart,
@InjectModel(User) private readonly userModel: typeof User,
@InjectModel(Product) private readonly productModel: typeof Product, @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<CartResponse> { if (existingCartItem) {
try { existingCartItem.quantity += Number(quantity);
if (!userId || !productId || !quantity) { existingCartItem.totalPrice = existingCartItem.quantity * existingCartItem.productPrice;
throw new HttpException("Missing required parameters: userId, productId, and quantity are required.", HttpStatus.BAD_REQUEST); await existingCartItem.save();
}
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 });
return { return {
message: "Product added to cart successfully!", message: "Product quantity updated in cart successfully!",
cartItem: newCartItem, 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<Cart[]> {
try { // Update cart item quantity
const cartItems = await this.cartModel.findAll({ async updateCart(userId: number, productId: number, quantity: number): Promise<Cart> {
where: { userId }, const cartItem = await this.cartModel.findOne({ where: { userId, productId } });
include: ["product"],
}); if (!cartItem) {
if (!cartItems || cartItems.length === 0) { throw new HttpException("Product not found in the cart.", HttpStatus.NOT_FOUND);
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);
} }
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 }> { async removeFromCart(userId: number, productId: number): Promise<{ message: string }> {
try { const cartItem = await this.cartModel.findOne({ where: { userId, productId } });
const cartItem = await this.cartModel.findOne({ where: { userId, productId } });
if (!cartItem) {
if (!cartItem) { throw new HttpException("Product not found in the cart.", HttpStatus.NOT_FOUND);
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,
);
} }
await cartItem.destroy();
return { message: "Item deleted from your cart successfully." };
} }
} }

@ -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;
}

@ -0,0 +1,8 @@
// update-cart.dto.ts
import { IsInt, Min } from 'class-validator';
export class UpdateCartDto {
@IsInt()
@Min(1)
quantity: number;
}

@ -8,14 +8,14 @@ export class Cart extends Model<Cart> {
@Column @Column
userId: number; userId: number;
@BelongsTo(() => User, { onDelete: 'CASCADE' }) @BelongsTo(() => User, { onDelete: "CASCADE" })
user: User; user: User;
@ForeignKey(() => Product) @ForeignKey(() => Product)
@Column @Column
productId: number; productId: number;
@BelongsTo(() => Product, { onDelete: 'CASCADE' }) @BelongsTo(() => Product, { onDelete: "CASCADE" })
product: Product; product: Product;
@Column({ @Column({
@ -23,4 +23,23 @@ export class Cart extends Model<Cart> {
allowNull: false, allowNull: false,
}) })
quantity: number; quantity: number;
@Column({
type: DataType.DECIMAL(10, 2),
allowNull: false,
})
productPrice: number;
@Column({
type: DataType.DECIMAL(10, 2),
allowNull: false,
})
totalPrice: number;
@Column({
type: DataType.STRING,
allowNull: false,
})
productName: string;
} }

Loading…
Cancel
Save