From 764fe06c508caa1e00145bd185d6272a0ca724ee Mon Sep 17 00:00:00 2001 From: liuyi Date: Sat, 24 May 2025 22:17:15 +0800 Subject: [PATCH] add constraint --- .../core/constraints/data.exist.constraint.ts | 69 +++++++++++++++++++ .../core/constraints/unique.constraint.ts | 68 ++++++++++++++++++ 2 files changed, 137 insertions(+) create mode 100644 src/modules/core/constraints/data.exist.constraint.ts create mode 100644 src/modules/core/constraints/unique.constraint.ts diff --git a/src/modules/core/constraints/data.exist.constraint.ts b/src/modules/core/constraints/data.exist.constraint.ts new file mode 100644 index 0000000..e56c52a --- /dev/null +++ b/src/modules/core/constraints/data.exist.constraint.ts @@ -0,0 +1,69 @@ +import { Injectable } from '@nestjs/common'; +import { + ValidationArguments, + ValidatorConstraint, + ValidatorConstraintInterface, + ValidationOptions, + registerDecorator, +} from 'class-validator'; +import { ObjectType, Repository, DataSource } from 'typeorm'; + +type Condition = { + entity: ObjectType; + map?: string; +}; + +@ValidatorConstraint({ name: 'dataExist', async: true }) +@Injectable() +export class DataExistConstraint implements ValidatorConstraintInterface { + constructor(private dataSource: DataSource) {} + + async validate(value: any, validationArguments?: ValidationArguments) { + let repo: Repository; + if (!value) { + return true; + } + let map = 'id'; + if ('entity' in validationArguments.constraints[0]) { + map = validationArguments.constraints[0].map ?? 'id'; + repo = this.dataSource.getRepository(validationArguments.constraints[0].entitiy); + } else { + repo = this.dataSource.getRepository(validationArguments.constraints[0]); + } + const item = await repo.findOne({ where: { [map]: value } }); + return !!item; + } + defaultMessage?(validationArguments?: ValidationArguments): string { + if (!validationArguments.constraints[0]) { + return 'Model not been specified!'; + } + return `All instance of ${validationArguments.constraints[0].name} must been exists in databse!`; + } +} + +function IsDataExist( + entity: ObjectType, + validationOptions?: ValidationOptions, +): (object: RecordAny, propertyName: string) => void; + +function IsDataExist( + condition: Condition, + validationOptions?: ValidationOptions, +): (object: RecordAny, propertyName: string) => void; + +function IsDataExist( + condition: Condition | ObjectType, + validationOptions?: ValidationOptions, +): (object: RecordAny, propertyName: string) => void { + return (object: RecordAny, propertyName: string) => { + registerDecorator({ + target: object.constructor, + propertyName, + options: validationOptions, + constraints: [condition], + validator: DataExistConstraint, + }); + }; +} + +export { IsDataExist }; diff --git a/src/modules/core/constraints/unique.constraint.ts b/src/modules/core/constraints/unique.constraint.ts new file mode 100644 index 0000000..e61d788 --- /dev/null +++ b/src/modules/core/constraints/unique.constraint.ts @@ -0,0 +1,68 @@ +import { Injectable } from '@nestjs/common'; +import { + registerDecorator, + ValidationArguments, + ValidatorConstraint, + ValidatorConstraintInterface, + ValidationOptions, +} from 'class-validator'; +import { isNil, merge } from 'lodash'; +import { DataSource, ObjectType } from 'typeorm'; + +type Condition = { + entity: ObjectType; + property?: string; +}; + +@Injectable() +@ValidatorConstraint({ name: 'dataUnique', async: true }) +export class UniqueConstraint implements ValidatorConstraintInterface { + constructor(private dataSource: DataSource) {} + + async validate(value: any, validationArguments?: ValidationArguments): Promise { + const config: Omit = { property: validationArguments.property }; + const condition = ('entity' in validationArguments.constraints[0] + ? merge(config, validationArguments.constraints[0]) + : { + ...config, + entity: validationArguments.constraints[0], + }) as unknown as Required; + if (!condition.entity) { + return false; + } + try { + const repo = this.dataSource.getRepository(condition.entity); + return isNil( + await repo.findOne({ where: { [condition.property]: value }, withDeleted: true }), + ); + } catch (err) { + return false; + } + } + defaultMessage?(validationArguments?: ValidationArguments): string { + const { entity, property } = validationArguments.constraints[0]; + const queryProperty = property ?? validationArguments.property; + if (!(validationArguments.object as any).getManager) { + return 'getManager function not been found!'; + } + if (!entity) { + return 'Model not been specified!'; + } + return `${queryProperty} of ${entity.name} must been unique!`; + } +} + +export function IsUnique( + params: ObjectType | Condition, + validationOptions: ValidationOptions, +) { + return (object: RecordAny, propertyName: string) => { + registerDecorator({ + target: object.constructor, + propertyName, + options: validationOptions, + constraints: [params], + validator: UniqueConstraint, + }); + }; +}