Compare commits

...

2 Commits

Author SHA1 Message Date
nicekid1 f190090be1 Create DTOs and refactor product module routes 2 months ago
nicekid1 716c4a6782 Enhance product models 2 months ago
  1. 65
      migrations/20250104112403-create-product.js
  2. 7
      src/admin/admin.service.ts
  3. 41
      src/products/dto/create-product.dto.ts
  4. 40
      src/products/dto/update-product.dto.ts
  5. 37
      src/products/entities/product.entity.ts
  6. 24
      src/products/products.controller.ts
  7. 11
      src/products/products.module.ts
  8. 128
      src/products/products.service.ts

@ -0,0 +1,65 @@
'use strict';
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable('Products', {
id: {
type: Sequelize.INTEGER,
allowNull: false,
autoIncrement: true,
primaryKey: true,
},
name: {
type: Sequelize.STRING,
allowNull: false,
},
description: {
type: Sequelize.STRING,
allowNull: false,
},
price: {
type: Sequelize.DECIMAL(10, 2),
allowNull: false,
},
imageUrl: {
type: Sequelize.STRING,
allowNull: true,
},
tags: {
type: Sequelize.ARRAY(Sequelize.STRING),
allowNull: true,
},
quantity: {
type: Sequelize.INTEGER,
allowNull: false,
defaultValue: 0,
},
brand: {
type: Sequelize.STRING,
allowNull: true,
},
color: {
type: Sequelize.STRING,
allowNull: true,
},
category: {
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('Products');
},
};

@ -41,12 +41,12 @@ export class AdminService {
const admin = await this.adminModel.findOne({ where: { email: loginAdminDto.email } });
if (!admin) {
throw new HttpException("Invalid email or password", HttpStatus.UNAUTHORIZED);
throw new HttpException("Invalid email or password or username", HttpStatus.UNAUTHORIZED);
}
const isValidPassword = await bcrypt.compare(loginAdminDto.password, admin.password);
if (!isValidPassword) {
throw new HttpException("Invalid email or password", HttpStatus.UNAUTHORIZED);
throw new HttpException("Invalid email or password or username", HttpStatus.UNAUTHORIZED);
}
const token = this.jwtService.sign(
@ -59,8 +59,7 @@ export class AdminService {
return { token };
} catch (error) {
console.log(error);
throw new HttpException("An error occurred during login.", HttpStatus.INTERNAL_SERVER_ERROR);
throw new HttpException(error.response, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
//edit admin profile method

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

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

@ -19,4 +19,41 @@ export class Product extends Model<Product> {
allowNull: false,
})
price: number;
@Column({
type: DataType.STRING,
allowNull: true,
})
imageUrl: string;
@Column({
type: DataType.ARRAY(DataType.STRING),
allowNull: true,
})
tags: string[];
@Column({
type: DataType.INTEGER,
allowNull: false,
defaultValue: 0,
})
quantity: number;
@Column({
type: DataType.STRING,
allowNull: true,
})
brand: string;
@Column({
type: DataType.STRING,
allowNull: true,
})
color: string;
@Column({
type: DataType.STRING,
allowNull: false,
})
category: string;
}

@ -1,33 +1,43 @@
import { Controller, Get, Post, Body, Param, Delete, Query, Put } from "@nestjs/common";
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<Product> {
return this.productsService.findOne(id);
}
@UseGuards(RoleGuard)
@Put(":id")
async update(@Param("id") id: string, @Body() body: { name?: string; description?: string; price?: number }): Promise<Product> {
const { name, description, price } = body;
return this.productsService.update(id, name, description, price);
async update(
@Param("id") id: string,
@Body() updateProductDto: UpdateProductDto
): Promise<Product> {
return this.productsService.update(id, updateProductDto);
}
@UseGuards(RoleGuard)
@Delete(':id')
async remove(@Param('id') id: string): Promise<{ message: string }> {
return this.productsService.remove(id);

@ -3,10 +3,17 @@ import { ProductsService } from "./products.service";
import { ProductsController } from "./products.controller";
import { SequelizeModule } from "@nestjs/sequelize";
import { Product } from "./entities/product.entity";
import { RoleGuard } from "src/guard/role.guard";
import { JwtModule } from "@nestjs/jwt";
@Module({
imports: [SequelizeModule.forFeature([Product])],
imports: [SequelizeModule.forFeature([Product]),
JwtModule.register({
secret: process.env.JWT_SECRET,
signOptions: { expiresIn: '1h' },
})
],
controllers: [ProductsController],
providers: [ProductsService],
providers: [ProductsService,RoleGuard],
})
export class ProductsModule {}

@ -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<Product> {
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<Product[]> {
const where: any = {};
// create a new product
async create(createProductDto: CreateProductDto): Promise<Product> {
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<Product> {
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<Product> {
// list of all product
async findAll(
search?: string,
priceMin?: number,
priceMax?: number,
): Promise<Product[]> {
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<Product> {
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);
}
}

Loading…
Cancel
Save