Skip to content

Commit

Permalink
feat(shop): added search endpoint with pagination
Browse files Browse the repository at this point in the history
  • Loading branch information
KostaD02 committed Aug 15, 2023
1 parent 7763ac6 commit d10b34d
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 18 deletions.
8 changes: 3 additions & 5 deletions src/enums/exceptions.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,12 @@ export enum ProductExceptionKeys {
IncorrectSortDirection = 'errors.incorrect_sort_direction',
}

export enum ProductPaginationExceptionKeys {
export enum GlobalExceptionKeys {
IncorrectMongooseID = 'error.incorrect_mongoose_id',
PageIndexNotNumber = 'errors.page_index.not_number',
PageIndexTooLow = 'errors.page_index.too_low',
PageSizeNotNumber = 'errors.page_size.not_number',
PageSizeTooLow = 'errors.page_size.too_low',
PageSizeTooHigh = 'errors.page_size.too_high',
}

export enum GlobalExceptionKeys {
IncorrectMongooseID = 'error.incorrect_mongoose_id',
EndPointNotFound = 'errors.endpoint_not_found',
}
8 changes: 6 additions & 2 deletions src/http-exceptions.filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
HttpException,
} from '@nestjs/common';
import { Request, Response } from 'express';
import { GlobalExceptionKeys } from './enums';

