From 0b2697aa67cef3431f17e5a52eef762de2b7f79b Mon Sep 17 00:00:00 2001 From: roggervalf Date: Mon, 17 Mar 2025 11:37:38 -0600 Subject: [PATCH] feat: add support for more operators than contains --- src/utils/filter/default-filter.parser.ts | 62 +++++++++++++++++++++-- src/utils/filter/filter.converter.ts | 37 +++++++++++--- src/utils/filter/filter.types.ts | 2 +- src/utils/filter/filter.utils.ts | 5 ++ 4 files changed, 93 insertions(+), 13 deletions(-) diff --git a/src/utils/filter/default-filter.parser.ts b/src/utils/filter/default-filter.parser.ts index b7dae176..7ed9718c 100644 --- a/src/utils/filter/default-filter.parser.ts +++ b/src/utils/filter/default-filter.parser.ts @@ -1,23 +1,77 @@ -import { Like, Raw } from 'typeorm' +import { Like, Not, Raw } from 'typeorm' import { Property } from '../../Property.js' import { FilterParser } from './filter.types.js' +import { OPERATORS } from './filter.utils.js' const uuidRegex = /^[0-9A-F]{8}-[0-9A-F]{4}-[5|4|3|2|1][0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i +const OPERATOR_SEPARATOR = '~'; + +const MATCHING_PATTERNS = { + EQ: 'equals', + NE: 'notEquals', + CO: 'contains', + EW: 'endsWith', + SW: 'startsWith', +}; + export const DefaultParser: FilterParser = { isParserForType: (filter) => filter.property.type() === 'string', parse: (filter, fieldKey) => { + const value = filter.value; if ( - uuidRegex.test(filter.value.toString()) + uuidRegex.test(value.toString()) || (filter.property as Property).column.type === 'uuid' ) { return { filterKey: fieldKey, filterValue: Raw((alias) => `CAST(${alias} AS CHAR(36)) = :value`, { - value: filter.value, + value, }), } } - return { filterKey: fieldKey, filterValue: Like(`%${filter.value}%`) } + + if (typeof value === 'object') { + if (value[MATCHING_PATTERNS.SW]) { + return { + filterKey: fieldKey, filterValue: Like(`${value[MATCHING_PATTERNS.SW]}%`) + } + } else if (value[MATCHING_PATTERNS.EW]) { + return { + filterKey: fieldKey, filterValue: Like(`%${value[MATCHING_PATTERNS.EW]}`) + } + } else if (value[MATCHING_PATTERNS.EQ]) { + return { + filterKey: fieldKey, filterValue: value[MATCHING_PATTERNS.EQ] + } + } else if (value[MATCHING_PATTERNS.NE]) { + return { + filterKey: fieldKey, filterValue: Not(value[MATCHING_PATTERNS.NE]) + } + } else { + const orPrefix = `${OPERATORS.OR}${OPERATOR_SEPARATOR}`; + if (value[`${orPrefix}${MATCHING_PATTERNS.SW}`]) { + return { + filterKey: fieldKey, filterValue: Like(`${value[`${orPrefix}${MATCHING_PATTERNS.SW}`]}%`), operator: OPERATORS.OR + } + } else if (value[`${orPrefix}${MATCHING_PATTERNS.EW}`]) { + return { + filterKey: fieldKey, filterValue: Like(`%${value[`${orPrefix}${MATCHING_PATTERNS.EW}`]}`), operator: OPERATORS.OR + } + } else if (value[`${orPrefix}${MATCHING_PATTERNS.EQ}`]) { + return { + filterKey: fieldKey, filterValue: value[`${orPrefix}${MATCHING_PATTERNS.EQ}`], operator: OPERATORS.OR + } + } else if (value[`${orPrefix}${MATCHING_PATTERNS.NE}`]) { + return { + filterKey: fieldKey, filterValue: Not(value[`${orPrefix}${MATCHING_PATTERNS.NE}`]), operator: OPERATORS.OR + } + } else if (value[OPERATORS.OR]) { + return { filterKey: fieldKey, filterValue: Like(`%${value[OPERATORS.OR]}%`), operator: OPERATORS.OR } + } + } + } + + return { filterKey: fieldKey, filterValue: Like(`%${value}%`) } }, } diff --git a/src/utils/filter/filter.converter.ts b/src/utils/filter/filter.converter.ts index 4b9678aa..9474e505 100644 --- a/src/utils/filter/filter.converter.ts +++ b/src/utils/filter/filter.converter.ts @@ -1,29 +1,50 @@ import { Filter } from 'adminjs' import { BaseEntity, FindOptionsWhere } from 'typeorm' import { DefaultParser } from './default-filter.parser.js' -import { parsers } from './filter.utils.js' +import { OPERATORS, parsers } from './filter.utils.js' export const convertFilter = ( filterObject?: Filter, -): FindOptionsWhere => { +): FindOptionsWhere[] | {} => { if (!filterObject) { return {} } const { filters } = filterObject ?? {} - const where = {} + const andStatements = {} + const orStatements: {[key: string]: any;}[] = [] Object.entries(filters ?? {}).forEach(([fieldKey, filter]) => { const parser = parsers.find((p) => p.isParserForType(filter)) if (parser) { - const { filterValue, filterKey } = parser.parse(filter, fieldKey) - where[filterKey] = filterValue + const { filterValue, filterKey, operator } = parser.parse(filter, fieldKey) + if (operator === OPERATORS.OR) { + orStatements.push({ [filterKey]: filterValue }) + } else { + andStatements[filterKey] = filterValue + } } else { - const { filterValue, filterKey } = DefaultParser.parse(filter, fieldKey) - where[filterKey] = filterValue + const { filterValue, filterKey, operator } = DefaultParser.parse(filter, fieldKey) + if (operator === OPERATORS.OR) { + orStatements.push({ [filterKey]: filterValue }) + } else { + andStatements[filterKey] = filterValue + } } }) - return where + if(Object.keys(andStatements).length > 0 || orStatements.length > 0) { + return [andStatements, ...orStatements]; + } + + if(Object.keys(andStatements).length > 0) { + return andStatements; + } + + if(orStatements.length > 0) { + return orStatements; + } + + return {}; } diff --git a/src/utils/filter/filter.types.ts b/src/utils/filter/filter.types.ts index c7393436..a57b77b6 100644 --- a/src/utils/filter/filter.types.ts +++ b/src/utils/filter/filter.types.ts @@ -5,5 +5,5 @@ export type FilterParser = { parse: ( filter: FilterElement, fieldKey: string - ) => { filterKey: string; filterValue: any }; + ) => { filterKey: string; filterValue: any;operator?: string; }; }; diff --git a/src/utils/filter/filter.utils.ts b/src/utils/filter/filter.utils.ts index 53ea4a67..b1635cc5 100644 --- a/src/utils/filter/filter.utils.ts +++ b/src/utils/filter/filter.utils.ts @@ -20,3 +20,8 @@ export const parsers = [ ReferenceParser, JSONParser, ] + +export const OPERATORS = { + AND: 'and', + OR: 'or', +}; \ No newline at end of file