Compare commits
No commits in common. "77e27a4b9349c8b676a540b85ae6525021dd09f5" and "03e70436c6a0e68d71dc52194caae9b329c79c39" have entirely different histories.
77e27a4b93
...
03e70436c6
@ -3,20 +3,15 @@
|
|||||||
"collection": "@nestjs/schematics",
|
"collection": "@nestjs/schematics",
|
||||||
"sourceRoot": "src",
|
"sourceRoot": "src",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"assets": [
|
|
||||||
"assets/**/*"
|
|
||||||
],
|
|
||||||
"deleteOutDir": true,
|
"deleteOutDir": true,
|
||||||
"builder": "swc",
|
"builder": "swc",
|
||||||
"typeCheck": true,
|
"typeCheck": true,
|
||||||
"plugins": [
|
"plugins": [{
|
||||||
{
|
|
||||||
"name": "@nestjs/swagger",
|
"name": "@nestjs/swagger",
|
||||||
"options": {
|
"options":{
|
||||||
"introspectComments": true,
|
"introspectComments": true,
|
||||||
"controllerKeyOfComment": "summary"
|
"controllerKeyOfComment": "summary"
|
||||||
}
|
}
|
||||||
}
|
}]
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
20
package.json
20
package.json
@ -6,8 +6,6 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"license": "UNLICENSED",
|
"license": "UNLICENSED",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"cli": "bun --bun src/console/bin.ts",
|
|
||||||
"dev": "cross-env NODE_ENV=development pnpm cli start -w",
|
|
||||||
"prebuild": "rimraf dist",
|
"prebuild": "rimraf dist",
|
||||||
"build": "cross-env NODE_ENV=production nest build",
|
"build": "cross-env NODE_ENV=production nest build",
|
||||||
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
||||||
@ -30,7 +28,6 @@
|
|||||||
"@nestjs/swagger": "^11.2.0",
|
"@nestjs/swagger": "^11.2.0",
|
||||||
"@nestjs/typeorm": "^11.0.0",
|
"@nestjs/typeorm": "^11.0.0",
|
||||||
"chalk": "^5.4.1",
|
"chalk": "^5.4.1",
|
||||||
"chokidar": "^4.0.3",
|
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"class-validator": "^0.14.2",
|
"class-validator": "^0.14.2",
|
||||||
"deepmerge": "^4.3.1",
|
"deepmerge": "^4.3.1",
|
||||||
@ -40,18 +37,21 @@
|
|||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"meilisearch": "^0.51.0",
|
"meilisearch": "^0.51.0",
|
||||||
"mysql2": "^3.14.1",
|
"mysql2": "^3.14.1",
|
||||||
"ora": "^8.2.0",
|
|
||||||
"pm2": "^6.0.8",
|
|
||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
"rimraf": "^6.0.1",
|
"rimraf": "^6.0.1",
|
||||||
"rxjs": "^7.8.2",
|
"rxjs": "^7.8.2",
|
||||||
"sanitize-html": "^2.17.0",
|
"sanitize-html": "^2.17.0",
|
||||||
"typeorm": "^0.3.24",
|
"typeorm": "^0.3.24",
|
||||||
"validator": "^13.15.15",
|
"validator": "^13.15.15",
|
||||||
"yaml": "^2.8.0",
|
"yaml": "^2.8.0"
|
||||||
"yargs": "^18.0.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@babel/core": "^7.27.4",
|
||||||
|
"@babel/plugin-proposal-decorators": "^7.27.1",
|
||||||
|
"@babel/plugin-transform-runtime": "^7.27.4",
|
||||||
|
"@babel/preset-env": "^7.27.2",
|
||||||
|
"@babel/preset-typescript": "^7.27.1",
|
||||||
|
"@babel/runtime": "^7.27.6",
|
||||||
"@eslint/compat": "^1.3.0",
|
"@eslint/compat": "^1.3.0",
|
||||||
"@eslint/eslintrc": "^3.3.1",
|
"@eslint/eslintrc": "^3.3.1",
|
||||||
"@eslint/js": "^9.29.0",
|
"@eslint/js": "^9.29.0",
|
||||||
@ -69,10 +69,9 @@
|
|||||||
"@types/sanitize-html": "^2.16.0",
|
"@types/sanitize-html": "^2.16.0",
|
||||||
"@types/supertest": "^6.0.3",
|
"@types/supertest": "^6.0.3",
|
||||||
"@types/validator": "^13.15.1",
|
"@types/validator": "^13.15.1",
|
||||||
"@types/yargs": "^17.0.33",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^8.34.0",
|
"@typescript-eslint/eslint-plugin": "^8.34.0",
|
||||||
"@typescript-eslint/parser": "^8.34.0",
|
"@typescript-eslint/parser": "^8.34.0",
|
||||||
"bun-types": "^1.2.16",
|
"babel-jest": "^30.0.0",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"eslint": "^9.29.0",
|
"eslint": "^9.29.0",
|
||||||
"eslint-config-airbnb-base": "^15.0.0",
|
"eslint-config-airbnb-base": "^15.0.0",
|
||||||
@ -87,6 +86,7 @@
|
|||||||
"prettier": "^3.5.3",
|
"prettier": "^3.5.3",
|
||||||
"source-map-support": "^0.5.21",
|
"source-map-support": "^0.5.21",
|
||||||
"supertest": "^7.1.1",
|
"supertest": "^7.1.1",
|
||||||
|
"ts-babel": "^6.1.7",
|
||||||
"ts-jest": "29.4.0",
|
"ts-jest": "29.4.0",
|
||||||
"ts-loader": "^9.5.2",
|
"ts-loader": "^9.5.2",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
@ -106,7 +106,7 @@
|
|||||||
"<rootDir>/test/**/*.test.ts"
|
"<rootDir>/test/**/*.test.ts"
|
||||||
],
|
],
|
||||||
"transform": {
|
"transform": {
|
||||||
"^.+\\.(js|jsx)$": "ts-jest",
|
"^.+\\.(js|jsx)$": "babel-jest",
|
||||||
"^.+\\.(ts|tsx)?$": "ts-jest"
|
"^.+\\.(ts|tsx)?$": "ts-jest"
|
||||||
},
|
},
|
||||||
"collectCoverageFrom": [
|
"collectCoverageFrom": [
|
||||||
|
2701
pnpm-lock.yaml
2701
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -1,8 +0,0 @@
|
|||||||
import { createApp } from '@/modules/core/helpers/app';
|
|
||||||
import { buildCli } from '@/modules/core/helpers/command';
|
|
||||||
import { createOptions } from '@/options';
|
|
||||||
|
|
||||||
console.error('Raw argv:', process.argv);
|
|
||||||
console.log('This is the very beginning of bin.ts');
|
|
||||||
|
|
||||||
buildCli(createApp(createOptions));
|
|
@ -1,34 +1,13 @@
|
|||||||
export enum PostBodyType {
|
export enum PostBodyType {
|
||||||
/**
|
|
||||||
* HTML格式
|
|
||||||
*/
|
|
||||||
HTML = 'html',
|
HTML = 'html',
|
||||||
/**
|
|
||||||
* Markdown格式
|
|
||||||
*/
|
|
||||||
MD = 'markdown',
|
MD = 'markdown',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum PostOrder {
|
export enum PostOrder {
|
||||||
/**
|
|
||||||
* 最新创建
|
|
||||||
*/
|
|
||||||
CREATED = 'createdAt',
|
CREATED = 'createdAt',
|
||||||
/**
|
|
||||||
* 最新创建
|
|
||||||
*/
|
|
||||||
UPDATED = 'updatedAt',
|
UPDATED = 'updatedAt',
|
||||||
/**
|
|
||||||
* 最新发布
|
|
||||||
*/
|
|
||||||
PUBLISHED = 'publishedAt',
|
PUBLISHED = 'publishedAt',
|
||||||
/**
|
|
||||||
* 评论数量
|
|
||||||
*/
|
|
||||||
COMMENTCOUNT = 'commentCount',
|
COMMENTCOUNT = 'commentCount',
|
||||||
/**
|
|
||||||
* 自定义排序
|
|
||||||
*/
|
|
||||||
CUSTOM = 'custom',
|
CUSTOM = 'custom',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,15 +9,12 @@ import { SearchService } from '@/modules/content/services';
|
|||||||
import { SanitizeService } from '@/modules/content/services/SanitizeService';
|
import { SanitizeService } from '@/modules/content/services/SanitizeService';
|
||||||
|
|
||||||
import { PostService } from '@/modules/content/services/post.service';
|
import { PostService } from '@/modules/content/services/post.service';
|
||||||
|
import { PostSubscriber } from '@/modules/content/subscribers/post.subscriber';
|
||||||
import { DatabaseModule } from '@/modules/database/database.module';
|
import { DatabaseModule } from '@/modules/database/database.module';
|
||||||
|
|
||||||
import { addSubscribers } from '@/modules/database/utils';
|
|
||||||
|
|
||||||
import { Configure } from '../config/configure';
|
import { Configure } from '../config/configure';
|
||||||
|
|
||||||
import { defauleContentConfig } from './config';
|
import { defauleContentConfig } from './config';
|
||||||
import * as subscribers from './subscribers';
|
|
||||||
import { ContentConfig } from './types';
|
import { ContentConfig } from './types';
|
||||||
|
|
||||||
@Module({})
|
@Module({})
|
||||||
@ -26,7 +23,7 @@ export class ContentModule {
|
|||||||
const config = await configure.get<ContentConfig>('content', defauleContentConfig);
|
const config = await configure.get<ContentConfig>('content', defauleContentConfig);
|
||||||
const providers: ModuleMetadata['providers'] = [
|
const providers: ModuleMetadata['providers'] = [
|
||||||
...Object.values(services),
|
...Object.values(services),
|
||||||
...(await addSubscribers(configure, Object.values(subscribers))),
|
PostSubscriber,
|
||||||
{
|
{
|
||||||
provide: PostService,
|
provide: PostService,
|
||||||
inject: [
|
inject: [
|
||||||
|
@ -15,10 +15,8 @@ import { ApiTags } from '@nestjs/swagger';
|
|||||||
|
|
||||||
import { Depends } from '@/modules/restful/decorators/depend.decorator';
|
import { Depends } from '@/modules/restful/decorators/depend.decorator';
|
||||||
|
|
||||||
import { PaginateDto } from '@/modules/restful/dtos/paginate.dto';
|
|
||||||
|
|
||||||
import { ContentModule } from '../content.module';
|
import { ContentModule } from '../content.module';
|
||||||
import { CreateCategoryDto, UpdateCategoryDto } from '../dtos/category.dto';
|
import { CreateCategoryDto, QueryCategoryDto, UpdateCategoryDto } from '../dtos/category.dto';
|
||||||
import { CategoryService } from '../services';
|
import { CategoryService } from '../services';
|
||||||
|
|
||||||
@ApiTags('Category Operate')
|
@ApiTags('Category Operate')
|
||||||
@ -44,7 +42,7 @@ export class CategoryController {
|
|||||||
@SerializeOptions({ groups: ['category-list'] })
|
@SerializeOptions({ groups: ['category-list'] })
|
||||||
async list(
|
async list(
|
||||||
@Query()
|
@Query()
|
||||||
options: PaginateDto,
|
options: QueryCategoryDto,
|
||||||
) {
|
) {
|
||||||
return this.service.paginate(options);
|
return this.service.paginate(options);
|
||||||
}
|
}
|
||||||
|
@ -15,10 +15,8 @@ import { DeleteDto } from '@/modules/content/dtos/delete.dto';
|
|||||||
|
|
||||||
import { Depends } from '@/modules/restful/decorators/depend.decorator';
|
import { Depends } from '@/modules/restful/decorators/depend.decorator';
|
||||||
|
|
||||||
import { PaginateDto } from '@/modules/restful/dtos/paginate.dto';
|
|
||||||
|
|
||||||
import { ContentModule } from '../content.module';
|
import { ContentModule } from '../content.module';
|
||||||
import { CreateTagDto, UpdateTagDto } from '../dtos/tag.dto';
|
import { CreateTagDto, QueryTagDto, UpdateTagDto } from '../dtos/tag.dto';
|
||||||
import { TagService } from '../services';
|
import { TagService } from '../services';
|
||||||
|
|
||||||
@Depends(ContentModule)
|
@Depends(ContentModule)
|
||||||
@ -30,7 +28,7 @@ export class TagController {
|
|||||||
@SerializeOptions({})
|
@SerializeOptions({})
|
||||||
async list(
|
async list(
|
||||||
@Query()
|
@Query()
|
||||||
options: PaginateDto,
|
options: QueryTagDto,
|
||||||
) {
|
) {
|
||||||
return this.service.paginate(options);
|
return this.service.paginate(options);
|
||||||
}
|
}
|
||||||
|
@ -16,9 +16,28 @@ import { DtoValidation } from '@/modules/core/decorator/dto.validation.decorator
|
|||||||
import { IsDataExist } from '@/modules/database/constraints/data.exist.constraint';
|
import { IsDataExist } from '@/modules/database/constraints/data.exist.constraint';
|
||||||
import { IsTreeUnique } from '@/modules/database/constraints/tree.unique.constraint';
|
import { IsTreeUnique } from '@/modules/database/constraints/tree.unique.constraint';
|
||||||
import { IsTreeUniqueExist } from '@/modules/database/constraints/tree.unique.exist.constraint';
|
import { IsTreeUniqueExist } from '@/modules/database/constraints/tree.unique.exist.constraint';
|
||||||
|
import { PaginateOptions } from '@/modules/database/types';
|
||||||
|
|
||||||
import { CategoryEntity } from '../entities';
|
import { CategoryEntity } from '../entities';
|
||||||
|
|
||||||
|
@DtoValidation({ type: 'query' })
|
||||||
|
export class QueryCategoryDto implements PaginateOptions {
|
||||||
|
@Transform(({ value }) => toNumber(value))
|
||||||
|
@Min(1, { always: true, message: 'The current page must be greater than 1.' })
|
||||||
|
@IsInt()
|
||||||
|
@IsOptional()
|
||||||
|
page = 1;
|
||||||
|
|
||||||
|
@Transform(({ value }) => toNumber(value))
|
||||||
|
@Min(1, {
|
||||||
|
always: true,
|
||||||
|
message: 'The number of data displayed per page must be greater than 1.',
|
||||||
|
})
|
||||||
|
@IsInt()
|
||||||
|
@IsOptional()
|
||||||
|
limit = 10;
|
||||||
|
}
|
||||||
|
|
||||||
@DtoValidation({ groups: ['create'] })
|
@DtoValidation({ groups: ['create'] })
|
||||||
export class CreateCategoryDto {
|
export class CreateCategoryDto {
|
||||||
@IsTreeUnique(CategoryEntity, {
|
@IsTreeUnique(CategoryEntity, {
|
||||||
|
@ -25,32 +25,16 @@ import { PaginateOptions } from '@/modules/database/types';
|
|||||||
|
|
||||||
import { CategoryEntity, PostEntity, TagEntity } from '../entities';
|
import { CategoryEntity, PostEntity, TagEntity } from '../entities';
|
||||||
|
|
||||||
/**
|
|
||||||
* 文章分页查询验证
|
|
||||||
*/
|
|
||||||
@DtoValidation({ type: 'query' })
|
@DtoValidation({ type: 'query' })
|
||||||
export class QueryPostDto implements PaginateOptions {
|
export class QueryPostDto implements PaginateOptions {
|
||||||
/**
|
|
||||||
* 是否查询已发布(全部文章:不填、只查询已发布的:true、只查询未发布的:false)
|
|
||||||
*/
|
|
||||||
@Transform(({ value }) => toBoolean(value))
|
@Transform(({ value }) => toBoolean(value))
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
isPublished?: boolean;
|
isPublished?: boolean;
|
||||||
|
|
||||||
/**
|
|
||||||
* 全文搜索
|
|
||||||
*/
|
|
||||||
@MaxLength(100, {
|
|
||||||
always: true,
|
|
||||||
message: '搜索字符串长度不得超过$constraint1',
|
|
||||||
})
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
search?: string;
|
search?: string;
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询结果排序,不填则综合排序
|
|
||||||
*/
|
|
||||||
@IsEnum(PostOrder, {
|
@IsEnum(PostOrder, {
|
||||||
message: `The sorting rule must be one of ${Object.values(PostOrder).join(',')}`,
|
message: `The sorting rule must be one of ${Object.values(PostOrder).join(',')}`,
|
||||||
})
|
})
|
||||||
@ -76,17 +60,11 @@ export class QueryPostDto implements PaginateOptions {
|
|||||||
@IsOptional()
|
@IsOptional()
|
||||||
trashed?: SelectTrashMode;
|
trashed?: SelectTrashMode;
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据分类ID查询此分类及其后代分类下的文章
|
|
||||||
*/
|
|
||||||
@IsDataExist(CategoryEntity, { always: true, message: 'The category does not exist' })
|
@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;
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据标签ID查询
|
|
||||||
*/
|
|
||||||
@IsUUID(undefined, { message: 'The ID format is incorrect' })
|
@IsUUID(undefined, { message: 'The ID format is incorrect' })
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
tag?: string;
|
tag?: string;
|
||||||
|
@ -1,13 +1,34 @@
|
|||||||
import { PartialType } from '@nestjs/swagger';
|
import { PartialType } from '@nestjs/swagger';
|
||||||
import { IsDefined, IsNotEmpty, IsOptional, IsUUID, MaxLength } from 'class-validator';
|
import { Transform } from 'class-transformer';
|
||||||
|
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 { DtoValidation } from '@/modules/core/decorator/dto.validation.decorator';
|
||||||
import { IsDataExist } from '@/modules/database/constraints';
|
import { IsDataExist } from '@/modules/database/constraints';
|
||||||
import { IsUnique } from '@/modules/database/constraints/unique.constraint';
|
import { IsUnique } from '@/modules/database/constraints/unique.constraint';
|
||||||
import { IsUniqueExist } from '@/modules/database/constraints/unique.exist.constraint';
|
import { IsUniqueExist } from '@/modules/database/constraints/unique.exist.constraint';
|
||||||
|
import { PaginateOptions } from '@/modules/database/types';
|
||||||
|
|
||||||
import { TagEntity } from '../entities';
|
import { TagEntity } from '../entities';
|
||||||
|
|
||||||
|
@DtoValidation({ type: 'query' })
|
||||||
|
export class QueryTagDto implements PaginateOptions {
|
||||||
|
@Transform(({ value }) => toNumber(value))
|
||||||
|
@Min(1, { always: true, message: 'The current page must be greater than 1.' })
|
||||||
|
@IsInt()
|
||||||
|
@IsOptional()
|
||||||
|
page = 1;
|
||||||
|
|
||||||
|
@Transform(({ value }) => toNumber(value))
|
||||||
|
@Min(1, {
|
||||||
|
always: true,
|
||||||
|
message: 'The number of data displayed per page must be greater than 1.',
|
||||||
|
})
|
||||||
|
@IsInt()
|
||||||
|
@IsOptional()
|
||||||
|
limit = 10;
|
||||||
|
}
|
||||||
|
|
||||||
@DtoValidation({ groups: ['create'] })
|
@DtoValidation({ groups: ['create'] })
|
||||||
export class CreateTagDto {
|
export class CreateTagDto {
|
||||||
@IsUnique(TagEntity, { groups: ['create'], message: 'The label names are repeated' })
|
@IsUnique(TagEntity, { groups: ['create'], message: 'The label names are repeated' })
|
||||||
|
@ -5,13 +5,12 @@ import {
|
|||||||
Entity,
|
Entity,
|
||||||
OneToMany,
|
OneToMany,
|
||||||
PrimaryColumn,
|
PrimaryColumn,
|
||||||
|
Relation,
|
||||||
Tree,
|
Tree,
|
||||||
TreeChildren,
|
TreeChildren,
|
||||||
TreeParent,
|
TreeParent,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
|
|
||||||
import type { Relation } from 'typeorm';
|
|
||||||
|
|
||||||
import { PostEntity } from '@/modules/content/entities/post.entity';
|
import { PostEntity } from '@/modules/content/entities/post.entity';
|
||||||
|
|
||||||
@Exclude()
|
@Exclude()
|
||||||
|
@ -6,13 +6,12 @@ import {
|
|||||||
Entity,
|
Entity,
|
||||||
ManyToOne,
|
ManyToOne,
|
||||||
PrimaryColumn,
|
PrimaryColumn,
|
||||||
|
Relation,
|
||||||
Tree,
|
Tree,
|
||||||
TreeChildren,
|
TreeChildren,
|
||||||
TreeParent,
|
TreeParent,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
|
|
||||||
import type { Relation } from 'typeorm';
|
|
||||||
|
|
||||||
import { PostEntity } from '@/modules/content/entities/post.entity';
|
import { PostEntity } from '@/modules/content/entities/post.entity';
|
||||||
|
|
||||||
@Exclude()
|
@Exclude()
|
||||||
|
@ -10,11 +10,10 @@ import {
|
|||||||
ManyToOne,
|
ManyToOne,
|
||||||
OneToMany,
|
OneToMany,
|
||||||
PrimaryColumn,
|
PrimaryColumn,
|
||||||
|
Relation,
|
||||||
UpdateDateColumn,
|
UpdateDateColumn,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
|
|
||||||
import type { Relation } from 'typeorm';
|
|
||||||
|
|
||||||
import { PostBodyType } from '@/modules/content/constants';
|
import { PostBodyType } from '@/modules/content/constants';
|
||||||
import { CategoryEntity } from '@/modules/content/entities/category.entity';
|
import { CategoryEntity } from '@/modules/content/entities/category.entity';
|
||||||
import { CommentEntity } from '@/modules/content/entities/comment.entity';
|
import { CommentEntity } from '@/modules/content/entities/comment.entity';
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { Exclude, Expose } from 'class-transformer';
|
import { Exclude, Expose } from 'class-transformer';
|
||||||
import { Column, Entity, ManyToMany, PrimaryColumn } from 'typeorm';
|
import { Column, Entity, ManyToMany, PrimaryColumn, Relation } from 'typeorm';
|
||||||
import type { Relation } from 'typeorm';
|
|
||||||
|
|
||||||
import { PostEntity } from '@/modules/content/entities/post.entity';
|
import { PostEntity } from '@/modules/content/entities/post.entity';
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ import { PostEntity } from '@/modules/content/entities/post.entity';
|
|||||||
import { CategoryRepository } from '@/modules/content/repositories';
|
import { CategoryRepository } from '@/modules/content/repositories';
|
||||||
import { PostRepository } from '@/modules/content/repositories/post.repository';
|
import { PostRepository } from '@/modules/content/repositories/post.repository';
|
||||||
import { SearchService } from '@/modules/content/services/search.service';
|
import { SearchService } from '@/modules/content/services/search.service';
|
||||||
import type { SearchType } from '@/modules/content/types';
|
import { SearchType } from '@/modules/content/types';
|
||||||
import { BaseService } from '@/modules/database/base/service';
|
import { BaseService } from '@/modules/database/base/service';
|
||||||
import { SelectTrashMode } from '@/modules/database/constants';
|
import { SelectTrashMode } from '@/modules/database/constants';
|
||||||
import { QueryHook } from '@/modules/database/types';
|
import { QueryHook } from '@/modules/database/types';
|
||||||
|
@ -1 +0,0 @@
|
|||||||
export * from './post.subscriber';
|
|
@ -1,33 +1,33 @@
|
|||||||
|
import { Optional } from '@nestjs/common';
|
||||||
import { isNil } from 'lodash';
|
import { isNil } from 'lodash';
|
||||||
import { DataSource, EventSubscriber, ObjectType } from 'typeorm';
|
import { DataSource, EventSubscriber, ObjectType } from 'typeorm';
|
||||||
|
|
||||||
import { Configure } from '@/modules/config/configure';
|
import { Configure } from '@/modules/config/configure';
|
||||||
import { PostBodyType } from '@/modules/content/constants';
|
import { PostBodyType } from '@/modules/content/constants';
|
||||||
import { PostEntity } from '@/modules/content/entities/post.entity';
|
import { PostEntity } from '@/modules/content/entities/post.entity';
|
||||||
|
import { PostRepository } from '@/modules/content/repositories/post.repository';
|
||||||
import { SanitizeService } from '@/modules/content/services/SanitizeService';
|
import { SanitizeService } from '@/modules/content/services/SanitizeService';
|
||||||
import { BaseSubscriber } from '@/modules/database/base/subscriber';
|
import { BaseSubscriber } from '@/modules/database/base/subscriber';
|
||||||
|
|
||||||
@EventSubscriber()
|
@EventSubscriber()
|
||||||
export class PostSubscriber extends BaseSubscriber<PostEntity> {
|
export class PostSubscriber extends BaseSubscriber<PostEntity> {
|
||||||
protected entity: ObjectType<PostEntity> = PostEntity;
|
protected entity: ObjectType<PostEntity> = PostEntity;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected dataSource: DataSource,
|
protected dataSource: DataSource,
|
||||||
protected _configure: Configure,
|
protected postRepository: PostRepository,
|
||||||
|
protected configure: Configure,
|
||||||
|
@Optional() protected sanitizeService: SanitizeService,
|
||||||
) {
|
) {
|
||||||
super(dataSource, _configure);
|
super(dataSource);
|
||||||
}
|
|
||||||
|
|
||||||
get configure(): Configure {
|
|
||||||
return this._configure;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async afterLoad(entity: PostEntity) {
|
async afterLoad(entity: PostEntity) {
|
||||||
const sanitizeService = (await this.configure.get('content.htmlEnabled'))
|
if (
|
||||||
? this.container.get(SanitizeService)
|
(await this.configure.get('content.htmlEnabled')) &&
|
||||||
: undefined;
|
!isNil(this.sanitizeService) &&
|
||||||
if (!isNil(sanitizeService) && entity.type === PostBodyType.HTML) {
|
entity.type === PostBodyType.HTML
|
||||||
entity.body = sanitizeService.sanitize(entity.body);
|
) {
|
||||||
|
entity.body = this.sanitizeService.sanitize(entity.body);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,52 +0,0 @@
|
|||||||
import { spawn } from 'node:child_process';
|
|
||||||
|
|
||||||
import { exit } from 'process';
|
|
||||||
|
|
||||||
import { Arguments } from 'yargs';
|
|
||||||
|
|
||||||
import { getCLIConfig } from '@/modules/core/commands/helpers/config';
|
|
||||||
import { BuildCommandArguments } from '@/modules/core/commands/types';
|
|
||||||
import { CommandItem } from '@/modules/core/types';
|
|
||||||
|
|
||||||
export const createBuildCommand: CommandItem<any, BuildCommandArguments> = async (app) => ({
|
|
||||||
command: ['build', 'b'],
|
|
||||||
describe: 'Build application by nest cli.',
|
|
||||||
builder: {
|
|
||||||
nestConfig: {
|
|
||||||
type: 'string',
|
|
||||||
alias: 'n',
|
|
||||||
describe: 'nest cli config file path.',
|
|
||||||
default: 'nest-cli.json',
|
|
||||||
},
|
|
||||||
tsConfig: {
|
|
||||||
type: 'string',
|
|
||||||
alias: 't',
|
|
||||||
describe: 'typescript config file path.',
|
|
||||||
default: 'tsconfig.build.json',
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
type: 'boolean',
|
|
||||||
alias: 'w',
|
|
||||||
describe: ' Run in watch mode (live-reload).',
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
preserveWatchOutput: {
|
|
||||||
type: 'boolean',
|
|
||||||
alias: 'po',
|
|
||||||
describe: 'Use "preserveWatchOutput" option when using tsc watch mode',
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
handler: async (args: Arguments<BuildCommandArguments>) => {
|
|
||||||
const config = getCLIConfig(args.tsConfig, args.nestConfig);
|
|
||||||
const params = ['build', '-c', args.nestConfig, '-p', args.tsConfig];
|
|
||||||
if (args.watch) {
|
|
||||||
params.push('-w');
|
|
||||||
}
|
|
||||||
if (args.preserveWatchOutput) {
|
|
||||||
params.push('po');
|
|
||||||
}
|
|
||||||
const child = spawn(config.paths.nest, params, config.subprocess.node);
|
|
||||||
child.on('exit', () => exit());
|
|
||||||
},
|
|
||||||
});
|
|
@ -1,21 +0,0 @@
|
|||||||
import { DemoCommandArguments } from '@/modules/core/commands/types';
|
|
||||||
import { CommandItem } from '@/modules/core/types';
|
|
||||||
|
|
||||||
export const DemoCommand: CommandItem<any, DemoCommandArguments> = async (app) => ({
|
|
||||||
command: ['demo', 'd'],
|
|
||||||
describe: 'a demo command',
|
|
||||||
handler: async (args: DemoCommandArguments) => {
|
|
||||||
const { configure } = app;
|
|
||||||
const appName = await configure.get<string>('app.name');
|
|
||||||
const sleep = args.sleep ? ' will to sleep' : '';
|
|
||||||
console.log(`just a demo command,app ${appName} ${sleep}`);
|
|
||||||
},
|
|
||||||
builder: {
|
|
||||||
sleep: {
|
|
||||||
type: 'boolean',
|
|
||||||
alias: 's',
|
|
||||||
describe: 'App will sleep ?',
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
@ -1,97 +0,0 @@
|
|||||||
import { join } from 'path';
|
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
||||||
import { ActionOnFile, AssetEntry } from '@nestjs/cli/lib/configuration';
|
|
||||||
import chokidar, { FSWatcher } from 'chokidar';
|
|
||||||
|
|
||||||
import { get } from 'lodash';
|
|
||||||
|
|
||||||
import { CLIConfig } from '@/modules/core/commands/types';
|
|
||||||
import { toBoolean } from '@/modules/core/helpers';
|
|
||||||
|
|
||||||
export class Asset {
|
|
||||||
private watchAssetsKeyValue: { [key: string]: boolean } = {};
|
|
||||||
|
|
||||||
private watchers: FSWatcher[] = [];
|
|
||||||
|
|
||||||
private actionInProgress = false;
|
|
||||||
|
|
||||||
closeWatchers() {
|
|
||||||
const timeout = 500;
|
|
||||||
const closeFn = () => {
|
|
||||||
if (this.actionInProgress) {
|
|
||||||
this.actionInProgress = false;
|
|
||||||
setTimeout(closeFn, timeout);
|
|
||||||
} else {
|
|
||||||
this.watchers.forEach((watch) => watch.close());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
setTimeout(closeFn, timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
watchAssets(config: CLIConfig, codePath: string, changer: () => void) {
|
|
||||||
const assets = get(config.options.nest, 'compilerOptions.assets', []) as AssetEntry[];
|
|
||||||
|
|
||||||
if (assets.length <= 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const isWatchEnabled = toBoolean(get(config, 'watchAssets', 'src'));
|
|
||||||
const filesToWatch = assets.map<AssetEntry>((item) => {
|
|
||||||
if (typeof item === 'string') {
|
|
||||||
return {
|
|
||||||
glob: join(codePath, item),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
glob: join(codePath, item.include!),
|
|
||||||
exclude: item.exclude ? join(codePath, item.exclude) : undefined,
|
|
||||||
flat: item.flat,
|
|
||||||
watchAssets: item.watchAssets,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const file of filesToWatch) {
|
|
||||||
const option: ActionOnFile = {
|
|
||||||
action: 'change',
|
|
||||||
item: file,
|
|
||||||
path: '',
|
|
||||||
sourceRoot: codePath,
|
|
||||||
watchAssetsMode: isWatchEnabled,
|
|
||||||
};
|
|
||||||
|
|
||||||
const watcher = chokidar
|
|
||||||
.watch(file.glob, { ignored: file.exclude })
|
|
||||||
.on('add', (path) =>
|
|
||||||
this.actionOnFIle({ ...option, path, action: 'change' }, changer),
|
|
||||||
)
|
|
||||||
.on('change', (path) =>
|
|
||||||
this.actionOnFIle({ ...option, path, action: 'change' }, changer),
|
|
||||||
)
|
|
||||||
.on('unlink', (path) =>
|
|
||||||
this.actionOnFIle({ ...option, path, action: 'unlink' }, changer),
|
|
||||||
);
|
|
||||||
this.watchers.push(watcher);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
throw new Error(
|
|
||||||
`An error occurred during the assets copying process. ${(e as any).message}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected actionOnFIle(option: ActionOnFile, changer: () => void) {
|
|
||||||
const { action, item, path, watchAssetsMode } = option;
|
|
||||||
const isWatchEnabled = watchAssetsMode || item.watchAssets;
|
|
||||||
|
|
||||||
if (!isWatchEnabled && this.watchAssetsKeyValue[path]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.watchAssetsKeyValue[path] = true;
|
|
||||||
this.actionInProgress = true;
|
|
||||||
if (action === 'change') {
|
|
||||||
changer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,113 +0,0 @@
|
|||||||
/* eslint-disable import/no-extraneous-dependencies */
|
|
||||||
import { join, resolve } from 'path';
|
|
||||||
|
|
||||||
import { exit } from 'process';
|
|
||||||
|
|
||||||
import { Configuration as NestCLIConfig } from '@nestjs/cli/lib/configuration';
|
|
||||||
import { isNil } from '@nestjs/common/utils/shared.utils';
|
|
||||||
import { existsSync, readFileSync } from 'fs-extra';
|
|
||||||
import { get, omit } from 'lodash';
|
|
||||||
import { StartOptions } from 'pm2';
|
|
||||||
import ts from 'typescript';
|
|
||||||
|
|
||||||
import { Configure } from '@/modules/config/configure';
|
|
||||||
import { CLIConfig, Pm2Option } from '@/modules/core/commands/types';
|
|
||||||
import { deepMerge, panic } from '@/modules/core/helpers';
|
|
||||||
import { AppConfig } from '@/modules/core/types';
|
|
||||||
|
|
||||||
const cwdPath = resolve(__dirname, '../../../../..');
|
|
||||||
|
|
||||||
export function getCLIConfig(
|
|
||||||
tsConfigFile: string,
|
|
||||||
nestConfigFile: string,
|
|
||||||
tsEntryFile?: string,
|
|
||||||
): CLIConfig {
|
|
||||||
let tsConfig: ts.CompilerOptions = {};
|
|
||||||
const tsConfigPath = join(cwdPath, tsConfigFile);
|
|
||||||
|
|
||||||
if (!existsSync(tsConfigPath)) {
|
|
||||||
panic(`ts config file ${tsConfigPath} not exists!`);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const allTsConfig = JSON.parse(readFileSync(tsConfigPath, 'utf8'));
|
|
||||||
tsConfig = get(allTsConfig, 'compilerOptions', {});
|
|
||||||
} catch (error) {
|
|
||||||
panic({ error, message: 'get ts config file failed.' });
|
|
||||||
}
|
|
||||||
let nestConfig: NestCLIConfig = {};
|
|
||||||
const nestConfigPath = join(cwdPath, nestConfigFile);
|
|
||||||
|
|
||||||
if (!existsSync(nestConfigPath)) {
|
|
||||||
panic(`ts config file ${nestConfigPath} not exists!`);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
nestConfig = JSON.parse(readFileSync(nestConfigPath, 'utf8'));
|
|
||||||
} catch (error) {
|
|
||||||
panic({ error, message: 'get nest config file failed.' });
|
|
||||||
}
|
|
||||||
|
|
||||||
const dist = get(tsConfig, 'outDir', 'dist');
|
|
||||||
const src = get(nestConfig, 'sourceRoot', 'src');
|
|
||||||
const homeDir = process.env.HOME;
|
|
||||||
const paths = {
|
|
||||||
cwd: cwdPath,
|
|
||||||
dist,
|
|
||||||
src,
|
|
||||||
js: join(dist, nestConfig.entryFile ?? 'main.js'),
|
|
||||||
ts: join(src, tsEntryFile ?? 'main.ts'),
|
|
||||||
bun: `${homeDir}/.bun/bin/bun`,
|
|
||||||
nest: './node_modules/@nestjs/cli/bin/nest.js',
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
options: { ts: tsConfig, nest: nestConfig },
|
|
||||||
paths,
|
|
||||||
subprocess: {
|
|
||||||
bun: {
|
|
||||||
cwd: cwdPath,
|
|
||||||
stdout: 'inherit',
|
|
||||||
env: process.env,
|
|
||||||
onExit: (proc) => {
|
|
||||||
proc.kill();
|
|
||||||
if (!isNil(proc.exitCode)) {
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
node: {
|
|
||||||
cwd: cwdPath,
|
|
||||||
env: process.env,
|
|
||||||
stdio: 'inherit',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getPm2Config(
|
|
||||||
configure: Configure,
|
|
||||||
option: Pm2Option,
|
|
||||||
config: CLIConfig,
|
|
||||||
script: string,
|
|
||||||
): Promise<StartOptions> {
|
|
||||||
const { name, pm2: customConfig = {} } = await configure.get<AppConfig>('app');
|
|
||||||
const defaultConfig: StartOptions = {
|
|
||||||
name,
|
|
||||||
cwd: cwdPath,
|
|
||||||
script,
|
|
||||||
args: option.command,
|
|
||||||
autorestart: true,
|
|
||||||
watch: option.watch,
|
|
||||||
ignore_watch: ['node_modules'],
|
|
||||||
env: process.env,
|
|
||||||
exec_mode: 'fork',
|
|
||||||
interpreter: config.paths.bun,
|
|
||||||
};
|
|
||||||
|
|
||||||
return deepMerge(
|
|
||||||
defaultConfig,
|
|
||||||
omit(customConfig, ['name', 'cwd', 'script', 'args', 'watch', 'interpreter']),
|
|
||||||
'replace',
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,125 +0,0 @@
|
|||||||
import { isNil } from '@nestjs/common/utils/shared.utils';
|
|
||||||
import { Subprocess } from 'bun';
|
|
||||||
import chalk from 'chalk';
|
|
||||||
import { pick } from 'lodash';
|
|
||||||
import pm2 from 'pm2';
|
|
||||||
|
|
||||||
import { Arguments } from 'yargs';
|
|
||||||
|
|
||||||
import { Configure } from '@/modules/config/configure';
|
|
||||||
import { Asset } from '@/modules/core/commands/helpers/asset';
|
|
||||||
import { getPm2Config } from '@/modules/core/commands/helpers/config';
|
|
||||||
import { generateSwaggerMetadata } from '@/modules/core/commands/helpers/swagger';
|
|
||||||
import { CLIConfig, StartCommandArguments } from '@/modules/core/commands/types';
|
|
||||||
import { AppConfig } from '@/modules/core/types';
|
|
||||||
|
|
||||||
export async function start(
|
|
||||||
args: Arguments<StartCommandArguments>,
|
|
||||||
config: CLIConfig,
|
|
||||||
): Promise<void> {
|
|
||||||
console.log('command start...');
|
|
||||||
const script = args.typescript ? config.paths.ts : config.paths.js;
|
|
||||||
const params = [config.paths.bun, 'run'];
|
|
||||||
if (args.watch) {
|
|
||||||
params.push('--watch');
|
|
||||||
}
|
|
||||||
if (args.debug) {
|
|
||||||
const inspectFlag =
|
|
||||||
typeof args.debug === 'string' ? `--inspect=${args.debug}` : '--inspect';
|
|
||||||
params.push(inspectFlag);
|
|
||||||
}
|
|
||||||
if (args.typescript) {
|
|
||||||
generateSwaggerMetadata(args, config, false);
|
|
||||||
}
|
|
||||||
params.push(script);
|
|
||||||
let child: Subprocess;
|
|
||||||
if (args.watch) {
|
|
||||||
const asset = new Asset();
|
|
||||||
const restart = () => {
|
|
||||||
if (!isNil(child)) {
|
|
||||||
child.kill();
|
|
||||||
}
|
|
||||||
child = Bun.spawn(params, config.subprocess.bun);
|
|
||||||
};
|
|
||||||
restart();
|
|
||||||
asset.watchAssets(config, config.paths.cwd, restart);
|
|
||||||
process.on('exit', () => {
|
|
||||||
child.kill();
|
|
||||||
asset.closeWatchers();
|
|
||||||
process.exit(0);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
Bun.spawn(params, {
|
|
||||||
...config.subprocess.bun,
|
|
||||||
onExit(proc) {
|
|
||||||
proc.kill();
|
|
||||||
process.exit(0);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function startPM2(
|
|
||||||
configure: Configure,
|
|
||||||
args: Arguments<StartCommandArguments>,
|
|
||||||
config: CLIConfig,
|
|
||||||
): Promise<void> {
|
|
||||||
console.log('command startPM2...');
|
|
||||||
const { name } = await configure.get<AppConfig>('app');
|
|
||||||
const script = args.typescript ? config.paths.ts : config.paths.js;
|
|
||||||
const pm2config = await getPm2Config(
|
|
||||||
configure,
|
|
||||||
{ command: 'start', ...pick(args, ['watch', 'typescript']) },
|
|
||||||
config,
|
|
||||||
script,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (pm2config.exec_mode === 'cluster' && args.typescript) {
|
|
||||||
console.log(
|
|
||||||
chalk.yellowBright(
|
|
||||||
'Cannot directly use bun to run ts code in cluster mode, so it will automatically change to fork mode.',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
console.log();
|
|
||||||
console.log(
|
|
||||||
chalk.bgCyanBright(
|
|
||||||
chalk.blackBright(
|
|
||||||
'If you really need the app to be started in cluster mode, be sure to compile it into js first, and then add the --no-ts arg when running',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
console.log();
|
|
||||||
pm2config.exec_mode = 'fork';
|
|
||||||
}
|
|
||||||
|
|
||||||
const connectCallback = (error?: any) => {
|
|
||||||
if (!isNil(error)) {
|
|
||||||
console.error(error);
|
|
||||||
process.exit(2);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const startCallback = (error?: any) => {
|
|
||||||
if (!isNil(error)) {
|
|
||||||
console.error(error);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
pm2.disconnect();
|
|
||||||
};
|
|
||||||
|
|
||||||
const restartCallback = (error?: any) => {
|
|
||||||
if (isNil(error)) {
|
|
||||||
pm2.disconnect();
|
|
||||||
} else {
|
|
||||||
pm2.start(pm2config, (err) => startCallback(err));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pm2.connect((err: any) => {
|
|
||||||
connectCallback(err);
|
|
||||||
generateSwaggerMetadata(args, config, false);
|
|
||||||
args.restart
|
|
||||||
? pm2.restart(name, restartCallback)
|
|
||||||
: pm2.start(pm2config, (e) => startCallback(e));
|
|
||||||
});
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
/* eslint-disable import/no-extraneous-dependencies */
|
|
||||||
import { join } from 'path';
|
|
||||||
|
|
||||||
import { PluginMetadataGenerator } from '@nestjs/cli/lib/compiler/plugins/plugin-metadata-generator';
|
|
||||||
import { PluginOptions } from '@nestjs/cli/lib/configuration';
|
|
||||||
import { ReadonlyVisitor } from '@nestjs/swagger/dist/plugin';
|
|
||||||
import { get, isNil } from 'lodash';
|
|
||||||
import { Arguments } from 'yargs';
|
|
||||||
|
|
||||||
import { CLIConfig, StartCommandArguments } from '@/modules/core/commands/types';
|
|
||||||
|
|
||||||
export function generateSwaggerMetadata(
|
|
||||||
args: Arguments<StartCommandArguments>,
|
|
||||||
config: CLIConfig,
|
|
||||||
watch: boolean,
|
|
||||||
) {
|
|
||||||
const cliPlugins = get(config.options.nest, 'compilerOptions.plugins', []) as (
|
|
||||||
| string
|
|
||||||
| RecordAny
|
|
||||||
)[];
|
|
||||||
const swaggerPlugin = cliPlugins.find(
|
|
||||||
(item) => item === '@nest/swagger' || (item as any).name === '@nest/swagger',
|
|
||||||
);
|
|
||||||
if (!isNil(swaggerPlugin) && args.typescript) {
|
|
||||||
const srcPath = join(config.paths.cwd, config.paths.src);
|
|
||||||
const generator = new PluginMetadataGenerator();
|
|
||||||
let swaggerPluginOption: PluginOptions;
|
|
||||||
if (typeof swaggerPlugin !== 'string' && 'options' in swaggerPlugin) {
|
|
||||||
swaggerPluginOption = swaggerPlugin.options;
|
|
||||||
}
|
|
||||||
generator.generate({
|
|
||||||
visitors: [new ReadonlyVisitor({ ...swaggerPluginOption, pathToSource: srcPath })],
|
|
||||||
outputDir: srcPath,
|
|
||||||
watch,
|
|
||||||
tsconfigPath: args.tsConfig,
|
|
||||||
printDiagnostics: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
export * from './demo.command';
|
|
||||||
export * from './build.command';
|
|
||||||
export * from './start.command';
|
|
@ -1,72 +0,0 @@
|
|||||||
import { Arguments } from 'yargs';
|
|
||||||
|
|
||||||
import { getCLIConfig } from '@/modules/core/commands/helpers/config';
|
|
||||||
import { start, startPM2 } from '@/modules/core/commands/helpers/start';
|
|
||||||
import { StartCommandArguments } from '@/modules/core/commands/types';
|
|
||||||
import { CommandItem } from '@/modules/core/types';
|
|
||||||
|
|
||||||
export const createStartCommand: CommandItem<any, StartCommandArguments> = async (app) => ({
|
|
||||||
command: ['start', 's'],
|
|
||||||
describe: 'Start app',
|
|
||||||
builder: {
|
|
||||||
nestConfig: {
|
|
||||||
type: 'string',
|
|
||||||
alias: 'n',
|
|
||||||
describe: 'nest cli config file path.',
|
|
||||||
default: 'nest-cli.json',
|
|
||||||
},
|
|
||||||
tsConfig: {
|
|
||||||
type: 'string',
|
|
||||||
alias: 't',
|
|
||||||
describe: 'typescript config file path.',
|
|
||||||
default: 'tsconfig.build.json',
|
|
||||||
},
|
|
||||||
entry: {
|
|
||||||
type: 'string',
|
|
||||||
alias: 'e',
|
|
||||||
describe:
|
|
||||||
'Specify entry file for ts runner, you can specify js entry file in nest-cli.json by entryFile.',
|
|
||||||
default: 'main.ts',
|
|
||||||
},
|
|
||||||
prod: {
|
|
||||||
type: 'boolean',
|
|
||||||
alias: 'p',
|
|
||||||
describe: 'Start app in production by pm2.',
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
restart: {
|
|
||||||
type: 'boolean',
|
|
||||||
alias: 'r',
|
|
||||||
describe: 'Restart app(only pm2),pm2 will auto run start if process not exists.',
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
typescript: {
|
|
||||||
type: 'boolean',
|
|
||||||
alias: 'ts',
|
|
||||||
describe: 'Run the .ts file directly.',
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
type: 'boolean',
|
|
||||||
alias: 'w',
|
|
||||||
describe: ' Run in watch mode (live-reload).',
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
debug: {
|
|
||||||
type: 'boolean',
|
|
||||||
alias: 'd',
|
|
||||||
describe: 'Whether to enable debug mode, only valid for non-production environments',
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
handler: async (args: Arguments<StartCommandArguments>) => {
|
|
||||||
console.log('createStartCommand handler start');
|
|
||||||
const { configure } = app;
|
|
||||||
const config = getCLIConfig(args.tsConfig, args.nestConfig, args.entry);
|
|
||||||
if (args.prod || args.restart) {
|
|
||||||
await startPM2(configure, args, config);
|
|
||||||
} else {
|
|
||||||
await start(args, config);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
@ -1,64 +0,0 @@
|
|||||||
/* eslint-disable import/no-extraneous-dependencies */
|
|
||||||
import { SpawnOptions as NodeSpawnOptions } from 'child_process';
|
|
||||||
|
|
||||||
import { Configuration as NestCLIConfig } from '@nestjs/cli/lib/configuration';
|
|
||||||
import type { SpawnOptions as BunSpawnOptions } from 'bun';
|
|
||||||
import ts from 'typescript';
|
|
||||||
|
|
||||||
export type DemoCommandArguments = {
|
|
||||||
sleep?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface CLIConfig {
|
|
||||||
options: {
|
|
||||||
ts: ts.CompilerOptions;
|
|
||||||
nest: NestCLIConfig;
|
|
||||||
};
|
|
||||||
paths: Record<'cwd' | 'dist' | 'src' | 'js' | 'ts' | 'bun' | 'nest', string>;
|
|
||||||
subprocess: {
|
|
||||||
bun: BunSpawnOptions.OptionsObject<any, any, any>;
|
|
||||||
node: NodeSpawnOptions;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export type StartCommandArguments = {
|
|
||||||
/**
|
|
||||||
* nest-cli.json的文件路径(相对于当前运行目录)
|
|
||||||
*/
|
|
||||||
nestConfig?: string;
|
|
||||||
/**
|
|
||||||
* 用于编译和运行的tsconfig.build.json的文件路径(相对于当前运行目录)
|
|
||||||
*/
|
|
||||||
tsConfig?: string;
|
|
||||||
/**
|
|
||||||
* 使用直接运行TS文件的入口文件,默认为main.ts. 如果是运行js文件,则通过nest-cli.json的entryFile指定
|
|
||||||
*/
|
|
||||||
entry?: string;
|
|
||||||
/**
|
|
||||||
* 是否使用PM2后台静默启动生产环境
|
|
||||||
*/
|
|
||||||
prod?: boolean;
|
|
||||||
/**
|
|
||||||
* 使用直接运行TS文件,这个配置只针对生产环境下是否通过
|
|
||||||
*/
|
|
||||||
typescript?: boolean;
|
|
||||||
/**
|
|
||||||
* 是否监控,所有环境都可以使用(但在非PM2启动的生产环境下此选项无效)
|
|
||||||
*/
|
|
||||||
watch?: boolean;
|
|
||||||
/**
|
|
||||||
* 是否开启debug模式,只对非生产环境有效
|
|
||||||
*/
|
|
||||||
debug?: boolean | string;
|
|
||||||
/**
|
|
||||||
* 是否重启应用(PM2进程)
|
|
||||||
*/
|
|
||||||
restart?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Pm2Option = Pick<StartCommandArguments, 'typescript' | 'watch'> & { command: string };
|
|
||||||
|
|
||||||
export type BuildCommandArguments = Pick<StartCommandArguments, 'tsConfig' | 'nestConfig'> & {
|
|
||||||
watch?: string;
|
|
||||||
preserveWatchOutput?: boolean;
|
|
||||||
};
|
|
@ -10,8 +10,6 @@ import { Configure } from '@/modules/config/configure';
|
|||||||
|
|
||||||
import { DEFAULT_VALIDATION_CONFIG } from '@/modules/content/constants';
|
import { DEFAULT_VALIDATION_CONFIG } from '@/modules/content/constants';
|
||||||
|
|
||||||
import { createCommands } from '@/modules/core/helpers/command';
|
|
||||||
|
|
||||||
import { CoreModule } from '../core.module';
|
import { CoreModule } from '../core.module';
|
||||||
import { AppFilter } from '../providers/app.filter';
|
import { AppFilter } from '../providers/app.filter';
|
||||||
import { AppInterceptor } from '../providers/app.interceptor';
|
import { AppInterceptor } from '../providers/app.interceptor';
|
||||||
@ -20,7 +18,7 @@ import { App, AppConfig, CreateOptions } from '../types';
|
|||||||
|
|
||||||
import { CreateModule } from './utils';
|
import { CreateModule } from './utils';
|
||||||
|
|
||||||
export const app: App = { configure: new Configure(), commands: [] };
|
export const app: App = { configure: new Configure() };
|
||||||
|
|
||||||
export const createApp = (options: CreateOptions) => async (): Promise<App> => {
|
export const createApp = (options: CreateOptions) => async (): Promise<App> => {
|
||||||
const { config, builder } = options;
|
const { config, builder } = options;
|
||||||
@ -36,7 +34,6 @@ export const createApp = (options: CreateOptions) => async (): Promise<App> => {
|
|||||||
app.container = await builder({ configure: app.configure, BootModule });
|
app.container = await builder({ configure: app.configure, BootModule });
|
||||||
|
|
||||||
useContainer(app.container.select(BootModule), { fallbackOnErrors: true });
|
useContainer(app.container.select(BootModule), { fallbackOnErrors: true });
|
||||||
app.commands = await createCommands(options.commands, app as Required<App>);
|
|
||||||
return app;
|
return app;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -92,11 +89,11 @@ export async function createBootModule(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function startApp(
|
export async function startApp(
|
||||||
creator: () => Promise<App>,
|
creater: () => Promise<App>,
|
||||||
listened: (app: App, startTime: Date) => () => Promise<void>,
|
listened: (app: App, startTime: Date) => () => Promise<void>,
|
||||||
) {
|
) {
|
||||||
const startTime = new Date();
|
const startTime = new Date();
|
||||||
const { container, configure } = await creator();
|
const { container, configure } = await creater();
|
||||||
app.container = container;
|
app.container = container;
|
||||||
app.configure = configure;
|
app.configure = configure;
|
||||||
const { port, host } = await configure.get<AppConfig>('app');
|
const { port, host } = await configure.get<AppConfig>('app');
|
||||||
|
@ -1,55 +0,0 @@
|
|||||||
import chalk from 'chalk';
|
|
||||||
import yargs, { Arguments, CommandModule } from 'yargs';
|
|
||||||
|
|
||||||
import { hideBin } from 'yargs/helpers';
|
|
||||||
|
|
||||||
import * as coreCommands from '../commands';
|
|
||||||
import { App, CommandCollection } from '../types';
|
|
||||||
|
|
||||||
export async function buildCli(creator: () => Promise<App>) {
|
|
||||||
console.log('buildCli start');
|
|
||||||
const app = await creator();
|
|
||||||
const bin = yargs(hideBin(process.argv));
|
|
||||||
app.commands.forEach((cmd) => {
|
|
||||||
bin.command(cmd);
|
|
||||||
});
|
|
||||||
bin.usage('Usage: $0 <command> [args]')
|
|
||||||
.scriptName('cli')
|
|
||||||
.demandCommand(1, '')
|
|
||||||
.fail((msg, err, y) => {
|
|
||||||
if (!msg && !err) {
|
|
||||||
bin.showHelp();
|
|
||||||
process.exit();
|
|
||||||
}
|
|
||||||
if (msg) {
|
|
||||||
console.error(chalk.red(msg));
|
|
||||||
}
|
|
||||||
if (err) {
|
|
||||||
console.error(chalk.red(err.message));
|
|
||||||
}
|
|
||||||
process.exit();
|
|
||||||
})
|
|
||||||
.strict()
|
|
||||||
.alias('v', 'version')
|
|
||||||
.help('h')
|
|
||||||
.alias('h', 'help')
|
|
||||||
.parse();
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createCommands(
|
|
||||||
factory: () => CommandCollection,
|
|
||||||
app: Required<App>,
|
|
||||||
): Promise<CommandModule<any, any>[]> {
|
|
||||||
const collection: CommandCollection = [...factory(), ...Object.values(coreCommands)];
|
|
||||||
const commands = await Promise.all(collection.map(async (command) => command(app)));
|
|
||||||
return commands.map((command) => ({
|
|
||||||
...command,
|
|
||||||
handler: async (args: Arguments<RecordAny>) => {
|
|
||||||
await app.container.close();
|
|
||||||
await command.handler(args);
|
|
||||||
if (command.instant) {
|
|
||||||
process.exit();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
}
|
|
@ -14,7 +14,7 @@ export function toBoolean(value?: string | boolean): boolean {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return JSON.parse(value.toLowerCase());
|
return JSON.parse(value.toLowerCase());
|
||||||
} catch {
|
} catch (error) {
|
||||||
return value as unknown as boolean;
|
return value as unknown as boolean;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -41,7 +41,7 @@ export function isAsyncFunction<T, P extends Array<any>>(
|
|||||||
callback: (...args: P) => T | Promise<T>,
|
callback: (...args: P) => T | Promise<T>,
|
||||||
): callback is (...args: P) => Promise<T> {
|
): callback is (...args: P) => Promise<T> {
|
||||||
const AsyncFunction = (async () => {}).constructor;
|
const AsyncFunction = (async () => {}).constructor;
|
||||||
return callback instanceof AsyncFunction;
|
return callback instanceof AsyncFunction === true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CreateModule(
|
export function CreateModule(
|
||||||
@ -76,14 +76,8 @@ export async function panic(option: PanicOption | string) {
|
|||||||
console.log(chalk.red(`\n❌ ${option}`));
|
console.log(chalk.red(`\n❌ ${option}`));
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
const { error, message, spinner, exit = true } = option;
|
const { error, message, exit = true } = option;
|
||||||
if (isNil(error)) {
|
isNil(error) ? console.log(chalk.red(`\n❌ ${message}`)) : console.log(chalk.red(error));
|
||||||
isNil(spinner)
|
|
||||||
? console.log(chalk.red(`\n❌ ${message}`))
|
|
||||||
: spinner.succeed(chalk.red(`\n❌ ${message}`));
|
|
||||||
} else {
|
|
||||||
isNil(spinner) ? console.log(chalk.red(error)) : spinner.fail(chalk.red(error));
|
|
||||||
}
|
|
||||||
if (exit) {
|
if (exit) {
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,6 @@
|
|||||||
import { ModuleMetadata, PipeTransform, Type } from '@nestjs/common';
|
import { ModuleMetadata, PipeTransform, Type } from '@nestjs/common';
|
||||||
import { NestFastifyApplication } from '@nestjs/platform-fastify';
|
import { NestFastifyApplication } from '@nestjs/platform-fastify';
|
||||||
|
|
||||||
import { Ora } from 'ora';
|
|
||||||
import { StartOptions } from 'pm2';
|
|
||||||
import { CommandModule } from 'yargs';
|
|
||||||
|
|
||||||
import { Configure } from '../config/configure';
|
import { Configure } from '../config/configure';
|
||||||
import { ConfigStorageOption, ConfigureFactory } from '../config/types';
|
import { ConfigStorageOption, ConfigureFactory } from '../config/types';
|
||||||
|
|
||||||
@ -12,8 +8,6 @@ export type App = {
|
|||||||
container?: NestFastifyApplication;
|
container?: NestFastifyApplication;
|
||||||
|
|
||||||
configure: Configure;
|
configure: Configure;
|
||||||
|
|
||||||
commands: CommandModule<RecordAny, RecordAny>[];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface CreateOptions {
|
export interface CreateOptions {
|
||||||
@ -36,8 +30,6 @@ export interface CreateOptions {
|
|||||||
|
|
||||||
storage: ConfigStorageOption;
|
storage: ConfigStorageOption;
|
||||||
};
|
};
|
||||||
|
|
||||||
commands: () => CommandCollection;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ContainerBuilder {
|
export interface ContainerBuilder {
|
||||||
@ -60,8 +52,6 @@ export interface AppConfig {
|
|||||||
url?: string;
|
url?: string;
|
||||||
|
|
||||||
prefix?: string;
|
prefix?: string;
|
||||||
|
|
||||||
pm2?: Omit<StartOptions, 'name' | 'cwd' | 'script' | 'args' | 'interpreter' | 'watch'>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PanicOption {
|
export interface PanicOption {
|
||||||
@ -70,20 +60,4 @@ export interface PanicOption {
|
|||||||
error?: any;
|
error?: any;
|
||||||
|
|
||||||
exit?: boolean;
|
exit?: boolean;
|
||||||
|
|
||||||
spinner?: Ora;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CommandOption<T = RecordAny, P = RecordAny> extends CommandModule<T, P> {
|
|
||||||
instant?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type CommandItem<T = RecordAny, P = RecordAny> = (
|
|
||||||
app: Required<App>,
|
|
||||||
) => Promise<CommandOption<T, P>>;
|
|
||||||
|
|
||||||
export type CommandCollection = Array<CommandItem<any, any>>;
|
|
||||||
|
|
||||||
export interface CreateOption {
|
|
||||||
commands: () => CommandCollection;
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { Optional } from '@nestjs/common';
|
import { Optional } from '@nestjs/common';
|
||||||
import { NestFastifyApplication } from '@nestjs/platform-fastify';
|
|
||||||
import { isNil } from 'lodash';
|
import { isNil } from 'lodash';
|
||||||
import {
|
import {
|
||||||
DataSource,
|
DataSource,
|
||||||
@ -18,10 +17,6 @@ import {
|
|||||||
UpdateEvent,
|
UpdateEvent,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
|
|
||||||
import { Configure } from '@/modules/config/configure';
|
|
||||||
|
|
||||||
import { app } from '@/modules/core/helpers/app';
|
|
||||||
|
|
||||||
import { RepositoryType } from '../types';
|
import { RepositoryType } from '../types';
|
||||||
import { getCustomRepository } from '../utils';
|
import { getCustomRepository } from '../utils';
|
||||||
|
|
||||||
@ -41,25 +36,12 @@ export abstract class BaseSubscriber<T extends ObjectLiteral>
|
|||||||
{
|
{
|
||||||
protected abstract entity: ObjectType<T>;
|
protected abstract entity: ObjectType<T>;
|
||||||
|
|
||||||
protected constructor(
|
protected constructor(@Optional() protected dataSource?: DataSource) {
|
||||||
@Optional() protected dataSource?: DataSource,
|
|
||||||
@Optional() protected _configure?: Configure,
|
|
||||||
) {
|
|
||||||
if (!isNil(this.dataSource)) {
|
if (!isNil(this.dataSource)) {
|
||||||
this.dataSource.subscribers.push(this);
|
this.dataSource.subscribers.push(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get configure() {
|
|
||||||
return isNil(this._configure)
|
|
||||||
? this.container.get(Configure, { strict: false })
|
|
||||||
: this._configure;
|
|
||||||
}
|
|
||||||
|
|
||||||
get container(): NestFastifyApplication {
|
|
||||||
return app.container;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected getDataSource(event: SubscriberEvent<T>) {
|
protected getDataSource(event: SubscriberEvent<T>) {
|
||||||
return this.dataSource ?? event.connection;
|
return this.dataSource ?? event.connection;
|
||||||
}
|
}
|
||||||
|
@ -1,34 +0,0 @@
|
|||||||
import { Arguments } from 'yargs';
|
|
||||||
|
|
||||||
import { CommandItem } from '@/modules/core/types';
|
|
||||||
import { MigrationCreateHandler } from '@/modules/database/commands/migration.create.handler';
|
|
||||||
import { MigrationCreateArguments } from '@/modules/database/commands/types';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建迁移
|
|
||||||
*
|
|
||||||
* @param configure
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
export const CreateMigrationCommand: CommandItem<any, MigrationCreateArguments> = async ({
|
|
||||||
configure,
|
|
||||||
}) => ({
|
|
||||||
source: true,
|
|
||||||
command: [],
|
|
||||||
describe: 'Creates a new migration file',
|
|
||||||
builder: {
|
|
||||||
connection: {
|
|
||||||
type: 'string',
|
|
||||||
alias: 'c',
|
|
||||||
describe: 'Connection name of typeorm to connect database.',
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
type: 'string',
|
|
||||||
alias: 'n',
|
|
||||||
describe: 'Name of the migration class.',
|
|
||||||
demandOption: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
handler: async (args: Arguments<MigrationCreateArguments>) =>
|
|
||||||
MigrationCreateHandler(configure, args),
|
|
||||||
});
|
|
@ -1,38 +0,0 @@
|
|||||||
import chalk from 'chalk';
|
|
||||||
import { isNil } from 'lodash';
|
|
||||||
import ora from 'ora';
|
|
||||||
import { Arguments } from 'yargs';
|
|
||||||
|
|
||||||
import { Configure } from '@/modules/config/configure';
|
|
||||||
import { panic } from '@/modules/core/helpers';
|
|
||||||
import { TypeormMigrationCreate } from '@/modules/database/commands/typeorm.migration.create';
|
|
||||||
import { MigrationCreateArguments } from '@/modules/database/commands/types';
|
|
||||||
import { DBOptions, TypeormOption } from '@/modules/database/types';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建迁移处理器
|
|
||||||
*
|
|
||||||
* @param configure
|
|
||||||
* @param args
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
export async function MigrationCreateHandler(
|
|
||||||
configure: Configure,
|
|
||||||
args: Arguments<MigrationCreateArguments>,
|
|
||||||
) {
|
|
||||||
const spinner = ora('start to create migration').start();
|
|
||||||
const cname = args.connection ?? 'default';
|
|
||||||
try {
|
|
||||||
const { connections = [] } = await configure.get<DBOptions>('database');
|
|
||||||
const dbConfig: TypeormOption = connections.find(({ name }) => name === cname);
|
|
||||||
if (isNil(dbConfig)) {
|
|
||||||
await panic(`database connection ${cname} not found`);
|
|
||||||
}
|
|
||||||
const runner = new TypeormMigrationCreate();
|
|
||||||
console.log();
|
|
||||||
await runner.handler({ name: cname, dir: dbConfig.paths.migration });
|
|
||||||
spinner.start(chalk.greenBright.underline('\n 👍 Finished create migration'));
|
|
||||||
} catch (e) {
|
|
||||||
await panic({ spinner, message: 'Create migration failed!', error: e });
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,58 +0,0 @@
|
|||||||
import { Arguments } from 'yargs';
|
|
||||||
|
|
||||||
import { CommandItem } from '@/modules/core/types';
|
|
||||||
import { MigrationGenerateHandler } from '@/modules/database/commands/migration.generate.handler';
|
|
||||||
import { MigrationGenerateArguments } from '@/modules/database/commands/types';
|
|
||||||
|
|
||||||
export const GenerateMigrationCommand: CommandItem<any, MigrationGenerateArguments> = async ({
|
|
||||||
configure,
|
|
||||||
}) => ({
|
|
||||||
instant: true,
|
|
||||||
command: ['db:migration:generate', 'dbmg'],
|
|
||||||
describe: 'Auto generates a new migration file with sql needs to be executed to update schema.',
|
|
||||||
builder: {
|
|
||||||
connection: {
|
|
||||||
type: 'string',
|
|
||||||
alias: 'c',
|
|
||||||
describe: 'Connection name of typeorm to connect database.',
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
type: 'string',
|
|
||||||
alias: 'n',
|
|
||||||
describe: 'Name of the migration class.',
|
|
||||||
},
|
|
||||||
run: {
|
|
||||||
type: 'boolean',
|
|
||||||
alias: 'r',
|
|
||||||
describe: 'Run migration after generated.',
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
dir: {
|
|
||||||
type: 'string',
|
|
||||||
alias: 'd',
|
|
||||||
describe: 'Which directory where migration should be generated.',
|
|
||||||
},
|
|
||||||
pretty: {
|
|
||||||
type: 'boolean',
|
|
||||||
alias: 'p',
|
|
||||||
describe: 'Pretty-print generated SQL',
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
dryrun: {
|
|
||||||
type: 'boolean',
|
|
||||||
alias: 'dr',
|
|
||||||
describe: 'Prints out the contents of the migration instead of writing it to a file',
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
check: {
|
|
||||||
type: 'boolean',
|
|
||||||
alias: 'ch',
|
|
||||||
describe:
|
|
||||||
'Verifies that the current database is up to date and that no migrations are needed. Otherwise exits with code 1.',
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
} as const,
|
|
||||||
|
|
||||||
handler: async (args: Arguments<MigrationGenerateArguments>) =>
|
|
||||||
MigrationGenerateHandler(configure, args),
|
|
||||||
});
|
|
@ -1,52 +0,0 @@
|
|||||||
import chalk from 'chalk';
|
|
||||||
import { isNil, pick } from 'lodash';
|
|
||||||
import ora from 'ora';
|
|
||||||
import { DataSource, DataSourceOptions } from 'typeorm';
|
|
||||||
import { Arguments } from 'yargs';
|
|
||||||
|
|
||||||
import { Configure } from '@/modules/config/configure';
|
|
||||||
import { getRandomString, panic } from '@/modules/core/helpers';
|
|
||||||
import { MigrationRunHandler } from '@/modules/database/commands/migration.run.handler';
|
|
||||||
import { TypeormMigrationGenerate } from '@/modules/database/commands/typeorm.migration.generate';
|
|
||||||
import { MigrationGenerateArguments } from '@/modules/database/commands/types';
|
|
||||||
|
|
||||||
import { DBOptions } from '../types';
|
|
||||||
|
|
||||||
export async function MigrationGenerateHandler(
|
|
||||||
configure: Configure,
|
|
||||||
args: Arguments<MigrationGenerateArguments>,
|
|
||||||
) {
|
|
||||||
await MigrationRunHandler(configure, { connection: args.connection } as any);
|
|
||||||
console.log();
|
|
||||||
const spinner = ora('Start to generate migration');
|
|
||||||
const cname = args.connection ?? 'default';
|
|
||||||
try {
|
|
||||||
spinner.start();
|
|
||||||
console.log();
|
|
||||||
const { connections = [] }: DBOptions = await configure.get<DBOptions>('database');
|
|
||||||
const dbConfig = connections.find(({ name }) => name === cname);
|
|
||||||
if (isNil(dbConfig)) {
|
|
||||||
await panic(`Database connection named ${cname} not exists!`);
|
|
||||||
}
|
|
||||||
console.log();
|
|
||||||
const runner = new TypeormMigrationGenerate();
|
|
||||||
const dataSource = new DataSource({ ...dbConfig } as DataSourceOptions);
|
|
||||||
console.log();
|
|
||||||
await runner.handler({
|
|
||||||
name: args.name ?? getRandomString(6),
|
|
||||||
dir: dbConfig.paths.migration,
|
|
||||||
dataSource,
|
|
||||||
...pick(args, ['pretty', 'outputJs', 'dryrun', 'check']),
|
|
||||||
});
|
|
||||||
if (dataSource.isInitialized) {
|
|
||||||
await dataSource.destroy();
|
|
||||||
}
|
|
||||||
spinner.succeed(chalk.greenBright.underline('\n 👍 Finished generate migration'));
|
|
||||||
if (args.run) {
|
|
||||||
console.log();
|
|
||||||
await MigrationRunHandler(configure, { connection: args.connection } as any);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
await panic({ spinner, message: 'Generate migration failed!', error });
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,53 +0,0 @@
|
|||||||
import { Arguments } from 'yargs';
|
|
||||||
|
|
||||||
import { CommandItem } from '@/modules/core/types';
|
|
||||||
import { MigrationRunHandler } from '@/modules/database/commands/migration.run.handler';
|
|
||||||
import { MigrationRunArguments } from '@/modules/database/commands/types';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 运行迁移
|
|
||||||
* @param configure
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
export const RunMigrationCommand: CommandItem<any, MigrationRunArguments> = async ({
|
|
||||||
configure,
|
|
||||||
}) => ({
|
|
||||||
source: true,
|
|
||||||
command: ['db:migration:run', 'dbmr'],
|
|
||||||
describe: 'Runs all pending migrations.',
|
|
||||||
builder: {
|
|
||||||
connection: {
|
|
||||||
type: 'string',
|
|
||||||
alias: 'c',
|
|
||||||
describe: 'Connection name of typeorm to connect database.',
|
|
||||||
},
|
|
||||||
transaction: {
|
|
||||||
type: 'string',
|
|
||||||
alias: 't',
|
|
||||||
describe:
|
|
||||||
'Indicates if transaction should be used or not for migration run/revert/reflash. Enabled by default.',
|
|
||||||
default: 'default',
|
|
||||||
},
|
|
||||||
fake: {
|
|
||||||
type: 'boolean',
|
|
||||||
alias: 'f',
|
|
||||||
describe:
|
|
||||||
'Fakes running the migrations if table schema has already been changed manually or externally ' +
|
|
||||||
'(e.g. through another project)',
|
|
||||||
},
|
|
||||||
refresh: {
|
|
||||||
type: 'boolean',
|
|
||||||
alias: 'r',
|
|
||||||
describe: 'drop database schema and run migration',
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
|
|
||||||
onlydrop: {
|
|
||||||
type: 'boolean',
|
|
||||||
alias: 'o',
|
|
||||||
describe: 'only drop database schema',
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
} as const,
|
|
||||||
handler: async (args: Arguments<MigrationRunArguments>) => MigrationRunHandler(configure, args),
|
|
||||||
});
|
|
@ -1,75 +0,0 @@
|
|||||||
import { join } from 'path';
|
|
||||||
|
|
||||||
import chalk from 'chalk';
|
|
||||||
import { isNil } from 'lodash';
|
|
||||||
import ora from 'ora';
|
|
||||||
import { DataSource, DataSourceOptions } from 'typeorm';
|
|
||||||
import { Arguments } from 'yargs';
|
|
||||||
|
|
||||||
import { Configure } from '@/modules/config/configure';
|
|
||||||
import { panic } from '@/modules/core/helpers';
|
|
||||||
import { TypeormMigrationRun } from '@/modules/database/commands/typeorm.migration.run';
|
|
||||||
import { MigrationRunArguments } from '@/modules/database/commands/types';
|
|
||||||
import { DBOptions } from '@/modules/database/types';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 运行迁移处理器
|
|
||||||
* @param configure
|
|
||||||
* @param args
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
export async function MigrationRunHandler(
|
|
||||||
configure: Configure,
|
|
||||||
args: Arguments<MigrationRunArguments>,
|
|
||||||
) {
|
|
||||||
const spinner = ora('Start to run migration...');
|
|
||||||
const cname = args.connection ?? 'default';
|
|
||||||
let dataSource: DataSource | undefined;
|
|
||||||
try {
|
|
||||||
spinner.start();
|
|
||||||
const { connections = [] }: DBOptions = await configure.get<DBOptions>('database');
|
|
||||||
const dbConfig = connections.find(({ name }) => name === cname);
|
|
||||||
if (isNil(dbConfig)) {
|
|
||||||
await panic(`Database connection named ${cname} not exists!`);
|
|
||||||
}
|
|
||||||
const dropSchema = args.refresh || args.onlydrop;
|
|
||||||
console.log();
|
|
||||||
const runner = new TypeormMigrationRun();
|
|
||||||
dataSource = new DataSource({ ...dbConfig } as DataSourceOptions);
|
|
||||||
if (dataSource && dataSource.isInitialized) {
|
|
||||||
await dataSource.destroy();
|
|
||||||
}
|
|
||||||
const options = {
|
|
||||||
subscribers: [],
|
|
||||||
synchronize: false,
|
|
||||||
migrationsRun: false,
|
|
||||||
dropSchema,
|
|
||||||
logging: ['error'],
|
|
||||||
migrations: [
|
|
||||||
join(dbConfig.paths.migration, '**/*.ts'),
|
|
||||||
join(dbConfig.paths.migration, '**/*.js'),
|
|
||||||
],
|
|
||||||
} as any;
|
|
||||||
if (dropSchema) {
|
|
||||||
dataSource.setOptions(options);
|
|
||||||
await dataSource.initialize();
|
|
||||||
await dataSource.destroy();
|
|
||||||
spinner.succeed(chalk.greenBright.underline('\n 👍 Finished drop database schema'));
|
|
||||||
if (args.onlydrop) {
|
|
||||||
process.exit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dataSource.setOptions({ ...options, dropSchema: false });
|
|
||||||
await dataSource.initialize();
|
|
||||||
console.log();
|
|
||||||
await runner.handler({ dataSource, transaction: args.transaction, fake: args.fake });
|
|
||||||
spinner.succeed(chalk.greenBright.underline('\n 👍 Finished run migrations'));
|
|
||||||
} catch (error) {
|
|
||||||
await panic({ spinner, message: 'Run migrations failed!', error });
|
|
||||||
} finally {
|
|
||||||
if (dataSource && dataSource.isInitialized) {
|
|
||||||
await dataSource.destroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
import { resolve } from 'path';
|
|
||||||
|
|
||||||
import chalk from 'chalk';
|
|
||||||
import { CommandUtils } from 'typeorm/commands/CommandUtils';
|
|
||||||
import { PlatformTools } from 'typeorm/platform/PlatformTools';
|
|
||||||
import { camelCase } from 'typeorm/util/StringUtils';
|
|
||||||
|
|
||||||
import { MigrationCreateOptions } from '@/modules/database/commands/types';
|
|
||||||
|
|
||||||
type HandleOptions = MigrationCreateOptions & { dir: string };
|
|
||||||
|
|
||||||
export class TypeormMigrationCreate {
|
|
||||||
async handler(args: HandleOptions) {
|
|
||||||
try {
|
|
||||||
const timestamp = new Date().getTime();
|
|
||||||
const directory = args.dir.startsWith('/')
|
|
||||||
? args.dir
|
|
||||||
: resolve(process.cwd(), args.dir);
|
|
||||||
const fileContent = TypeormMigrationCreate.getTemplate(args.name, timestamp);
|
|
||||||
const fileName = `${timestamp}-${args.name}`;
|
|
||||||
const filePath = `${directory}/${fileName}`;
|
|
||||||
await CommandUtils.createFile(`${filePath}.ts`, fileContent);
|
|
||||||
console.log(
|
|
||||||
`Migration ${chalk.blue(`${filePath}.ts`)} has been generated successfully.`,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
PlatformTools.logCmdErr('Error during migration creation:', e);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static getTemplate(name: string, timestamp: number): string {
|
|
||||||
return `import typeorm = require('typeorm');
|
|
||||||
class ${camelCase(name, true)}${timestamp} implements typeorm.MigrationInterface {
|
|
||||||
|
|
||||||
public async up(queryRunner: typeorm.QueryRunner): Promise<void> {
|
|
||||||
}
|
|
||||||
|
|
||||||
public async down(queryRunner: typeorm.QueryRunner): Promise<void> {
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,152 +0,0 @@
|
|||||||
import { resolve } from 'path';
|
|
||||||
|
|
||||||
import chalk from 'chalk';
|
|
||||||
import { upperFirst } from 'lodash';
|
|
||||||
import { format } from 'mysql2';
|
|
||||||
import { DataSource } from 'typeorm';
|
|
||||||
|
|
||||||
import { CommandUtils } from 'typeorm/commands/CommandUtils';
|
|
||||||
import { PlatformTools } from 'typeorm/platform/PlatformTools';
|
|
||||||
|
|
||||||
import { camelCase } from 'typeorm/util/StringUtils';
|
|
||||||
|
|
||||||
import { MigrationGenerateOptions } from '@/modules/database/commands/types';
|
|
||||||
|
|
||||||
type HandlerOptions = MigrationGenerateOptions & { dataSource: DataSource } & { dir: string };
|
|
||||||
|
|
||||||
export class TypeormMigrationGenerate {
|
|
||||||
async handler(args: HandlerOptions) {
|
|
||||||
const timestamp = new Date().getTime();
|
|
||||||
const fileExt = '.ts';
|
|
||||||
const directory = args.dir.startsWith('/') ? args.dir : resolve(process.cwd(), args.dir);
|
|
||||||
const filename = `${timestamp}-${args.name}`;
|
|
||||||
const filePath = `${directory}/${filename}${fileExt}`;
|
|
||||||
const { dataSource } = args;
|
|
||||||
|
|
||||||
try {
|
|
||||||
dataSource.setOptions({
|
|
||||||
synchronize: false,
|
|
||||||
migrationsRun: false,
|
|
||||||
dropSchema: false,
|
|
||||||
logging: false,
|
|
||||||
});
|
|
||||||
await dataSource.initialize();
|
|
||||||
const upSqls: string[] = [];
|
|
||||||
const downSqls: string[] = [];
|
|
||||||
|
|
||||||
try {
|
|
||||||
const sqlInMemory = await dataSource.driver.createSchemaBuilder().log();
|
|
||||||
if (args.pretty) {
|
|
||||||
sqlInMemory.upQueries.forEach((upSql) => {
|
|
||||||
upSql.query = TypeormMigrationGenerate.prettifyQuery(upSql.query);
|
|
||||||
});
|
|
||||||
sqlInMemory.downQueries.forEach((downSql) => {
|
|
||||||
downSql.query = TypeormMigrationGenerate.prettifyQuery(downSql.query);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
sqlInMemory.upQueries.forEach((upQuery) => {
|
|
||||||
upSqls.push(
|
|
||||||
` await queryRunner.query(\`${upQuery.query.replace(
|
|
||||||
/`/g,
|
|
||||||
'\\`',
|
|
||||||
)}\`${TypeormMigrationGenerate.queryParams(upQuery.parameters)});`,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
sqlInMemory.downQueries.forEach((downQuery) => {
|
|
||||||
downSqls.push(
|
|
||||||
` await queryRunner.query(\`${downQuery.query.replace(
|
|
||||||
/`/g,
|
|
||||||
'\\`',
|
|
||||||
)}\`${TypeormMigrationGenerate.queryParams(downQuery.parameters)});`,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
await dataSource.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!upSqls.length) {
|
|
||||||
console.log(chalk.green(`No changes in database schema were found`));
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
const fileContent = TypeormMigrationGenerate.getTemplate(
|
|
||||||
args.name,
|
|
||||||
timestamp,
|
|
||||||
upSqls,
|
|
||||||
downSqls.reverse(),
|
|
||||||
);
|
|
||||||
if (args.check) {
|
|
||||||
console.log(
|
|
||||||
chalk.yellow(
|
|
||||||
`Unexpected changes in database schema were found in check mode:\n\n${chalk.white(
|
|
||||||
fileContent,
|
|
||||||
)}`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args.dryrun) {
|
|
||||||
console.log(
|
|
||||||
chalk.green(
|
|
||||||
`Migration ${chalk.blue(
|
|
||||||
filePath,
|
|
||||||
)} has content:\n\n${chalk.white(fileContent)}`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
await CommandUtils.createFile(filePath, fileContent);
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
chalk.green(
|
|
||||||
`Migration ${chalk.blue(filePath)} has been generated successfully.`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
PlatformTools.logCmdErr('Error during migration generation:', e);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static queryParams(params: any[] | undefined): string {
|
|
||||||
if (!params || !params.length) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
return `,${JSON.stringify(params)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static prettifyQuery(query: string) {
|
|
||||||
const formatQuery = format(query, { indent: ' ' });
|
|
||||||
return `\n${formatQuery.replace(/^/gm, ' ')}\n `;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static getTemplate(
|
|
||||||
name: string,
|
|
||||||
timestamp: number,
|
|
||||||
upSqls: string[],
|
|
||||||
downSqls: string[],
|
|
||||||
): string {
|
|
||||||
const migrationName = `${camelCase(upperFirst(name), true)}${timestamp}`;
|
|
||||||
|
|
||||||
return `import typeorm = require('typeorm');
|
|
||||||
|
|
||||||
class ${migrationName} implements typeorm.MigrationInterface {
|
|
||||||
name = '${migrationName}'
|
|
||||||
|
|
||||||
public async up(queryRunner: typeorm.QueryRunner): Promise<void> {
|
|
||||||
${upSqls.join(`
|
|
||||||
`)}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async down(queryRunner: typeorm.QueryRunner): Promise<void> {
|
|
||||||
${downSqls.join(`
|
|
||||||
`)}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = ${migrationName}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
import { DataSource } from 'typeorm';
|
|
||||||
|
|
||||||
import { MigrationRunOptions } from '@/modules/database/commands/types';
|
|
||||||
|
|
||||||
type HandlerOptions = MigrationRunOptions & { dataSource: DataSource };
|
|
||||||
|
|
||||||
export class TypeormMigrationRun {
|
|
||||||
async handler({ transaction, fake, dataSource }: HandlerOptions) {
|
|
||||||
const options = {
|
|
||||||
transaction: dataSource.options.migrationsTransactionMode ?? 'all',
|
|
||||||
fake,
|
|
||||||
};
|
|
||||||
switch (transaction) {
|
|
||||||
case 'all':
|
|
||||||
options.transaction = 'all';
|
|
||||||
break;
|
|
||||||
case 'none':
|
|
||||||
case 'false':
|
|
||||||
options.transaction = 'none';
|
|
||||||
break;
|
|
||||||
case 'each':
|
|
||||||
options.transaction = 'each';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
await dataSource.runMigrations(options);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,57 +0,0 @@
|
|||||||
import { Arguments } from 'yargs';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 基础数据库命令参数类型
|
|
||||||
*/
|
|
||||||
export type TypeOrmArguments = Arguments<{ connection?: string }>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建迁移命令参数
|
|
||||||
*/
|
|
||||||
export type MigrationCreateArguments = TypeOrmArguments & MigrationCreateOptions;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建迁移处理器选项
|
|
||||||
*/
|
|
||||||
export interface MigrationCreateOptions {
|
|
||||||
name?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 生成迁移命令参数
|
|
||||||
*/
|
|
||||||
export type MigrationGenerateArguments = TypeOrmArguments & MigrationGenerateOptions;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 生成迁移处理器选项
|
|
||||||
*/
|
|
||||||
export interface MigrationGenerateOptions {
|
|
||||||
name?: string;
|
|
||||||
run?: boolean;
|
|
||||||
pretty?: boolean;
|
|
||||||
dryrun?: boolean;
|
|
||||||
check?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 运行迁移的命令参数
|
|
||||||
*/
|
|
||||||
export type MigrationRunArguments = TypeOrmArguments & MigrationRunOptions;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 运行迁移处理器选项
|
|
||||||
*/
|
|
||||||
export interface MigrationRunOptions extends MigrationRevertOptions {
|
|
||||||
refresh?: boolean;
|
|
||||||
|
|
||||||
onlydrop?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 恢复迁移处理器选项
|
|
||||||
*/
|
|
||||||
export interface MigrationRevertOptions {
|
|
||||||
transaction?: string;
|
|
||||||
|
|
||||||
fake?: boolean;
|
|
||||||
}
|
|
@ -1,5 +1,3 @@
|
|||||||
import { resolve } from 'path';
|
|
||||||
|
|
||||||
import { ConfigureFactory, ConfigureRegister } from '../config/types';
|
import { ConfigureFactory, ConfigureRegister } from '../config/types';
|
||||||
import { createConnectionOptions } from '../config/utils';
|
import { createConnectionOptions } from '../config/utils';
|
||||||
import { deepMerge } from '../core/helpers';
|
import { deepMerge } from '../core/helpers';
|
||||||
@ -20,11 +18,7 @@ export const createDBConfig: (
|
|||||||
export const createDBOptions = (options: DBConfig) => {
|
export const createDBOptions = (options: DBConfig) => {
|
||||||
const newOptions: DBOptions = {
|
const newOptions: DBOptions = {
|
||||||
common: deepMerge(
|
common: deepMerge(
|
||||||
{
|
{ charset: 'utf8mb4', logging: ['error'] },
|
||||||
charset: 'utf8mb4',
|
|
||||||
logging: ['error'],
|
|
||||||
paths: { migration: resolve(__dirname, '../../database/migrations') },
|
|
||||||
},
|
|
||||||
options.common ?? {},
|
options.common ?? {},
|
||||||
'replace',
|
'replace',
|
||||||
),
|
),
|
||||||
@ -35,7 +29,7 @@ export const createDBOptions = (options: DBConfig) => {
|
|||||||
const newOption = { ...connection, entities };
|
const newOption = { ...connection, entities };
|
||||||
return deepMerge(
|
return deepMerge(
|
||||||
newOptions.common,
|
newOptions.common,
|
||||||
{ ...newOption, autoLoadEntities: true, synchronize: false } as any,
|
{ ...newOption, autoLoadEntities: true } as any,
|
||||||
'replace',
|
'replace',
|
||||||
) as TypeormOption;
|
) as TypeormOption;
|
||||||
});
|
});
|
||||||
|
@ -17,27 +17,12 @@ export enum SelectTrashMode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum OrderType {
|
export enum OrderType {
|
||||||
/**
|
|
||||||
* 升序排序
|
|
||||||
*/
|
|
||||||
ASC = 'ASC',
|
ASC = 'ASC',
|
||||||
/**
|
|
||||||
* 降序排序
|
|
||||||
*/
|
|
||||||
DESC = 'DESC',
|
DESC = 'DESC',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum TreeChildrenResolve {
|
export enum TreeChildrenResolve {
|
||||||
/**
|
|
||||||
* 子信息删除
|
|
||||||
*/
|
|
||||||
DELETE = 'delete',
|
DELETE = 'delete',
|
||||||
/**
|
|
||||||
* 子信息上升层级
|
|
||||||
*/
|
|
||||||
UP = 'up',
|
UP = 'up',
|
||||||
/**
|
|
||||||
* 子信息上升到顶层
|
|
||||||
*/
|
|
||||||
ROOT = 'root',
|
ROOT = 'root',
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,8 @@ import { TypeOrmModuleOptions } from '@nestjs/typeorm';
|
|||||||
import {
|
import {
|
||||||
FindTreeOptions,
|
FindTreeOptions,
|
||||||
ObjectLiteral,
|
ObjectLiteral,
|
||||||
Repository,
|
|
||||||
SelectQueryBuilder,
|
SelectQueryBuilder,
|
||||||
|
Repository,
|
||||||
TreeRepository,
|
TreeRepository,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
|
|
||||||
@ -70,21 +70,13 @@ export type RepositoryType<T extends ObjectLiteral> =
|
|||||||
| BaseTreeRepository<T>;
|
| BaseTreeRepository<T>;
|
||||||
|
|
||||||
export type DBConfig = {
|
export type DBConfig = {
|
||||||
common: RecordAny & DBAdditionalOption;
|
common: RecordAny;
|
||||||
connections: Array<TypeOrmModuleOptions & { name?: string }>;
|
connections: Array<TypeOrmModuleOptions & { name?: string }>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TypeormOption = Omit<TypeOrmModuleOptions, 'name' | 'migrations'> & {
|
export type TypeormOption = Omit<TypeOrmModuleOptions, 'name' | 'migrations'> & { name: string };
|
||||||
name: string;
|
|
||||||
} & DBAdditionalOption;
|
|
||||||
|
|
||||||
export type DBOptions = RecordAny & {
|
export type DBOptions = RecordAny & {
|
||||||
common: RecordAny;
|
common: RecordAny;
|
||||||
connections: TypeormOption[];
|
connections: TypeormOption[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type DBAdditionalOption = {
|
|
||||||
paths?: {
|
|
||||||
migration?: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
@ -1,16 +1,7 @@
|
|||||||
import { Type } from '@nestjs/common';
|
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
|
||||||
import { EntityClassOrSchema } from '@nestjs/typeorm/dist/interfaces/entity-class-or-schema.type';
|
|
||||||
import { isArray, isNil } from 'lodash';
|
import { isArray, isNil } from 'lodash';
|
||||||
import { DataSource, ObjectLiteral, ObjectType, Repository, SelectQueryBuilder } from 'typeorm';
|
import { DataSource, ObjectLiteral, ObjectType, Repository, SelectQueryBuilder } from 'typeorm';
|
||||||
|
|
||||||
import { Configure } from '@/modules/config/configure';
|
import { OrderQueryType, PaginateOptions, PaginateReturn } from '@/modules/database/types';
|
||||||
import {
|
|
||||||
DBOptions,
|
|
||||||
OrderQueryType,
|
|
||||||
PaginateOptions,
|
|
||||||
PaginateReturn,
|
|
||||||
} from '@/modules/database/types';
|
|
||||||
|
|
||||||
import { CUSTOM_REPOSITORY_METADATA } from './constants';
|
import { CUSTOM_REPOSITORY_METADATA } from './constants';
|
||||||
|
|
||||||
@ -106,48 +97,3 @@ export const getCustomRepository = <P extends Repository<T>, T extends ObjectLit
|
|||||||
const base = dataSource.getRepository<ObjectType<any>>(entity);
|
const base = dataSource.getRepository<ObjectType<any>>(entity);
|
||||||
return new Repo(base.target, base.manager, base.queryRunner) as P;
|
return new Repo(base.target, base.manager, base.queryRunner) as P;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const addEntities = async (
|
|
||||||
configure: Configure,
|
|
||||||
entities: EntityClassOrSchema[] = [],
|
|
||||||
dataSource = 'default',
|
|
||||||
) => {
|
|
||||||
const database = await configure.get<DBOptions>('database');
|
|
||||||
if (isNil(database)) {
|
|
||||||
throw new Error('Database not exists');
|
|
||||||
}
|
|
||||||
const dbConfig = database.connections.find(({ name }) => name === dataSource);
|
|
||||||
if (isNil(dbConfig)) {
|
|
||||||
throw new Error(`Database connection ${dataSource} not exists`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const oldEntities = (dbConfig.entities ?? []) as ObjectLiteral[];
|
|
||||||
const newEntities = database.connections.map((conn) =>
|
|
||||||
conn.name === dataSource ? { ...conn, entities: [...oldEntities, ...entities] } : conn,
|
|
||||||
);
|
|
||||||
configure.set('database.connections', newEntities);
|
|
||||||
return TypeOrmModule.forFeature(entities, dataSource);
|
|
||||||
};
|
|
||||||
|
|
||||||
export async function addSubscribers(
|
|
||||||
configure: Configure,
|
|
||||||
subscribers: Type<any>[] = [],
|
|
||||||
dataSource = 'default',
|
|
||||||
) {
|
|
||||||
const database = await configure.get<DBOptions>('database');
|
|
||||||
if (isNil(database)) {
|
|
||||||
throw new Error('Database not exists');
|
|
||||||
}
|
|
||||||
const dbConfig = database.connections.find(({ name }) => name === dataSource);
|
|
||||||
if (isNil(dbConfig)) {
|
|
||||||
throw new Error(`Database connection ${dataSource} not exists`);
|
|
||||||
}
|
|
||||||
const oldSubscribers = (dbConfig.subscribers ?? []) as any[];
|
|
||||||
const newSubscribers = database.connections.map((conn) =>
|
|
||||||
conn.name === dataSource
|
|
||||||
? { ...conn, subscribers: [...oldSubscribers, subscribers] }
|
|
||||||
: conn,
|
|
||||||
);
|
|
||||||
configure.set('database.connections', newSubscribers);
|
|
||||||
return subscribers;
|
|
||||||
}
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { ConfigureFactory, ConfigureRegister } from '../config/types';
|
import { ConfigureFactory, ConfigureRegister } from '../config/types';
|
||||||
import { createConnectionOptions } from '../config/utils';
|
import { createConnectionOptions } from '../config/utils';
|
||||||
|
|
||||||
import type { MeiliConfig } from './types';
|
import { MeiliConfig } from './types';
|
||||||
|
|
||||||
export const createMeiliConfig: (
|
export const createMeiliConfig: (
|
||||||
registre: ConfigureRegister<RePartial<MeiliConfig>>,
|
registre: ConfigureRegister<RePartial<MeiliConfig>>,
|
||||||
|
@ -3,7 +3,7 @@ import { Injectable } from '@nestjs/common';
|
|||||||
import { isNil } from 'lodash';
|
import { isNil } from 'lodash';
|
||||||
import { MeiliSearch } from 'meilisearch';
|
import { MeiliSearch } from 'meilisearch';
|
||||||
|
|
||||||
import type { MeiliConfig } from '@/modules/meilisearch/types';
|
import { MeiliConfig } from '@/modules/meilisearch/types';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MeiliService {
|
export class MeiliService {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Transform } from 'class-transformer';
|
import { Transform } from 'class-transformer';
|
||||||
import { IsNumber, IsOptional, Min } from 'class-validator';
|
import { Min, IsNumber, IsOptional } 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';
|
||||||
@ -14,7 +14,7 @@ export class PaginateDto implements PaginateOptions {
|
|||||||
* 当前页
|
* 当前页
|
||||||
*/
|
*/
|
||||||
@Transform(({ value }) => toNumber(value))
|
@Transform(({ value }) => toNumber(value))
|
||||||
@Min(1, { message: 'The current page must be greater than 1.' })
|
@Min(1, { message: '当前页必须大于1' })
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
page?: number = 1;
|
page?: number = 1;
|
||||||
@ -23,7 +23,7 @@ export class PaginateDto implements PaginateOptions {
|
|||||||
* 每页数据量
|
* 每页数据量
|
||||||
*/
|
*/
|
||||||
@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, { message: '每页显示数据必须大于1' })
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
limit?: number = 10;
|
limit?: number = 10;
|
||||||
|
@ -17,7 +17,6 @@ import { RestfulModule } from './modules/restful/restful.module';
|
|||||||
import { ApiConfig } from './modules/restful/types';
|
import { ApiConfig } from './modules/restful/types';
|
||||||
|
|
||||||
export const createOptions: CreateOptions = {
|
export const createOptions: CreateOptions = {
|
||||||
commands: () => [],
|
|
||||||
config: { factories: configs as any, storage: { enable: true } },
|
config: { factories: configs as any, storage: { enable: true } },
|
||||||
modules: async (configure) => [
|
modules: async (configure) => [
|
||||||
DatabaseModule.forRoot(configure),
|
DatabaseModule.forRoot(configure),
|
||||||
|
@ -21,8 +21,6 @@ import { createOptions } from '@/options';
|
|||||||
import { generateRandomNumber, generateUniqueRandomNumbers } from './generate-mock-data';
|
import { generateRandomNumber, generateUniqueRandomNumbers } from './generate-mock-data';
|
||||||
import { categoriesData, commentData, INIT_DATA, postData, tagData } from './test-data';
|
import { categoriesData, commentData, INIT_DATA, postData, tagData } from './test-data';
|
||||||
|
|
||||||
const URL_PREFIX = '/api/v1/content';
|
|
||||||
|
|
||||||
describe('nest app test', () => {
|
describe('nest app test', () => {
|
||||||
let datasource: DataSource;
|
let datasource: DataSource;
|
||||||
let app: NestFastifyApplication;
|
let app: NestFastifyApplication;
|
||||||
@ -81,7 +79,7 @@ describe('nest app test', () => {
|
|||||||
ids.map(async (id) => {
|
ids.map(async (id) => {
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
url: `${URL_PREFIX}/category/${id}`,
|
url: `/category/${id}`,
|
||||||
});
|
});
|
||||||
categories.push(result.json());
|
categories.push(result.json());
|
||||||
return result.json();
|
return result.json();
|
||||||
@ -121,7 +119,7 @@ describe('nest app test', () => {
|
|||||||
it('create category without name', async () => {
|
it('create category without name', async () => {
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${URL_PREFIX}/category`,
|
url: '/category',
|
||||||
body: {},
|
body: {},
|
||||||
});
|
});
|
||||||
expect(result.json()).toEqual({
|
expect(result.json()).toEqual({
|
||||||
@ -137,7 +135,7 @@ describe('nest app test', () => {
|
|||||||
it('create category with long name', async () => {
|
it('create category with long name', async () => {
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${URL_PREFIX}/category`,
|
url: '/category',
|
||||||
body: { name: 'A'.repeat(30) },
|
body: { name: 'A'.repeat(30) },
|
||||||
});
|
});
|
||||||
expect(result.json()).toEqual({
|
expect(result.json()).toEqual({
|
||||||
@ -151,7 +149,7 @@ describe('nest app test', () => {
|
|||||||
const rootCategory = categories.find((c) => !c.parent);
|
const rootCategory = categories.find((c) => !c.parent);
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${URL_PREFIX}/category`,
|
url: '/category',
|
||||||
body: { name: rootCategory.name },
|
body: { name: rootCategory.name },
|
||||||
});
|
});
|
||||||
expect(result.json()).toEqual({
|
expect(result.json()).toEqual({
|
||||||
@ -165,7 +163,7 @@ describe('nest app test', () => {
|
|||||||
const testData = categories.find((item) => !isNil(item.parent));
|
const testData = categories.find((item) => !isNil(item.parent));
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${URL_PREFIX}/category`,
|
url: '/category',
|
||||||
body: {
|
body: {
|
||||||
name: testData.name,
|
name: testData.name,
|
||||||
parent: testData.parent.id,
|
parent: testData.parent.id,
|
||||||
@ -181,7 +179,7 @@ describe('nest app test', () => {
|
|||||||
it('create category with invalid parent id format', async () => {
|
it('create category with invalid parent id format', async () => {
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${URL_PREFIX}/category`,
|
url: '/category',
|
||||||
body: {
|
body: {
|
||||||
name: 'New Category',
|
name: 'New Category',
|
||||||
parent: 'invalid-uuid',
|
parent: 'invalid-uuid',
|
||||||
@ -200,7 +198,7 @@ describe('nest app test', () => {
|
|||||||
it('create category with non-existent parent id', async () => {
|
it('create category with non-existent parent id', async () => {
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${URL_PREFIX}/category`,
|
url: '/category',
|
||||||
body: {
|
body: {
|
||||||
name: 'New Category',
|
name: 'New Category',
|
||||||
parent: '74e655b3-b69a-42ae-a101-41c224386e74',
|
parent: '74e655b3-b69a-42ae-a101-41c224386e74',
|
||||||
@ -216,7 +214,7 @@ describe('nest app test', () => {
|
|||||||
it('create category with negative custom order', async () => {
|
it('create category with negative custom order', async () => {
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${URL_PREFIX}/category`,
|
url: '/category',
|
||||||
body: {
|
body: {
|
||||||
name: 'New Category',
|
name: 'New Category',
|
||||||
customOrder: -1,
|
customOrder: -1,
|
||||||
@ -232,7 +230,7 @@ describe('nest app test', () => {
|
|||||||
it('create category with empty name', async () => {
|
it('create category with empty name', async () => {
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${URL_PREFIX}/category`,
|
url: '/category',
|
||||||
body: { name: '' },
|
body: { name: '' },
|
||||||
});
|
});
|
||||||
expect(result.json()).toEqual({
|
expect(result.json()).toEqual({
|
||||||
@ -245,7 +243,7 @@ describe('nest app test', () => {
|
|||||||
it('create category with whitespace name', async () => {
|
it('create category with whitespace name', async () => {
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${URL_PREFIX}/category`,
|
url: '/category',
|
||||||
body: { name: ' ' },
|
body: { name: ' ' },
|
||||||
});
|
});
|
||||||
expect(result.json()).toEqual({
|
expect(result.json()).toEqual({
|
||||||
@ -259,7 +257,7 @@ describe('nest app test', () => {
|
|||||||
const name = 'A'.repeat(25);
|
const name = 'A'.repeat(25);
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${URL_PREFIX}/category`,
|
url: '/category',
|
||||||
body: { name },
|
body: { name },
|
||||||
});
|
});
|
||||||
expect(result.statusCode).toEqual(201);
|
expect(result.statusCode).toEqual(201);
|
||||||
@ -267,14 +265,14 @@ describe('nest app test', () => {
|
|||||||
expect(category.name).toBe(name);
|
expect(category.name).toBe(name);
|
||||||
await app.inject({
|
await app.inject({
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
url: `${URL_PREFIX}/category/${result.json().id}`,
|
url: `/category/${result.json().id}`,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('create category with name one char over limit (26 chars)', async () => {
|
it('create category with name one char over limit (26 chars)', async () => {
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${URL_PREFIX}/category`,
|
url: '/category',
|
||||||
body: { name: 'A'.repeat(26) },
|
body: { name: 'A'.repeat(26) },
|
||||||
});
|
});
|
||||||
expect(result.json()).toEqual({
|
expect(result.json()).toEqual({
|
||||||
@ -288,7 +286,7 @@ describe('nest app test', () => {
|
|||||||
const rootCategory = categories.find((c) => !c.parent);
|
const rootCategory = categories.find((c) => !c.parent);
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${URL_PREFIX}/category`,
|
url: '/category',
|
||||||
body: { name: rootCategory.name },
|
body: { name: rootCategory.name },
|
||||||
});
|
});
|
||||||
expect(result.json()).toEqual({
|
expect(result.json()).toEqual({
|
||||||
@ -304,7 +302,7 @@ describe('nest app test', () => {
|
|||||||
|
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${URL_PREFIX}/category`,
|
url: '/category',
|
||||||
body: {
|
body: {
|
||||||
name: existingChild.name,
|
name: existingChild.name,
|
||||||
parent: parentCategory.id,
|
parent: parentCategory.id,
|
||||||
@ -324,7 +322,7 @@ describe('nest app test', () => {
|
|||||||
|
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${URL_PREFIX}/category`,
|
url: '/category',
|
||||||
body: {
|
body: {
|
||||||
name: childName,
|
name: childName,
|
||||||
parent: parent2.id,
|
parent: parent2.id,
|
||||||
@ -333,14 +331,14 @@ describe('nest app test', () => {
|
|||||||
expect(result.statusCode).toEqual(201);
|
expect(result.statusCode).toEqual(201);
|
||||||
await app.inject({
|
await app.inject({
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
url: `${URL_PREFIX}/category/${result.json().id}`,
|
url: `/category/${result.json().id}`,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('create category with parent set to null string', async () => {
|
it('create category with parent set to null string', async () => {
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${URL_PREFIX}/category`,
|
url: '/category',
|
||||||
body: {
|
body: {
|
||||||
name: 'Root Category',
|
name: 'Root Category',
|
||||||
parent: 'null', // 注意:这里传递字符串 'null'
|
parent: 'null', // 注意:这里传递字符串 'null'
|
||||||
@ -351,14 +349,14 @@ describe('nest app test', () => {
|
|||||||
expect(category.parent).toBeNull();
|
expect(category.parent).toBeNull();
|
||||||
await app.inject({
|
await app.inject({
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
url: `${URL_PREFIX}/category/${result.json().id}`,
|
url: `/category/${result.json().id}`,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('create category with parent set to null value', async () => {
|
it('create category with parent set to null value', async () => {
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${URL_PREFIX}/category`,
|
url: '/category',
|
||||||
body: {
|
body: {
|
||||||
name: 'Root Category',
|
name: 'Root Category',
|
||||||
parent: null,
|
parent: null,
|
||||||
@ -369,14 +367,14 @@ describe('nest app test', () => {
|
|||||||
expect(category.parent).toBeNull();
|
expect(category.parent).toBeNull();
|
||||||
await app.inject({
|
await app.inject({
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
url: `${URL_PREFIX}/category/${result.json().id}`,
|
url: `/category/${result.json().id}`,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('create category with empty parent id', async () => {
|
it('create category with empty parent id', async () => {
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${URL_PREFIX}/category`,
|
url: '/category',
|
||||||
body: {
|
body: {
|
||||||
name: 'New Category',
|
name: 'New Category',
|
||||||
parent: '',
|
parent: '',
|
||||||
@ -395,7 +393,7 @@ describe('nest app test', () => {
|
|||||||
it('create category with malformed UUID parent id', async () => {
|
it('create category with malformed UUID parent id', async () => {
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${URL_PREFIX}/category`,
|
url: '/category',
|
||||||
body: {
|
body: {
|
||||||
name: 'New Category',
|
name: 'New Category',
|
||||||
parent: 'not-a-valid-uuid-123',
|
parent: 'not-a-valid-uuid-123',
|
||||||
@ -414,7 +412,7 @@ describe('nest app test', () => {
|
|||||||
it('create category with customOrder as string', async () => {
|
it('create category with customOrder as string', async () => {
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${URL_PREFIX}/category`,
|
url: '/category',
|
||||||
body: {
|
body: {
|
||||||
name: 'New Category',
|
name: 'New Category',
|
||||||
customOrder: '10', // 字符串形式的数字
|
customOrder: '10', // 字符串形式的数字
|
||||||
@ -425,14 +423,14 @@ describe('nest app test', () => {
|
|||||||
expect(category.customOrder).toBe(10);
|
expect(category.customOrder).toBe(10);
|
||||||
await app.inject({
|
await app.inject({
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
url: `${URL_PREFIX}/category/${result.json().id}`,
|
url: `/category/${result.json().id}`,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('create category with customOrder as float', async () => {
|
it('create category with customOrder as float', async () => {
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${URL_PREFIX}/category`,
|
url: '/category',
|
||||||
body: {
|
body: {
|
||||||
name: 'New Category',
|
name: 'New Category',
|
||||||
customOrder: 5.5,
|
customOrder: 5.5,
|
||||||
@ -448,7 +446,7 @@ describe('nest app test', () => {
|
|||||||
it('create category with customOrder as negative number', async () => {
|
it('create category with customOrder as negative number', async () => {
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${URL_PREFIX}/category`,
|
url: '/category',
|
||||||
body: {
|
body: {
|
||||||
name: 'New Category',
|
name: 'New Category',
|
||||||
customOrder: -1,
|
customOrder: -1,
|
||||||
@ -464,7 +462,7 @@ describe('nest app test', () => {
|
|||||||
it('create category with customOrder as zero', async () => {
|
it('create category with customOrder as zero', async () => {
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${URL_PREFIX}/category`,
|
url: '/category',
|
||||||
body: {
|
body: {
|
||||||
name: 'New Category',
|
name: 'New Category',
|
||||||
customOrder: 0,
|
customOrder: 0,
|
||||||
@ -475,14 +473,14 @@ describe('nest app test', () => {
|
|||||||
expect(category.customOrder).toBe(0);
|
expect(category.customOrder).toBe(0);
|
||||||
await app.inject({
|
await app.inject({
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
url: `${URL_PREFIX}/category/${result.json().id}`,
|
url: `/category/${result.json().id}`,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('create category with customOrder as large number', async () => {
|
it('create category with customOrder as large number', async () => {
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${URL_PREFIX}/category`,
|
url: '/category',
|
||||||
body: {
|
body: {
|
||||||
name: 'New Category',
|
name: 'New Category',
|
||||||
customOrder: 999999,
|
customOrder: 999999,
|
||||||
@ -491,7 +489,7 @@ describe('nest app test', () => {
|
|||||||
expect(result.statusCode).toEqual(201);
|
expect(result.statusCode).toEqual(201);
|
||||||
await app.inject({
|
await app.inject({
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
url: `${URL_PREFIX}/category/${result.json().id}`,
|
url: `/category/${result.json().id}`,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -499,7 +497,7 @@ describe('nest app test', () => {
|
|||||||
const parent = categories.find((c) => !c.parent);
|
const parent = categories.find((c) => !c.parent);
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${URL_PREFIX}/category`,
|
url: '/category',
|
||||||
body: {
|
body: {
|
||||||
name: 'Valid New Category',
|
name: 'Valid New Category',
|
||||||
parent: parent.id,
|
parent: parent.id,
|
||||||
@ -513,7 +511,7 @@ describe('nest app test', () => {
|
|||||||
expect(category.customOrder).toBe(5);
|
expect(category.customOrder).toBe(5);
|
||||||
await app.inject({
|
await app.inject({
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
url: `${URL_PREFIX}/category/${result.json().id}`,
|
url: `/category/${result.json().id}`,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -522,7 +520,7 @@ describe('nest app test', () => {
|
|||||||
const category = categories[0];
|
const category = categories[0];
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${URL_PREFIX}/category`,
|
url: '/category',
|
||||||
body: {
|
body: {
|
||||||
name: 'Invalid Category',
|
name: 'Invalid Category',
|
||||||
parent: category.id,
|
parent: category.id,
|
||||||
@ -537,7 +535,7 @@ describe('nest app test', () => {
|
|||||||
it('update category without id', async () => {
|
it('update category without id', async () => {
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
url: `${URL_PREFIX}/category`,
|
url: '/category',
|
||||||
body: { name: 'Updated Category' },
|
body: { name: 'Updated Category' },
|
||||||
});
|
});
|
||||||
expect(result.json()).toEqual({
|
expect(result.json()).toEqual({
|
||||||
@ -554,7 +552,7 @@ describe('nest app test', () => {
|
|||||||
it('update category with invalid id format', async () => {
|
it('update category with invalid id format', async () => {
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
url: `${URL_PREFIX}/category`,
|
url: '/category',
|
||||||
body: {
|
body: {
|
||||||
id: 'invalid-uuid',
|
id: 'invalid-uuid',
|
||||||
name: 'Updated Category',
|
name: 'Updated Category',
|
||||||
@ -574,7 +572,7 @@ describe('nest app test', () => {
|
|||||||
it('update category with non-existent id', async () => {
|
it('update category with non-existent id', async () => {
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
url: `${URL_PREFIX}/category`,
|
url: '/category',
|
||||||
body: {
|
body: {
|
||||||
id: '74e655b3-b69a-42ae-a101-41c224386e74',
|
id: '74e655b3-b69a-42ae-a101-41c224386e74',
|
||||||
name: 'Updated Category',
|
name: 'Updated Category',
|
||||||
@ -587,7 +585,7 @@ describe('nest app test', () => {
|
|||||||
const category = categories[0];
|
const category = categories[0];
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
url: `${URL_PREFIX}/category`,
|
url: '/category',
|
||||||
body: {
|
body: {
|
||||||
id: category.id,
|
id: category.id,
|
||||||
name: 'A'.repeat(30),
|
name: 'A'.repeat(30),
|
||||||
@ -606,7 +604,7 @@ describe('nest app test', () => {
|
|||||||
|
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
url: `${URL_PREFIX}/category`,
|
url: '/category',
|
||||||
body: {
|
body: {
|
||||||
id: child1.id,
|
id: child1.id,
|
||||||
name: child2.name,
|
name: child2.name,
|
||||||
@ -623,7 +621,7 @@ describe('nest app test', () => {
|
|||||||
const category = categories[0];
|
const category = categories[0];
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
url: `${URL_PREFIX}/category`,
|
url: '/category',
|
||||||
body: {
|
body: {
|
||||||
id: category.id,
|
id: category.id,
|
||||||
parent: 'invalid-uuid',
|
parent: 'invalid-uuid',
|
||||||
@ -643,7 +641,7 @@ describe('nest app test', () => {
|
|||||||
const category = categories[0];
|
const category = categories[0];
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
url: `${URL_PREFIX}/category`,
|
url: '/category',
|
||||||
body: {
|
body: {
|
||||||
id: category.id,
|
id: category.id,
|
||||||
parent: '74e655b3-b69a-42ae-a101-41c224386e74',
|
parent: '74e655b3-b69a-42ae-a101-41c224386e74',
|
||||||
@ -660,7 +658,7 @@ describe('nest app test', () => {
|
|||||||
const category = categories[0];
|
const category = categories[0];
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
url: `${URL_PREFIX}/category`,
|
url: '/category',
|
||||||
body: {
|
body: {
|
||||||
id: category.id,
|
id: category.id,
|
||||||
customOrder: -1,
|
customOrder: -1,
|
||||||
@ -677,7 +675,7 @@ describe('nest app test', () => {
|
|||||||
it('query categories with invalid page', async () => {
|
it('query categories with invalid page', async () => {
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
url: `${URL_PREFIX}/category?page=0`,
|
url: '/category?page=0',
|
||||||
});
|
});
|
||||||
expect(result.json()).toEqual({
|
expect(result.json()).toEqual({
|
||||||
message: ['The current page must be greater than 1.'],
|
message: ['The current page must be greater than 1.'],
|
||||||
@ -689,7 +687,7 @@ describe('nest app test', () => {
|
|||||||
it('query categories with invalid limit', async () => {
|
it('query categories with invalid limit', async () => {
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
url: `${URL_PREFIX}/category?limit=0`,
|
url: '/category?limit=0',
|
||||||
});
|
});
|
||||||
expect(result.json()).toEqual({
|
expect(result.json()).toEqual({
|
||||||
message: ['The number of data displayed per page must be greater than 1.'],
|
message: ['The number of data displayed per page must be greater than 1.'],
|
||||||
@ -709,7 +707,7 @@ describe('nest app test', () => {
|
|||||||
it('create tag without name', async () => {
|
it('create tag without name', async () => {
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${URL_PREFIX}/tag`,
|
url: '/tag',
|
||||||
body: {},
|
body: {},
|
||||||
});
|
});
|
||||||
expect(result.json()).toEqual({
|
expect(result.json()).toEqual({
|
||||||
@ -726,7 +724,7 @@ describe('nest app test', () => {
|
|||||||
it('create tag with long name', async () => {
|
it('create tag with long name', async () => {
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${URL_PREFIX}/tag`,
|
url: '/tag',
|
||||||
body: { name: 'A'.repeat(256) },
|
body: { name: 'A'.repeat(256) },
|
||||||
});
|
});
|
||||||
expect(result.json()).toEqual({
|
expect(result.json()).toEqual({
|
||||||
@ -740,7 +738,7 @@ describe('nest app test', () => {
|
|||||||
const existingTag = tags[0];
|
const existingTag = tags[0];
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${URL_PREFIX}/tag`,
|
url: '/tag',
|
||||||
body: { name: existingTag.name },
|
body: { name: existingTag.name },
|
||||||
});
|
});
|
||||||
expect(result.json()).toEqual({
|
expect(result.json()).toEqual({
|
||||||
@ -753,7 +751,7 @@ describe('nest app test', () => {
|
|||||||
it('create tag with long description', async () => {
|
it('create tag with long description', async () => {
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${URL_PREFIX}/tag`,
|
url: '/tag',
|
||||||
body: {
|
body: {
|
||||||
name: 'NewTag',
|
name: 'NewTag',
|
||||||
desc: 'A'.repeat(501),
|
desc: 'A'.repeat(501),
|
||||||
@ -770,7 +768,7 @@ describe('nest app test', () => {
|
|||||||
it('update tag without id', async () => {
|
it('update tag without id', async () => {
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
url: `${URL_PREFIX}/tag`,
|
url: '/tag',
|
||||||
body: { name: 'Updated Tag' },
|
body: { name: 'Updated Tag' },
|
||||||
});
|
});
|
||||||
expect(result.json()).toEqual({
|
expect(result.json()).toEqual({
|
||||||
@ -787,7 +785,7 @@ describe('nest app test', () => {
|
|||||||
it('update tag with invalid id format', async () => {
|
it('update tag with invalid id format', async () => {
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
url: `${URL_PREFIX}/tag`,
|
url: '/tag',
|
||||||
body: {
|
body: {
|
||||||
id: 'invalid-uuid',
|
id: 'invalid-uuid',
|
||||||
name: 'Updated Tag',
|
name: 'Updated Tag',
|
||||||
@ -803,7 +801,7 @@ describe('nest app test', () => {
|
|||||||
it('update tag with non-existent id', async () => {
|
it('update tag with non-existent id', async () => {
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
url: `${URL_PREFIX}/tag`,
|
url: '/tag',
|
||||||
body: {
|
body: {
|
||||||
id: '74e655b3-b69a-42ae-a101-41c224386e74',
|
id: '74e655b3-b69a-42ae-a101-41c224386e74',
|
||||||
name: 'Updated Tag',
|
name: 'Updated Tag',
|
||||||
@ -820,7 +818,7 @@ describe('nest app test', () => {
|
|||||||
const tag = tags[0];
|
const tag = tags[0];
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
url: `${URL_PREFIX}/tag`,
|
url: '/tag',
|
||||||
body: {
|
body: {
|
||||||
id: tag.id,
|
id: tag.id,
|
||||||
name: 'A'.repeat(256),
|
name: 'A'.repeat(256),
|
||||||
@ -837,7 +835,7 @@ describe('nest app test', () => {
|
|||||||
const [tag1, tag2] = tags;
|
const [tag1, tag2] = tags;
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
url: `${URL_PREFIX}/tag`,
|
url: '/tag',
|
||||||
body: {
|
body: {
|
||||||
id: tag1.id,
|
id: tag1.id,
|
||||||
name: tag2.name,
|
name: tag2.name,
|
||||||
@ -854,7 +852,7 @@ describe('nest app test', () => {
|
|||||||
const tag = tags[0];
|
const tag = tags[0];
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
url: `${URL_PREFIX}/tag`,
|
url: '/tag',
|
||||||
body: {
|
body: {
|
||||||
id: tag.id,
|
id: tag.id,
|
||||||
desc: 'A'.repeat(501),
|
desc: 'A'.repeat(501),
|
||||||
@ -880,7 +878,7 @@ describe('nest app test', () => {
|
|||||||
it('create post without title', async () => {
|
it('create post without title', async () => {
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${URL_PREFIX}/posts`,
|
url: '/posts',
|
||||||
body: { body: 'Post content' },
|
body: { body: 'Post content' },
|
||||||
});
|
});
|
||||||
expect(result.json()).toEqual({
|
expect(result.json()).toEqual({
|
||||||
@ -896,7 +894,7 @@ describe('nest app test', () => {
|
|||||||
it('create post without body', async () => {
|
it('create post without body', async () => {
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${URL_PREFIX}/posts`,
|
url: '/posts',
|
||||||
body: { title: 'New Post' },
|
body: { title: 'New Post' },
|
||||||
});
|
});
|
||||||
expect(result.json()).toEqual({
|
expect(result.json()).toEqual({
|
||||||
@ -909,7 +907,7 @@ describe('nest app test', () => {
|
|||||||
it('create post with long title', async () => {
|
it('create post with long title', async () => {
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${URL_PREFIX}/posts`,
|
url: '/posts',
|
||||||
body: {
|
body: {
|
||||||
title: 'A'.repeat(256),
|
title: 'A'.repeat(256),
|
||||||
body: 'Post content',
|
body: 'Post content',
|
||||||
@ -925,7 +923,7 @@ describe('nest app test', () => {
|
|||||||
it('create post with long summary', async () => {
|
it('create post with long summary', async () => {
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${URL_PREFIX}/posts`,
|
url: '/posts',
|
||||||
body: {
|
body: {
|
||||||
title: 'New Post',
|
title: 'New Post',
|
||||||
body: 'Content',
|
body: 'Content',
|
||||||
@ -942,7 +940,7 @@ describe('nest app test', () => {
|
|||||||
it('create post with invalid category', async () => {
|
it('create post with invalid category', async () => {
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${URL_PREFIX}/posts`,
|
url: '/posts',
|
||||||
body: {
|
body: {
|
||||||
title: 'New Post',
|
title: 'New Post',
|
||||||
body: 'Content',
|
body: 'Content',
|
||||||
@ -959,7 +957,7 @@ describe('nest app test', () => {
|
|||||||
it('create post with non-existent category', async () => {
|
it('create post with non-existent category', async () => {
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${URL_PREFIX}/posts`,
|
url: '/posts',
|
||||||
body: {
|
body: {
|
||||||
title: 'New Post',
|
title: 'New Post',
|
||||||
body: 'Content',
|
body: 'Content',
|
||||||
@ -976,7 +974,7 @@ describe('nest app test', () => {
|
|||||||
it('create post with invalid tag format', async () => {
|
it('create post with invalid tag format', async () => {
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${URL_PREFIX}/posts`,
|
url: '/posts',
|
||||||
body: {
|
body: {
|
||||||
title: 'New Post',
|
title: 'New Post',
|
||||||
body: 'Content',
|
body: 'Content',
|
||||||
@ -993,7 +991,7 @@ describe('nest app test', () => {
|
|||||||
it('create post with non-existent tag', async () => {
|
it('create post with non-existent tag', async () => {
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${URL_PREFIX}/posts`,
|
url: '/posts',
|
||||||
body: {
|
body: {
|
||||||
title: 'New Post',
|
title: 'New Post',
|
||||||
body: 'Content',
|
body: 'Content',
|
||||||
@ -1010,7 +1008,7 @@ describe('nest app test', () => {
|
|||||||
it('create post with long keyword', async () => {
|
it('create post with long keyword', async () => {
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${URL_PREFIX}/posts`,
|
url: '/posts',
|
||||||
body: {
|
body: {
|
||||||
title: 'New Post',
|
title: 'New Post',
|
||||||
body: 'Content',
|
body: 'Content',
|
||||||
@ -1027,7 +1025,7 @@ describe('nest app test', () => {
|
|||||||
it('create post with negative custom order', async () => {
|
it('create post with negative custom order', async () => {
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${URL_PREFIX}/posts`,
|
url: '/posts',
|
||||||
body: {
|
body: {
|
||||||
title: 'New Post',
|
title: 'New Post',
|
||||||
body: 'Content',
|
body: 'Content',
|
||||||
@ -1045,7 +1043,7 @@ describe('nest app test', () => {
|
|||||||
it('update post without id', async () => {
|
it('update post without id', async () => {
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
url: `${URL_PREFIX}/posts`,
|
url: '/posts',
|
||||||
body: { title: 'Updated Post' },
|
body: { title: 'Updated Post' },
|
||||||
});
|
});
|
||||||
expect(result.json()).toEqual({
|
expect(result.json()).toEqual({
|
||||||
@ -1061,7 +1059,7 @@ describe('nest app test', () => {
|
|||||||
it('update post with invalid id format', async () => {
|
it('update post with invalid id format', async () => {
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
url: `${URL_PREFIX}/posts`,
|
url: '/posts',
|
||||||
body: {
|
body: {
|
||||||
id: 'invalid-uuid',
|
id: 'invalid-uuid',
|
||||||
title: 'Updated Post',
|
title: 'Updated Post',
|
||||||
@ -1080,7 +1078,7 @@ describe('nest app test', () => {
|
|||||||
it('update post with non-existent id', async () => {
|
it('update post with non-existent id', async () => {
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
url: `${URL_PREFIX}/posts`,
|
url: '/posts',
|
||||||
body: {
|
body: {
|
||||||
id: '74e655b3-b69a-42ae-a101-41c224386e74',
|
id: '74e655b3-b69a-42ae-a101-41c224386e74',
|
||||||
title: 'Updated Post non-existent id',
|
title: 'Updated Post non-existent id',
|
||||||
@ -1097,7 +1095,7 @@ describe('nest app test', () => {
|
|||||||
const post = posts[0];
|
const post = posts[0];
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
url: `${URL_PREFIX}/posts`,
|
url: '/posts',
|
||||||
body: {
|
body: {
|
||||||
id: post.id,
|
id: post.id,
|
||||||
title: 'A'.repeat(256),
|
title: 'A'.repeat(256),
|
||||||
@ -1124,7 +1122,7 @@ describe('nest app test', () => {
|
|||||||
const post = posts[0];
|
const post = posts[0];
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${URL_PREFIX}/comment`,
|
url: '/comment',
|
||||||
body: { post: post.id },
|
body: { post: post.id },
|
||||||
});
|
});
|
||||||
expect(result.json()).toEqual({
|
expect(result.json()).toEqual({
|
||||||
@ -1140,7 +1138,7 @@ describe('nest app test', () => {
|
|||||||
it('create comment without post', async () => {
|
it('create comment without post', async () => {
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${URL_PREFIX}/comment`,
|
url: '/comment',
|
||||||
body: { body: 'Test comment' },
|
body: { body: 'Test comment' },
|
||||||
});
|
});
|
||||||
expect(result.json()).toEqual({
|
expect(result.json()).toEqual({
|
||||||
@ -1154,7 +1152,7 @@ describe('nest app test', () => {
|
|||||||
const post = posts[0];
|
const post = posts[0];
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${URL_PREFIX}/comment`,
|
url: '/comment',
|
||||||
body: {
|
body: {
|
||||||
body: 'A'.repeat(1001),
|
body: 'A'.repeat(1001),
|
||||||
post: post.id,
|
post: post.id,
|
||||||
@ -1170,7 +1168,7 @@ describe('nest app test', () => {
|
|||||||
it('create comment with invalid post format', async () => {
|
it('create comment with invalid post format', async () => {
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${URL_PREFIX}/comment`,
|
url: '/comment',
|
||||||
body: {
|
body: {
|
||||||
body: 'Test comment',
|
body: 'Test comment',
|
||||||
post: 'invalid-uuid',
|
post: 'invalid-uuid',
|
||||||
@ -1186,7 +1184,7 @@ describe('nest app test', () => {
|
|||||||
it('create comment with non-existent post', async () => {
|
it('create comment with non-existent post', async () => {
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${URL_PREFIX}/comment`,
|
url: '/comment',
|
||||||
body: {
|
body: {
|
||||||
body: 'Test comment',
|
body: 'Test comment',
|
||||||
post: '74e655b3-b69a-42ae-a101-41c224386e74',
|
post: '74e655b3-b69a-42ae-a101-41c224386e74',
|
||||||
@ -1203,7 +1201,7 @@ describe('nest app test', () => {
|
|||||||
const post = posts[0];
|
const post = posts[0];
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${URL_PREFIX}/comment`,
|
url: '/comment',
|
||||||
body: {
|
body: {
|
||||||
body: 'Test comment',
|
body: 'Test comment',
|
||||||
post: post.id,
|
post: post.id,
|
||||||
@ -1221,7 +1219,7 @@ describe('nest app test', () => {
|
|||||||
const post = posts[0];
|
const post = posts[0];
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${URL_PREFIX}/comment`,
|
url: '/comment',
|
||||||
body: {
|
body: {
|
||||||
body: 'Test comment',
|
body: 'Test comment',
|
||||||
post: post.id,
|
post: post.id,
|
||||||
@ -1253,7 +1251,7 @@ async function addCategory(
|
|||||||
const item = data[index];
|
const item = data[index];
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${URL_PREFIX}/category`,
|
url: '/category',
|
||||||
body: { ...pick(item, ['name', 'customOrder']), parent: parentId },
|
body: { ...pick(item, ['name', 'customOrder']), parent: parentId },
|
||||||
});
|
});
|
||||||
const addedItem: CategoryEntity = result.json();
|
const addedItem: CategoryEntity = result.json();
|
||||||
@ -1271,7 +1269,7 @@ async function addTag(app: NestFastifyApplication, data: RecordAny[]): Promise<T
|
|||||||
const item = data[index];
|
const item = data[index];
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${URL_PREFIX}/tag`,
|
url: '/tag',
|
||||||
body: item,
|
body: item,
|
||||||
});
|
});
|
||||||
const addedItem: TagEntity = result.json();
|
const addedItem: TagEntity = result.json();
|
||||||
@ -1295,7 +1293,7 @@ async function addPost(
|
|||||||
item.tags = generateUniqueRandomNumbers(0, tags.length - 1, 3).map((idx) => tags[idx]);
|
item.tags = generateUniqueRandomNumbers(0, tags.length - 1, 3).map((idx) => tags[idx]);
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${URL_PREFIX}/posts`,
|
url: '/posts',
|
||||||
body: item,
|
body: item,
|
||||||
});
|
});
|
||||||
const addedItem: PostEntity = result.json();
|
const addedItem: PostEntity = result.json();
|
||||||
@ -1325,7 +1323,7 @@ async function addComment(
|
|||||||
: undefined;
|
: undefined;
|
||||||
const result = await app.inject({
|
const result = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${URL_PREFIX}/comment`,
|
url: '/comment',
|
||||||
body: item,
|
body: item,
|
||||||
});
|
});
|
||||||
const addedItem = result.json();
|
const addedItem = result.json();
|
||||||
|
@ -31,7 +31,6 @@
|
|||||||
"lib": ["esnext", "DOM", "ScriptHost", "WebWorker"],
|
"lib": ["esnext", "DOM", "ScriptHost", "WebWorker"],
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
"types": ["bun-types", "@types/jest"],
|
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"]
|
"@/*": ["./src/*"]
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user