From e78d1c90d133eda904946c185bd4587e5783c91a Mon Sep 17 00:00:00 2001 From: ashar Date: Thu, 30 May 2024 09:36:20 +0700 Subject: [PATCH] feat(SPG-5) Filter --- .../helpers/query/specific-search.helper.ts | 63 ++++++++++ .../domain/entities/base-filter.entity.ts | 18 +++ .../infrastructure/dto/base-filter.dto.ts | 114 ++++++++++++++++++ 3 files changed, 195 insertions(+) create mode 100644 src/core/helpers/query/specific-search.helper.ts create mode 100644 src/core/modules/domain/entities/base-filter.entity.ts create mode 100644 src/core/modules/infrastructure/dto/base-filter.dto.ts diff --git a/src/core/helpers/query/specific-search.helper.ts b/src/core/helpers/query/specific-search.helper.ts new file mode 100644 index 0000000..e1645b4 --- /dev/null +++ b/src/core/helpers/query/specific-search.helper.ts @@ -0,0 +1,63 @@ +import { Brackets, SelectQueryBuilder } from 'typeorm'; + +export interface Param { + cols: string; + data: string[]; + additional?: any[]; + leftJoin?: any[]; +} + +export class SpecificSearchFilter { + constructor( + private query: SelectQueryBuilder, + private readonly table: string, + private readonly params: Param[], + ) {} + + getFilter() { + const params = this.params?.filter((item) => { + return item.data !== undefined; + }); + return this.bySearch(this.query, params); + } + + bySearch(query: SelectQueryBuilder, params: Param[]) { + query.andWhere( + new Brackets((qb) => { + params.forEach((param) => { + const { cols, data, additional, leftJoin } = param; + + const arr = data?.map((el) => + el.includes("'") + ? `'%${el.trim().replace(/'/g, "''").replace(/\s+/g, ' ')}%'` + : `'%${el.trim().replace(/\s+/g, ' ')}%'`, + ); + + const aliases = !cols.match(/\./g) + ? this.table.concat(`.${cols}`) + : cols; + + qb['andWhere'](`${aliases} ILIKE ANY(ARRAY[${arr}])`); + + if (additional?.length > 0) { + qb['orWhere']( + new Brackets((subQb) => { + for (const addition of additional) { + subQb['orWhere'](`${addition}`); + } + }), + ); + } + + if (leftJoin?.length) { + for (const join of leftJoin) { + qb['leftJoinAndSelect'](`${join.relations}`, `${join.alias}`); + } + } + }); + }), + ); + + return query; + } +} diff --git a/src/core/modules/domain/entities/base-filter.entity.ts b/src/core/modules/domain/entities/base-filter.entity.ts new file mode 100644 index 0000000..6ee27c7 --- /dev/null +++ b/src/core/modules/domain/entities/base-filter.entity.ts @@ -0,0 +1,18 @@ +import { ORDER_TYPE, STATUS } from "src/core/strings/constants/base.constants"; + +export interface BaseFilterEntity { + page: number; + limit: number; + q?: string; + names: string[]; + entity_ids: string[]; + order_by: string; + order_type: ORDER_TYPE; + statuses: STATUS[]; + created_ids: string[]; + created_from: number; + created_to: number; + updated_ids: string[]; + updated_from: number; + updated_to: number; +} \ No newline at end of file diff --git a/src/core/modules/infrastructure/dto/base-filter.dto.ts b/src/core/modules/infrastructure/dto/base-filter.dto.ts new file mode 100644 index 0000000..f0c3820 --- /dev/null +++ b/src/core/modules/infrastructure/dto/base-filter.dto.ts @@ -0,0 +1,114 @@ +import { ApiProperty } from "@nestjs/swagger"; +import { BaseFilterEntity } from "../../domain/entities/base-filter.entity"; +import { Transform } from "class-transformer"; +import { IsArray, IsEnum, IsNumber, IsString, ValidateIf } from "class-validator"; +import { ORDER_TYPE, STATUS } from "src/core/strings/constants/base.constants"; + +export class BaseFilterDto implements BaseFilterEntity { + @ApiProperty({ type: Number, required: false, default: 1 }) + @Transform((body) => Number(body.value)) + @ValidateIf((body) => body.page) + @IsNumber() + page = 1; + + @ApiProperty({ type: Number, required: false, default: 10 }) + @Transform((body) => Number(body.value)) + @ValidateIf((body) => body.limit) + @IsNumber() + limit = 10; + + @ApiProperty({ type: String, required: false }) + q: string; + + @ApiProperty({ type: ['string'], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + names: string[]; + + @ApiProperty({ type: String, required: false }) + order_by: string; + + @ApiProperty({ + type: 'string', + required: false, + description: `Select ("${ORDER_TYPE.ASC}", "${ORDER_TYPE.DESC}")`, + }) + @ValidateIf((body) => body.order_type) + @IsEnum(ORDER_TYPE, { + message: `order_type must be a valid enum ${JSON.stringify( + Object.values(ORDER_TYPE), + )}`, + }) + order_type: ORDER_TYPE; + + @ApiProperty({ + type: ['string'], + required: false, + description: `Select ["${STATUS.ACTIVE}"]`, + }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + @ValidateIf((body) => { + return body.status; + }) + @IsArray() + @IsString({ each: true }) + @IsEnum(STATUS, { + message: `Status must be a valid enum ${JSON.stringify( + Object.values(STATUS), + )}`, + each: true, + }) + statuses: STATUS[]; + + @ApiProperty({ type: [String], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + @ValidateIf((body) => body.created_ids) + @IsArray() + @IsString({ each: true }) + created_ids: string[]; + + @ApiProperty({ type: 'integer', required: false }) + @ValidateIf((body) => body.created_from) + created_from: number; + + @ApiProperty({ type: 'integer', required: false }) + @ValidateIf((body) => body.created_to) + created_to: number; + + @ApiProperty({ type: [String], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + @ValidateIf((body) => body.updated_ids) + @IsArray() + @IsString({ each: true }) + updated_ids: string[]; + + @ApiProperty({ type: 'integer', required: false }) + @Transform((body) => { + return typeof body.value == 'string' ? Number(body.value) : body.value; + }) + @ValidateIf((body) => body.updated_from) + updated_from: number; + + @ApiProperty({ type: 'integer', required: false }) + @Transform((body) => { + return typeof body.value == 'string' ? Number(body.value) : body.value; + }) + @ValidateIf((body) => body.updated_to) + updated_to: number; + + @ApiProperty({ type: [String], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + @ValidateIf((body) => body.entity_ids) + @IsArray() + @IsString({ each: true }) + entity_ids: string[]; +}