diff --git a/src/__test__/searchProduct.test.ts b/src/__test__/searchProduct.test.ts index 8f23282..b8d4ff5 100644 --- a/src/__test__/searchProduct.test.ts +++ b/src/__test__/searchProduct.test.ts @@ -23,7 +23,7 @@ describe('Search Products Controller Test', () => { it('should search products by category', async () => { const response = await request(app) - .get('/api/v1/search?category=categoryName') + .get('/api/v1/search?category=1&category&2') .set('Authorization', `Bearer ${buyerToken}`); expect(response.status).toBe(200); expect(response.body.data).toBeDefined(); diff --git a/src/controller/searchProducts.ts b/src/controller/searchProducts.ts index a5c0179..f5a76db 100644 --- a/src/controller/searchProducts.ts +++ b/src/controller/searchProducts.ts @@ -1,15 +1,29 @@ import { Request, Response } from 'express'; import dbConnection from '../database'; import Product from '../database/models/productEntity'; -import Category from '../database/models/categoryEntity'; import errorHandler from '../middlewares/errorHandler'; const productRepository = dbConnection.getRepository(Product); -const categoryRepository = dbConnection.getRepository(Category); + +interface searchParams { + keyword?: string; + category?: number[]; + productName?: string; + rating?: number[]; + minPrice?:number; + maxPrice?:number; + sort?: string; + page?:number; + limit?:number; +} + +export const paginate = (query: any, page: number, limit: number) => { + return query.skip((page - 1) * limit).take(limit); +}; export const searchProducts = errorHandler( async (req: Request, res: Response) => { - const { keyword, category, productName, sort } = req.query; + const { keyword, category, productName, rating, minPrice, maxPrice, sort='DESC', page=1, limit=9 }: searchParams = req.query; let queryBuilder = productRepository.createQueryBuilder('product'); @@ -20,35 +34,38 @@ export const searchProducts = errorHandler( ); } - if (category) { - const categoryEntity = await categoryRepository.findOne({ - where: { name: category as string }, - }); - if (categoryEntity) { - queryBuilder = queryBuilder.andWhere( - 'product.categoryId = :categoryId', - { categoryId: categoryEntity.id } - ); - } + if (category && category.length > 0) { + queryBuilder = queryBuilder.andWhere('product.categoryId IN (:...category)', { category }); } if (productName) { queryBuilder = queryBuilder.andWhere('product.name ILIKE :productName', { - productName: `%${productName}%`, + productName: `%${productName}%` + }); + } + + if (rating && rating.length > 0) { + queryBuilder = queryBuilder.andWhere('product.averageRating IN (:...rating)', { rating }); + } + + if (minPrice && maxPrice) { + queryBuilder = queryBuilder.andWhere('product.salesPrice BETWEEN :minPrice AND :maxPrice', { + minPrice, + maxPrice, }); } if (sort) { const sortDirection = sort.toString().toUpperCase() as 'ASC' | 'DESC'; - queryBuilder = queryBuilder.orderBy('product.salesPrice', sortDirection); + queryBuilder = queryBuilder.orderBy('product.createdAt', sortDirection); } const total = await queryBuilder.getCount(); - const products = await queryBuilder.getMany(); + const products = await paginate(queryBuilder, page, limit).getMany(); return res.status(200).json({ data: products, - total, + total }); } -); +); \ No newline at end of file diff --git a/src/docs/searchProduct.ts b/src/docs/searchProduct.ts index 5c91be9..a6b85f0 100644 --- a/src/docs/searchProduct.ts +++ b/src/docs/searchProduct.ts @@ -13,13 +13,34 @@ * description: Keyword to search for in product name, short description, or long description. * - in: query * name: category - * type: string - * description: Name of the category to filter the products. + * type: array + * description: An array of category IDs + * - in: query + * name: rating + * type: array + * description: An array of ratings * - in: query * name: productName * type: string * description: Name of the product to filter the products. * - in: query + * name: page + * type: number + * description: page to return + * - in: query + * name: limit + * type: number + * description: number of items to return per page + * - in: query + * name: minPrice + * type: number + * description: minPrice of products to return + * - in: query + * name: maxPrice + * type: number + * enum: [asc, desc] + * description: maxPrice of products to return + * - in: query * name: sort * type: string * enum: [asc, desc] diff --git a/src/middlewares/validateSearchParams.ts b/src/middlewares/validateSearchParams.ts index 38cd0a9..5a134b6 100644 --- a/src/middlewares/validateSearchParams.ts +++ b/src/middlewares/validateSearchParams.ts @@ -2,7 +2,11 @@ import { query, validationResult } from 'express-validator'; import { Request, Response, NextFunction } from 'express'; export const validateSearchParams = [ query('keyword').optional().isString().withMessage('Keyword must be a string'), - query('category').optional().isString().withMessage('Category must be a string'), + query('category').optional().isArray().withMessage('Category must be an array'), + query('rating').optional().isArray().withMessage('Rating must be an array'), + query('page').optional().isString().withMessage('Page must be a number'), + query('minPrice').optional().isString().withMessage('minPrice must be a number'), + query('maxPrice').optional().isString().withMessage('maxPrice must be a number'), query('brand').optional().isString().withMessage('Brand must be a string'), query('productName').optional().isString().withMessage('Product name must be a string'), query('sort').optional().isIn(['asc', 'desc']).withMessage('Sort order must be either asc or desc'),