2023-02-23 07:58:47 +00:00
|
|
|
import {
|
|
|
|
Brackets,
|
2023-09-25 12:58:04 +00:00
|
|
|
FindOperator,
|
|
|
|
FindOptionsRelationByString,
|
2023-02-23 07:58:47 +00:00
|
|
|
FindOptionsRelations,
|
2023-03-01 08:02:13 +00:00
|
|
|
FindOptionsUtils,
|
2023-09-25 12:58:04 +00:00
|
|
|
FindOptionsWhere,
|
|
|
|
ObjectLiteral,
|
|
|
|
Repository,
|
|
|
|
SelectQueryBuilder,
|
2023-02-23 07:58:47 +00:00
|
|
|
} from 'typeorm'
|
2023-03-14 21:12:49 +00:00
|
|
|
import { PaginateQuery } from './decorator'
|
2023-09-25 12:58:04 +00:00
|
|
|
import { Logger, ServiceUnavailableException } from '@nestjs/common'
|
2023-02-09 08:21:09 +00:00
|
|
|
import { mapKeys } from 'lodash'
|
2021-08-19 14:42:18 +00:00
|
|
|
import { stringify } from 'querystring'
|
2022-03-14 19:02:01 +00:00
|
|
|
import { WherePredicateOperator } from 'typeorm/query-builder/WhereClause'
|
2023-01-30 11:17:14 +00:00
|
|
|
import {
|
2023-02-15 08:05:52 +00:00
|
|
|
checkIsEmbedded,
|
2023-09-25 12:58:04 +00:00
|
|
|
checkIsRelation,
|
2023-01-30 11:17:14 +00:00
|
|
|
Column,
|
|
|
|
extractVirtualProperty,
|
|
|
|
fixColumnAlias,
|
|
|
|
getPropertiesByColumnName,
|
2023-09-25 12:58:04 +00:00
|
|
|
getQueryUrlComponents,
|
|
|
|
includesAllPrimaryKeyColumns,
|
|
|
|
isEntityKey,
|
2023-01-30 11:17:14 +00:00
|
|
|
Order,
|
|
|
|
positiveNumberOrDefault,
|
|
|
|
RelationColumn,
|
|
|
|
SortBy,
|
|
|
|
} from './helper'
|
2023-03-14 21:10:23 +00:00
|
|
|
import { addFilter, FilterOperator, FilterSuffix } from './filter'
|
2023-09-09 16:41:35 +00:00
|
|
|
import { OrmUtils } from 'typeorm/util/OrmUtils'
|
2020-06-26 21:25:03 +00:00
|
|
|
|
2022-11-16 07:58:00 +00:00
|
|
|
const logger: Logger = new Logger('nestjs-paginate')
|
|
|
|
|
2023-03-14 21:10:23 +00:00
|
|
|
export { FilterOperator, FilterSuffix }
|
|
|
|
|
2020-06-26 21:25:03 +00:00
|
|
|
export class Paginated<T> {
|
|
|
|
data: T[]
|
|
|
|
meta: {
|
|
|
|
itemsPerPage: number
|
|
|
|
totalItems: number
|
|
|
|
currentPage: number
|
|
|
|
totalPages: number
|
|
|
|
sortBy: SortBy<T>
|
2021-10-12 11:01:53 +00:00
|
|
|
searchBy: Column<T>[]
|
2020-06-28 17:34:00 +00:00
|
|
|
search: string
|
2023-03-21 19:27:52 +00:00
|
|
|
select: string[]
|
2023-09-25 12:58:04 +00:00
|
|
|
filter?: {
|
|
|
|
[column: string]: string | string[]
|
|
|
|
}
|
2020-06-26 21:25:03 +00:00
|
|
|
}
|
|
|
|
links: {
|
|
|
|
first?: string
|
|
|
|
previous?: string
|
|
|
|
current: string
|
|
|
|
next?: string
|
|
|
|
last?: string
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-24 11:06:56 +00:00
|
|
|
export enum PaginationType {
|
|
|
|
LIMIT_AND_OFFSET = 'limit',
|
|
|
|
TAKE_AND_SKIP = 'take',
|
|
|
|
}
|
|
|
|
|
2020-06-26 21:25:03 +00:00
|
|
|
export interface PaginateConfig<T> {
|
2023-09-09 16:41:35 +00:00
|
|
|
relations?: FindOptionsRelations<T> | RelationColumn<T>[] | FindOptionsRelationByString
|
2020-06-26 21:25:03 +00:00
|
|
|
sortableColumns: Column<T>[]
|
2022-08-20 22:25:48 +00:00
|
|
|
nullSort?: 'first' | 'last'
|
2020-06-28 17:34:00 +00:00
|
|
|
searchableColumns?: Column<T>[]
|
2023-03-14 18:12:30 +00:00
|
|
|
select?: Column<T>[] | string[]
|
2020-06-26 21:25:03 +00:00
|
|
|
maxLimit?: number
|
|
|
|
defaultSortBy?: SortBy<T>
|
|
|
|
defaultLimit?: number
|
2022-06-20 08:38:36 +00:00
|
|
|
where?: FindOptionsWhere<T> | FindOptionsWhere<T>[]
|
2023-02-09 08:21:09 +00:00
|
|
|
filterableColumns?: {
|
2023-03-18 19:07:48 +00:00
|
|
|
[key in Column<T> | string]?: (FilterOperator | FilterSuffix)[] | true
|
2023-02-09 08:21:09 +00:00
|
|
|
}
|
2023-03-01 08:02:13 +00:00
|
|
|
loadEagerRelations?: boolean
|
2022-05-15 19:01:53 +00:00
|
|
|
withDeleted?: boolean
|
2023-03-24 11:06:56 +00:00
|
|
|
paginationType?: PaginationType
|
2022-09-30 11:11:55 +00:00
|
|
|
relativePath?: boolean
|
|
|
|
origin?: string
|
2023-10-25 13:30:56 +00:00
|
|
|
ignoreSearchByInQueryParam?: boolean
|
|
|
|
ignoreSelectInQueryParam?: boolean
|
2021-08-19 14:42:18 +00:00
|
|
|
}
|
|
|
|
|
2022-12-13 11:35:08 +00:00
|
|
|
export const DEFAULT_MAX_LIMIT = 100
|
|
|
|
export const DEFAULT_LIMIT = 20
|
|
|
|
export const NO_PAGINATION = 0
|
|
|
|
|
2023-09-25 12:58:04 +00:00
|
|
|
function generateWhereStatement<T>(
|
|
|
|
queryBuilder: SelectQueryBuilder<T>,
|
|
|
|
obj: FindOptionsWhere<T> | FindOptionsWhere<T>[]
|
|
|
|
) {
|
|
|
|
const toTransform = Array.isArray(obj) ? obj : [obj]
|
|
|
|
return toTransform.map((item) => flattenWhereAndTransform(queryBuilder, item).join(' AND ')).join(' OR ')
|
|
|
|
}
|
|
|
|
|
|
|
|
function flattenWhereAndTransform<T>(
|
|
|
|
queryBuilder: SelectQueryBuilder<T>,
|
|
|
|
obj: FindOptionsWhere<T>,
|
|
|
|
separator = '.',
|
|
|
|
parentKey = ''
|
|
|
|
) {
|
|
|
|
return Object.entries(obj).flatMap(([key, value]) => {
|
|
|
|
if (obj.hasOwnProperty(key)) {
|
|
|
|
const joinedKey = parentKey ? `${parentKey}${separator}${key}` : key
|
|
|
|
|
|
|
|
if (typeof value === 'object' && value !== null && !(value instanceof FindOperator)) {
|
|
|
|
return flattenWhereAndTransform(queryBuilder, value as FindOptionsWhere<T>, separator, joinedKey)
|
|
|
|
} else {
|
|
|
|
const property = getPropertiesByColumnName(joinedKey)
|
|
|
|
const { isVirtualProperty, query: virtualQuery } = extractVirtualProperty(queryBuilder, property)
|
|
|
|
const isRelation = checkIsRelation(queryBuilder, property.propertyPath)
|
|
|
|
const isEmbedded = checkIsEmbedded(queryBuilder, property.propertyPath)
|
|
|
|
const alias = fixColumnAlias(
|
|
|
|
property,
|
|
|
|
queryBuilder.alias,
|
|
|
|
isRelation,
|
|
|
|
isVirtualProperty,
|
|
|
|
isEmbedded,
|
|
|
|
virtualQuery
|
|
|
|
)
|
2023-10-04 07:20:18 +00:00
|
|
|
const whereClause = queryBuilder['createWhereConditionExpression'](
|
2023-09-25 12:58:04 +00:00
|
|
|
queryBuilder['getWherePredicateCondition'](alias, value)
|
|
|
|
)
|
2023-10-04 07:20:18 +00:00
|
|
|
|
|
|
|
const allJoinedTables = queryBuilder.expressionMap.joinAttributes.reduce(
|
|
|
|
(acc, attr) => {
|
|
|
|
acc[attr.alias.name] = true
|
|
|
|
return acc
|
|
|
|
},
|
|
|
|
{} as Record<string, boolean>
|
|
|
|
)
|
|
|
|
|
|
|
|
const allTablesInPath = property.column.split('.').slice(0, -1)
|
|
|
|
const tablesToJoin = allTablesInPath.map((table, idx) => {
|
|
|
|
if (idx === 0) {
|
|
|
|
return table
|
|
|
|
}
|
|
|
|
return [...allTablesInPath.slice(0, idx), table].join('.')
|
|
|
|
})
|
|
|
|
|
|
|
|
tablesToJoin.forEach((table) => {
|
|
|
|
const pathSplit = table.split('.')
|
|
|
|
const fullPath =
|
|
|
|
pathSplit.length === 1
|
|
|
|
? ''
|
|
|
|
: `_${pathSplit
|
|
|
|
.slice(0, -1)
|
|
|
|
.map((p) => p + '_rel')
|
|
|
|
.join('_')}`
|
|
|
|
const tableName = pathSplit[pathSplit.length - 1]
|
|
|
|
const tableAliasWithProperty = `${queryBuilder.alias}${fullPath}.${tableName}`
|
|
|
|
const joinTableAlias = `${queryBuilder.alias}${fullPath}_${tableName}_rel`
|
|
|
|
|
|
|
|
const baseTableAlias = allJoinedTables[joinTableAlias]
|
|
|
|
|
|
|
|
if (baseTableAlias) {
|
|
|
|
return
|
|
|
|
} else {
|
|
|
|
queryBuilder.leftJoin(tableAliasWithProperty, joinTableAlias)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
return whereClause
|
2023-09-25 12:58:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-11-16 07:58:33 +00:00
|
|
|
export async function paginate<T extends ObjectLiteral>(
|
2020-06-26 21:25:03 +00:00
|
|
|
query: PaginateQuery,
|
|
|
|
repo: Repository<T> | SelectQueryBuilder<T>,
|
|
|
|
config: PaginateConfig<T>
|
|
|
|
): Promise<Paginated<T>> {
|
2022-12-13 11:35:08 +00:00
|
|
|
const page = positiveNumberOrDefault(query.page, 1, 1)
|
|
|
|
|
|
|
|
const defaultLimit = config.defaultLimit || DEFAULT_LIMIT
|
|
|
|
const maxLimit = positiveNumberOrDefault(config.maxLimit, DEFAULT_MAX_LIMIT)
|
|
|
|
const queryLimit = positiveNumberOrDefault(query.limit, defaultLimit)
|
|
|
|
|
|
|
|
const isPaginated = !(queryLimit === NO_PAGINATION && maxLimit === NO_PAGINATION)
|
|
|
|
|
|
|
|
const limit = isPaginated ? Math.min(queryLimit || defaultLimit, maxLimit || DEFAULT_MAX_LIMIT) : NO_PAGINATION
|
|
|
|
|
2020-06-28 17:34:00 +00:00
|
|
|
const sortBy = [] as SortBy<T>
|
2021-10-12 11:01:53 +00:00
|
|
|
const searchBy: Column<T>[] = []
|
2020-06-26 21:25:03 +00:00
|
|
|
|
|
|
|
let [items, totalItems]: [T[], number] = [[], 0]
|
|
|
|
|
2023-02-23 07:58:47 +00:00
|
|
|
const queryBuilder = repo instanceof Repository ? repo.createQueryBuilder('__root') : repo
|
2020-06-28 17:34:00 +00:00
|
|
|
|
2023-03-01 08:02:13 +00:00
|
|
|
if (repo instanceof Repository && !config.relations && config.loadEagerRelations === true) {
|
|
|
|
if (!config.relations) {
|
|
|
|
FindOptionsUtils.joinEagerRelations(queryBuilder, queryBuilder.alias, repo.metadata)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-13 11:35:08 +00:00
|
|
|
if (isPaginated) {
|
2023-03-24 11:06:56 +00:00
|
|
|
// Allow user to choose between limit/offset and take/skip.
|
2023-04-02 19:50:28 +00:00
|
|
|
// However, using limit/offset can cause problems when joining one-to-many etc.
|
|
|
|
if (config.paginationType === PaginationType.LIMIT_AND_OFFSET) {
|
2023-03-24 11:06:56 +00:00
|
|
|
queryBuilder.limit(limit).offset((page - 1) * limit)
|
2023-04-02 19:50:28 +00:00
|
|
|
} else {
|
|
|
|
queryBuilder.take(limit).skip((page - 1) * limit)
|
2023-03-24 11:06:56 +00:00
|
|
|
}
|
2022-03-14 19:02:01 +00:00
|
|
|
}
|
|
|
|
|
2023-02-23 07:58:47 +00:00
|
|
|
if (config.relations) {
|
2023-09-09 16:41:35 +00:00
|
|
|
const relations = Array.isArray(config.relations)
|
|
|
|
? OrmUtils.propertyPathsToTruthyObject(config.relations)
|
|
|
|
: config.relations
|
|
|
|
const createQueryBuilderRelations = (
|
|
|
|
prefix: string,
|
|
|
|
relations: FindOptionsRelations<T> | RelationColumn<T>[],
|
|
|
|
alias?: string
|
|
|
|
) => {
|
|
|
|
Object.keys(relations).forEach((relationName) => {
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
|
|
const relationSchema = relations![relationName]!
|
|
|
|
|
2023-07-16 17:43:36 +00:00
|
|
|
queryBuilder.leftJoinAndSelect(
|
2023-09-09 16:41:35 +00:00
|
|
|
`${alias ?? prefix}.${relationName}`,
|
|
|
|
`${alias ?? prefix}_${relationName}_rel`
|
2023-07-16 17:43:36 +00:00
|
|
|
)
|
2023-09-09 16:41:35 +00:00
|
|
|
|
|
|
|
if (typeof relationSchema === 'object') {
|
|
|
|
createQueryBuilderRelations(relationName, relationSchema, `${alias ?? prefix}_${relationName}_rel`)
|
|
|
|
}
|
2023-02-23 07:58:47 +00:00
|
|
|
})
|
|
|
|
}
|
2023-09-09 16:41:35 +00:00
|
|
|
createQueryBuilderRelations(queryBuilder.alias, relations)
|
2022-03-14 19:02:01 +00:00
|
|
|
}
|
2020-06-26 21:25:03 +00:00
|
|
|
|
2023-03-17 22:24:16 +00:00
|
|
|
let nullSort: 'NULLS LAST' | 'NULLS FIRST' | undefined = undefined
|
2022-08-22 18:23:28 +00:00
|
|
|
if (config.nullSort) {
|
|
|
|
nullSort = config.nullSort === 'last' ? 'NULLS LAST' : 'NULLS FIRST'
|
|
|
|
}
|
|
|
|
|
2023-03-17 21:23:46 +00:00
|
|
|
if (config.sortableColumns.length < 1) {
|
2023-04-24 06:54:07 +00:00
|
|
|
const message = "Missing required 'sortableColumns' config."
|
|
|
|
logger.debug(message)
|
|
|
|
throw new ServiceUnavailableException(message)
|
2023-03-17 21:23:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (query.sortBy) {
|
|
|
|
for (const order of query.sortBy) {
|
|
|
|
if (isEntityKey(config.sortableColumns, order[0]) && ['ASC', 'DESC'].includes(order[1])) {
|
|
|
|
sortBy.push(order as Order<T>)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!sortBy.length) {
|
|
|
|
sortBy.push(...(config.defaultSortBy || [[config.sortableColumns[0], 'ASC']]))
|
|
|
|
}
|
|
|
|
|
2022-03-14 19:02:01 +00:00
|
|
|
for (const order of sortBy) {
|
2023-01-30 11:17:14 +00:00
|
|
|
const columnProperties = getPropertiesByColumnName(order[0])
|
|
|
|
const { isVirtualProperty } = extractVirtualProperty(queryBuilder, columnProperties)
|
|
|
|
const isRelation = checkIsRelation(queryBuilder, columnProperties.propertyPath)
|
2023-02-15 08:05:52 +00:00
|
|
|
const isEmbeded = checkIsEmbedded(queryBuilder, columnProperties.propertyPath)
|
2023-03-17 22:14:59 +00:00
|
|
|
let alias = fixColumnAlias(columnProperties, queryBuilder.alias, isRelation, isVirtualProperty, isEmbeded)
|
|
|
|
if (isVirtualProperty) {
|
|
|
|
alias = `"${alias}"`
|
|
|
|
}
|
2023-01-30 11:17:14 +00:00
|
|
|
queryBuilder.addOrderBy(alias, order[1], nullSort)
|
2020-06-28 17:34:00 +00:00
|
|
|
}
|
2020-06-26 21:25:03 +00:00
|
|
|
|
2023-02-15 09:16:35 +00:00
|
|
|
// When we partial select the columns (main or relation) we must add the primary key column otherwise
|
2023-03-01 11:23:17 +00:00
|
|
|
// typeorm will not be able to map the result.
|
2023-03-21 19:27:52 +00:00
|
|
|
const selectParams =
|
2023-10-25 13:30:56 +00:00
|
|
|
config.select && query.select && !config.ignoreSelectInQueryParam
|
|
|
|
? config.select.filter((column) => query.select.includes(column))
|
|
|
|
: config.select
|
2023-03-01 11:23:17 +00:00
|
|
|
if (selectParams?.length > 0 && includesAllPrimaryKeyColumns(queryBuilder, selectParams)) {
|
2023-02-15 09:16:35 +00:00
|
|
|
const cols: string[] = selectParams.reduce((cols, currentCol) => {
|
2023-03-21 19:27:52 +00:00
|
|
|
const columnProperties = getPropertiesByColumnName(currentCol)
|
|
|
|
const isRelation = checkIsRelation(queryBuilder, columnProperties.propertyPath)
|
|
|
|
cols.push(fixColumnAlias(columnProperties, queryBuilder.alias, isRelation))
|
2023-02-15 09:16:35 +00:00
|
|
|
return cols
|
|
|
|
}, [])
|
|
|
|
queryBuilder.select(cols)
|
2022-07-27 17:58:00 +00:00
|
|
|
}
|
|
|
|
|
2023-09-25 12:58:04 +00:00
|
|
|
if (config.where && repo instanceof Repository) {
|
|
|
|
const baseWhereStr = generateWhereStatement(queryBuilder, config.where)
|
|
|
|
queryBuilder.andWhere(`(${baseWhereStr})`)
|
2021-08-19 14:42:18 +00:00
|
|
|
}
|
|
|
|
|
2022-05-15 19:01:53 +00:00
|
|
|
if (config.withDeleted) {
|
|
|
|
queryBuilder.withDeleted()
|
|
|
|
}
|
|
|
|
|
2023-03-14 21:10:23 +00:00
|
|
|
if (config.searchableColumns) {
|
2023-10-25 13:30:56 +00:00
|
|
|
if (query.searchBy && !config.ignoreSearchByInQueryParam) {
|
2023-03-14 21:10:23 +00:00
|
|
|
for (const column of query.searchBy) {
|
|
|
|
if (isEntityKey(config.searchableColumns, column)) {
|
|
|
|
searchBy.push(column)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
searchBy.push(...config.searchableColumns)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-12 11:01:53 +00:00
|
|
|
if (query.search && searchBy.length) {
|
2022-03-14 19:02:01 +00:00
|
|
|
queryBuilder.andWhere(
|
|
|
|
new Brackets((qb: SelectQueryBuilder<T>) => {
|
|
|
|
for (const column of searchBy) {
|
2023-01-30 11:17:14 +00:00
|
|
|
const property = getPropertiesByColumnName(column)
|
|
|
|
const { isVirtualProperty, query: virtualQuery } = extractVirtualProperty(qb, property)
|
|
|
|
const isRelation = checkIsRelation(qb, property.propertyPath)
|
2023-02-15 08:05:52 +00:00
|
|
|
const isEmbeded = checkIsEmbedded(qb, property.propertyPath)
|
|
|
|
const alias = fixColumnAlias(
|
|
|
|
property,
|
|
|
|
qb.alias,
|
|
|
|
isRelation,
|
|
|
|
isVirtualProperty,
|
|
|
|
isEmbeded,
|
|
|
|
virtualQuery
|
|
|
|
)
|
2023-03-14 18:12:30 +00:00
|
|
|
|
2023-01-30 11:17:14 +00:00
|
|
|
const condition: WherePredicateOperator = {
|
|
|
|
operator: 'ilike',
|
2023-03-14 18:12:30 +00:00
|
|
|
parameters: [alias, `:${property.column}`],
|
2023-01-30 11:17:14 +00:00
|
|
|
}
|
2023-01-13 14:53:41 +00:00
|
|
|
|
2023-03-17 22:24:16 +00:00
|
|
|
if (['postgres', 'cockroachdb'].includes(queryBuilder.connection.options.type)) {
|
2023-03-16 19:54:09 +00:00
|
|
|
condition.parameters[0] = `CAST(${condition.parameters[0]} AS text)`
|
2022-03-14 19:02:01 +00:00
|
|
|
}
|
2023-01-30 11:17:14 +00:00
|
|
|
|
|
|
|
qb.orWhere(qb['createWhereConditionExpression'](condition), {
|
2023-03-14 18:12:30 +00:00
|
|
|
[property.column]: `%${query.search}%`,
|
2023-01-30 11:17:14 +00:00
|
|
|
})
|
2022-03-14 19:02:01 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
)
|
2020-06-26 21:25:03 +00:00
|
|
|
}
|
|
|
|
|
2021-08-19 14:42:18 +00:00
|
|
|
if (query.filter) {
|
2023-02-09 08:21:09 +00:00
|
|
|
addFilter(queryBuilder, query, config.filterableColumns)
|
2021-08-19 14:42:18 +00:00
|
|
|
}
|
|
|
|
|
2022-12-13 11:35:08 +00:00
|
|
|
if (isPaginated) {
|
|
|
|
;[items, totalItems] = await queryBuilder.getManyAndCount()
|
|
|
|
} else {
|
|
|
|
items = await queryBuilder.getMany()
|
|
|
|
}
|
2020-06-26 21:25:03 +00:00
|
|
|
|
2023-03-17 21:23:46 +00:00
|
|
|
let path: string
|
|
|
|
const { queryOrigin, queryPath } = getQueryUrlComponents(query.path)
|
|
|
|
if (config.relativePath) {
|
|
|
|
path = queryPath
|
|
|
|
} else if (config.origin) {
|
|
|
|
path = config.origin + queryPath
|
|
|
|
} else {
|
|
|
|
path = queryOrigin + queryPath
|
|
|
|
}
|
|
|
|
|
2021-08-19 14:42:18 +00:00
|
|
|
const sortByQuery = sortBy.map((order) => `&sortBy=${order.join(':')}`).join('')
|
|
|
|
const searchQuery = query.search ? `&search=${query.search}` : ''
|
2021-10-12 11:01:53 +00:00
|
|
|
|
|
|
|
const searchByQuery =
|
2023-10-25 13:30:56 +00:00
|
|
|
query.searchBy && searchBy.length && !config.ignoreSearchByInQueryParam
|
|
|
|
? searchBy.map((column) => `&searchBy=${column}`).join('')
|
|
|
|
: ''
|
2021-10-12 11:01:53 +00:00
|
|
|
|
2023-03-21 19:27:52 +00:00
|
|
|
// Only expose select in meta data if query select differs from config select
|
|
|
|
const isQuerySelected = selectParams?.length !== config.select?.length
|
|
|
|
const selectQuery = isQuerySelected ? `&select=${selectParams.join(',')}` : ''
|
|
|
|
|
2021-08-19 14:42:18 +00:00
|
|
|
const filterQuery = query.filter
|
|
|
|
? '&' +
|
2021-10-11 07:46:31 +00:00
|
|
|
stringify(
|
|
|
|
mapKeys(query.filter, (_param, name) => 'filter.' + name),
|
|
|
|
'&',
|
|
|
|
'=',
|
|
|
|
{ encodeURIComponent: (str) => str }
|
|
|
|
)
|
2021-08-19 14:42:18 +00:00
|
|
|
: ''
|
|
|
|
|
2023-03-21 19:27:52 +00:00
|
|
|
const options = `&limit=${limit}${sortByQuery}${searchQuery}${searchByQuery}${selectQuery}${filterQuery}`
|
2020-06-26 21:25:03 +00:00
|
|
|
|
|
|
|
const buildLink = (p: number): string => path + '?page=' + p + options
|
|
|
|
|
2022-12-13 11:35:08 +00:00
|
|
|
const totalPages = isPaginated ? Math.ceil(totalItems / limit) : 1
|
|
|
|
|
2020-06-26 21:25:03 +00:00
|
|
|
const results: Paginated<T> = {
|
|
|
|
data: items,
|
|
|
|
meta: {
|
2022-12-13 11:35:08 +00:00
|
|
|
itemsPerPage: isPaginated ? limit : items.length,
|
2023-01-03 08:52:35 +00:00
|
|
|
totalItems: isPaginated ? totalItems : items.length,
|
2020-06-26 21:25:03 +00:00
|
|
|
currentPage: page,
|
2022-12-13 11:35:08 +00:00
|
|
|
totalPages,
|
2020-06-26 21:25:03 +00:00
|
|
|
sortBy,
|
2021-08-19 14:42:18 +00:00
|
|
|
search: query.search,
|
2021-10-12 11:32:03 +00:00
|
|
|
searchBy: query.search ? searchBy : undefined,
|
2023-03-21 19:27:52 +00:00
|
|
|
select: isQuerySelected ? selectParams : undefined,
|
2021-08-19 14:42:18 +00:00
|
|
|
filter: query.filter,
|
2020-06-26 21:25:03 +00:00
|
|
|
},
|
|
|
|
links: {
|
|
|
|
first: page == 1 ? undefined : buildLink(1),
|
|
|
|
previous: page - 1 < 1 ? undefined : buildLink(page - 1),
|
|
|
|
current: buildLink(page),
|
|
|
|
next: page + 1 > totalPages ? undefined : buildLink(page + 1),
|
2022-02-10 18:21:59 +00:00
|
|
|
last: page == totalPages || !totalItems ? undefined : buildLink(totalPages),
|
2020-06-26 21:25:03 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
return Object.assign(new Paginated<T>(), results)
|
|
|
|
}
|