Endpoints finalized. Test covered.
This commit is contained in:
parent
8d4e09f0f6
commit
a6e20cdfe3
@ -17,8 +17,10 @@ services:
|
||||
image: ${ES_IMAGE_NAME}:${ES_IMAGE_VERSION}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: ${ES_CONTAINER_NAME}
|
||||
restart: always
|
||||
ports:
|
||||
- ${ES_PORT}:${ES_PORT}
|
||||
environment:
|
||||
- xpack.security.enabled=false
|
||||
- discovery.type=single-node
|
||||
|
2
package-lock.json
generated
2
package-lock.json
generated
@ -38,7 +38,7 @@
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/jest": "27.5.1",
|
||||
"@types/node": "^17.0.38",
|
||||
"@types/supertest": "^2.0.11",
|
||||
"@types/supertest": "^2.0.12",
|
||||
"@typescript-eslint/eslint-plugin": "^5.0.0",
|
||||
"@typescript-eslint/parser": "^5.0.0",
|
||||
"eslint": "^8.0.1",
|
||||
|
@ -51,7 +51,7 @@
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/jest": "27.5.1",
|
||||
"@types/node": "^17.0.38",
|
||||
"@types/supertest": "^2.0.11",
|
||||
"@types/supertest": "^2.0.12",
|
||||
"@typescript-eslint/eslint-plugin": "^5.0.0",
|
||||
"@typescript-eslint/parser": "^5.0.0",
|
||||
"eslint": "^8.0.1",
|
||||
@ -75,7 +75,8 @@
|
||||
"json",
|
||||
"ts"
|
||||
],
|
||||
"rootDir": "src",
|
||||
"modulePaths": ["<rootDir>"],
|
||||
"rootDir": "./",
|
||||
"testRegex": ".*\\.spec\\.ts$",
|
||||
"transform": {
|
||||
"^.+\\.(t|j)s$": "ts-jest"
|
||||
|
@ -1,22 +1,45 @@
|
||||
import { Controller, Get, Param, Put, Query, Res } from "@nestjs/common";
|
||||
import { SearchService } from "src/core/services/common/search.service";
|
||||
import { Response } from "express";
|
||||
import { Controller, Get, Next, Param, ParseUUIDPipe, Put, Query, Req, Res } from "@nestjs/common";
|
||||
import { EsResponseDto } from "src/core/domain/dtos";
|
||||
import { SearchService } from "../../core/services/common/search.service";
|
||||
import { response, Response } from "express";
|
||||
|
||||
@Controller('papers')
|
||||
export class PapersController {
|
||||
constructor(private searchService: SearchService) {}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param query
|
||||
* @param response
|
||||
* @returns a response with a set of matching papers
|
||||
*/
|
||||
@Get('search')
|
||||
getByContext(@Query('query') query: string/*, @Query('page') page, @Query('limit') limit*/): object {
|
||||
return this.searchService.findByContext(query);
|
||||
getByContext(@Query('query') query: string, @Res() response: Response/*, @Query('page') page, @Query('limit') limit*/) {
|
||||
return this.searchService.findByContext(query).then(
|
||||
(res) => {
|
||||
response.status(200).send(res);
|
||||
},
|
||||
(err) => {
|
||||
response.status(err).send();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
getByID(@Param('id') id: string): object {
|
||||
if(!id) {
|
||||
//response.status(400).send({msg: "fff"});
|
||||
} else {
|
||||
return this.searchService.findByID(id);
|
||||
/**
|
||||
*
|
||||
* @param uuid
|
||||
* @param response
|
||||
* @returns a response with a requested object
|
||||
*/
|
||||
@Get(':uuid')
|
||||
getByID(@Param('uuid', ParseUUIDPipe) uuid: string, @Res() response: Response) {
|
||||
return this.searchService.findByID(uuid).then(
|
||||
(res) => {
|
||||
response.status(200).send(res);
|
||||
},
|
||||
(err) => {
|
||||
response.status(err).send();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
83
src/core/domain/dtos/es-response.dto.ts
Normal file
83
src/core/domain/dtos/es-response.dto.ts
Normal file
@ -0,0 +1,83 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { IsBoolean, IsDefined, IsNotEmpty, IsNumber, IsObject, IsOptional } from "class-validator";
|
||||
|
||||
/**
|
||||
* List of allowed properties in this DTO
|
||||
*/
|
||||
const allowedProperties = ['took', 'timed_out', '_shards', 'hits'];
|
||||
|
||||
/**
|
||||
* Elasticsearch response DTO
|
||||
*/
|
||||
export class EsResponseDto {
|
||||
/**
|
||||
* Number of milliseconds it
|
||||
* took Elasticsearch to execute the request
|
||||
*/
|
||||
@IsDefined()
|
||||
@IsNotEmpty()
|
||||
@IsNumber()
|
||||
@ApiProperty({
|
||||
description: 'took',
|
||||
example: 5
|
||||
})
|
||||
took: number;
|
||||
|
||||
/**
|
||||
* Status of the request
|
||||
* If 'true' - the request timed out before completion
|
||||
*/
|
||||
@IsDefined()
|
||||
@IsNotEmpty()
|
||||
@IsBoolean()
|
||||
@ApiProperty({
|
||||
description: 'timed_out',
|
||||
example: false,
|
||||
})
|
||||
timed_out: boolean;
|
||||
|
||||
/**
|
||||
* Contains a number of Elasticsearch shards
|
||||
* used for the request
|
||||
*/
|
||||
@IsOptional()
|
||||
@IsObject()
|
||||
@ApiProperty({
|
||||
description: '_shards',
|
||||
example: {
|
||||
total: 1,
|
||||
successful: 1,
|
||||
skipped: 0,
|
||||
failed: 0,
|
||||
}
|
||||
})
|
||||
_shards: object;
|
||||
|
||||
/**
|
||||
* Contains returned documents and metadata
|
||||
*/
|
||||
@IsOptional()
|
||||
@IsObject()
|
||||
@ApiProperty({
|
||||
description: 'hits',
|
||||
example: {
|
||||
total: {
|
||||
value: 3,
|
||||
relation: 'eq'
|
||||
},
|
||||
max_score: 1.2,
|
||||
hits: [{
|
||||
_index: 'papers',
|
||||
_id: '01002',
|
||||
_score: 1.2,
|
||||
_source: {
|
||||
|
||||
},
|
||||
fields: {
|
||||
|
||||
}
|
||||
}],
|
||||
}
|
||||
})
|
||||
hits: object;
|
||||
}
|
@ -0,0 +1 @@
|
||||
export * from './es-response.dto'
|
@ -1,6 +1,7 @@
|
||||
import { HttpService } from "@nestjs/axios";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { map, Observable } from "rxjs";
|
||||
import { HttpException, HttpStatus, Injectable, NotAcceptableException, NotFoundException } from "@nestjs/common";
|
||||
import { map, NotFoundError, take } from "rxjs";
|
||||
import { EsResponseDto } from "src/core/domain/dtos";
|
||||
|
||||
/**
|
||||
* Search service provider
|
||||
@ -9,26 +10,51 @@ import { map, Observable } from "rxjs";
|
||||
export class SearchService {
|
||||
constructor(private readonly httpService: HttpService) {}
|
||||
|
||||
// Find paper by its ID
|
||||
findByID(id: string): object {
|
||||
/**
|
||||
* Finds a paper by its own ID
|
||||
* @param uuid
|
||||
* @returns Elasticsearch hits or an error object
|
||||
*/
|
||||
async findByID(uuid: string): Promise<any> { // Should I change 'object' to specific DTO?
|
||||
let es_query = {
|
||||
query: {
|
||||
query_string: {
|
||||
query: 'id:' + id
|
||||
query: 'id:' + uuid
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.httpService.get('http://localhost:9200/_search', {
|
||||
// Specify ES_PORT env!
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
(this.httpService.get<EsResponseDto>('http://localhost:9200/_search', {
|
||||
data: es_query,
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
}).pipe(
|
||||
map(response=>response.data)
|
||||
);
|
||||
}))
|
||||
.pipe(take(1), map(axiosRes => axiosRes.data))
|
||||
.subscribe((res: any) => {
|
||||
if (res.timed_out) {
|
||||
reject(504);
|
||||
}
|
||||
|
||||
// Find paper by context
|
||||
findByContext(query_str: string): object {
|
||||
if (!res.hits.hits.length) {
|
||||
reject(404);
|
||||
}
|
||||
|
||||
resolve(res.hits);
|
||||
});
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds relevant documents by context using the given query string
|
||||
* @param query_str
|
||||
* @returns Elasticsearch hits or an error object
|
||||
*/
|
||||
findByContext(query_str: string): Promise<EsResponseDto> {
|
||||
let es_query = {
|
||||
query: {
|
||||
query_string: {
|
||||
@ -38,11 +64,27 @@ export class SearchService {
|
||||
}
|
||||
}
|
||||
|
||||
return this.httpService.get('http://localhost:9200/_search', {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
(this.httpService.get<EsResponseDto>('http://localhost:9200/_search', {
|
||||
data: es_query,
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
}).pipe(
|
||||
map(response=>response.data)
|
||||
);
|
||||
}))
|
||||
.pipe(take(1), map(axiosRes => axiosRes.data))
|
||||
.subscribe((res: any) => {
|
||||
if (res.timed_out) {
|
||||
reject(504);
|
||||
}
|
||||
|
||||
if (!res.hits.hits.length) {
|
||||
reject(404);
|
||||
}
|
||||
|
||||
resolve(res.hits);
|
||||
});
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -7,7 +7,7 @@ import { LoggerInterceptor } from '../../core/interceptors'
|
||||
import * as modules from '../../core/modules'
|
||||
import { CommonModule } from './common/common.module';
|
||||
import { PrometheusModule } from '@willsoto/nestjs-prometheus';
|
||||
import { PapersController } from 'src/application';
|
||||
import { PapersController } from 'src/application/controller/papers.controller';
|
||||
import { SearchModule } from './search.module';
|
||||
|
||||
/**
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { HttpModule } from "@nestjs/axios";
|
||||
import { Module } from "@nestjs/common";
|
||||
import { SearchService } from "src/core/services/common/search.service";
|
||||
import { SearchService } from "../../core/services/common/search.service";
|
||||
|
||||
/**
|
||||
* search module
|
||||
|
51
src/test/papers.endpoint.spec.ts
Normal file
51
src/test/papers.endpoint.spec.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { Test, TestingModule } from "@nestjs/testing";
|
||||
import request from 'supertest'
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { AppModule } from "../infrastructure/modules";
|
||||
|
||||
describe('PapersController', () => {
|
||||
let app: INestApplication;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
imports: [AppModule],
|
||||
}).compile();
|
||||
|
||||
app = module.createNestApplication();
|
||||
await app.init();
|
||||
});
|
||||
|
||||
/**
|
||||
* Getting paper by its ID
|
||||
*/
|
||||
it('Should be 404', () => {
|
||||
let uuid = 'eeeb2d01-8315-454e-b33f-3d6caa25db43';
|
||||
return request(app.getHttpServer())
|
||||
.get('/papers/' + uuid)
|
||||
.expect(404);
|
||||
});
|
||||
|
||||
it('Should be 200', () => {
|
||||
let uuid = 'eeeb2d01-8315-454e-b33f-3d6caa25db42';
|
||||
return request(app.getHttpServer())
|
||||
.get('/papers/' + uuid)
|
||||
.expect(200);
|
||||
});
|
||||
|
||||
/**
|
||||
* Getting paper by the given context
|
||||
*/
|
||||
it('Should be 404', () => {
|
||||
let q_str = 'Explosion'; // Non-existing word in the test-paper
|
||||
return request(app.getHttpServer())
|
||||
.get('/papers/search?query=' + q_str)
|
||||
.expect(404);
|
||||
});
|
||||
|
||||
it('Should be 200', () => {
|
||||
let q_str = 'Docker'; // Existing word in the test-paper
|
||||
return request(app.getHttpServer())
|
||||
.get('/papers/search?query=' + q_str)
|
||||
.expect(200);
|
||||
});
|
||||
});
|
@ -1,18 +0,0 @@
|
||||
import { Test, TestingModule } from "@nestjs/testing";
|
||||
import { PapersController } from "../src/application/controller/papers.controller";
|
||||
|
||||
describe('PapersController', () => {
|
||||
let controller: PapersController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [PapersController],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<PapersController>(PapersController);
|
||||
});
|
||||
|
||||
it('Should be defined', () => {
|
||||
expect(controller).toBeDefined();
|
||||
});
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user