Skip to content

Commit

Permalink
fix(search): fix bugs in search functionality
Browse files Browse the repository at this point in the history
- fix bugs in search functionality
- add new search criteria
- add pagination

[Fixes #148]
  • Loading branch information
jkarenzi committed Jul 16, 2024
1 parent 88c23e7 commit 9554f6c
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 22 deletions.
2 changes: 1 addition & 1 deletion src/__test__/searchProduct.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
53 changes: 35 additions & 18 deletions src/controller/searchProducts.ts
Original file line number Diff line number Diff line change
@@ -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');

Expand All @@ -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
});
}
);
);
25 changes: 23 additions & 2 deletions src/docs/searchProduct.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
6 changes: 5 additions & 1 deletion src/middlewares/validateSearchParams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down

0 comments on commit 9554f6c

Please sign in to comment.