@Catch(HttpException)
export class HttpExceptionsFilter implements ExceptionFilter {
Expand All @@ -15,14 +16,17 @@ export class HttpExceptionsFilter implements ExceptionFilter {
const status = exception.getStatus();

const exceptionResponse = exception.getResponse() as {
message: string[];
message: string | string[];
error: string;
statusCode: number;
};

response.status(status).json({
error: exceptionResponse.error,
errorKeys: exceptionResponse.message,
errorKeys:
exceptionResponse.message === `Cannot GET ${request.url}`
? [GlobalExceptionKeys.EndPointNotFound]
: exceptionResponse.message,
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
Expand Down
12 changes: 6 additions & 6 deletions src/modules/shop/dtos/all-product.dto.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import { IsOptional, IsNumber, Min, Max } from 'class-validator';
import { API_CONFIG } from 'src/consts';
import { ProductPaginationExceptionKeys } from 'src/enums';
import { GlobalExceptionKeys } from 'src/enums';

export class AllProductsQueryDto {
@IsOptional()
@IsNumber({}, { message: ProductPaginationExceptionKeys.PageIndexNotNumber })
@IsNumber({}, { message: GlobalExceptionKeys.PageIndexNotNumber })
@Min(API_CONFIG.MINIMUM_PAGE_INDEX, {
message: ProductPaginationExceptionKeys.PageIndexTooLow,
message: GlobalExceptionKeys.PageIndexTooLow,
})
page_index: number;

@IsOptional()
@IsNumber({}, { message: ProductPaginationExceptionKeys.PageSizeNotNumber })
@IsNumber({}, { message: GlobalExceptionKeys.PageSizeNotNumber })
@Min(API_CONFIG.MINIMUM_PAGE_SIZE, {
message: ProductPaginationExceptionKeys.PageSizeTooLow,
message: GlobalExceptionKeys.PageSizeTooLow,
})
@Max(API_CONFIG.MAXIMUM_PAGE_SIZE, {
message: ProductPaginationExceptionKeys.PageSizeTooHigh,
message: GlobalExceptionKeys.PageSizeTooHigh,
})
page_size: number;
}
24 changes: 23 additions & 1 deletion src/modules/shop/dtos/search-product-query.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,31 @@ import {
Min,
} from 'class-validator';
import { API_CONFIG } from 'src/consts';
import { ProductExceptionKeys, SortDirection, SortProductsBy } from 'src/enums';
import {
GlobalExceptionKeys,
ProductExceptionKeys,
SortDirection,
SortProductsBy,
} from 'src/enums';

export class SearchProductsQueryDto {
@IsOptional()
@IsNumber({}, { message: GlobalExceptionKeys.PageIndexNotNumber })
@Min(API_CONFIG.MINIMUM_PAGE_INDEX, {
message: GlobalExceptionKeys.PageIndexTooLow,
})
page_index: number;

@IsOptional()
@IsNumber({}, { message: GlobalExceptionKeys.PageSizeNotNumber })
@Min(API_CONFIG.MINIMUM_PAGE_SIZE, {
message: GlobalExceptionKeys.PageSizeTooLow,
})
@Max(API_CONFIG.MAXIMUM_PAGE_SIZE, {
message: GlobalExceptionKeys.PageSizeTooHigh,
})
page_size: number;

@IsOptional()
@IsString()
keywords: string;
Expand Down
81 changes: 77 additions & 4 deletions src/modules/shop/product/products.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import mongoose, { Model } from 'mongoose';
import mongoose, { Model, SortOrder } from 'mongoose';

import {
CreateProductDto,
Expand All @@ -10,7 +10,12 @@ import {
} from '../dtos';
import { Product, ProductDocument } from 'src/schemas';
import { ExceptionService } from 'src/shared';
import { ExceptionStatusKeys, GlobalExceptionKeys } from 'src/enums';
import {
ExceptionStatusKeys,
GlobalExceptionKeys,
SortDirection,
SortProductsBy,
} from 'src/enums';
import { ProductCategory } from 'src/interfaces';
import { API_CONFIG } from 'src/consts';

Expand Down Expand Up @@ -69,6 +74,7 @@ export class ProductsService {
const skip = responsePerPage * (Math.floor(currentPage) - 1);
const products = await this.productModel
.find({})
.sort({ 'price.current': 1 })
.limit(responsePerPage)
.skip(skip);
const productsCount = await this.productModel.countDocuments({});
Expand All @@ -82,8 +88,75 @@ export class ProductsService {
}

async searchProduct(query: SearchProductsQueryDto) {
// TODO: implement later
return [];
const currentPage = query.page_index || API_CONFIG.MINIMUM_PAGE_INDEX;
const responsePerPage = query.page_size || API_CONFIG.RESPONSE_PER_PAGE;
const skip = responsePerPage * (Math.floor(currentPage) - 1);
const queryObject: {
'price.current'?: object;
'category.id'?: string;
title?: object;
brand?: string;
rating?: object;
} = {};

const sortObject: {
'price.current'?: SortOrder;
title?: SortOrder;
rating?: SortOrder;
issueDate?: SortOrder;
} = {};

if (query.keywords) {
queryObject.title = { $regex: query.keywords, $options: 'i' };
}
if (query.category_id) {
queryObject['category.id'] = query.category_id;
}
if (query.price_min) {
queryObject['price.current'] = { $gt: query.price_min };
}
if (query.price_max) {
queryObject['price.current'] = {
...queryObject['price.current'],
$lt: query.price_max,
};
}
if (query.brand) {
queryObject.brand = query.brand;
}
if (query.rating) {
queryObject.rating = { $gt: query.rating };
}
if (query.sort_by && query.sort_direction) {
const sortDirection: SortOrder =
query.sort_direction === SortDirection.Ascending ? 1 : -1;
if (query.sort_by === SortProductsBy.Title) {
sortObject.title = sortDirection;
} else if (query.sort_by === SortProductsBy.Rating) {
sortObject.rating = sortDirection;
} else if (query.sort_by === SortProductsBy.Price) {
sortObject['price.current'] = sortDirection;
} else if (query.sort_by === SortProductsBy.IssueDate) {
sortObject.issueDate = sortDirection;
} else {
sortObject['price.current'] = 1;
}
} else {
sortObject['price.current'] = 1;
}
const productsCount = await this.productModel.countDocuments({});
const products = await this.productModel
.find({ ...queryObject })
.sort({ ...sortObject })
.limit(responsePerPage)
.skip(skip);
return {
total: productsCount,
limit: responsePerPage,
page: currentPage,
skip,
products,
};
}

async getCategories(): Promise<ProductCategory[]> {
Expand Down

0 comments on commit d10b34d

Please sign in to comment.