From eb7305366cc09aac255a5860e16ac7f30682b122 Mon Sep 17 00:00:00 2001 From: mahyargdz Date: Sun, 14 Sep 2025 15:38:25 +0330 Subject: [PATCH] feat: add bulk to cart add --- src/modules/cart/DTO/bulkAddToCart.dto.ts | 52 ++++++++++++++++++++ src/modules/cart/cart.controller.ts | 12 +++++ src/modules/cart/cart.service.ts | 60 +++++++++++++++++++++++ 3 files changed, 124 insertions(+) create mode 100644 src/modules/cart/DTO/bulkAddToCart.dto.ts diff --git a/src/modules/cart/DTO/bulkAddToCart.dto.ts b/src/modules/cart/DTO/bulkAddToCart.dto.ts new file mode 100644 index 0000000..f9b0c53 --- /dev/null +++ b/src/modules/cart/DTO/bulkAddToCart.dto.ts @@ -0,0 +1,52 @@ +import { Expose, Type } from "class-transformer"; +import { IsArray, IsNotEmpty, IsNumber, IsString, Min, ValidateNested } from "class-validator"; + +import { ApiProperty } from "../../../common/decorator/swggerDocs"; +import { IsValidId } from "../../../common/decorator/validation.decorator"; +import { ProductVariantModel } from "../../product/models/productVariant.model"; + +export class BulkCartItemDTO { + @Expose() + @IsNotEmpty() + @IsNumber() + @ApiProperty({ type: "number", description: "product id", example: 100000 }) + productId: number; + + @Expose() + @IsNotEmpty() + @IsString() + @IsValidId(ProductVariantModel) + @ApiProperty({ type: "string", description: "variant id of a product", example: "66c0d81ecc135bbc22fc44e3" }) + variantId: string; + + @Expose() + @IsNotEmpty() + @IsNumber() + @Min(1) + @ApiProperty({ type: "number", description: "quantity of a product", example: 2 }) + quantity: number; +} + +export class BulkAddToCartDTO { + @Expose() + @IsArray() + @ValidateNested({ each: true }) + @Type(() => BulkCartItemDTO) + @ApiProperty({ + type: "string", + description: "Array of items to add to cart", + example: [ + { + productId: 100000, + variantId: "66c0d81ecc135bbc22fc44e3", + quantity: 2, + }, + { + productId: 100001, + variantId: "66c0d81ecc135bbc22fc44e4", + quantity: 1, + }, + ], + }) + items: BulkCartItemDTO[]; +} diff --git a/src/modules/cart/cart.controller.ts b/src/modules/cart/cart.controller.ts index d6b19fe..27ef75f 100644 --- a/src/modules/cart/cart.controller.ts +++ b/src/modules/cart/cart.controller.ts @@ -5,6 +5,7 @@ import { controller, httpGet, httpPost, request, requestBody } from "inversify-e import { CartService } from "./cart.service"; import { HttpStatus } from "../../common"; import { AddToCartDTO } from "./DTO/addToCart.dto"; +import { BulkAddToCartDTO } from "./DTO/bulkAddToCart.dto"; import { RemoveFromCartDTO } from "./DTO/removeFromCart.dto"; import { UpdateCartDTO } from "./DTO/updateCart.dto"; import { BaseController } from "../../common/base/controller"; @@ -41,6 +42,17 @@ class CartController extends BaseController { return this.response(data, HttpStatus.Created); } + @ApiOperation("add multiple products to user cart") + @ApiResponse("successful", HttpStatus.Created) + @ApiModel(BulkAddToCartDTO) + @ApiAuth() + @httpPost("/bulk-add", Guard.authUser(), ValidationMiddleware.validateInput(BulkAddToCartDTO)) + public async bulkAddToCart(@requestBody() bulkAddToCartDto: BulkAddToCartDTO, @request() req: Request) { + const user = req.user as IUser; + const data = await this.cartService.bulkAddToCartS(bulkAddToCartDto, user._id.toString()); + return this.response(data, HttpStatus.Created); + } + @ApiOperation("update the user cart") @ApiResponse("successful", HttpStatus.Ok) @ApiModel(UpdateCartDTO) diff --git a/src/modules/cart/cart.service.ts b/src/modules/cart/cart.service.ts index 2484e9b..6cd1698 100644 --- a/src/modules/cart/cart.service.ts +++ b/src/modules/cart/cart.service.ts @@ -3,6 +3,7 @@ import { ClientSession, Types, startSession } from "mongoose"; import { CartRepository, CartShipItemRepo } from "./cart.repository"; import { AddToCartDTO } from "./DTO/addToCart.dto"; +import { BulkAddToCartDTO } from "./DTO/bulkAddToCart.dto"; import { CartDTO } from "./DTO/cart.dto"; import { RemoveFromCartDTO } from "./DTO/removeFromCart.dto"; import { UpdateCartDTO } from "./DTO/updateCart.dto"; @@ -79,6 +80,65 @@ class CartService { } } + async bulkAddToCartS(bulkAddToCartDto: BulkAddToCartDTO, userId: string) { + const session = await startSession(); + session.startTransaction(); + try { + const { items } = bulkAddToCartDto; + + // Get or create cart + let cart = await this.cartRepo.model.findOne({ user: userId }); + if (!cart) { + cart = (await this.cartRepo.model.create([{ user: userId }], { session }))[0]; + } + + // Validate all products before making any changes + for (const item of items) { + const existingCartItem = cart.items.find( + (cartItem) => cartItem.product === item.productId && cartItem.variant.toString() === item.variantId, + ); + const existingQuantity = existingCartItem ? existingCartItem.quantity : 0; + + await this.validateProduct(item.productId, item.variantId, existingQuantity + item.quantity); + } + + // Process each item + for (const item of items) { + const existingCartItem = cart.items.find( + (cartItem) => cartItem.product === item.productId && cartItem.variant.toString() === item.variantId, + ); + + if (existingCartItem) { + // Update quantity if item already exists in the cart + existingCartItem.quantity += item.quantity; + } else { + // Add new item to the cart + cart.items.push({ + product: item.productId, + quantity: item.quantity, + variant: new Types.ObjectId(item.variantId), + }); + } + } + + await cart.save({ session }); + + // Commit the transaction + await session.commitTransaction(); + session.endSession(); + + return { + message: CartMessage.ItemAdded, + cart: await this.getCartS(userId), + }; + } catch (error) { + // Rollback any changes made in the transaction + await session.abortTransaction(); + session.endSession(); + throw error; // Re-throw the error to be handled by the calling errorHandler + } + } + async updateCartS(updateCartDto: UpdateCartDTO, userId: string) { const { productId, variantId, quantity } = updateCartDto;