feat: extend filter with enable all ops config

This commit is contained in:
ppetzold 2023-03-18 20:07:48 +01:00
parent 0c45c6da63
commit 3fa48d4f63
4 changed files with 78 additions and 17 deletions

View File

@ -370,12 +370,28 @@ const config: PaginateConfig<CatEntity> = {
const result = await paginate<CatEntity>(query, catRepo, config) const result = await paginate<CatEntity>(query, catRepo, config)
``` ```
## Single Filters ## Filters
Filter operators must be whitelisted per column in `PaginateConfig`. Filter operators must be whitelisted per column in `PaginateConfig`.
### Examples ### Examples
#### Code
```typescript
const config: PaginateConfig<CatEntity> = {
// ...
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.name=$eq:Milo` is equivalent with `?filter.name=Milo`
`?filter.age=$btw:4,6` where column `age` is between `4` and `6` `?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
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 ### Examples

View File

@ -227,7 +227,7 @@ export function getFilterTokens(raw?: string): FilterToken | null {
export function parseFilter( export function parseFilter(
query: PaginateQuery, query: PaginateQuery,
filterableColumns?: { [column: string]: (FilterOperator | FilterSuffix)[] } filterableColumns?: { [column: string]: (FilterOperator | FilterSuffix)[] | true }
): ColumnsFilters { ): ColumnsFilters {
const filter: ColumnsFilters = {} const filter: ColumnsFilters = {}
if (!filterableColumns || !query.filter) { if (!filterableColumns || !query.filter) {
@ -242,20 +242,28 @@ export function parseFilter(
const statements = !Array.isArray(input) ? [input] : input const statements = !Array.isArray(input) ? [input] : input
for (const raw of statements) { for (const raw of statements) {
const token = getFilterTokens(raw) const token = getFilterTokens(raw)
if ( if (!token) {
!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))
)
) {
continue 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] = { const params: (typeof filter)[0][0] = {
comparator: token.comparator, comparator: token.comparator,
@ -297,7 +305,7 @@ export function parseFilter(
export function addFilter<T>( export function addFilter<T>(
qb: SelectQueryBuilder<T>, qb: SelectQueryBuilder<T>,
query: PaginateQuery, query: PaginateQuery,
filterableColumns?: { [column: string]: (FilterOperator | FilterSuffix)[] } filterableColumns?: { [column: string]: (FilterOperator | FilterSuffix)[] | true }
): SelectQueryBuilder<T> { ): SelectQueryBuilder<T> {
const filter = parseFilter(query, filterableColumns) const filter = parseFilter(query, filterableColumns)
return qb.andWhere( return qb.andWhere(

View File

@ -2155,4 +2155,41 @@ describe('paginate', () => {
expect(result.data[0].home).toBeDefined() expect(result.data[0].home).toBeDefined()
expect(result.data[0].home.pillows).toStrictEqual(cat.home.pillows) 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<CatEntity> = {
sortableColumns: ['id'],
filterableColumns: {
id: true,
},
}
const query: PaginateQuery = {
path: '',
filter: {
id: '$not:$in:1,2,5',
},
}
const result = await paginate<CatEntity>(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<CatEntity> = {
sortableColumns: ['id'],
}
const query: PaginateQuery = {
path: '',
filter: {
id: '$not:$in:1,2,5',
},
}
const result = await paginate<CatEntity>(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')
})
}) })

View File

@ -66,7 +66,7 @@ export interface PaginateConfig<T> {
defaultLimit?: number defaultLimit?: number
where?: FindOptionsWhere<T> | FindOptionsWhere<T>[] where?: FindOptionsWhere<T> | FindOptionsWhere<T>[]
filterableColumns?: { filterableColumns?: {
[key in Column<T> | string]?: (FilterOperator | FilterSuffix)[] [key in Column<T> | string]?: (FilterOperator | FilterSuffix)[] | true
} }
loadEagerRelations?: boolean loadEagerRelations?: boolean
withDeleted?: boolean withDeleted?: boolean