UNPKG

zeroant-common

Version:
246 lines (218 loc) 7.19 kB
import { type ParsedQs } from 'qs' import { InternalServerError } from 'zeroant-response/serverErrors/internalServerError.serverError' import { ErrorCode, ErrorDescription } from '../constants.js' import { type IPaginationResult } from '../interfaces/pagination.interface.js' import _ from 'lodash' import crypto from 'node:crypto' import { type ModelFactory } from 'zeroant-factory/model.factory' export class PaginationDto<T = Record<string, any>, Relation = Record<string, any>> { count?: number sort: Record<string, 'asc' | 'desc'> = {} relations: Relation = {} as any filter: T = {} as any as T filterWhiteList: string[] = [] relationWhiteList: string[] = [] sortWhiteList: string[] = [] models: Record<string, ModelFactory> = {} #page?: number #start?: number #limit = 10 // constructor () {} static fromQuery(query: ParsedQs, whiteList: { relation?: string[]; filter?: string[]; sort?: string[] } = {}) { // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions return new PaginationDto() .setRelationWhiteList(...(whiteList?.relation ?? [])) .setFilterWhiteList(...(whiteList?.filter ?? [])) .setSortWhiteList(...(whiteList?.sort ?? [])) .fromQuery(query) } fromQuery(query: ParsedQs) { // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions const page = Number(query?.page) const limit = Number(query?.limit) if (query?.start !== undefined || query?.start !== null) { this.setStart(Number(query?.start)).setLimit(limit) } else { this.setPage(page).setLimit(limit) } return this.setSortFromQuery(query.sort).setFilterFromQuery(query.filter).setRelationsFromQuery(query.relations) } setStart(start: number) { // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions this.#start = start || 0 return this } setPage(page: number) { page = Number(page) this.#page = page > 0 ? page : 1 return this } setLimit(limit: number) { limit = Number(limit) this.#limit = limit > 0 ? limit : 10 return this } setFilterWhiteList(...filterWhiteList: string[]) { this.filterWhiteList = filterWhiteList return this } setSortWhiteList(...sortWhiteList: string[]) { this.sortWhiteList = sortWhiteList return this } setRelationWhiteList(...relationWhiteList: string[]) { this.relationWhiteList = relationWhiteList return this } setSortFromQuery(query: any) { const sort: Record<string, 'asc' | 'desc'> = this.sort if (Array.isArray(query)) { for (const field of query) { const [key, value = 'asc'] = (field as string).split(':') const [findKey, replaceKey = undefined] = this.getWhitelist(this.sortWhiteList, key) if (findKey === key) { sort[replaceKey ?? key] = value as any } } } else if (typeof query === 'string') { const fields = query.split(',') for (const field of fields) { const [key, value = 'asc'] = field.split(':') const [findKey, replaceKey = undefined] = this.getWhitelist(this.sortWhiteList, key) if (findKey === key) { sort[replaceKey ?? key] = value as any } } } return this.setSort(sort) } setRelationsFromQuery(query: any) { const relations: Relation = this.relations if (Array.isArray(query)) { for (const field of query) { const [findKey, replaceKey] = this.getWhitelist(this.relationWhiteList, field) if (field === findKey) { // replacement should use .include. _.set(relations as never, replaceKey ?? field, true) } } } else if (typeof query === 'string') { const fields = query.split(',') for (const field of fields) { const [findKey, replaceKey] = this.getWhitelist(this.relationWhiteList, field) if (field === findKey) { // replacement should use .include. _.set(relations as never, replaceKey ?? field, true) } } } return this.setRelations(relations) } getWhitelist(whiteList: string[], key: string) { const [findKey, replaceKey = undefined] = (whiteList.find((findKey) => findKey === key || findKey.startsWith(key + ':')) ?? '').split( ':' ) return [findKey, replaceKey] } setFilterFromQuery(query: any) { const filter: T = this.filter if (Array.isArray(query)) { for (const field of query) { const [key, value = undefined] = (field as string).split(':') const [findKey, replaceKey = undefined] = this.getWhitelist(this.filterWhiteList, key) if (findKey === key && value !== undefined) { // replacement should use .is. _.set(filter as never, replaceKey ?? findKey, value) } } } else if (typeof query === 'string') { const fields = query.split(',') for (const field of fields) { const [key, value = undefined] = field.split(':') const [findKey, replaceKey = undefined] = this.getWhitelist(this.filterWhiteList, key) if (findKey === key && value !== undefined) { // replacement should use .is. _.set(filter as never, replaceKey ?? findKey, value) } } } return this.setFilter(filter) } setSort(sort: Record<string, 'asc' | 'desc'>) { this.sort = sort return this } setFilter(filter: T) { this.filter = filter return this } setRelations(relations: Relation) { this.relations = relations return this } getRelations() { return this.relations } getLimit(): number { return this.#limit * 1 } getSort(): any { return this.sort } getFilter(): any { return this.filter } getPage(): number { return this.#page !== undefined && this.#page > 0 ? this.#page : 1 } getStart(): number { return this.#start !== undefined ? this.#start : 0 } getSkip(): number { if (this.#start !== undefined) { return this.getStart() } return (this.getPage() - 1) * this.getLimit() } getTotalPages(): number { this.ensureCount() return Math.ceil((this.count as number) / this.getLimit()) } setCount(count: number) { this.count = count return this } ensureCount() { if (this.count === undefined) { throw new InternalServerError( ErrorCode.INVALID_PAYLOAD, ErrorDescription.INVALID_PAYLOAD, 'Please ensure to call setCount before using getTotalPages(), buildResult(' ) } } buildResult<T>(result: T[]): IPaginationResult<T> { if (this.#start !== undefined) { return { meta: { totalCount: this.count as number }, items: result } } return { meta: { totalCount: this.count as number, currentPage: this.getPage(), totalPages: this.getTotalPages() }, items: result } } hash(prefix = ''): string { return crypto .createHash('sha256') .update(JSON.stringify([prefix, this.getFilter(), this.getPage(), this.getSkip(), this.getRelations(), this.getLimit()])) .digest('hex') } }