Compare commits
11 Commits
4350232069
...
50dbb06b29
Author | SHA1 | Date | |
---|---|---|---|
50dbb06b29 | |||
3fe801d448 | |||
88447f0db6 | |||
e26cb841fc | |||
c283df576b | |||
a75a27e627 | |||
05160509ec | |||
43d34250e2 | |||
e0d2f7652c | |||
a08964bd4a | |||
e71f08a3de |
@ -2,6 +2,8 @@ import { NestFactory } from '@nestjs/core';
|
||||
|
||||
import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify';
|
||||
|
||||
import { useContainer } from 'class-validator';
|
||||
|
||||
import { AppModule } from './app.module';
|
||||
|
||||
async function bootstrap() {
|
||||
@ -10,6 +12,7 @@ async function bootstrap() {
|
||||
logger: ['error', 'warn'],
|
||||
});
|
||||
app.setGlobalPrefix('api');
|
||||
useContainer(app.select(AppModule), { fallbackOnErrors: true });
|
||||
await app.listen(process.env.PORT ?? 3000, () => {
|
||||
console.log('api: http://localhost:3000');
|
||||
});
|
||||
|
@ -2,8 +2,8 @@ import { PartialType } from '@nestjs/swagger';
|
||||
import { Transform } from 'class-transformer';
|
||||
import {
|
||||
IsDefined,
|
||||
IsInt,
|
||||
IsNotEmpty,
|
||||
IsNumber,
|
||||
IsOptional,
|
||||
IsUUID,
|
||||
MaxLength,
|
||||
@ -13,25 +13,41 @@ import {
|
||||
import { toNumber } from 'lodash';
|
||||
|
||||
import { DtoValidation } from '@/modules/core/decorator/dto.validation.decorator';
|
||||
import { IsDataExist } from '@/modules/database/constraints/data.exist.constraint';
|
||||
import { IsTreeUnique } from '@/modules/database/constraints/tree.unique.constraint';
|
||||
import { IsTreeUniqueExist } from '@/modules/database/constraints/tree.unique.exist.constraint';
|
||||
import { PaginateOptions } from '@/modules/database/types';
|
||||
|
||||
import { CategoryEntity } from '../entities';
|
||||
|
||||
@DtoValidation({ type: 'query' })
|
||||
export class QueryCategoryDto implements PaginateOptions {
|
||||
@Transform(({ value }) => toNumber(value))
|
||||
@Min(1, { message: 'The current page must be greater than 1.' })
|
||||
@IsNumber()
|
||||
@Min(1, { always: true, message: 'The current page must be greater than 1.' })
|
||||
@IsInt()
|
||||
@IsOptional()
|
||||
page = 1;
|
||||
|
||||
@Transform(({ value }) => toNumber(value))
|
||||
@Min(1, { message: 'The number of data displayed per page must be greater than 1.' })
|
||||
@IsNumber()
|
||||
@Min(1, {
|
||||
always: true,
|
||||
message: 'The number of data displayed per page must be greater than 1.',
|
||||
})
|
||||
@IsInt()
|
||||
@IsOptional()
|
||||
limit = 10;
|
||||
}
|
||||
|
||||
@DtoValidation({ groups: ['create'] })
|
||||
export class CreateCategoryDto {
|
||||
@IsTreeUnique(CategoryEntity, {
|
||||
groups: ['create'],
|
||||
message: 'The Category names are duplicated',
|
||||
})
|
||||
@IsTreeUniqueExist(CategoryEntity, {
|
||||
groups: ['update'],
|
||||
message: 'The Category names are duplicated',
|
||||
})
|
||||
@MaxLength(25, {
|
||||
always: true,
|
||||
message: 'The length of the category name shall not exceed $constraint1',
|
||||
@ -40,6 +56,7 @@ export class CreateCategoryDto {
|
||||
@IsOptional({ groups: ['update'] })
|
||||
name: string;
|
||||
|
||||
@IsDataExist(CategoryEntity, { always: true, message: 'The parent category does not exist' })
|
||||
@IsUUID(undefined, {
|
||||
always: true,
|
||||
message: 'The format of the parent category ID is incorrect.',
|
||||
@ -51,13 +68,17 @@ export class CreateCategoryDto {
|
||||
|
||||
@Transform(({ value }) => toNumber(value))
|
||||
@Min(0, { always: true, message: 'The sorted value must be greater than 0.' })
|
||||
@IsNumber(undefined, { always: true })
|
||||
@IsInt({ always: true })
|
||||
@IsOptional({ always: true })
|
||||
customOrder?: number = 0;
|
||||
}
|
||||
|
||||
@DtoValidation({ groups: ['update'] })
|
||||
export class UpdateCategoryDto extends PartialType(CreateCategoryDto) {
|
||||
@IsDataExist(CategoryEntity, {
|
||||
groups: ['update'],
|
||||
message: 'category id not exist when update',
|
||||
})
|
||||
@IsUUID(undefined, { message: 'The ID format is incorrect', groups: ['update'] })
|
||||
@IsDefined({ groups: ['update'], message: 'The ID must be specified' })
|
||||
id: string;
|
||||
|
@ -2,8 +2,8 @@ import { PickType } from '@nestjs/swagger';
|
||||
import { Transform } from 'class-transformer';
|
||||
import {
|
||||
IsDefined,
|
||||
IsInt,
|
||||
IsNotEmpty,
|
||||
IsNumber,
|
||||
IsOptional,
|
||||
IsUUID,
|
||||
MaxLength,
|
||||
@ -13,22 +13,29 @@ import {
|
||||
import { toNumber } from 'lodash';
|
||||
|
||||
import { DtoValidation } from '@/modules/core/decorator/dto.validation.decorator';
|
||||
import { IsDataExist } from '@/modules/database/constraints/data.exist.constraint';
|
||||
import { PaginateOptions } from '@/modules/database/types';
|
||||
|
||||
import { CommentEntity, PostEntity } from '../entities';
|
||||
|
||||
@DtoValidation({ type: 'query' })
|
||||
export class QueryCommentDto implements PaginateOptions {
|
||||
@Transform(({ value }) => toNumber(value))
|
||||
@Min(1, { message: 'The current page must be greater than 1.' })
|
||||
@IsNumber()
|
||||
@Min(1, { always: true, message: 'The current page must be greater than 1.' })
|
||||
@IsInt()
|
||||
@IsOptional()
|
||||
page = 1;
|
||||
|
||||
@Transform(({ value }) => toNumber(value))
|
||||
@Min(1, { message: 'The number of data displayed per page must be greater than 1.' })
|
||||
@IsNumber()
|
||||
@Min(1, {
|
||||
always: true,
|
||||
message: 'The number of data displayed per page must be greater than 1.',
|
||||
})
|
||||
@IsInt()
|
||||
@IsOptional()
|
||||
limit = 10;
|
||||
|
||||
@IsDataExist(PostEntity, { message: 'The post does not exist' })
|
||||
@IsUUID(undefined, { message: 'The ID format is incorrect' })
|
||||
@IsOptional()
|
||||
post?: string;
|
||||
@ -43,10 +50,12 @@ export class CreateCommentDto {
|
||||
@IsNotEmpty({ message: '' })
|
||||
body: string;
|
||||
|
||||
@IsDataExist(PostEntity, { message: 'The post does not exist' })
|
||||
@IsUUID(undefined, { message: 'The ID format is incorrect' })
|
||||
@IsDefined({ message: 'The ID must be specified' })
|
||||
post: string;
|
||||
|
||||
@IsDataExist(CommentEntity, { message: 'The parent comment does not exist' })
|
||||
@IsUUID(undefined, { message: 'The ID format is incorrect', always: true })
|
||||
@ValidateIf((value) => value.parent !== null && value.parent)
|
||||
@IsOptional({ always: true })
|
||||
|
@ -5,8 +5,8 @@ import {
|
||||
IsBoolean,
|
||||
IsDefined,
|
||||
IsEnum,
|
||||
IsInt,
|
||||
IsNotEmpty,
|
||||
IsNumber,
|
||||
IsOptional,
|
||||
IsUUID,
|
||||
MaxLength,
|
||||
@ -19,8 +19,11 @@ import { isNil, toNumber } from 'lodash';
|
||||
import { PostOrder } from '@/modules/content/constants';
|
||||
import { DtoValidation } from '@/modules/core/decorator/dto.validation.decorator';
|
||||
import { toBoolean } from '@/modules/core/helpers';
|
||||
import { IsDataExist } from '@/modules/database/constraints/data.exist.constraint';
|
||||
import { PaginateOptions } from '@/modules/database/types';
|
||||
|
||||
import { CategoryEntity, PostEntity, TagEntity } from '../entities';
|
||||
|
||||
@DtoValidation({ type: 'query' })
|
||||
export class QueryPostDto implements PaginateOptions {
|
||||
@Transform(({ value }) => toBoolean(value))
|
||||
@ -35,17 +38,21 @@ export class QueryPostDto implements PaginateOptions {
|
||||
orderBy: PostOrder;
|
||||
|
||||
@Transform(({ value }) => toNumber(value))
|
||||
@Min(1, { message: 'The current page must be greater than 1.' })
|
||||
@IsNumber()
|
||||
@Min(1, { always: true, message: 'The current page must be greater than 1.' })
|
||||
@IsInt()
|
||||
@IsOptional()
|
||||
page = 1;
|
||||
|
||||
@Transform(({ value }) => toNumber(value))
|
||||
@Min(1, { message: 'The number of data displayed per page must be greater than 1.' })
|
||||
@IsNumber()
|
||||
@Min(1, {
|
||||
always: true,
|
||||
message: 'The number of data displayed per page must be greater than 1.',
|
||||
})
|
||||
@IsInt()
|
||||
@IsOptional()
|
||||
limit = 10;
|
||||
|
||||
@IsDataExist(CategoryEntity, { always: true, message: 'The category does not exist' })
|
||||
@IsUUID(undefined, { message: 'The ID format is incorrect' })
|
||||
@IsOptional()
|
||||
category?: string;
|
||||
@ -91,11 +98,12 @@ export class CreatePostDto {
|
||||
keywords?: string[];
|
||||
|
||||
@Transform(({ value }) => toNumber(value))
|
||||
@Min(0, { message: 'The sorted value must be greater than 0.' })
|
||||
@IsNumber(undefined, { always: true })
|
||||
@Min(0, { message: 'The sorted value must be greater than 0.', always: true })
|
||||
@IsInt({ always: true })
|
||||
@IsOptional({ always: true })
|
||||
customOrder?: number;
|
||||
|
||||
@IsDataExist(CategoryEntity, { always: true, message: 'The category does not exist' })
|
||||
@IsUUID(undefined, {
|
||||
always: true,
|
||||
message: 'The ID format is incorrect',
|
||||
@ -103,6 +111,11 @@ export class CreatePostDto {
|
||||
@IsOptional({ always: true })
|
||||
category?: string;
|
||||
|
||||
@IsDataExist(TagEntity, {
|
||||
always: true,
|
||||
each: true,
|
||||
message: 'The tag does not exist',
|
||||
})
|
||||
@IsUUID(undefined, {
|
||||
always: true,
|
||||
each: true,
|
||||
@ -119,5 +132,6 @@ export class UpdatePostDto extends PartialType(CreatePostDto) {
|
||||
message: 'The format of the article ID is incorrect.',
|
||||
})
|
||||
@IsDefined({ groups: ['update'], message: 'The article ID must be specified' })
|
||||
@IsDataExist(PostEntity, { groups: ['update'], message: 'post id not exist when update' })
|
||||
id: string;
|
||||
}
|
||||
|
@ -1,36 +1,38 @@
|
||||
import { PartialType } from '@nestjs/swagger';
|
||||
import { Transform } from 'class-transformer';
|
||||
import {
|
||||
IsDefined,
|
||||
IsNotEmpty,
|
||||
IsNumber,
|
||||
IsOptional,
|
||||
IsUUID,
|
||||
MaxLength,
|
||||
Min,
|
||||
} from 'class-validator';
|
||||
import { IsDefined, IsInt, IsNotEmpty, IsOptional, IsUUID, MaxLength, Min } from 'class-validator';
|
||||
import { toNumber } from 'lodash';
|
||||
|
||||
import { DtoValidation } from '@/modules/core/decorator/dto.validation.decorator';
|
||||
import { IsDataExist } from '@/modules/database/constraints';
|
||||
import { IsUnique } from '@/modules/database/constraints/unique.constraint';
|
||||
import { IsUniqueExist } from '@/modules/database/constraints/unique.exist.constraint';
|
||||
import { PaginateOptions } from '@/modules/database/types';
|
||||
|
||||
import { TagEntity } from '../entities';
|
||||
|
||||
@DtoValidation({ type: 'query' })
|
||||
export class QueryTagDto implements PaginateOptions {
|
||||
@Transform(({ value }) => toNumber(value))
|
||||
@Min(1, { message: 'The current page must be greater than 1.' })
|
||||
@IsNumber()
|
||||
@Min(1, { always: true, message: 'The current page must be greater than 1.' })
|
||||
@IsInt()
|
||||
@IsOptional()
|
||||
page = 1;
|
||||
|
||||
@Transform(({ value }) => toNumber(value))
|
||||
@Min(1, { message: 'The number of data displayed per page must be greater than 1.' })
|
||||
@IsNumber()
|
||||
@Min(1, {
|
||||
always: true,
|
||||
message: 'The number of data displayed per page must be greater than 1.',
|
||||
})
|
||||
@IsInt()
|
||||
@IsOptional()
|
||||
limit = 10;
|
||||
}
|
||||
|
||||
@DtoValidation({ groups: ['create'] })
|
||||
export class CreateTagDto {
|
||||
@IsUnique(TagEntity, { groups: ['create'], message: 'The label names are repeated' })
|
||||
@IsUniqueExist(TagEntity, { groups: ['update'], message: 'The label names are repeated' })
|
||||
@MaxLength(255, {
|
||||
always: true,
|
||||
message: 'The maximum length of the label name is $constraint1',
|
||||
@ -49,6 +51,7 @@ export class CreateTagDto {
|
||||
|
||||
@DtoValidation({ groups: ['update'] })
|
||||
export class UpdateTagDto extends PartialType(CreateTagDto) {
|
||||
@IsDataExist(TagEntity, { groups: ['update'], message: 'tag id not exist when update' })
|
||||
@IsUUID(undefined, { message: 'The ID format is incorrect', groups: ['update'] })
|
||||
@IsDefined({ groups: ['update'], message: 'The ID must be specified' })
|
||||
id: string;
|
||||
|
@ -22,7 +22,7 @@ export class CategoryEntity extends BaseEntity {
|
||||
id: string;
|
||||
|
||||
@Expose()
|
||||
@Column({ comment: '分类名称', unique: true })
|
||||
@Column({ comment: '分类名称' })
|
||||
name: string;
|
||||
|
||||
@Expose({ groups: ['category-tree', 'category-list', 'category-detail'] })
|
||||
@ -40,7 +40,7 @@ export class CategoryEntity extends BaseEntity {
|
||||
parent: Relation<CategoryEntity> | null;
|
||||
|
||||
@Type(() => CategoryEntity)
|
||||
@Expose({ groups: ['category-tree'] })
|
||||
@Expose({ groups: ['category-tree', 'category-detail'] })
|
||||
@TreeChildren({ cascade: true })
|
||||
children: Relation<CategoryEntity>[];
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ export class CategoryService {
|
||||
}
|
||||
|
||||
async detail(id: string) {
|
||||
return this.repository.findOneOrFail({ where: { id }, relations: ['parent'] });
|
||||
return this.repository.findOneOrFail({ where: { id }, relations: ['parent', 'children'] });
|
||||
}
|
||||
|
||||
async create(data: CreateCategoryDto) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { ArgumentMetadata, BadRequestException, Paramtype, ValidationPipe } from '@nestjs/common';
|
||||
|
||||
import { isObject, omit } from 'lodash';
|
||||
import { isNil, isObject, isString, omit } from 'lodash';
|
||||
|
||||
import { DTO_VALIDATION_OPTIONS } from '../contants';
|
||||
import { deepMerge } from '../helpers';
|
||||
@ -31,12 +31,13 @@ export class AppPipe extends ValidationPipe {
|
||||
if (isObject(val) && 'mimetype' in val) {
|
||||
return [key, omit(val, ['fields'])];
|
||||
}
|
||||
if (key === 'name' && isString(val)) {
|
||||
return [key, isNil(val) ? val : val.trim()];
|
||||
}
|
||||
return [key, val];
|
||||
}),
|
||||
)
|
||||
: value;
|
||||
console.log(value);
|
||||
console.log(toValidate);
|
||||
try {
|
||||
let result = await super.transform(toValidate, metadata);
|
||||
if (typeof result.transform === 'function') {
|
||||
|
5
src/modules/database/constraints/index.ts
Normal file
5
src/modules/database/constraints/index.ts
Normal file
@ -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';
|
87
src/modules/database/constraints/tree.unique.constraint.ts
Normal file
87
src/modules/database/constraints/tree.unique.constraint.ts
Normal file
@ -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,
|
||||
});
|
||||
};
|
||||
}
|
74
src/modules/database/constraints/unique.exist.constraint.ts
Normal file
74
src/modules/database/constraints/unique.exist.constraint.ts
Normal file
@ -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,
|
||||
});
|
||||
};
|
||||
}
|
@ -5,6 +5,14 @@ import { DataSource, ObjectType } from 'typeorm';
|
||||
|
||||
import { CUSTOM_REPOSITORY_METADATA } from '@/modules/database/constants';
|
||||
|
||||
import {
|
||||
DataExistConstraint,
|
||||
TreeUniqueConstraint,
|
||||
TreeUniqueExistContraint,
|
||||
UniqueConstraint,
|
||||
UniqueExistConstraint,
|
||||
} from './constraints';
|
||||
|
||||
@Module({})
|
||||
export class DatabaseModule {
|
||||
static forRoot(configRegister: () => TypeOrmModuleOptions): DynamicModule {
|
||||
@ -12,6 +20,13 @@ export class DatabaseModule {
|
||||
global: true,
|
||||
module: DatabaseModule,
|
||||
imports: [TypeOrmModule.forRoot(configRegister())],
|
||||
providers: [
|
||||
DataExistConstraint,
|
||||
UniqueConstraint,
|
||||
UniqueExistConstraint,
|
||||
TreeUniqueConstraint,
|
||||
TreeUniqueExistContraint,
|
||||
],
|
||||
};
|
||||
}
|
||||
static forRepository<T extends Type<any>>(
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user