add constraint

This commit is contained in:
2025-05-29 11:07:25 +08:00
parent 05160509ec
commit a75a27e627
11 changed files with 60 additions and 31 deletions
@@ -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<any>;
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<any>;
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<any>,
validationOptions?: ValidationOptions,
): (object: RecordAny, propertyName: string) => void;
function IsDataExist(
condition: Condition,
validationOptions?: ValidationOptions,
): (object: RecordAny, propertyName: string) => void;
function IsDataExist(
condition: Condition | ObjectType<any>,
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 };
@@ -0,0 +1,5 @@
export * from './data.exist.constraint';
export * from './tree.unique.constraint';
export * from './tree.unique.exist.constraint';
export * from './unique.constraint';
export * from './unique.exist.constraint';
@@ -0,0 +1,87 @@
import { Injectable } from '@nestjs/common';
import {
ValidatorConstraint,
ValidatorConstraintInterface,
ValidationArguments,
registerDecorator,
ValidationOptions,
} from 'class-validator';
import { merge, isNil } from 'lodash';
import { DataSource, ObjectType } from 'typeorm';
type Condition = {
entity: ObjectType<any>;
parentKey?: string;
property?: string;
};
@ValidatorConstraint({ name: 'treeDataUnique', async: true })
@Injectable()
export class TreeUniqueConstraint implements ValidatorConstraintInterface {
constructor(private dataSource: DataSource) {}
async validate(value: any, args: ValidationArguments) {
// 获取要验证的模型和字段
const config: Omit<Condition, 'entity'> = {
parentKey: 'parent',
property: args.property,
};
const condition = ('entity' in args.constraints[0]
? merge(config, args.constraints[0])
: {
...config,
entity: args.constraints[0],
}) as unknown as Required<Condition>;
if (!condition.entity) {
return false;
}
if (isNil(value)) {
return true;
}
const argsObj = args.object as any;
try {
// 查询是否存在数据,如果已经存在则验证失败
const repo = this.dataSource.getTreeRepository(condition.entity);
const collections = await repo.find({
where: {
parent: !argsObj[condition.parentKey]
? null
: { id: argsObj[condition.parentKey] },
},
});
return collections.every((item) => item[condition.property] !== value);
} catch (err) {
// 如果数据库操作异常则验证失败
return false;
}
}
defaultMessage(args: ValidationArguments) {
const { entity, property } = args.constraints[0];
const queryProperty = property ?? args.property;
if (!(args.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 IsTreeUnique(
params: ObjectType<any> | Condition,
validationOptions?: ValidationOptions,
) {
return (object: Record<string, any>, propertyName: string) => {
registerDecorator({
target: object.constructor,
propertyName,
options: validationOptions,
constraints: [params],
validator: TreeUniqueConstraint,
});
};
}
@@ -0,0 +1,99 @@
import { Injectable } from '@nestjs/common';
import {
registerDecorator,
ValidationArguments,
ValidationOptions,
ValidatorConstraint,
ValidatorConstraintInterface,
} from 'class-validator';
import { merge } from 'lodash';
import { DataSource, ObjectType } from 'typeorm';
type Condition = {
entity: ObjectType<any>;
ignore?: string;
ignoreKey?: string;
property?: string;
};
@ValidatorConstraint({ name: 'treeDataUniqueExist', async: true })
@Injectable()
export class TreeUniqueExistContraint implements ValidatorConstraintInterface {
constructor(private dataSource: DataSource) {}
async validate(value: any, args: ValidationArguments) {
const config: Omit<Condition, 'entity'> = {
ignore: 'id',
property: args.property,
};
const condition = ('entity' in args.constraints[0]
? merge(config, args.constraints[0])
: {
...config,
entity: args.constraints[0],
}) as unknown as Required<Condition>;
if (!condition.entity) {
return false;
}
if (!condition.ignoreKey) {
condition.ignoreKey = condition.ignore;
}
const argsObj = args.object as any;
// 在传入的dto数据中获取需要忽略的字段的值
const ignoreValue = argsObj[condition.ignore];
const findValue = argsObj[condition.ignoreKey];
if (!ignoreValue || !findValue) {
return false;
}
// 通过entity获取repository
const repo = this.dataSource.getRepository(condition.entity);
// 查询忽略字段之外的数据是否对queryProperty的值唯一
const item = await repo.findOne({
where: {
[condition.ignoreKey]: findValue,
},
relations: ['parent'],
});
if (!item) {
return false;
}
const rows = await repo.find({
where: { parent: item.parent ? { id: item.parent.id } : null },
withDeleted: true,
});
return !rows.find(
(row) => row[condition.property] === value && row[condition.ignore] !== ignoreValue,
);
}
defaultMessage(args: ValidationArguments) {
const { entity, property } = args.constraints[0];
const queryProperty = property ?? args.property;
if (!(args.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 IsTreeUniqueExist(
params: ObjectType<any> | Condition,
validationOptions?: ValidationOptions,
) {
return (object: Record<string, any>, propertyName: string) => {
registerDecorator({
target: object.constructor,
propertyName,
options: validationOptions,
constraints: [params],
validator: TreeUniqueExistContraint,
});
};
}
@@ -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<any>;
property?: string;
};
@Injectable()
@ValidatorConstraint({ name: 'dataUnique', async: true })
export class UniqueConstraint implements ValidatorConstraintInterface {
constructor(private dataSource: DataSource) {}
async validate(value: any, validationArguments?: ValidationArguments): Promise<boolean> {
const config: Omit<Condition, 'entity'> = { property: validationArguments.property };
const condition = ('entity' in validationArguments.constraints[0]
? merge(config, validationArguments.constraints[0])
: {
...config,
entity: validationArguments.constraints[0],
}) as unknown as Required<Condition>;
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<any> | Condition,
validationOptions: ValidationOptions,
) {
return (object: RecordAny, propertyName: string) => {
registerDecorator({
target: object.constructor,
propertyName,
options: validationOptions,
constraints: [params],
validator: UniqueConstraint,
});
};
}
@@ -0,0 +1,74 @@
import { Injectable } from '@nestjs/common';
import {
registerDecorator,
ValidationArguments,
ValidationOptions,
ValidatorConstraint,
ValidatorConstraintInterface,
} from 'class-validator';
import { isNil, merge } from 'lodash';
import { DataSource, Not, ObjectType } from 'typeorm';
type Condition = {
entity: ObjectType<any>;
ignore?: string;
ignoreKey?: string;
property?: string;
};
@Injectable()
@ValidatorConstraint({ name: 'dataUniqueExist', async: true })
export class UniqueExistConstraint implements ValidatorConstraintInterface {
constructor(protected dataSource: DataSource) {}
async validate(value: any, args?: ValidationArguments): Promise<boolean> {
const config: Omit<Condition, 'entity'> = {
ignore: 'id',
property: args.property,
};
const condition = ('entity' in args.constraints[0]
? merge(config, args.constraints[0])
: { ...config, entity: args.constraints[0] }) as unknown as Required<Condition>;
if (!condition.entity) {
return false;
}
const ignoreValue = (args.object as any)[
isNil(condition.ignoreKey) ? condition.ignore : condition.ignoreKey
];
if (ignoreValue === undefined) {
return false;
}
const repo = this.dataSource.getRepository(condition.entity);
return isNil(
await repo.findOne({
where: { [condition.property]: value, [condition.ignore]: Not(ignoreValue) },
withDeleted: true,
}),
);
}
defaultMessage?(args?: ValidationArguments): string {
const { entity, property } = args.constraints[0];
const queryProperty = property ?? args.property;
if (!(args.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 IsUniqueExist(params: ObjectType<any> | Condition, options?: ValidationOptions) {
return (object: RecordAny, propertyName: string) => {
registerDecorator({
target: object.constructor,
propertyName,
options,
constraints: [params],
validator: UniqueExistConstraint,
});
};
}
+7 -5
View File
@@ -5,11 +5,13 @@ import { DataSource, ObjectType } from 'typeorm';
import { CUSTOM_REPOSITORY_METADATA } from '@/modules/database/constants';
import { DataExistConstraint } from '../core/constraints/data.exist.constraint';
import { TreeUniqueConstraint } from '../core/constraints/tree.unique.constraint';
import { TreeUniqueExistContraint } from '../core/constraints/tree.unique.exist.constraint';
import { UniqueConstraint } from '../core/constraints/unique.constraint';
import { UniqueExistConstraint } from '../core/constraints/unique.exist.constraint';
import {
DataExistConstraint,
TreeUniqueConstraint,
TreeUniqueExistContraint,
UniqueConstraint,
UniqueExistConstraint,
} from './constraints';
@Module({})
export class DatabaseModule {