src/core/interceptors/page.interceptor.ts
Pagination-implementing interceptor
Properties |
|
Methods |
constructor(httpService: HttpService)
|
||||||
Defined in src/core/interceptors/page.interceptor.ts:59
|
||||||
Injects needed dependencies and instantiates the storage object
Parameters :
|
Async deletePIT | ||||||||
deletePIT(pitID: string)
|
||||||||
Deletes the PIT specified by provided ID
Parameters :
Returns :
Promise<boolean>
true/false, depending on the result of deletion of the PIT |
Public Async getPIT | |||||||||||||||
getPIT(alive: number, unit: EsTime)
|
|||||||||||||||
Acquires a PIT ID from Elasticsearch, needed for a request
Parameters :
Returns :
Promise<EsPit>
PIT object |
Async intercept | |||||||||
intercept(context: ExecutionContext, next: CallHandler
|
|||||||||
Defined in src/core/interceptors/page.interceptor.ts:75
|
|||||||||
Override of intercept() method, specified in NestInterceptor interface
Parameters :
Returns :
Promise<Observable<PageDto>>
Page with content and metadata |
Private Readonly ES_PORT |
Default value : process.env.ES_PORT
|
Elastichsearch server port-number |
Private prevSearch |
Type : PrevSearch
|
Info about previously completed search |
import { HttpService } from "@nestjs/axios";
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from "@nestjs/common";
import { reverse } from "dns";
import { Observable, map, take } from "rxjs";
import { EsResponseDto, PageDto } from "../domain/dtos";
import { EsQueryDto } from "../domain/dtos/es-query.dto";
import { RequestDto } from "../domain/dtos/request.dto";
import { SearchQueryDto } from "../domain/dtos/search-q.dto";
import { SearchResultDto } from "../domain/dtos/search-result.dto";
import { EsTime } from "../domain/enums/es-time.enum";
import { Order } from "../domain/enums/page-order.enum";
import { PageMeta } from "../domain/interfaces";
import { EsPit } from "../domain/interfaces/es-pit.interface";
import { SearchInfo } from "../domain/interfaces/search-info.interface";
import { SearchService } from "../services/common/search.service";
/**
* Previous search data storage
*/
class PrevSearch implements SearchInfo {
/**
* Constructs an uninitialized object
*/
constructor() {
this.pit = undefined;
this.tiebreaker = undefined;
this.prevPage = -1;
}
/**
* PIT object of the previous search
*/
pit: EsPit;
/**
* Tiebreaker and sort parameters
*/
tiebreaker: unknown[];
/**
* Number of the previous page
*/
prevPage: number;
/**
* Checks if there was the search before current one
* @returns true/false, showing whether or not there was another search before
*/
public isSet(): boolean {
if (this.pit && this.tiebreaker && this.prevPage !== -1) return true;
return false;
}
}
/**
* Pagination-implementing interceptor
*/
@Injectable()
export class PageInterceptor implements NestInterceptor {
/**
* Injects needed dependencies and instantiates the storage object
* @param httpService
* @param searchService
*/
constructor(private readonly httpService: HttpService) {
this.prevSearch = new PrevSearch;
}
/**
* Override of intercept() method, specified in NestInterceptor interface
* @param context
* @param next
* @returns Page with content and metadata
*/
async intercept(context: ExecutionContext, next: CallHandler<any>): Promise<Observable<PageDto>> {
let request: RequestDto = context.switchToHttp().getRequest<RequestDto>();
const query: SearchQueryDto = request.query;
let reverse: boolean = false;
request.es_query = new EsQueryDto();
request.es_query.query = {
query_string: {
query: query.query,
default_field: 'content',
}
};
request.es_query.sort = [
{ _score: { order: !query?.order ? Order.DESC : query.order } },
{ _shard_doc: 'desc' }
];
if (this.prevSearch.isSet()) {
request.es_query.pit = this.prevSearch.pit;
request.es_query.search_after = this.prevSearch.tiebreaker;
let limit = !query?.limit ? 10 : query.limit;
request.es_query.size = limit * Math.abs(query.page - this.prevSearch.prevPage);
if (query.page < this.prevSearch.prevPage) {
request.es_query.sort = [{ _score: { order: 'asc' } }];
request.es_query.size += limit - 1;
reverse = true;
} else if (query.page == this.prevSearch.prevPage) {
//...
}
} else {
this.prevSearch.pit = request.es_query.pit = await this.getPIT(1);
request.es_query.size = !query?.limit ? 10 : query.limit;
}
return next.handle().pipe(
map((res) => {
// Setting the page meta-data
let meta: PageMeta = {
total: res.hits.total.value,
pagenum: !query?.page ? 1 : query.page,
order: query?.order?.toUpperCase() === Order.ASC ? Order.ASC : Order.DESC,
hasNext: false,
hasPrev: false,
pagesize: !query?.limit ? 10 : query.limit,
};
// meta.hasNext = res.hits.hits[meta.pagenum * meta.pagesize] ? true : false;
// meta.hasPrev = res.hits.hits[(meta.pagenum - 1) * meta.pagesize - 1] ? true: false;
// Saving the search info
this.prevSearch.pit.id = res.pit_id;
this.prevSearch.tiebreaker = res.hits.hits[res.hits.hits.length - 1].sort;
this.prevSearch.prevPage = query.page;
let data = res.hits.hits.slice(-meta.pagesize);
if (reverse) {
console.log('REVERSE');
this.prevSearch.tiebreaker = data[0].sort;
data.reverse();
reverse = false;
}
// Return the page
return new PageDto(data, meta);
})
);
}
/**
* Elastichsearch server port-number
*/
private readonly ES_PORT = process.env.ES_PORT;
/**
* Info about previously completed search
*/
private prevSearch: PrevSearch;
/**
* Acquires a PIT ID from Elasticsearch, needed for a request
* @param alive, amount of time in minutes (defaults to 1). If time unit is not specified - defaults to minutes.
* @returns PIT object <EsPit> containing PIT ID and keep_alive value
*/
public async getPIT(alive: number, unit: EsTime = EsTime.min): Promise<EsPit> {
return new Promise((resolve, reject) => {
try {
(this.httpService.post<EsPit>(`http://localhost:${this.ES_PORT}/papers/_pit?keep_alive=${alive+unit}`)
.pipe(take(1), map(axiosRes => axiosRes.data))
.subscribe((res) => {
res.keep_alive = alive + unit;
resolve(res);
}));
} catch (error) {
reject(error);
}
});
}
/**
* Deletes the PIT specified by provided ID
* @param pitID, ID of the PIT, that would be deleted
* @returns true/false, depending on the result of deletion of the PIT
*/
async deletePIT(pitID: string): Promise<boolean> {
return new Promise((resolve, reject) => {
try {
this.httpService.delete(`http://localhost:${this.ES_PORT}/_pit`, {
data: { id: pitID },
headers: { 'Content-Type': 'application/json' },
})
.pipe(take(1), map(axiosRes => axiosRes.data))
.subscribe((res) => {
resolve(res.succeeded);
});
} catch (error) {
reject(error);
}
})
}
}
/*
public saveInfo(pit: EsPit, tiebreaker: unknown[], page: number) {
this.pit.id = pit.id;
this.pit.keep_alive = pit.keep_alive;
this.tiebreaker = tiebreaker.slice();
this.prevPage = page;
}
public clearInfo() {
this.pit = undefined;
this.tiebreaker = undefined;
this.prevPage = -1;
}*/
// getQueryParams(str: string): any {
// let parameters: object = {};
// let pairs: string[] = str.split(',');
// parameters['main'] = pairs[0];
// pairs.shift();
// if(!pairs || pairs[0] === '') return parameters;
// for (const pair of pairs) {
// const key: string = pair.substring(0, pair.indexOf('='));
// const value: string = pair.substring(pair.indexOf('=') + 1);
// parameters[key] = value;
// }
// return parameters;
// }
/**
* OLD WAY PAGINATION
* // Setting the page data
// const data = res.hits.slice((meta.pagenum - 1) * meta.pagesize, meta.pagenum * meta.pagesize);
*/
// if (query.page == 1) {
// this.prevSearch.pit = request.es_query.pit = await this.getPIT(1);
// } else {
// if (!this.prevSearch.isSet()) {
// this.prevSearch.pit = request.es_query.pit = await this.getPIT(1);
// request.es_query.size = query.limit * (query.page - 1);
// this.searchService.findByContext(request.es_query).then((res: SearchResultDto) => {
// request.es_query.search_after = res.data.hits.hits[res.data.hits.hits.length - 1].sort;
// });
// } else {
// if (query.page == this.prevSearch.prevPage) {
// return;
// } else {
// request.es_query.pit = this.prevSearch.pit;
// request.es_query.search_after = this.prevSearch.tiebreaker;
// request.es_query.size = (query.page - this.prevSearch.prevPage);
// }
// // request.es_query.pit = this.prevSearch.pit;
// // request.es_query.search_after = this.prevSearch.tiebreaker;
// }
// }