diff --git a/README.md b/README.md index 514b43f..89cddd4 100644 --- a/README.md +++ b/README.md @@ -370,12 +370,28 @@ const config: PaginateConfig = { const result = await paginate(query, catRepo, config) ``` -## Single Filters +## Filters Filter operators must be whitelisted per column in `PaginateConfig`. ### Examples +#### Code + +```typescript +const config: PaginateConfig = { + // ... + filterableColumns: { + // Whitelist operators individually + id: [FilterOperator.EQ, FilterSuffix.NOT], + 'toys.name': [FilterOperator.IN], + + // Whitelist all operators on a single column + age: true, + }, +} +``` + `?filter.name=$eq:Milo` is equivalent with `?filter.name=Milo` `?filter.age=$btw:4,6` where column `age` is between `4` and `6` @@ -398,7 +414,7 @@ Filter operators must be whitelisted per column in `PaginateConfig`. ## Multi Filters -Multi filters are filters that can be applied to a single column with a comparator. As for single filters, multi filters must be whitelisted per column in `PaginateConfig`. +Multi filters are filters that can be applied to a single column with a comparator. ### Examples diff --git a/src/filter.ts b/src/filter.ts index 5a212ee..57978aa 100644 --- a/src/filter.ts +++ b/src/filter.ts @@ -227,7 +227,7 @@ export function getFilterTokens(raw?: string): FilterToken | null { export function parseFilter( query: PaginateQuery, - filterableColumns?: { [column: string]: (FilterOperator | FilterSuffix)[] } + filterableColumns?: { [column: string]: (FilterOperator | FilterSuffix)[] | true } ): ColumnsFilters { const filter: ColumnsFilters = {} if (!filterableColumns || !query.filter) { @@ -242,20 +242,28 @@ export function parseFilter( const statements = !Array.isArray(input) ? [input] : input for (const raw of statements) { const token = getFilterTokens(raw) - if ( - !token || - !( - allowedOperators.includes(token.operator) || - (token.suffix === FilterSuffix.NOT && - allowedOperators.includes(token.suffix) && - token.operator === FilterOperator.EQ) || - (token.suffix && - allowedOperators.includes(token.suffix) && - allowedOperators.includes(token.operator)) - ) - ) { + if (!token) { continue } + if (allowedOperators === true) { + if (token.operator && !isOperator(token.operator)) { + continue + } + if (token.suffix && !isSuffix(token.suffix)) { + continue + } + } else { + if ( + token.operator && + token.operator !== FilterOperator.EQ && + !allowedOperators.includes(token.operator) + ) { + continue + } + if (token.suffix && !allowedOperators.includes(token.suffix)) { + continue + } + } const params: (typeof filter)[0][0] = { comparator: token.comparator, @@ -297,7 +305,7 @@ export function parseFilter( export function addFilter( qb: SelectQueryBuilder, query: PaginateQuery, - filterableColumns?: { [column: string]: (FilterOperator | FilterSuffix)[] } + filterableColumns?: { [column: string]: (FilterOperator | FilterSuffix)[] | true } ): SelectQueryBuilder { const filter = parseFilter(query, filterableColumns) return qb.andWhere( diff --git a/src/paginate.spec.ts b/src/paginate.spec.ts index 804954a..4accbbe 100644 --- a/src/paginate.spec.ts +++ b/src/paginate.spec.ts @@ -2155,4 +2155,41 @@ describe('paginate', () => { expect(result.data[0].home).toBeDefined() expect(result.data[0].home.pillows).toStrictEqual(cat.home.pillows) }) + + it('should allow all filters on a field when passing boolean', async () => { + const config: PaginateConfig = { + sortableColumns: ['id'], + filterableColumns: { + id: true, + }, + } + const query: PaginateQuery = { + path: '', + filter: { + id: '$not:$in:1,2,5', + }, + } + + const result = await paginate(query, catRepo, config) + + expect(result.data).toStrictEqual([cats[2], cats[3]]) + expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&filter.id=$not:$in:1,2,5') + }) + + it('should ignore all filters on a field when not passing anything', async () => { + const config: PaginateConfig = { + sortableColumns: ['id'], + } + const query: PaginateQuery = { + path: '', + filter: { + id: '$not:$in:1,2,5', + }, + } + + const result = await paginate(query, catRepo, config) + + expect(result.data).toStrictEqual([cats[0], cats[1], cats[2], cats[3], cats[4]]) + expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&filter.id=$not:$in:1,2,5') + }) }) diff --git a/src/paginate.ts b/src/paginate.ts index c55563e..bef1aa9 100644 --- a/src/paginate.ts +++ b/src/paginate.ts @@ -66,7 +66,7 @@ export interface PaginateConfig { defaultLimit?: number where?: FindOptionsWhere | FindOptionsWhere[] filterableColumns?: { - [key in Column | string]?: (FilterOperator | FilterSuffix)[] + [key in Column | string]?: (FilterOperator | FilterSuffix)[] | true } loadEagerRelations?: boolean withDeleted?: boolean