diff --git a/packages/crud-request/src/request-query.parser.ts b/packages/crud-request/src/request-query.parser.ts index c6a44150..8035697b 100644 --- a/packages/crud-request/src/request-query.parser.ts +++ b/packages/crud-request/src/request-query.parser.ts @@ -333,6 +333,6 @@ export class RequestQueryParser implements ParsedRequestParams { break; } - return { field: option.field, operator: 'eq', value }; + return { field: option.field, operator: '$eq', value }; } } diff --git a/packages/crud-request/src/types/request-query.types.ts b/packages/crud-request/src/types/request-query.types.ts index 2eba4eb7..e6c52da3 100644 --- a/packages/crud-request/src/types/request-query.types.ts +++ b/packages/crud-request/src/types/request-query.types.ts @@ -1,5 +1,3 @@ -import { ObjectLiteral } from '@nestjsx/util'; - export type QueryFields = string[]; export type QueryFilter = { @@ -24,7 +22,9 @@ export type QuerySort = { export type QuerySortArr = [string, QuerySortOperator]; -export type ComparisonOperator = +export type QuerySortOperator = 'ASC' | 'DESC'; + +type DeprecatedCondOperator = | 'eq' | 'ne' | 'gt' @@ -39,31 +39,41 @@ export type ComparisonOperator = | 'notin' | 'isnull' | 'notnull' - | 'between' - | keyof SFieldOperator; -export type QuerySortOperator = 'ASC' | 'DESC'; + | 'between'; export enum CondOperator { - EQUALS = 'eq', - NOT_EQUALS = 'ne', - GREATER_THAN = 'gt', - LOWER_THAN = 'lt', - GREATER_THAN_EQUALS = 'gte', - LOWER_THAN_EQUALS = 'lte', - STARTS = 'starts', - ENDS = 'ends', - CONTAINS = 'cont', - EXCLUDES = 'excl', - IN = 'in', - NOT_IN = 'notin', - IS_NULL = 'isnull', - NOT_NULL = 'notnull', - BETWEEN = 'between', + EQUALS = '$eq', + NOT_EQUALS = '$ne', + GREATER_THAN = '$gt', + LOWER_THAN = '$lt', + GREATER_THAN_EQUALS = '$gte', + LOWER_THAN_EQUALS = '$lte', + STARTS = '$starts', + ENDS = '$ends', + CONTAINS = '$cont', + EXCLUDES = '$excl', + IN = '$in', + NOT_IN = '$notin', + IS_NULL = '$isnull', + NOT_NULL = '$notnull', + BETWEEN = '$between', + EQUALS_LOW = '$eqL', + NOT_EQUALS_LOW = '$neL', + STARTS_LOW = '$startsL', + ENDS_LOW = '$endsL', + CONTAINS_LOW = '$contL', + EXCLUDES_LOW = '$exclL', + IN_LOW = '$inL', + NOT_IN_LOW = '$betweenL', } +export type ComparisonOperator = DeprecatedCondOperator | keyof SFieldOperator; + // new search export type SPrimitivesVal = string | number | boolean; + export type SFiledValues = SPrimitivesVal | Array; + export type SFieldOperator = { $eq?: SFiledValues; $ne?: SFiledValues; @@ -80,9 +90,19 @@ export type SFieldOperator = { $between?: SFiledValues; $isnull?: SFiledValues; $notnull?: SFiledValues; + $eqL?: SFiledValues; + $neL?: SFiledValues; + $startsL?: SFiledValues; + $endsL?: SFiledValues; + $contL?: SFiledValues; + $exclL?: SFiledValues; + $inL?: SFiledValues; + $notinL?: SFiledValues; + $betweenL?: SFiledValues; $or?: SFieldOperator; $and?: never; }; + export type SField = SPrimitivesVal | SFieldOperator; export type SFields = { diff --git a/packages/crud-request/test/request-query.parser.spec.ts b/packages/crud-request/test/request-query.parser.spec.ts index 28c0db0c..b38a8c50 100644 --- a/packages/crud-request/test/request-query.parser.spec.ts +++ b/packages/crud-request/test/request-query.parser.spec.ts @@ -451,10 +451,14 @@ describe('#request-query', () => { }; const test = qp.parseParams(params, options); const expected = [ - { field: 'foo', operator: 'eq', value: 'cb1751fd-7fcf-4eb5-b38e-86428b1fd88d' }, - { field: 'bb', operator: 'eq', value: 1 }, - { field: 'buz', operator: 'eq', value: 'string' }, - { field: 'bigInt', operator: 'eq', value: '9007199254740999' }, + { + field: 'foo', + operator: '$eq', + value: 'cb1751fd-7fcf-4eb5-b38e-86428b1fd88d', + }, + { field: 'bb', operator: '$eq', value: 1 }, + { field: 'buz', operator: '$eq', value: 'string' }, + { field: 'bigInt', operator: '$eq', value: '9007199254740999' }, ]; expect(test.paramsFilter).toMatchObject(expected); }); @@ -468,7 +472,7 @@ describe('#request-query', () => { bar: { field: 'bar', type: 'number' }, }; const test = qp.parseParams(params, options); - const expected = [{ field: 'bar', operator: 'eq', value: 123 }]; + const expected = [{ field: 'bar', operator: '$eq', value: 123 }]; expect(test.paramsFilter).toMatchObject(expected); }); }); diff --git a/packages/crud-typeorm/src/typeorm-crud.service.ts b/packages/crud-typeorm/src/typeorm-crud.service.ts index 1774f735..0c2ef729 100644 --- a/packages/crud-typeorm/src/typeorm-crud.service.ts +++ b/packages/crud-typeorm/src/typeorm-crud.service.ts @@ -35,11 +35,12 @@ import { SelectQueryBuilder, DeepPartial, WhereExpression, + ConnectionOptions, } from 'typeorm'; import { RelationMetadata } from 'typeorm/metadata/RelationMetadata'; -import { Param } from '@nestjs/common'; export class TypeOrmCrudService extends CrudService { + protected dbName: ConnectionOptions['type']; protected entityColumns: string[]; protected entityPrimaryColumns: string[]; protected entityColumnsHash: ObjectLiteral = {}; @@ -48,6 +49,7 @@ export class TypeOrmCrudService extends CrudService { constructor(protected repo: Repository) { super(); + this.dbName = this.repo.metadata.connection.options.type; this.onInitMapEntityColumns(); this.onInitMapRelations(); } @@ -814,6 +816,7 @@ export class TypeOrmCrudService extends CrudService { param: any, ): { str: string; params: ObjectLiteral } { const field = this.getFieldWithAlias(cond.field); + const likeOperator = this.dbName === 'postgres' ? 'ILIKE' : 'LIKE'; let str: string; let params: ObjectLiteral; @@ -867,18 +870,12 @@ export class TypeOrmCrudService extends CrudService { break; case '$in': - /* istanbul ignore if */ - if (!Array.isArray(cond.value) || !cond.value.length) { - this.throwBadRequestException(`Invalid column '${cond.field}' value`); - } + this.checkFilterIsArray(cond); str = `${field} IN (:...${param})`; break; case '$notin': - /* istanbul ignore if */ - if (!Array.isArray(cond.value) || !cond.value.length) { - this.throwBadRequestException(`Invalid column '${cond.field}' value`); - } + this.checkFilterIsArray(cond); str = `${field} NOT IN (:...${param})`; break; @@ -893,10 +890,7 @@ export class TypeOrmCrudService extends CrudService { break; case '$between': - /* istanbul ignore if */ - if (!Array.isArray(cond.value) || !cond.value.length || cond.value.length !== 2) { - this.throwBadRequestException(`Invalid column '${cond.field}' value`); - } + this.checkFilterIsArray(cond, cond.value.length !== 2); str = `${field} BETWEEN :${param}0 AND :${param}1`; params = { [`${param}0`]: cond.value[0], @@ -904,6 +898,54 @@ export class TypeOrmCrudService extends CrudService { }; break; + // case insensitive + case '$eqL': + str = `LOWER(${field}) = :${param}`; + break; + + case '$neL': + str = `LOWER(${field}) != :${param}`; + break; + + case '$startsL': + str = `${field} ${likeOperator} :${param}`; + params = { [param]: `${cond.value}%` }; + break; + + case '$endsL': + str = `${field} ${likeOperator} :${param}`; + params = { [param]: `%${cond.value}` }; + break; + + case '$contL': + str = `${field} ${likeOperator} :${param}`; + params = { [param]: `%${cond.value}%` }; + break; + + case '$exclL': + str = `${field} NOT ${likeOperator} :${param}`; + params = { [param]: `%${cond.value}%` }; + break; + + case '$inL': + this.checkFilterIsArray(cond); + str = `LOWER(${field}) IN (:...${param})`; + break; + + case '$notinL': + this.checkFilterIsArray(cond); + str = `LOWER(${field}) NOT IN (:...${param})`; + break; + + case '$betweenL': + this.checkFilterIsArray(cond, cond.value.length !== 2); + str = `LOWER(${field}) BETWEEN :${param}0 AND :${param}1`; + params = { + [`${param}0`]: cond.value[0], + [`${param}1`]: cond.value[1], + }; + break; + /* istanbul ignore next */ default: str = `${field} = :${param}`; @@ -917,6 +959,16 @@ export class TypeOrmCrudService extends CrudService { return { str, params }; } + private checkFilterIsArray(cond: QueryFilter, withLength?: boolean) { + if ( + !Array.isArray(cond.value) || + !cond.value.length || + (!isNil(withLength) ? withLength : false) + ) { + this.throwBadRequestException(`Invalid column '${cond.field}' value`); + } + } + private getPrimaryParam(params: any): string { return objKeys(params).find((param) => params[param] && params[param].primary); }