# hometask documentation

### File

`src/core/interceptors/page.interceptor.ts`

### Description

Pagination-implementing interceptor

### Index

###### Properties

- Private Readonly ES_PORT
- Private prevSearch

###### Methods

- Async deletePIT
- Public Async getPIT
- Async intercept

### Constructor

`constructor(httpService: HttpService)`
Defined in src/core/interceptors/page.interceptor.ts:59

Injects needed dependencies and instantiates the storage object

Parameters :

Name | Type | Optional
httpService | `HttpService` | No

### Methods

Async deletePIT
`deletePIT(pitID: string)`
Defined in src/core/interceptors/page.interceptor.ts:180

Deletes the PIT specified by provided ID

Parameters :

Name | Type | Optional | Description
pitID | `string` | No | , ID of the PIT, that would be deleted

Returns : `Promise<boolean>`

true/false, depending on the result of deletion of the PIT

Public Async getPIT
`getPIT(alive: number, unit: EsTime)`
Defined in src/core/interceptors/page.interceptor.ts:160

Acquires a PIT ID from Elasticsearch, needed for a request

Parameters :

Name | Type | Optional | Default value | Description
alive | `number` | No |  | , amount of time in minutes (defaults to 1). If time unit is not specified - defaults to minutes.
unit | `EsTime` | No | `EsTime.min` |

Returns : `Promise<EsPit>`

PIT object <EsPit> containing PIT ID and keep_alive value

Async intercept
`intercept(context: ExecutionContext, next: CallHandler<any>)`
Defined in src/core/interceptors/page.interceptor.ts:75

Override of intercept() method, specified in NestInterceptor interface

Parameters :

Name | Type | Optional
context | `ExecutionContext` | No
next | `CallHandler<any>` | No

Returns : `Promise<Observable<PageDto>>`

Page with content and metadata

### Properties

Private Readonly ES_PORT
Default value : `process.env.ES_PORT`
Defined in src/core/interceptors/page.interceptor.ts:148
Elastichsearch server port-number

Private prevSearch
Type : `PrevSearch`
Defined in src/core/interceptors/page.interceptor.ts:153
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;
            //     }
            // }

# results matching ""

# No results matching ""