Skip to content

Commit ca7d285

Browse files
authored
Merge pull request #21 from SoftwareBrothers/feat/allow-raw-filters
feat: Allow passing custom filters to typeorm adapter
2 parents f6de206 + 95faf53 commit ca7d285

11 files changed

+155
-37
lines changed

src/Resource.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { BaseEntity } from 'typeorm'
33
import { BaseResource, ValidationError, Filter, BaseRecord, flat } from 'admin-bro'
44

55
import { Property } from './Property'
6-
import { convertFilter } from './utils/convertFilter'
6+
import { convertFilter } from './utils/filter/filter.converter'
77

88
type ParamsType = Record<string, any>;
99

src/utils/convertFilter.ts

-36
This file was deleted.
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { FilterParser } from './filter.types'
2+
3+
/**
4+
* It wasn't possible to pass raw filters to adapters with admin-bro
5+
* This solution allows you to pass custom filters to typeorm adapter modyfing list handler
6+
*
7+
* In your custom list handler modify creating filters in this way:
8+
*
9+
* ```
10+
* // That makes `Filter` class to create proper filter object.
11+
* filters[propertyToFilterBy] = 1;
12+
* const filter = await new Filter(filters, resource).populate();
13+
* // This parser recognizes `custom` field and passes the value directly to typeorm
14+
* filter.filters[propertyToFilterBy].custom = In([1,2,3]);
15+
*
16+
*/
17+
export const CustomParser: FilterParser = {
18+
isParserForType: (filter) => (filter as any)?.custom,
19+
parse: (filter, fieldKey) => ({ filterKey: fieldKey, filterValue: (filter as any)?.custom }),
20+
}
+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { Between, LessThanOrEqual, MoreThanOrEqual } from 'typeorm'
2+
import { FilterParser } from './filter.types'
3+
4+
export const DateParser: FilterParser = {
5+
isParserForType: (filter) => ['date', 'datetime'].includes(filter.property.type()),
6+
parse: (filter, fieldKey) => {
7+
if (
8+
typeof filter.value !== 'string'
9+
&& filter.value.from
10+
&& filter.value.to
11+
) {
12+
return {
13+
filterKey: fieldKey,
14+
filterValue: Between(
15+
new Date(filter.value.from),
16+
new Date(filter.value.to),
17+
),
18+
}
19+
}
20+
if (typeof filter.value !== 'string' && filter.value.from) {
21+
return {
22+
filterKey: fieldKey,
23+
filterValue: MoreThanOrEqual(new Date(filter.value.from).toISOString()),
24+
}
25+
}
26+
if (typeof filter.value !== 'string' && filter.value.to) {
27+
return {
28+
filterKey: fieldKey,
29+
filterValue: LessThanOrEqual(new Date(filter.value.to)),
30+
}
31+
}
32+
33+
throw new Error('Cannot parse date filter')
34+
},
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { Like } from 'typeorm'
2+
import { FilterParser } from './filter.types'
3+
4+
export const DefaultParser: FilterParser = {
5+
isParserForType: (filter) => filter.property.type() === 'string',
6+
parse: (filter, fieldKey) => ({ filterKey: fieldKey, filterValue: Like(`%${filter.value}%`) }),
7+
}
+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { Property } from '../../Property'
2+
import { FilterParser } from './filter.types'
3+
4+
export const EnumParser: FilterParser = {
5+
isParserForType: (filter) => (filter.property as Property).column.type === 'enum',
6+
parse: (filter, fieldKey) => ({ filterKey: fieldKey, filterValue: filter.value }),
7+
}

src/utils/filter/filter.converter.ts

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { Filter } from 'admin-bro'
2+
import { BaseEntity, FindConditions } from 'typeorm'
3+
import { DefaultParser } from './default-filter.parser'
4+
import { parsers } from './filter.utils'
5+
6+
export const convertFilter = (
7+
filterObject?: Filter,
8+
): FindConditions<BaseEntity> => {
9+
if (!filterObject) {
10+
return {}
11+
}
12+
13+
const { filters } = filterObject ?? {}
14+
const where = {}
15+
16+
Object.entries(filters ?? {}).forEach(([fieldKey, filter]) => {
17+
const parser = parsers.find((p) => p.isParserForType(filter))
18+
19+
if (parser) {
20+
const { filterValue, filterKey } = parser.parse(filter, fieldKey)
21+
where[filterKey] = filterValue
22+
} else {
23+
const { filterValue, filterKey } = DefaultParser.parse(filter, fieldKey)
24+
where[filterKey] = filterValue
25+
}
26+
})
27+
28+
return where
29+
}

src/utils/filter/filter.types.ts

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { FilterElement } from 'admin-bro'
2+
import { FindManyOptions } from 'typeorm'
3+
4+
export type FilterParser = {
5+
isParserForType: (filter: FilterElement) => boolean;
6+
parse: (
7+
filter: FilterElement,
8+
fieldKey: string
9+
) => { filterKey: string; filterValue: FindManyOptions['where'] };
10+
};

src/utils/filter/filter.utils.ts

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { CustomParser } from './custom-filter.parser'
2+
import { DateParser } from './date-filter.parser'
3+
import { EnumParser } from './enum-filter.parser'
4+
import { JSONParser } from './json-filter.parser'
5+
import { ReferenceParser } from './reference-filter.parser'
6+
7+
export const safeParseJSON = (json: string): any | null => {
8+
try {
9+
return JSON.parse(json)
10+
} catch (e) {
11+
return null
12+
}
13+
}
14+
15+
export const parsers = [
16+
// Has to be the first one, as it is intended to use custom filter if user overrides that
17+
CustomParser,
18+
DateParser,
19+
EnumParser,
20+
ReferenceParser,
21+
JSONParser,
22+
]
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { FilterParser } from './filter.types'
2+
import { safeParseJSON } from './filter.utils'
3+
4+
export const JSONParser: FilterParser = {
5+
isParserForType: (filter) => ['boolean', 'number', 'float', 'object', 'array'].includes(
6+
filter.property.type(),
7+
),
8+
parse: (filter, fieldKey) => ({
9+
filterKey: fieldKey,
10+
filterValue: safeParseJSON(filter.value as string),
11+
}),
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Property } from '../../Property'
2+
import { FilterParser } from './filter.types'
3+
4+
export const ReferenceParser: FilterParser = {
5+
isParserForType: (filter) => filter.property.type() === 'reference',
6+
parse: (filter) => {
7+
const [column] = (filter.property as Property).column.propertyPath.split(
8+
'.',
9+
)
10+
return { filterKey: column, filterValue: filter.value }
11+
},
12+
}

0 commit comments

Comments
 (0)