diff --git a/README.md b/README.md index 7038616..d0703f9 100644 --- a/README.md +++ b/README.md @@ -280,6 +280,22 @@ const paginateConfig: PaginateConfig { * Description: Overrides the origin of absolute resource links if set. */ origin: 'http://cats.example', + + /** + * Required: false + * Type: boolean + * Default: false + * Description: Prevent `searchBy` query param from limiting search scope further. Search will depend upon `searchableColumns` config option only + */ + ignoreSearchByInQueryParam: true, + + /** + * Required: false + * Type: boolean + * Default: false + * Description: Prevent `select` query param from limiting selection further. Partial selection will depend upon `select` config option only + */ + ignoreSelectInQueryParam: true, } ``` @@ -466,7 +482,6 @@ You can use two default decorators @ApiOkResponsePaginated and @ApiPagination to `@ApiPaginationQuery` is for query params - ```typescript @Get() @ApiOkPaginatedResponse( diff --git a/src/paginate.spec.ts b/src/paginate.spec.ts index 5612349..5ef167e 100644 --- a/src/paginate.spec.ts +++ b/src/paginate.spec.ts @@ -2512,6 +2512,110 @@ describe('paginate', () => { expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&filter.id=$not:$in:1,2,5') }) + it('should use searchBy in query param when ignoreSearchByInQueryParam is not defined', async () => { + const config: PaginateConfig = { + sortableColumns: ['id'], + searchableColumns: ['name', 'color'], + } + const query: PaginateQuery = { + path: '', + search: 'Milo', + searchBy: ['color'], + } + + const result = await paginate(query, catRepo, config) + expect(result.data.length).toEqual(0) + expect(result.meta.searchBy).toStrictEqual(['color']) + expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&search=Milo&searchBy=color') + }) + + it('should use searchBy in query param when ignoreSearchByInQueryParam is set to false', async () => { + const config: PaginateConfig = { + sortableColumns: ['id'], + ignoreSearchByInQueryParam: false, + searchableColumns: ['name', 'color'], + } + const query: PaginateQuery = { + path: '', + search: 'Milo', + searchBy: ['color'], + } + + const result = await paginate(query, catRepo, config) + expect(result.data.length).toEqual(0) + expect(result.meta.searchBy).toStrictEqual(['color']) + expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&search=Milo&searchBy=color') + }) + + it('should ignore searchBy in query param when ignoreSearchByInQueryParam is set to true', async () => { + const config: PaginateConfig = { + sortableColumns: ['id'], + ignoreSearchByInQueryParam: true, + searchableColumns: ['name', 'color'], + } + const query: PaginateQuery = { + path: '', + search: 'Milo', + searchBy: ['color'], + } + + const result = await paginate(query, catRepo, config) + expect(result.data.length).toEqual(1) + expect(result.data).toStrictEqual([cats[0]]) + expect(result.meta.searchBy).toStrictEqual(['name', 'color']) + expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&search=Milo') + }) + + it('should use select in query param when ignoreSelectInQueryParam is not defined', async () => { + const config: PaginateConfig = { + sortableColumns: ['id'], + select: ['id', 'name', 'color'], + } + const query: PaginateQuery = { + path: '', + select: ['id', 'color'], + } + + const result = await paginate(query, catRepo, config) + expect(result.data[0]).toEqual({ id: cats[0].id, color: cats[0].color }) + expect(result.meta.select).toStrictEqual(['id', 'color']) + expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&select=id,color') + }) + + it('should use select in query param when ignoreSelectInQueryParam is set to false', async () => { + const config: PaginateConfig = { + sortableColumns: ['id'], + ignoreSelectInQueryParam: false, + select: ['id', 'name', 'color'], + } + const query: PaginateQuery = { + path: '', + select: ['id', 'color'], + } + + const result = await paginate(query, catRepo, config) + expect(result.data[0]).toEqual({ id: cats[0].id, color: cats[0].color }) + expect(result.meta.select).toStrictEqual(['id', 'color']) + expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&select=id,color') + }) + + it('should ignore select in query param when ignoreSelectInQueryParam is set to true', async () => { + const config: PaginateConfig = { + sortableColumns: ['id'], + ignoreSelectInQueryParam: true, + select: ['id', 'name', 'color'], + } + const query: PaginateQuery = { + path: '', + select: ['id', 'color'], + } + + const result = await paginate(query, catRepo, config) + expect(result.data[0]).toEqual({ id: cats[0].id, color: cats[0].color, name: cats[0].name }) + expect(result.meta.select).toEqual(undefined) + expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC') + }) + describe('should return result based on date column filter', () => { it('with $not and $null operators', async () => { const config: PaginateConfig = { diff --git a/src/paginate.ts b/src/paginate.ts index 9592d04..9765065 100644 --- a/src/paginate.ts +++ b/src/paginate.ts @@ -83,6 +83,8 @@ export interface PaginateConfig { paginationType?: PaginationType relativePath?: boolean origin?: string + ignoreSearchByInQueryParam?: boolean + ignoreSelectInQueryParam?: boolean } export const DEFAULT_MAX_LIMIT = 100 @@ -272,7 +274,9 @@ export async function paginate( // When we partial select the columns (main or relation) we must add the primary key column otherwise // typeorm will not be able to map the result. const selectParams = - config.select && query.select ? config.select.filter((column) => query.select.includes(column)) : config.select + config.select && query.select && !config.ignoreSelectInQueryParam + ? config.select.filter((column) => query.select.includes(column)) + : config.select if (selectParams?.length > 0 && includesAllPrimaryKeyColumns(queryBuilder, selectParams)) { const cols: string[] = selectParams.reduce((cols, currentCol) => { const columnProperties = getPropertiesByColumnName(currentCol) @@ -293,7 +297,7 @@ export async function paginate( } if (config.searchableColumns) { - if (query.searchBy) { + if (query.searchBy && !config.ignoreSearchByInQueryParam) { for (const column of query.searchBy) { if (isEntityKey(config.searchableColumns, column)) { searchBy.push(column) @@ -362,7 +366,9 @@ export async function paginate( const searchQuery = query.search ? `&search=${query.search}` : '' const searchByQuery = - query.searchBy && searchBy.length ? searchBy.map((column) => `&searchBy=${column}`).join('') : '' + query.searchBy && searchBy.length && !config.ignoreSearchByInQueryParam + ? searchBy.map((column) => `&searchBy=${column}`).join('') + : '' // Only expose select in meta data if query select differs from config select const isQuerySelected = selectParams?.length !== config.select?.length