feat: add searchBy functionality (#99)
This commit is contained in:
parent
e5eba96dfe
commit
7695096a60
9230
package-lock.json
generated
9230
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -43,6 +43,7 @@ describe('Decorator', () => {
|
|||||||
limit: undefined,
|
limit: undefined,
|
||||||
sortBy: undefined,
|
sortBy: undefined,
|
||||||
search: undefined,
|
search: undefined,
|
||||||
|
searchBy: undefined,
|
||||||
filter: undefined,
|
filter: undefined,
|
||||||
path: 'http://localhost/items',
|
path: 'http://localhost/items',
|
||||||
})
|
})
|
||||||
@ -68,6 +69,7 @@ describe('Decorator', () => {
|
|||||||
['createdAt', 'DESC'],
|
['createdAt', 'DESC'],
|
||||||
],
|
],
|
||||||
search: 'white',
|
search: 'white',
|
||||||
|
searchBy: undefined,
|
||||||
path: 'http://localhost/items',
|
path: 'http://localhost/items',
|
||||||
filter: {
|
filter: {
|
||||||
name: '$not:$eq:Kitty',
|
name: '$not:$eq:Kitty',
|
||||||
|
@ -6,6 +6,7 @@ export interface PaginateQuery {
|
|||||||
page?: number
|
page?: number
|
||||||
limit?: number
|
limit?: number
|
||||||
sortBy?: [string, string][]
|
sortBy?: [string, string][]
|
||||||
|
searchBy?: string[]
|
||||||
search?: string
|
search?: string
|
||||||
filter?: { [column: string]: string | string[] }
|
filter?: { [column: string]: string | string[] }
|
||||||
path: string
|
path: string
|
||||||
@ -17,6 +18,8 @@ export const Paginate = createParamDecorator((_data: unknown, ctx: ExecutionCont
|
|||||||
const path = request.protocol + '://' + request.get('host') + request.baseUrl + request.path
|
const path = request.protocol + '://' + request.get('host') + request.baseUrl + request.path
|
||||||
|
|
||||||
const sortBy: [string, string][] = []
|
const sortBy: [string, string][] = []
|
||||||
|
const searchBy: string[] = []
|
||||||
|
|
||||||
if (query.sortBy) {
|
if (query.sortBy) {
|
||||||
const params = !Array.isArray(query.sortBy) ? [query.sortBy] : query.sortBy
|
const params = !Array.isArray(query.sortBy) ? [query.sortBy] : query.sortBy
|
||||||
for (const param of params) {
|
for (const param of params) {
|
||||||
@ -29,6 +32,15 @@ export const Paginate = createParamDecorator((_data: unknown, ctx: ExecutionCont
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (query.searchBy) {
|
||||||
|
const params = !Array.isArray(query.searchBy) ? [query.searchBy] : query.searchBy
|
||||||
|
for (const param of params) {
|
||||||
|
if (isString(param)) {
|
||||||
|
searchBy.push(param)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const filter = mapKeys(
|
const filter = mapKeys(
|
||||||
pickBy(
|
pickBy(
|
||||||
query,
|
query,
|
||||||
@ -44,6 +56,7 @@ export const Paginate = createParamDecorator((_data: unknown, ctx: ExecutionCont
|
|||||||
limit: query.limit ? parseInt(query.limit.toString(), 10) : undefined,
|
limit: query.limit ? parseInt(query.limit.toString(), 10) : undefined,
|
||||||
sortBy: sortBy.length ? sortBy : undefined,
|
sortBy: sortBy.length ? sortBy : undefined,
|
||||||
search: query.search ? query.search.toString() : undefined,
|
search: query.search ? query.search.toString() : undefined,
|
||||||
|
searchBy: searchBy.length ? searchBy : undefined,
|
||||||
filter: Object.keys(filter).length ? filter : undefined,
|
filter: Object.keys(filter).length ? filter : undefined,
|
||||||
path,
|
path,
|
||||||
}
|
}
|
||||||
|
@ -165,6 +165,29 @@ describe('paginate', () => {
|
|||||||
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&search=i')
|
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&search=i')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should return result based on search term and searchBy columns', async () => {
|
||||||
|
const config: PaginateConfig<CatEntity> = {
|
||||||
|
sortableColumns: ['id', 'name', 'color'],
|
||||||
|
searchableColumns: ['name', 'color'],
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchTerm = 'white'
|
||||||
|
const expectedResultData = cats.filter((cat: CatEntity) => cat.color === searchTerm)
|
||||||
|
|
||||||
|
const query: PaginateQuery = {
|
||||||
|
path: '',
|
||||||
|
search: searchTerm,
|
||||||
|
searchBy: ['color'],
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await paginate<CatEntity>(query, repo, config)
|
||||||
|
|
||||||
|
expect(result.meta.search).toStrictEqual(searchTerm)
|
||||||
|
expect(result.meta.searchBy).toStrictEqual(['color'])
|
||||||
|
expect(result.data).toStrictEqual(expectedResultData)
|
||||||
|
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&search=white&searchBy=color')
|
||||||
|
})
|
||||||
|
|
||||||
it('should return result based on where config and filter', async () => {
|
it('should return result based on where config and filter', async () => {
|
||||||
const config: PaginateConfig<CatEntity> = {
|
const config: PaginateConfig<CatEntity> = {
|
||||||
sortableColumns: ['id'],
|
sortableColumns: ['id'],
|
||||||
|
@ -32,6 +32,7 @@ export class Paginated<T> {
|
|||||||
currentPage: number
|
currentPage: number
|
||||||
totalPages: number
|
totalPages: number
|
||||||
sortBy: SortBy<T>
|
sortBy: SortBy<T>
|
||||||
|
searchBy: Column<T>[]
|
||||||
search: string
|
search: string
|
||||||
filter?: { [column: string]: string | string[] }
|
filter?: { [column: string]: string | string[] }
|
||||||
}
|
}
|
||||||
@ -73,13 +74,14 @@ export async function paginate<T>(
|
|||||||
let page = query.page || 1
|
let page = query.page || 1
|
||||||
const limit = Math.min(query.limit || config.defaultLimit || 20, config.maxLimit || 100)
|
const limit = Math.min(query.limit || config.defaultLimit || 20, config.maxLimit || 100)
|
||||||
const sortBy = [] as SortBy<T>
|
const sortBy = [] as SortBy<T>
|
||||||
|
const searchBy: Column<T>[] = []
|
||||||
const path = query.path
|
const path = query.path
|
||||||
|
|
||||||
function isEntityKey(sortableColumns: Column<T>[], column: string): column is Column<T> {
|
function isEntityKey(entityColumns: Column<T>[], column: string): column is Column<T> {
|
||||||
return !!sortableColumns.find((c) => c === column)
|
return !!entityColumns.find((c) => c === column)
|
||||||
}
|
}
|
||||||
|
|
||||||
const { sortableColumns } = config
|
const { sortableColumns, searchableColumns } = config
|
||||||
if (config.sortableColumns.length < 1) throw new ServiceUnavailableException()
|
if (config.sortableColumns.length < 1) throw new ServiceUnavailableException()
|
||||||
|
|
||||||
if (query.sortBy) {
|
if (query.sortBy) {
|
||||||
@ -89,10 +91,21 @@ export async function paginate<T>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sortBy.length) {
|
if (!sortBy.length) {
|
||||||
sortBy.push(...(config.defaultSortBy || [[sortableColumns[0], 'ASC']]))
|
sortBy.push(...(config.defaultSortBy || [[sortableColumns[0], 'ASC']]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (query.searchBy && searchableColumns) {
|
||||||
|
for (const column of query.searchBy) {
|
||||||
|
if (isEntityKey(searchableColumns, column)) {
|
||||||
|
searchBy.push(column as Column<T>)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (!query.searchBy && searchableColumns) {
|
||||||
|
searchBy.push(...searchableColumns)
|
||||||
|
}
|
||||||
|
|
||||||
if (page < 1) page = 1
|
if (page < 1) page = 1
|
||||||
|
|
||||||
let [items, totalItems]: [T[], number] = [[], 0]
|
let [items, totalItems]: [T[], number] = [[], 0]
|
||||||
@ -120,9 +133,9 @@ export async function paginate<T>(
|
|||||||
queryBuilder = queryBuilder.andWhere(new Brackets((queryBuilder) => queryBuilder.andWhere(config.where))) // Postgres fix (https://github.com/ppetzold/nestjs-paginate/pull/97)
|
queryBuilder = queryBuilder.andWhere(new Brackets((queryBuilder) => queryBuilder.andWhere(config.where))) // Postgres fix (https://github.com/ppetzold/nestjs-paginate/pull/97)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.search && config.searchableColumns) {
|
if (query.search && searchBy.length) {
|
||||||
const search: ObjectLiteral[] = []
|
const search: ObjectLiteral[] = []
|
||||||
for (const column of config.searchableColumns) {
|
for (const column of searchBy) {
|
||||||
search.push({ [column]: ILike(`%${query.search}%`) })
|
search.push({ [column]: ILike(`%${query.search}%`) })
|
||||||
}
|
}
|
||||||
queryBuilder = queryBuilder.andWhere(search)
|
queryBuilder = queryBuilder.andWhere(search)
|
||||||
@ -203,6 +216,12 @@ export async function paginate<T>(
|
|||||||
|
|
||||||
const sortByQuery = sortBy.map((order) => `&sortBy=${order.join(':')}`).join('')
|
const sortByQuery = sortBy.map((order) => `&sortBy=${order.join(':')}`).join('')
|
||||||
const searchQuery = query.search ? `&search=${query.search}` : ''
|
const searchQuery = query.search ? `&search=${query.search}` : ''
|
||||||
|
|
||||||
|
const searchByQuery =
|
||||||
|
query.search && query.searchBy && searchBy.length
|
||||||
|
? searchBy.map((column) => `&searchBy=${column}`).join('')
|
||||||
|
: ''
|
||||||
|
|
||||||
const filterQuery = query.filter
|
const filterQuery = query.filter
|
||||||
? '&' +
|
? '&' +
|
||||||
stringify(
|
stringify(
|
||||||
@ -213,7 +232,7 @@ export async function paginate<T>(
|
|||||||
)
|
)
|
||||||
: ''
|
: ''
|
||||||
|
|
||||||
const options = `&limit=${limit}${sortByQuery}${searchQuery}${filterQuery}`
|
const options = `&limit=${limit}${sortByQuery}${searchQuery}${filterQuery}${searchByQuery}`
|
||||||
|
|
||||||
const buildLink = (p: number): string => path + '?page=' + p + options
|
const buildLink = (p: number): string => path + '?page=' + p + options
|
||||||
|
|
||||||
@ -226,6 +245,7 @@ export async function paginate<T>(
|
|||||||
totalPages: totalPages,
|
totalPages: totalPages,
|
||||||
sortBy,
|
sortBy,
|
||||||
search: query.search,
|
search: query.search,
|
||||||
|
searchBy,
|
||||||
filter: query.filter,
|
filter: query.filter,
|
||||||
},
|
},
|
||||||
links: {
|
links: {
|
||||||
|
Loading…
Reference in New Issue
Block a user