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 { 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() {
|
||||||
@ -10,6 +12,7 @@ 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,25 +13,41 @@ 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, { message: 'The current page must be greater than 1.' })
|
@Min(1, { always: true, message: 'The current page must be greater than 1.' })
|
||||||
@IsNumber()
|
@IsInt()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
page = 1;
|
page = 1;
|
||||||
|
|
||||||
@Transform(({ value }) => toNumber(value))
|
@Transform(({ value }) => toNumber(value))
|
||||||
@Min(1, { message: 'The number of data displayed per page must be greater than 1.' })
|
@Min(1, {
|
||||||
@IsNumber()
|
always: true,
|
||||||
|
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',
|
||||||
@ -40,6 +56,7 @@ 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.',
|
||||||
@ -51,13 +68,17 @@ 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.' })
|
||||||
@IsNumber(undefined, { always: true })
|
@IsInt({ 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,22 +13,29 @@ 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, { message: 'The current page must be greater than 1.' })
|
@Min(1, { always: true, message: 'The current page must be greater than 1.' })
|
||||||
@IsNumber()
|
@IsInt()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
page = 1;
|
page = 1;
|
||||||
|
|
||||||
@Transform(({ value }) => toNumber(value))
|
@Transform(({ value }) => toNumber(value))
|
||||||
@Min(1, { message: 'The number of data displayed per page must be greater than 1.' })
|
@Min(1, {
|
||||||
@IsNumber()
|
always: true,
|
||||||
|
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;
|
||||||
@ -43,10 +50,12 @@ 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,8 +19,11 @@ 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))
|
||||||
@ -35,17 +38,21 @@ export class QueryPostDto implements PaginateOptions {
|
|||||||
orderBy: PostOrder;
|
orderBy: PostOrder;
|
||||||
|
|
||||||
@Transform(({ value }) => toNumber(value))
|
@Transform(({ value }) => toNumber(value))
|
||||||
@Min(1, { message: 'The current page must be greater than 1.' })
|
@Min(1, { always: true, message: 'The current page must be greater than 1.' })
|
||||||
@IsNumber()
|
@IsInt()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
page = 1;
|
page = 1;
|
||||||
|
|
||||||
@Transform(({ value }) => toNumber(value))
|
@Transform(({ value }) => toNumber(value))
|
||||||
@Min(1, { message: 'The number of data displayed per page must be greater than 1.' })
|
@Min(1, {
|
||||||
@IsNumber()
|
always: true,
|
||||||
|
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;
|
||||||
@ -91,11 +98,12 @@ 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.' })
|
@Min(0, { message: 'The sorted value must be greater than 0.', always: true })
|
||||||
@IsNumber(undefined, { always: true })
|
@IsInt({ 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',
|
||||||
@ -103,6 +111,11 @@ 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,
|
||||||
@ -119,5 +132,6 @@ 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,36 +1,38 @@
|
|||||||
import { PartialType } from '@nestjs/swagger';
|
import { PartialType } from '@nestjs/swagger';
|
||||||
import { Transform } from 'class-transformer';
|
import { Transform } from 'class-transformer';
|
||||||
import {
|
import { IsDefined, IsInt, IsNotEmpty, IsOptional, IsUUID, MaxLength, Min } from 'class-validator';
|
||||||
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, { message: 'The current page must be greater than 1.' })
|
@Min(1, { always: true, message: 'The current page must be greater than 1.' })
|
||||||
@IsNumber()
|
@IsInt()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
page = 1;
|
page = 1;
|
||||||
|
|
||||||
@Transform(({ value }) => toNumber(value))
|
@Transform(({ value }) => toNumber(value))
|
||||||
@Min(1, { message: 'The number of data displayed per page must be greater than 1.' })
|
@Min(1, {
|
||||||
@IsNumber()
|
always: true,
|
||||||
|
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',
|
||||||
@ -49,6 +51,7 @@ 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: '分类名称', unique: true })
|
@Column({ comment: '分类名称' })
|
||||||
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'] })
|
@Expose({ groups: ['category-tree', 'category-detail'] })
|
||||||
@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'] });
|
return this.repository.findOneOrFail({ where: { id }, relations: ['parent', 'children'] });
|
||||||
}
|
}
|
||||||
|
|
||||||
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 { isObject, omit } from 'lodash';
|
import { isNil, isObject, isString, omit } from 'lodash';
|
||||||
|
|
||||||
import { DTO_VALIDATION_OPTIONS } from '../contants';
|
import { DTO_VALIDATION_OPTIONS } from '../contants';
|
||||||
import { deepMerge } from '../helpers';
|
import { deepMerge } from '../helpers';
|
||||||
@ -31,12 +31,13 @@ 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') {
|
||||||
|
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 { 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 {
|
||||||
@ -12,6 +20,13 @@ 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