From 13b5bc629a7c3fa2fbcc78370aee4190dcd9b4d1 Mon Sep 17 00:00:00 2001 From: liuyi Date: Fri, 20 Jun 2025 11:06:39 +0800 Subject: [PATCH] add db migration --- src/modules/content/content.module.ts | 7 ++- src/modules/content/subscribers/index.ts | 1 + .../content/subscribers/post.subscriber.ts | 24 ++++---- src/modules/core/helpers/utils.ts | 10 +++- src/modules/database/base/subscriber.ts | 20 ++++++- src/modules/database/config.ts | 10 +++- src/modules/database/types.ts | 14 ++++- src/modules/database/utils.ts | 56 ++++++++++++++++++- 8 files changed, 119 insertions(+), 23 deletions(-) create mode 100644 src/modules/content/subscribers/index.ts diff --git a/src/modules/content/content.module.ts b/src/modules/content/content.module.ts index c78cc77..76ebf92 100644 --- a/src/modules/content/content.module.ts +++ b/src/modules/content/content.module.ts @@ -9,12 +9,15 @@ import { SearchService } from '@/modules/content/services'; import { SanitizeService } from '@/modules/content/services/SanitizeService'; import { PostService } from '@/modules/content/services/post.service'; -import { PostSubscriber } from '@/modules/content/subscribers/post.subscriber'; + import { DatabaseModule } from '@/modules/database/database.module'; +import { addSubscribers } from '@/modules/database/utils'; + import { Configure } from '../config/configure'; import { defauleContentConfig } from './config'; +import * as subscribers from './subscribers'; import { ContentConfig } from './types'; @Module({}) @@ -23,7 +26,7 @@ export class ContentModule { const config = await configure.get('content', defauleContentConfig); const providers: ModuleMetadata['providers'] = [ ...Object.values(services), - PostSubscriber, + ...(await addSubscribers(configure, Object.values(subscribers))), { provide: PostService, inject: [ diff --git a/src/modules/content/subscribers/index.ts b/src/modules/content/subscribers/index.ts new file mode 100644 index 0000000..1fd30c6 --- /dev/null +++ b/src/modules/content/subscribers/index.ts @@ -0,0 +1 @@ +export * from './post.subscriber'; diff --git a/src/modules/content/subscribers/post.subscriber.ts b/src/modules/content/subscribers/post.subscriber.ts index 0a5ebd5..18c12e4 100644 --- a/src/modules/content/subscribers/post.subscriber.ts +++ b/src/modules/content/subscribers/post.subscriber.ts @@ -1,33 +1,33 @@ -import { Optional } from '@nestjs/common'; import { isNil } from 'lodash'; import { DataSource, EventSubscriber, ObjectType } from 'typeorm'; import { Configure } from '@/modules/config/configure'; import { PostBodyType } from '@/modules/content/constants'; import { PostEntity } from '@/modules/content/entities/post.entity'; -import { PostRepository } from '@/modules/content/repositories/post.repository'; import { SanitizeService } from '@/modules/content/services/SanitizeService'; import { BaseSubscriber } from '@/modules/database/base/subscriber'; @EventSubscriber() export class PostSubscriber extends BaseSubscriber { protected entity: ObjectType = PostEntity; + constructor( protected dataSource: DataSource, - protected postRepository: PostRepository, - protected configure: Configure, - @Optional() protected sanitizeService: SanitizeService, + protected _configure: Configure, ) { - super(dataSource); + super(dataSource, _configure); + } + + get configure(): Configure { + return this._configure; } async afterLoad(entity: PostEntity) { - if ( - (await this.configure.get('content.htmlEnabled')) && - !isNil(this.sanitizeService) && - entity.type === PostBodyType.HTML - ) { - entity.body = this.sanitizeService.sanitize(entity.body); + const sanitizeService = (await this.configure.get('content.htmlEnabled')) + ? this.container.get(SanitizeService) + : undefined; + if (!isNil(sanitizeService) && entity.type === PostBodyType.HTML) { + entity.body = sanitizeService.sanitize(entity.body); } } } diff --git a/src/modules/core/helpers/utils.ts b/src/modules/core/helpers/utils.ts index aebdffb..4ac6060 100644 --- a/src/modules/core/helpers/utils.ts +++ b/src/modules/core/helpers/utils.ts @@ -76,8 +76,14 @@ export async function panic(option: PanicOption | string) { console.log(chalk.red(`\n❌ ${option}`)); process.exit(1); } - const { error, message, exit = true } = option; - isNil(error) ? console.log(chalk.red(`\n❌ ${message}`)) : console.log(chalk.red(error)); + const { error, message, spinner, exit = true } = option; + if (isNil(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) { process.exit(1); } diff --git a/src/modules/database/base/subscriber.ts b/src/modules/database/base/subscriber.ts index 3662030..0292058 100644 --- a/src/modules/database/base/subscriber.ts +++ b/src/modules/database/base/subscriber.ts @@ -1,4 +1,5 @@ import { Optional } from '@nestjs/common'; +import { NestFastifyApplication } from '@nestjs/platform-fastify'; import { isNil } from 'lodash'; import { DataSource, @@ -17,6 +18,10 @@ import { UpdateEvent, } from 'typeorm'; +import { Configure } from '@/modules/config/configure'; + +import { app } from '@/modules/core/helpers/app'; + import { RepositoryType } from '../types'; import { getCustomRepository } from '../utils'; @@ -36,12 +41,25 @@ export abstract class BaseSubscriber { protected abstract entity: ObjectType; - protected constructor(@Optional() protected dataSource?: DataSource) { + protected constructor( + @Optional() protected dataSource?: DataSource, + @Optional() protected _configure?: Configure, + ) { if (!isNil(this.dataSource)) { 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) { return this.dataSource ?? event.connection; } diff --git a/src/modules/database/config.ts b/src/modules/database/config.ts index 01d25ba..8d8d341 100644 --- a/src/modules/database/config.ts +++ b/src/modules/database/config.ts @@ -1,3 +1,5 @@ +import { resolve } from 'path'; + import { ConfigureFactory, ConfigureRegister } from '../config/types'; import { createConnectionOptions } from '../config/utils'; import { deepMerge } from '../core/helpers'; @@ -18,7 +20,11 @@ export const createDBConfig: ( export const createDBOptions = (options: DBConfig) => { const newOptions: DBOptions = { common: deepMerge( - { charset: 'utf8mb4', logging: ['error'] }, + { + charset: 'utf8mb4', + logging: ['error'], + paths: { migration: resolve(__dirname, '../../database/migrations') }, + }, options.common ?? {}, 'replace', ), @@ -29,7 +35,7 @@ export const createDBOptions = (options: DBConfig) => { const newOption = { ...connection, entities }; return deepMerge( newOptions.common, - { ...newOption, autoLoadEntities: true } as any, + { ...newOption, autoLoadEntities: true, synchronize: false } as any, 'replace', ) as TypeormOption; }); diff --git a/src/modules/database/types.ts b/src/modules/database/types.ts index a121c00..dabcd95 100644 --- a/src/modules/database/types.ts +++ b/src/modules/database/types.ts @@ -2,8 +2,8 @@ import { TypeOrmModuleOptions } from '@nestjs/typeorm'; import { FindTreeOptions, ObjectLiteral, - SelectQueryBuilder, Repository, + SelectQueryBuilder, TreeRepository, } from 'typeorm'; @@ -70,13 +70,21 @@ export type RepositoryType = | BaseTreeRepository; export type DBConfig = { - common: RecordAny; + common: RecordAny & DBAdditionalOption; connections: Array; }; -export type TypeormOption = Omit & { name: string }; +export type TypeormOption = Omit & { + name: string; +} & DBAdditionalOption; export type DBOptions = RecordAny & { common: RecordAny; connections: TypeormOption[]; }; + +type DBAdditionalOption = { + path?: { + migration?: string; + }; +}; diff --git a/src/modules/database/utils.ts b/src/modules/database/utils.ts index c1cc9d2..ed915d0 100644 --- a/src/modules/database/utils.ts +++ b/src/modules/database/utils.ts @@ -1,7 +1,16 @@ +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 { DataSource, ObjectLiteral, ObjectType, Repository, SelectQueryBuilder } from 'typeorm'; -import { OrderQueryType, PaginateOptions, PaginateReturn } from '@/modules/database/types'; +import { Configure } from '@/modules/config/configure'; +import { + DBOptions, + OrderQueryType, + PaginateOptions, + PaginateReturn, +} from '@/modules/database/types'; import { CUSTOM_REPOSITORY_METADATA } from './constants'; @@ -97,3 +106,48 @@ export const getCustomRepository =

, T extends ObjectLit const base = dataSource.getRepository>(entity); 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('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[] = [], + dataSource = 'default', +) { + const database = await configure.get('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; +}