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