Merge pull request 'Fixed validation issues. Cleared unnecessary DTOs' (#12) from feature/exception-filter into develop

Reviewed-on: http://85.143.176.51:3000/free-land/backend/pulls/12
This commit is contained in:
moeidtopcoder 2022-10-07 10:33:45 +00:00
commit 71e0ab996a
11 changed files with 53 additions and 170 deletions

View File

@ -1,11 +1,9 @@
import { Body, Controller, Get, HttpCode, Param, ParseUUIDPipe, Query, Req, UseFilters, UseInterceptors, UsePipes } from "@nestjs/common"; import { Controller, Get, HttpCode, Param, ParseUUIDPipe, Query, Req, UseFilters, UseInterceptors } from "@nestjs/common";
import { SearchService } from "../../core/services/common/search.service"; import { SearchService } from "../../core/services/common/search.service";
import { PageInterceptor } from "../../core/interceptors/page.interceptor"; import { PageInterceptor } from "../../core/interceptors/page.interceptor";
import { ApiExtraModels, ApiGatewayTimeoutResponse, ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger"; import { ApiExtraModels, ApiGatewayTimeoutResponse, ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger";
import { RequestDto } from "../../core/domain/dtos/request.dto"; import { EsHitDto, EsResponseDto, PageDto, PaperDto, SearchQueryDto } from "../../core/domain";
import { EsHitDto, EsResponseDto, PageDto, PaperDto } from "../../core/domain";
import { HttpExceptionFilter } from "src/core/filters/http-exception.filter"; import { HttpExceptionFilter } from "src/core/filters/http-exception.filter";
import { QueryStringPipe } from "src/core/pipes/query-str.pipe";
/** /**
* /papers/ route controller * /papers/ route controller
@ -15,8 +13,7 @@ import { QueryStringPipe } from "src/core/pipes/query-str.pipe";
version: '1', version: '1',
path: 'papers', path: 'papers',
}) })
@ApiExtraModels(RequestDto, EsHitDto, EsResponseDto) @ApiExtraModels(EsHitDto, EsResponseDto)
// @UseInterceptors(CacheInterceptor)
export class PapersController { export class PapersController {
constructor(private searchService: SearchService) {} constructor(private searchService: SearchService) {}
@ -39,10 +36,10 @@ export class PapersController {
description: 'Elasticsearch request timed out' description: 'Elasticsearch request timed out'
}) })
@Get('search') @Get('search')
@UseInterceptors(PageInterceptor)
@HttpCode(200) @HttpCode(200)
getByContext(@Req() request: RequestDto): Promise<EsResponseDto> { @UseInterceptors(PageInterceptor)
return this.searchService.findByContext(request.es_query).then( getByContext(@Query() request: SearchQueryDto): Promise<EsResponseDto> {
return this.searchService.findByContext(request).then(
(response) => { (response) => {
return response; return response;
}, },

View File

@ -3,6 +3,4 @@ export * from './elastic/es-response.dto';
export * from './elastic/es-hit.dto'; export * from './elastic/es-hit.dto';
export * from './page.dto'; export * from './page.dto';
export * from './search-q.dto'; export * from './search-q.dto';
export * from './search-result.dto';
export * from './paper.dto'; export * from './paper.dto';
export * from './request.dto';

View File

@ -1,48 +0,0 @@
import { ApiExtraModels, ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
import { IsDefined, IsNotEmpty, IsOptional } from "class-validator";
import { EsQueryDto } from "./elastic/es-query.dto";
import { SearchQueryDto } from "./search-q.dto";
/**
* List of allowed properties in this DTO
*/
const allowedProperties = ['query', 'es_query'];
/**
* Request object, which contains query parameters and Elasticsearch query object
*/
@ApiExtraModels()
export class RequestDto {
/**
* Query parameters object
*/
@IsDefined()
@IsNotEmpty()
@ApiProperty({
type: SearchQueryDto,
description: 'Actual query with parameters acquired from the request',
example: {}
})
query: SearchQueryDto;
/**
* Elasticsearch query object
*/
@IsOptional()
@ApiPropertyOptional({
type: EsQueryDto,
description: 'Elasticsearch query body constructed by pagination mechanism',
example: {},
})
es_query?: EsQueryDto;
/**
* Constructs an object with provided parameters
* @param query
* @param es_query
*/
constructor(query: SearchQueryDto, es_query: EsQueryDto) {
this.query = query;
this.es_query = es_query;
}
}

View File

@ -1,10 +1,11 @@
import { ApiExtraModels, ApiPropertyOptional } from "@nestjs/swagger"; import { ApiExtraModels, ApiPropertyOptional } from "@nestjs/swagger";
import { IsDefined, IsInt, IsNotEmpty, IsOptional, IsString } from "class-validator"; import { Type } from "class-transformer";
import { IsDefined, IsInt, IsNotEmpty, IsOptional, IsString, Min } from "class-validator";
/** /**
* List of allowed properties in this DTO * List of allowed properties in this DTO
*/ */
const allowedProperties = ['query', 'pagen', 'limit', 'order']; const allowedProperties = ['query', 'limit', 'offset', 'order'];
/** /**
* Elasticsearch response DTO * Elasticsearch response DTO
@ -28,9 +29,12 @@ export class SearchQueryDto {
*/ */
@IsOptional() @IsOptional()
@IsInt() @IsInt()
@Type(() => Number)
@Min(1)
@ApiPropertyOptional({ @ApiPropertyOptional({
description: 'Limits the number of displayed elements', description: 'Limits the number of displayed elements',
example: 10, example: 10,
required: false
}) })
limit?: number; limit?: number;
@ -39,9 +43,12 @@ export class SearchQueryDto {
*/ */
@IsOptional() @IsOptional()
@IsInt() @IsInt()
@Type(() => Number)
@Min(0)
@ApiPropertyOptional({ @ApiPropertyOptional({
description: 'Offset from the start of the list of hits', description: 'Offset from the start of the list of hits',
example: 0, example: 0,
required: false,
}) })
offset?: number; offset?: number;
@ -53,13 +60,10 @@ export class SearchQueryDto {
@ApiPropertyOptional({ @ApiPropertyOptional({
description: 'Indicates in which order elements need to be displayed', description: 'Indicates in which order elements need to be displayed',
example: 'asc', example: 'asc',
required: false,
}) })
order?: string; order?: string;
/**
*
*/
/** /**
* Constructs an object with provided parameters * Constructs an object with provided parameters
* @param query * @param query
@ -67,9 +71,10 @@ export class SearchQueryDto {
* @param limit * @param limit
* @param order * @param order
*/ */
constructor(query: string, page: number, limit: number, order: string) { constructor(query: string = undefined, limit: number = 10, offset: number = 0, order: string = undefined) {
this.query = query; this.query = query;
this.limit = limit; this.limit = limit;
this.offset = offset;
this.order = order; this.order = order;
} }
} }

View File

@ -1,53 +0,0 @@
import { ApiExtraModels, ApiProperty } from "@nestjs/swagger";
import { IsArray, IsDefined, IsInt, IsNotEmpty } from "class-validator";
import { EsResponseDto } from "./elastic/es-response.dto";
/**
* List of allowed properties in this DTO
*/
const allowedProperties = ['data', 'status'];
/**
* Elasticsearch response DTO
*/
@ApiExtraModels()
export class SearchResultDto {
/**
* Status code
*/
@IsDefined()
@IsNotEmpty()
@IsInt()
@ApiProperty({
description: 'Status code',
example: 200,
})
statusCode: number;
/**
* All the data acquired.
*/
@IsDefined()
@IsNotEmpty()
@IsArray()
@ApiProperty({
description: 'Data acquired from the Elasticsearch',
example: {
took: 1,
timed_out: false,
_shards: {},
hits: {}
},
})
data: EsResponseDto;
/**
* Constructs an object with provided parameters
* @param code
* @param data
*/
constructor(code: number, data: EsResponseDto) {
this.statusCode = code;
this.data = data;
}
}

View File

@ -2,9 +2,6 @@ import { HttpService } from "@nestjs/axios";
import { CACHE_MANAGER, CallHandler, ExecutionContext, Inject, Injectable, NestInterceptor } from "@nestjs/common"; import { CACHE_MANAGER, CallHandler, ExecutionContext, Inject, Injectable, NestInterceptor } from "@nestjs/common";
import { Observable, map, take, switchMap, of } from "rxjs"; import { Observable, map, take, switchMap, of } from "rxjs";
import { PageDto } from "../domain/dtos"; import { PageDto } from "../domain/dtos";
import { EsQueryDto } from "../domain/dtos/elastic/es-query.dto";
import { RequestDto } from "../domain/dtos/request.dto";
import { SearchQueryDto } from "../domain/dtos/search-q.dto";
import { EsTime } from "../domain/enums/es-time.enum"; import { EsTime } from "../domain/enums/es-time.enum";
import { Order, toOrder } from "../domain/enums/page-order.enum"; import { Order, toOrder } from "../domain/enums/page-order.enum";
import { EsPit } from "../domain/interfaces/elastic/es-pit.interface"; import { EsPit } from "../domain/interfaces/elastic/es-pit.interface";
@ -43,29 +40,20 @@ export class PageInterceptor implements NestInterceptor {
* @returns Page with content and metadata * @returns Page with content and metadata
*/ */
async intercept(context: ExecutionContext, next: CallHandler<any>): Promise<Observable<PageDto>> { async intercept(context: ExecutionContext, next: CallHandler<any>): Promise<Observable<PageDto>> {
const request: RequestDto = context.switchToHttp().getRequest<RequestDto>(); const query = context.switchToHttp().getRequest().query;
const query: SearchQueryDto = request.query;
const offset = !query.offset ? 0 : query.offset; // const offset = !query.offset ? 0 : query.offset;
const limit = !query.limit ? 10 : query.limit; const offset = query.offset;
const order = !query.order ? Order.DESC : query.order; // const limit = !query.limit ? 10 : query.limit;
const limit = query.limit;
// const order = !query.order ? Order.DESC : query.order;
const order = query.order;
const prev_page = await this.cacheManager.get('prev_page'); const prev_page = await this.cacheManager.get('prev_page');
if (prev_page) { if (prev_page) {
if (offset == prev_page[1] && limit == prev_page[2] && order == prev_page[3]) return of(prev_page[0]); if (offset == prev_page[1] && limit == prev_page[2] && order == prev_page[3]) return of(prev_page[0]);
} }
// Contruct a body for querying Elasticsearch
request.es_query = new EsQueryDto();
request.es_query.query = {
query_string: {
query: query.query,
default_field: 'content',
}
};
request.es_query.from = offset;
request.es_query.size = limit;
return next.handle().pipe( return next.handle().pipe(
switchMap(async (res) => { switchMap(async (res) => {
// Setting the page meta-data // Setting the page meta-data

View File

@ -1,13 +0,0 @@
import { ArgumentMetadata, BadRequestException, ImATeapotException, Injectable, PipeTransform } from "@nestjs/common";
import { RequestDto } from "../domain";
@Injectable()
export class QueryStringPipe implements PipeTransform {
constructor() {}
transform(value: RequestDto, metadata: ArgumentMetadata): RequestDto {
console.log(value.query.limit)
return value;
}
}

View File

@ -1,7 +1,7 @@
import { HttpService } from "@nestjs/axios"; import { HttpService } from "@nestjs/axios";
import { GatewayTimeoutException, Injectable, NotFoundException } from "@nestjs/common"; import { GatewayTimeoutException, Injectable, NotFoundException } from "@nestjs/common";
import { map, take } from "rxjs"; import { map, take } from "rxjs";
import { EsResponseDto} from "../../domain/dtos"; import { EsResponseDto, SearchQueryDto} from "../../domain/dtos";
import { EsQueryDto } from "../../domain/dtos/elastic/es-query.dto"; import { EsQueryDto } from "../../domain/dtos/elastic/es-query.dto";
/** /**
@ -32,10 +32,9 @@ export class SearchService {
* @returns Elasticsearch hits or an error object * @returns Elasticsearch hits or an error object
*/ */
async findByID(uuid: string): Promise<EsResponseDto> { // Should I change 'object' to specific DTO? async findByID(uuid: string): Promise<EsResponseDto> { // Should I change 'object' to specific DTO?
let ESQ: EsQueryDto = new EsQueryDto; const es_query: EsQueryDto = new EsQueryDto();
es_query.size = 1;
ESQ.size = 1; es_query.query = {
ESQ.query = {
query_string: { query_string: {
query: ('id:' + uuid), query: ('id:' + uuid),
} }
@ -44,7 +43,7 @@ export class SearchService {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
try { try {
(this.httpService.get<EsResponseDto>(`http://${this.ES_IP}:${this.ES_PORT}/_search`, { (this.httpService.get<EsResponseDto>(`http://${this.ES_IP}:${this.ES_PORT}/_search`, {
data: ESQ, data: es_query,
headers: {'Content-Type': 'application/json'}, headers: {'Content-Type': 'application/json'},
})) }))
?.pipe(take(1), map(axiosRes => axiosRes.data)) ?.pipe(take(1), map(axiosRes => axiosRes.data))
@ -68,7 +67,18 @@ export class SearchService {
* @param query, <EsQueryDto> * @param query, <EsQueryDto>
* @returns Elasticsearch hits or an error object * @returns Elasticsearch hits or an error object
*/ */
async findByContext(es_query: EsQueryDto): Promise<EsResponseDto> { async findByContext(query: SearchQueryDto): Promise<EsResponseDto> {
// Contruct a body for querying Elasticsearch
const es_query: EsQueryDto = new EsQueryDto();
es_query.query = {
query_string: {
query: query.query,
default_field: 'content',
}
};
es_query.from = query.offset;
es_query.size = query.limit;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
try { try {
(this.httpService.get<EsResponseDto>(`http://${this.ES_IP}:${this.ES_PORT}/_search`, { (this.httpService.get<EsResponseDto>(`http://${this.ES_IP}:${this.ES_PORT}/_search`, {

View File

@ -8,7 +8,6 @@ import * as modules from '../../core/modules'
import { CommonModule } from './common/common.module'; import { CommonModule } from './common/common.module';
import { PrometheusModule } from '@willsoto/nestjs-prometheus'; import { PrometheusModule } from '@willsoto/nestjs-prometheus';
import { SearchModule } from './search.module'; import { SearchModule } from './search.module';
import { QueryStringPipe } from 'src/core/pipes/query-str.pipe';
/** /**
* application modules list * application modules list

View File

@ -4,7 +4,7 @@ import { NestFactory } from '@nestjs/core';
import { AppModule } from './infrastructure/modules/app.module'; import { AppModule } from './infrastructure/modules/app.module';
import { SwaggerModule, DocumentBuilder, SwaggerDocumentOptions } from '@nestjs/swagger'; import { SwaggerModule, DocumentBuilder, SwaggerDocumentOptions } from '@nestjs/swagger';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import { QueryStringPipe } from './core/pipes/query-str.pipe';
/** /**
* Main entry point of the application * Main entry point of the application
* @returns Nothing * @returns Nothing

View File

@ -152,7 +152,7 @@ describe('Unit tests for SearchService', () => {
} }
} }
searchService.findByContext(es_query); // searchService.findByContext(es_query);
expect(httpGetSpy).toHaveBeenCalledWith<[string, object]>(expect.anything(), { expect(httpGetSpy).toHaveBeenCalledWith<[string, object]>(expect.anything(), {
data: es_query, data: es_query,
headers: { 'Content-Type': 'application/json' } headers: { 'Content-Type': 'application/json' }