From f190090be164a24362fea323091128126de50b6a Mon Sep 17 00:00:00 2001 From: nicekid1 <86746988+nicekid1@users.noreply.github.com> Date: Sat, 4 Jan 2025 16:05:25 +0330 Subject: [PATCH] Create DTOs and refactor product module routes --- src/products/dto/create-product.dto.ts | 41 ++++++++ src/products/dto/update-product.dto.ts | 40 ++++++++ src/products/products.controller.ts | 20 ++-- src/products/products.service.ts | 128 +++++++++++++++---------- 4 files changed, 171 insertions(+), 58 deletions(-) create mode 100644 src/products/dto/create-product.dto.ts create mode 100644 src/products/dto/update-product.dto.ts diff --git a/src/products/dto/create-product.dto.ts b/src/products/dto/create-product.dto.ts new file mode 100644 index 0000000..1d3fba9 --- /dev/null +++ b/src/products/dto/create-product.dto.ts @@ -0,0 +1,41 @@ +import { IsString, IsNumber, IsOptional, IsNotEmpty, IsArray } from 'class-validator'; + +export class CreateProductDto { + @IsString() + @IsNotEmpty() + name: string; + + @IsString() + @IsNotEmpty() + description: string; + + @IsNumber() + @IsNotEmpty() + price: number; + + @IsOptional() + @IsString() + imageUrl?: string; + + @IsOptional() + @IsArray() + @IsString({ each: true }) + tags?: string[]; + + @IsOptional() + @IsNumber() + @IsNotEmpty() + quantity?: number; + + @IsOptional() + @IsString() + brand?: string; + + @IsOptional() + @IsString() + color?: string; + + @IsString() + @IsNotEmpty() + category: string; +} diff --git a/src/products/dto/update-product.dto.ts b/src/products/dto/update-product.dto.ts new file mode 100644 index 0000000..a6adc80 --- /dev/null +++ b/src/products/dto/update-product.dto.ts @@ -0,0 +1,40 @@ +import { IsString, IsNumber, IsOptional, IsArray } from 'class-validator'; + +export class UpdateProductDto { + @IsOptional() + @IsString() + name?: string; + + @IsOptional() + @IsString() + description?: string; + + @IsOptional() + @IsNumber() + price?: number; + + @IsOptional() + @IsString() + imageUrl?: string; + + @IsOptional() + @IsArray() + @IsString({ each: true }) + tags?: string[]; + + @IsOptional() + @IsNumber() + quantity?: number; + + @IsOptional() + @IsString() + brand?: string; + + @IsOptional() + @IsString() + color?: string; + + @IsOptional() + @IsString() + category?: string; +} diff --git a/src/products/products.controller.ts b/src/products/products.controller.ts index e93cc5d..43ab505 100644 --- a/src/products/products.controller.ts +++ b/src/products/products.controller.ts @@ -1,35 +1,43 @@ import { Controller, Get, Post, Body, Param, Delete, Query, Put, UseGuards } from "@nestjs/common"; import { ProductsService } from "./products.service"; import { Product } from "./entities/product.entity"; +import { CreateProductDto } from "./dto/create-product.dto"; +import { UpdateProductDto } from "./dto/update-product.dto"; import { RoleGuard } from "src/guard/role.guard"; @Controller("products") export class ProductsController { constructor(private readonly productsService: ProductsService) {} + @UseGuards(RoleGuard) @Post() - async create(@Body() body: { name: string; description: string; price: number }) { - const { name, description, price } = body; - const product = await this.productsService.create(name, description, price); + async create(@Body() createProductDto: CreateProductDto) { + const product = await this.productsService.create(createProductDto); return { message: "Product created successfully!", product, }; } + @Get() async findAll(@Query() query: { search?: string; priceMin?: number; priceMax?: number }) { const { search, priceMin, priceMax } = query; return this.productsService.findAll(search, priceMin, priceMax); } + @Get(":id") async findOne(@Param("id") id: string): Promise { return this.productsService.findOne(id); } + @UseGuards(RoleGuard) @Put(":id") - async update(@Param("id") id: string, @Body() body: { name?: string; description?: string; price?: number }): Promise { - const { name, description, price } = body; - return this.productsService.update(id, name, description, price); + async update( + @Param("id") id: string, + @Body() updateProductDto: UpdateProductDto + ): Promise { + return this.productsService.update(id, updateProductDto); } + @UseGuards(RoleGuard) @Delete(':id') async remove(@Param('id') id: string): Promise<{ message: string }> { return this.productsService.remove(id); diff --git a/src/products/products.service.ts b/src/products/products.service.ts index 7d90340..e6cde9a 100644 --- a/src/products/products.service.ts +++ b/src/products/products.service.ts @@ -1,74 +1,97 @@ import { Injectable } from "@nestjs/common"; import { InjectModel } from "@nestjs/sequelize"; import { Product } from "./entities/product.entity"; +import { CreateProductDto } from "./dto/create-product.dto"; +import { UpdateProductDto } from "./dto/update-product.dto"; import { Op } from "sequelize"; import { HttpException, HttpStatus } from "@nestjs/common"; + @Injectable() export class ProductsService { constructor(@InjectModel(Product) private readonly productModel: typeof Product) {} - async create(name: string, description: string, price: number): Promise { - try { - if (!name || !description || price <= 0) { - throw new Error("Invalid input data"); - } - const product = await this.productModel.create({ name, description, price }); - return product; - } catch (error) { - throw new Error("Error creating product"); - } - } - - async findAll(search?: string, priceMin?: number, priceMax?: number): Promise { - const where: any = {}; - + // create a new product + async create(createProductDto: CreateProductDto): Promise { try { - if (search) { - where.name = { - [Op.iLike]: `%${search}%`, - }; + const existingProduct = await this.productModel.findOne({ + where: { name: createProductDto.name }, + }); + + if (existingProduct) { + throw new HttpException( + 'Product with this name already exists.', + HttpStatus.BAD_REQUEST, + ); } - if (priceMin || priceMax) { - where.price = {}; - if (priceMin) where.price[Op.gte] = priceMin; - if (priceMax) where.price[Op.lte] = priceMax; - } - - const products = await this.productModel.findAll({ where }); - - if (!products || products.length === 0) { - throw new HttpException("No products found matching the given criteria.", HttpStatus.NOT_FOUND); - } - - return products; + const product = await this.productModel.create(createProductDto); + return product; } catch (error) { - if (error instanceof HttpException) { - throw error; - } - - throw new HttpException("An error occurred while retrieving products.", HttpStatus.INTERNAL_SERVER_ERROR); + console.error(error); + throw new HttpException( + 'An error occurred while creating the product.', + HttpStatus.INTERNAL_SERVER_ERROR, + ); } } + // find a product by id async findOne(id: string): Promise { try { const product = await this.productModel.findByPk(id); if (!product) { - throw new HttpException("Product not found with the given id.", HttpStatus.NOT_FOUND); + throw new HttpException( + 'Product not found with the given id.', + HttpStatus.NOT_FOUND, + ); } return product; } catch (error) { - if (error instanceof HttpException) { - throw error; - } - - throw new HttpException("An error occurred while retrieving the product.", HttpStatus.INTERNAL_SERVER_ERROR); + console.error(error); + throw new HttpException( + error.response, + HttpStatus.INTERNAL_SERVER_ERROR, + ); } } - async update(id: string, name?: string, description?: string, price?: number): Promise { + + // list of all product + async findAll( + search?: string, + priceMin?: number, + priceMax?: number, + ): Promise { + try { + // ساخت شرط‌های جستجو و فیلتر + const where: any = {}; + if (search) { + where.name = { [Op.like]: `%${search}%` }; // جستجوی نام محصول به صورت جزئی + } + if (priceMin !== undefined) { + where.price = { ...(where.price || {}), [Op.gte]: priceMin }; // فیلتر حداقل قیمت + } + if (priceMax !== undefined) { + where.price = { ...(where.price || {}), [Op.lte]: priceMax }; // فیلتر حداکثر قیمت + } + + const products = await this.productModel.findAll({ where }); + + if (!products || products.length === 0) { + throw new HttpException('No products found.', HttpStatus.NOT_FOUND); + } + + return products; + } catch (error) { + console.error(error); + throw new HttpException( + 'An error occurred while retrieving products.', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + }} + // update a product info + async update(id: string, updateProductDto: UpdateProductDto): Promise { try { const product = await this.productModel.findByPk(id); @@ -76,21 +99,26 @@ export class ProductsService { throw new HttpException("Product not found.", HttpStatus.NOT_FOUND); } + const { name, description, price, imageUrl, tags, quantity, brand, color, category } = updateProductDto; if (name) product.name = name; if (description) product.description = description; if (price) product.price = price; + if (imageUrl) product.imageUrl = imageUrl; + if (tags) product.tags = tags; + if (quantity) product.quantity = quantity; + if (brand) product.brand = brand; + if (color) product.color = color; + if (category) product.category = category; await product.save(); return product; } catch (error) { - if (error instanceof HttpException) { - throw error; - } - throw new HttpException("An error occurred while updating the product.", HttpStatus.INTERNAL_SERVER_ERROR); } } + + // delete a product async remove(id: string): Promise<{ message: string }> { try { const product = await this.productModel.findByPk(id); @@ -103,10 +131,6 @@ export class ProductsService { return { message: "Product deleted successfully." }; } catch (error) { - if (error instanceof HttpException) { - throw error; - } - throw new HttpException("An error occurred while deleting the product.", HttpStatus.INTERNAL_SERVER_ERROR); } }