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:
commit
71e0ab996a
@ -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;
|
||||||
},
|
},
|
||||||
|
@ -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';
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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`, {
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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' }
|
||||||
|
Loading…
x
Reference in New Issue
Block a user