refactor: app module

Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
Innei
2022-08-22 23:22:45 +08:00
parent 250e7fdd37
commit a27b79eae3
21 changed files with 210 additions and 188 deletions

View File

@@ -2,15 +2,12 @@ import { AxiosRequestConfig } from 'axios'
import cluster from 'cluster'
import { argv } from 'zx-cjs'
export const isDev = process.env.NODE_ENV == 'development'
export const isTest = !!process.env.TEST
export const cwd = process.cwd()
import { cwd, isDev, isMainCluster, isTest } from './global/env.global'
export const PORT = argv.port || process.env.PORT || 2333
export const API_VERSION = 2
export const isInDemoMode = argv.demo || false
export const DEMO_MODE = argv.demo || false
export const CROSS_DOMAIN = {
allowedOrigins: argv.allowed_origins
@@ -32,7 +29,7 @@ export const CROSS_DOMAIN = {
}
export const MONGO_DB = {
dbName: argv.collection_name || (isInDemoMode ? 'mx-space_demo' : 'mx-space'),
dbName: argv.collection_name || (DEMO_MODE ? 'mx-space_demo' : 'mx-space'),
host: argv.db_host || '127.0.0.1',
port: argv.db_port || 27017,
get uri() {
@@ -69,11 +66,6 @@ export const CLUSTER = {
workers: argv.cluster_workers,
}
/** Is main cluster in PM2 */
export const isMainCluster =
process.env.NODE_APP_INSTANCE && parseInt(process.env.NODE_APP_INSTANCE) === 0
export const isMainProcess = cluster.isPrimary || isMainCluster
export const DEBUG_MODE = {
httpRequestVerbose:
argv.httpRequestVerbose ?? argv.http_request_verbose ?? true,

View File

@@ -11,7 +11,7 @@ import { ApiController } from '~/common/decorator/api-controller.decorator'
import { InjectModel } from '~/transformers/model.transformer'
import PKG from '../package.json'
import { isInDemoMode } from './app.config'
import { DEMO_MODE } from './app.config'
import { Auth } from './common/decorator/auth.decorator'
import { HttpCache } from './common/decorator/cache.decorator'
import { IpLocation, IpRecord } from './common/decorator/ip.decorator'
@@ -36,7 +36,7 @@ export class AppController {
return {
name: PKG.name,
author: PKG.author,
version: isDev ? 'dev' : `${isInDemoMode ? 'demo/' : ''}${PKG.version}`,
version: isDev ? 'dev' : `${DEMO_MODE ? 'demo/' : ''}${PKG.version}`,
homepage: PKG.homepage,
issues: PKG.issues,
}

View File

@@ -1,7 +1,7 @@
import { Module, NestModule, Type } from '@nestjs/common'
import { DynamicModule, Module, NestModule, Type } from '@nestjs/common'
import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core'
import { isInDemoMode } from './app.config'
import { DEMO_MODE } from './app.config'
import { AppController } from './app.controller'
import { AllExceptionsFilter } from './common/filters/any-exception.filter'
import { RolesGuard } from './common/guard/roles.guard'
@@ -52,92 +52,97 @@ import { HelperModule } from './processors/helper/helper.module'
import { LoggerModule } from './processors/logger/logger.module'
import { RedisModule } from './processors/redis/redis.module'
@Module({
imports: [
LoggerModule,
DatabaseModule,
RedisModule,
@Module({})
export class AppModule {
static register(isInit: boolean): DynamicModule {
return {
module: AppModule,
imports: [
LoggerModule,
DatabaseModule,
RedisModule,
AggregateModule,
AnalyzeModule,
AuthModule,
BackupModule,
CategoryModule,
CommentModule,
ConfigsModule,
isInDemoMode && DemoModule,
DependencyModule,
FeedModule,
FileModule,
HealthModule,
InitModule,
LinkModule,
MarkdownModule,
NoteModule,
OptionModule,
PageModule,
PostModule,
ProjectModule,
PTYModule,
RecentlyModule,
UpdateModule,
TopicModule,
SayModule,
SearchModule,
ServerlessModule,
SitemapModule,
SnippetModule,
ToolModule,
UserModule,
AggregateModule,
AnalyzeModule,
AuthModule,
BackupModule,
CategoryModule,
CommentModule,
ConfigsModule,
DEMO_MODE && DemoModule,
DependencyModule,
FeedModule,
FileModule,
HealthModule,
!isInit && InitModule,
LinkModule,
MarkdownModule,
NoteModule,
OptionModule,
PageModule,
PostModule,
ProjectModule,
PTYModule,
RecentlyModule,
UpdateModule,
TopicModule,
SayModule,
SearchModule,
ServerlessModule,
SitemapModule,
SnippetModule,
ToolModule,
UserModule,
PageProxyModule,
RenderEjsModule,
PageProxyModule,
RenderEjsModule,
GatewayModule,
HelperModule,
GatewayModule,
HelperModule,
isDev ? DebugModule : undefined,
].filter(Boolean) as Type<NestModule>[],
controllers: [AppController],
providers: [
{
provide: APP_INTERCEPTOR,
useClass: QueryInterceptor,
},
isDev ? DebugModule : undefined,
].filter(Boolean) as Type<NestModule>[],
controllers: [AppController],
providers: [
{
provide: APP_INTERCEPTOR,
useClass: QueryInterceptor,
},
{
provide: APP_INTERCEPTOR,
useClass: HttpCacheInterceptor, // 4
},
{
provide: APP_INTERCEPTOR,
useClass: AnalyzeInterceptor,
},
{
provide: APP_INTERCEPTOR,
useClass: CountingInterceptor, // 3
},
{
provide: APP_INTERCEPTOR,
useClass: JSONTransformInterceptor, // 2
},
{
provide: APP_INTERCEPTOR,
useClass: ResponseInterceptor, // 1
},
{
provide: APP_INTERCEPTOR,
useClass: IdempotenceInterceptor, // 0
},
{
provide: APP_INTERCEPTOR,
useClass: HttpCacheInterceptor, // 4
},
{
provide: APP_INTERCEPTOR,
useClass: AnalyzeInterceptor,
},
{
provide: APP_INTERCEPTOR,
useClass: CountingInterceptor, // 3
},
{
provide: APP_INTERCEPTOR,
useClass: JSONTransformInterceptor, // 2
},
{
provide: APP_INTERCEPTOR,
useClass: ResponseInterceptor, // 1
},
{
provide: APP_INTERCEPTOR,
useClass: IdempotenceInterceptor, // 0
},
{
provide: APP_FILTER,
useClass: AllExceptionsFilter,
},
{
provide: APP_GUARD,
useClass: RolesGuard,
},
],
})
export class AppModule {}
{
provide: APP_FILTER,
useClass: AllExceptionsFilter,
},
{
provide: APP_GUARD,
useClass: RolesGuard,
},
],
}
}
}

View File

@@ -6,16 +6,17 @@ import { LogLevel, Logger, ValidationPipe } from '@nestjs/common'
import { ContextIdFactory, NestFactory } from '@nestjs/core'
import { NestFastifyApplication } from '@nestjs/platform-fastify'
import { API_VERSION, CROSS_DOMAIN, PORT, isMainProcess } from './app.config'
import { API_VERSION, CROSS_DOMAIN, PORT } from './app.config'
import { AppModule } from './app.module'
import { fastifyApp } from './common/adapters/fastify.adapter'
import { RedisIoAdapter } from './common/adapters/socket.adapter'
import { SpiderGuard } from './common/guard/spider.guard'
import { LoggingInterceptor } from './common/interceptors/logging.interceptor'
import { AggregateByTenantContextIdStrategy } from './common/strategies/context.strategy'
import { isTest } from './global/env.global'
import { isMainProcess, isTest } from './global/env.global'
import { migrateDatabase } from './migration/migrate'
import { MyLogger } from './processors/logger/logger.service'
import { checkInit } from './utils/check-init.util'
const Origin: false | string[] = Array.isArray(CROSS_DOMAIN.allowedOrigins)
? [...CROSS_DOMAIN.allowedOrigins, '*.shizuri.net', '22333322.xyz']
@@ -26,8 +27,10 @@ declare const module: any
export async function bootstrap() {
process.title = `Mix Space (${cluster.isPrimary ? 'master' : 'worker'})`
await migrateDatabase()
const isInit = await checkInit()
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
AppModule.register(isInit),
fastifyApp,
{
logger: ['error'].concat(

View File

@@ -2,7 +2,7 @@ import cluster from 'cluster'
import { Cron } from '@nestjs/schedule'
import { isMainProcess } from '~/app.config'
import { isMainProcess } from '~/global/env.global'
export const CronOnce = (...rest: Parameters<typeof Cron>): MethodDecorator => {
// If not in cluster mode, and PM2 main worker

View File

@@ -14,7 +14,15 @@ import { isDev, isTest } from './env.global'
class Reporter extends FancyReporter {
isInVirtualTerminal = typeof process.stdout.columns === 'undefined' // HACK: if got `undefined` that means in PM2 pty
private latestLogTime: number = performance.now()
protected formatDate(date: Date): string {
if (isDev) {
const now = performance.now()
const delta = now - this.latestLogTime
this.latestLogTime = now
return `+${delta | 0}ms ${super.formatDate(date)}`
}
return this.isInVirtualTerminal ? '' : super.formatDate(date)
}

View File

@@ -1 +1,10 @@
export { isDev, cwd, isTest } from '~/app.config'
import cluster from 'cluster'
export const isMainCluster =
process.env.NODE_APP_INSTANCE && parseInt(process.env.NODE_APP_INSTANCE) === 0
export const isMainProcess = cluster.isPrimary || isMainCluster
export const isDev = process.env.NODE_ENV == 'development'
export const isTest = !!process.env.TEST
export const cwd = process.cwd()

View File

@@ -6,7 +6,6 @@ import 'zx-cjs/globals'
import { Logger } from '@nestjs/common'
import { CLUSTER } from '~/app.config'
import {
DATA_DIR,
LOG_DIR,
@@ -20,6 +19,8 @@ import { consola, registerStdLogger } from './consola.global'
import './dayjs.global'
import { CLUSTER } from '~/app.config'
import { cwd, isDev } from './env.global'
import { registerJSONGlobal } from './json.global'
@@ -56,8 +57,9 @@ function registerGlobal() {
}
export function register() {
registerStdLogger()
mkdirs()
registerGlobal()
registerStdLogger()
registerJSONGlobal()
mkdirs()
}

View File

@@ -1,14 +1,13 @@
import { existsSync } from 'fs-extra'
import { MongoClient } from 'mongodb'
import * as APP_CONFIG from '../app.config'
import { isMainProcess } from '~/global/env.global'
import { getDatabaseConnection } from '~/utils/database.util'
import { DATA_DIR } from '../constants/path.constant'
import VersionList from './history'
const { MONGO_DB } = APP_CONFIG
export async function migrateDatabase() {
if (!APP_CONFIG.isMainProcess) {
if (!isMainProcess) {
return
}
@@ -19,9 +18,8 @@ export async function migrateDatabase() {
const migratedSet = new Set(migrateRecord.split('\n'))
const client = new MongoClient(`mongodb://${MONGO_DB.host}:${MONGO_DB.port}`)
await client.connect()
const db = client.db(MONGO_DB.dbName)
const connection = await getDatabaseConnection()
const db = connection.db
for (const migrate of VersionList) {
if (migratedSet.has(migrate.name)) {
@@ -39,6 +37,4 @@ export async function migrateDatabase() {
await fs.writeFile(migrateFilePath, [...migratedSet].join('\n'), {
flag: 'w+',
})
await client.close()
}

View File

@@ -1,4 +1,4 @@
import { isInDemoMode } from '~/app.config'
import { DEMO_MODE } from '~/app.config'
import { IConfig } from './configs.interface'
@@ -41,7 +41,7 @@ export const generateDefaultConfig: () => IConfig = () => ({
},
friendLinkOptions: { allowApply: true },
backupOptions: {
enable: isInDemoMode ? false : true,
enable: DEMO_MODE ? false : true,
region: null!,
bucket: null!,
secretId: null!,

View File

@@ -5,7 +5,6 @@ import {
Get,
Param,
Patch,
Scope,
UnprocessableEntityException,
} from '@nestjs/common'
@@ -16,10 +15,7 @@ import { ConfigsService } from '../configs/configs.service'
import { ConfigKeyDto } from '../option/dtos/config.dto'
import { InitService } from './init.service'
@ApiController({
path: '/init',
scope: Scope.REQUEST,
})
@ApiController('/init')
@ApiName
export class InitController {
constructor(

View File

@@ -1,4 +1,4 @@
import { Injectable, Logger } from '@nestjs/common'
import { Injectable } from '@nestjs/common'
import { DATA_DIR, TEMP_DIR } from '~/constants/path.constant'
@@ -6,7 +6,6 @@ import { UserService } from '../user/user.service'
@Injectable()
export class InitService {
private logger = new Logger(InitService.name)
constructor(private readonly userService: UserService) {}
getTempdir() {

View File

@@ -1,12 +1,10 @@
import { Module } from '@nestjs/common'
import { InitModule } from '../init/init.module'
import { PageProxyController } from './pageproxy.controller'
import { PageProxyService } from './pageproxy.service'
@Module({
controllers: [PageProxyController],
providers: [PageProxyService],
imports: [InitModule],
})
export class PageProxyModule {}

View File

@@ -7,14 +7,10 @@ import PKG from '~/../package.json'
import { API_VERSION } from '~/app.config'
import { ConfigsService } from '../configs/configs.service'
import { InitService } from '../init/init.service'
@Injectable()
export class PageProxyService {
constructor(
private readonly configs: ConfigsService,
private readonly initService: InitService,
) {}
constructor(private readonly configs: ConfigsService) {}
async checkCanAccessAdminProxy() {
const { adminExtra } = await this.configs.waitForConfigReady()
@@ -61,7 +57,6 @@ export class PageProxyService {
LOGIN_BG: adminExtra.background,
TITLE: adminExtra.title,
WEB_URL: webUrl,
INIT: await this.initService.isInit(),
} as IInjectableData)}`}
${
BASE_API

View File

@@ -11,7 +11,7 @@ import {
WebSocketGateway,
} from '@nestjs/websockets'
import { isInDemoMode } from '~/app.config'
import { DEMO_MODE } from '~/app.config'
import { BusinessEvents } from '~/constants/business-event.constant'
import { RedisKeys } from '~/constants/cache.constant'
import { DATA_DIR } from '~/constants/path.constant'
@@ -43,7 +43,7 @@ export class PTYGateway
client: Socket,
data?: { password?: string; cols: number; rows: number },
) {
if (isInDemoMode) {
if (DEMO_MODE) {
client.send(
this.gatewayMessageFormat(
BusinessEvents.PTY_MESSAGE,

View File

@@ -1,52 +1,7 @@
/**
* @copy https://github.com/surmon-china/nodepress/blob/main/src/processors/database/database.provider.ts
*/
import { mongoose } from '@typegoose/typegoose'
import { MONGO_DB } from '~/app.config'
import { DB_CONNECTION_TOKEN } from '~/constants/system.constant'
import { getDatabaseConnection } from '~/utils/database.util'
export const databaseProvider = {
provide: DB_CONNECTION_TOKEN,
useFactory: async () => {
let reconnectionTask: NodeJS.Timeout | null = null
const RECONNECT_INTERVAL = 6000
const connection = () => {
return mongoose.connect(MONGO_DB.uri, {})
}
const Badge = `[${chalk.yellow('MongoDB')}]`
const color = (str: TemplateStringsArray, ...args: any[]) => {
return str.map((s) => chalk.green(s)).join('')
}
mongoose.connection.on('connecting', () => {
consola.info(Badge, color`connecting...`)
})
mongoose.connection.on('open', () => {
consola.info(Badge, color`readied!`)
if (reconnectionTask) {
clearTimeout(reconnectionTask)
reconnectionTask = null
}
})
mongoose.connection.on('disconnected', () => {
consola.error(
Badge,
chalk.red(
`disconnected! retry when after ${RECONNECT_INTERVAL / 1000}s`,
),
)
reconnectionTask = setTimeout(connection, RECONNECT_INTERVAL)
})
mongoose.connection.on('error', (error) => {
consola.error(Badge, 'error!', error)
mongoose.disconnect()
})
return await connection().then((mongoose) => mongoose.connection)
},
useFactory: getDatabaseConnection,
}

View File

@@ -9,7 +9,7 @@ import { Inject, Injectable, Logger, forwardRef } from '@nestjs/common'
import { OnEvent } from '@nestjs/event-emitter'
import { CronExpression } from '@nestjs/schedule'
import { isInDemoMode } from '~/app.config'
import { DEMO_MODE } from '~/app.config'
import { CronDescription } from '~/common/decorator/cron-description.decorator'
import { CronOnce } from '~/common/decorator/cron-once.decorator'
import { RedisKeys } from '~/constants/cache.constant'
@@ -64,7 +64,7 @@ export class CronService {
@CronOnce(CronExpression.EVERY_DAY_AT_1AM, { name: 'backupDB' })
@CronDescription('备份 DB 并上传 COS')
async backupDB({ uploadCOS = true }: { uploadCOS?: boolean } = {}) {
if (isInDemoMode) {
if (DEMO_MODE) {
return
}
const backup = await this.backupService.backup()

View File

@@ -1,11 +1,11 @@
import { isInDemoMode } from '~/app.config'
import { DEMO_MODE } from '~/app.config'
import { BanInDemoExcpetion } from '~/common/exceptions/ban-in-demo.exception'
/**
* 检查是否在 demo 模式下,禁用此功能
*/
export const banInDemo = () => {
if (isInDemoMode) {
if (DEMO_MODE) {
throw new BanInDemoExcpetion()
}
}

View File

@@ -0,0 +1,9 @@
import { getDatabaseConnection } from './database.util'
export const checkInit = async () => {
const connection = await getDatabaseConnection()
const db = connection.db
const isUserExist = (await db.collection('users').countDocuments()) > 0
return isUserExist
}

View File

@@ -0,0 +1,55 @@
/**
* @see https://github.com/surmon-china/nodepress/blob/main/src/processors/database/database.provider.ts
*/
import mongoose from 'mongoose'
import { MONGO_DB } from '~/app.config'
let databaseConnection: mongoose.Connection | null = null
export const getDatabaseConnection = async () => {
if (databaseConnection) {
return databaseConnection
}
let reconnectionTask: NodeJS.Timeout | null = null
const RECONNECT_INTERVAL = 6000
const connection = () => {
return mongoose.connect(MONGO_DB.uri, {})
}
const Badge = `[${chalk.yellow('MongoDB')}]`
const color = (str: TemplateStringsArray) => {
return str.map((s) => chalk.green(s)).join('')
}
mongoose.connection.on('connecting', () => {
consola.info(Badge, color`connecting...`)
})
mongoose.connection.on('open', () => {
consola.info(Badge, color`readied!`)
if (reconnectionTask) {
clearTimeout(reconnectionTask)
reconnectionTask = null
}
})
mongoose.connection.on('disconnected', () => {
consola.error(
Badge,
chalk.red(`disconnected! retry when after ${RECONNECT_INTERVAL / 1000}s`),
)
reconnectionTask = setTimeout(connection, RECONNECT_INTERVAL)
})
mongoose.connection.on('error', (error) => {
consola.error(Badge, 'error!', error)
mongoose.disconnect()
})
databaseConnection = await connection().then(
(mongoose) => mongoose.connection,
)
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return databaseConnection!
}

View File

@@ -1,8 +1,8 @@
import { isInDemoMode } from '~/app.config'
import { DEMO_MODE } from '~/app.config'
import { RedisKeys } from '~/constants/cache.constant'
type Prefix = 'mx' | 'mx-demo'
const prefix = isInDemoMode ? 'mx-demo' : 'mx'
const prefix = DEMO_MODE ? 'mx-demo' : 'mx'
export const getRedisKey = <T extends string = RedisKeys | '*'>(
key: T,