mirror of https://github.com/mx-space/core
feat: demo mode
This commit is contained in:
parent
f3f452ed98
commit
ad1b5f6df3
|
@ -1 +1,2 @@
|
|||
*.paw filter=lfs diff=lfs merge=lfs -text
|
||||
demo-data.zip filter=lfs diff=lfs merge=lfs -text
|
||||
|
|
|
@ -5,6 +5,9 @@ import { cwd, isDev, 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 CROSS_DOMAIN = {
|
||||
allowedOrigins: argv.allowed_origins
|
||||
? argv.allowed_origins?.split?.(',')
|
||||
|
@ -23,7 +26,7 @@ export const CROSS_DOMAIN = {
|
|||
}
|
||||
|
||||
export const MONGO_DB = {
|
||||
dbName: argv.collection_name || 'mx-space',
|
||||
dbName: argv.collection_name || (isInDemoMode ? 'mx-space_demo' : 'mx-space'),
|
||||
host: argv.db_host || '127.0.0.1',
|
||||
port: argv.db_port || 27017,
|
||||
get uri() {
|
||||
|
|
|
@ -11,6 +11,7 @@ import { ApiTags } from '@nestjs/swagger'
|
|||
import { InjectModel } from '~/transformers/model.transformer'
|
||||
|
||||
import PKG from '../package.json'
|
||||
import { isInDemoMode } 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'
|
||||
|
@ -35,7 +36,7 @@ export class AppController {
|
|||
return {
|
||||
name: PKG.name,
|
||||
author: PKG.author,
|
||||
version: isDev ? 'dev' : PKG.version,
|
||||
version: isDev ? 'dev' : `${isInDemoMode ? 'demo/' : ''}${PKG.version}`,
|
||||
homepage: PKG.homepage,
|
||||
issues: PKG.issues,
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { MiddlewareConsumer, Module, NestModule, Type } from '@nestjs/common'
|
||||
import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core'
|
||||
|
||||
import { isInDemoMode } from './app.config'
|
||||
import { AppController } from './app.controller'
|
||||
import { AllExceptionsFilter } from './common/filters/any-exception.filter'
|
||||
import { RolesGuard } from './common/guard/roles.guard'
|
||||
|
@ -19,6 +20,7 @@ import { CategoryModule } from './modules/category/category.module'
|
|||
import { CommentModule } from './modules/comment/comment.module'
|
||||
import { ConfigsModule } from './modules/configs/configs.module'
|
||||
import { DebugModule } from './modules/debug/debug.module'
|
||||
import { DemoModule } from './modules/demo/demo.module'
|
||||
import { FeedModule } from './modules/feed/feed.module'
|
||||
import { FileModule } from './modules/file/file.module'
|
||||
import { HealthModule } from './modules/health/health.module'
|
||||
|
@ -60,6 +62,7 @@ import { LoggerModule } from './processors/logger/logger.module'
|
|||
CategoryModule,
|
||||
CommentModule,
|
||||
ConfigsModule,
|
||||
isInDemoMode && DemoModule,
|
||||
FeedModule,
|
||||
FileModule,
|
||||
HealthModule,
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
import { Observable } from 'rxjs'
|
||||
|
||||
import { CanActivate, UseGuards, applyDecorators } from '@nestjs/common'
|
||||
|
||||
import { banInDemo } from '~/utils'
|
||||
|
||||
class DemoGuard implements CanActivate {
|
||||
canActivate(): boolean | Promise<boolean> | Observable<boolean> {
|
||||
banInDemo()
|
||||
return true
|
||||
}
|
||||
}
|
||||
export const BanInDemo = applyDecorators(UseGuards(DemoGuard))
|
|
@ -0,0 +1,9 @@
|
|||
import { ErrorCodeEnum } from '~/constants/error-code.constant'
|
||||
|
||||
import { BusinessException } from './business.exception'
|
||||
|
||||
export class BanInDemoExcpetion extends BusinessException {
|
||||
constructor() {
|
||||
super(ErrorCodeEnum.BanInDemo)
|
||||
}
|
||||
}
|
|
@ -1,9 +1,13 @@
|
|||
export enum ErrorCodeEnum {
|
||||
SlugNotAvailable = 'slug_not_available',
|
||||
|
||||
BanInDemo = 'ban_in_demo',
|
||||
}
|
||||
|
||||
export const ErrorCode = Object.freeze<Record<ErrorCodeEnum, [string, number]>>(
|
||||
{
|
||||
[ErrorCodeEnum.SlugNotAvailable]: ['slug 不可用', 400],
|
||||
|
||||
[ErrorCodeEnum.BanInDemo]: ['Demo 模式下此操作不可用', 400],
|
||||
},
|
||||
)
|
||||
|
|
|
@ -18,6 +18,7 @@ import {
|
|||
import { ApiProperty, ApiResponseProperty } from '@nestjs/swagger'
|
||||
|
||||
import { Auth } from '~/common/decorator/auth.decorator'
|
||||
import { BanInDemo } from '~/common/decorator/demo.decorator'
|
||||
import { HTTPDecorators } from '~/common/decorator/http.decorator'
|
||||
import { ApiName } from '~/common/decorator/openapi.decorator'
|
||||
import { UploadService } from '~/processors/helper/helper.upload.service'
|
||||
|
@ -28,6 +29,7 @@ import { BackupService } from './backup.service'
|
|||
@Controller({ path: 'backups', scope: Scope.REQUEST })
|
||||
@ApiName
|
||||
@Auth()
|
||||
@BanInDemo
|
||||
export class BackupController {
|
||||
constructor(
|
||||
private readonly backupService: BackupService,
|
||||
|
|
|
@ -79,7 +79,8 @@ export class BackupService {
|
|||
await $`mongodump -h ${MONGO_DB.host} --port ${MONGO_DB.port} -d ${MONGO_DB.dbName} --excludeCollection analyzes -o ${backupDirPath} >/dev/null 2>&1`
|
||||
// 打包 DB
|
||||
cd(backupDirPath)
|
||||
await quiet($`zip -r backup-${dateDir} mx-space/* && rm -rf mx-space`)
|
||||
await nothrow(quiet($`mv ${MONGO_DB.dbName} mx-space`))
|
||||
await quiet($`zip -r backup-${dateDir} mx-space/* && rm -rf mx-space`)
|
||||
|
||||
// 打包数据目录
|
||||
|
||||
|
@ -204,6 +205,7 @@ export class BackupService {
|
|||
this.cacheService.cleanAllRedisKey(),
|
||||
this.cacheService.cleanCatch(),
|
||||
])
|
||||
await rm(join(dirPath, 'backup_data'), { force: true, recursive: true })
|
||||
}
|
||||
|
||||
async rollbackTo(dirname: string) {
|
||||
|
|
|
@ -15,11 +15,12 @@ import { EventEmitter2 } from '@nestjs/event-emitter'
|
|||
import { DocumentType, ReturnModelType } from '@typegoose/typegoose'
|
||||
import { BeAnObject } from '@typegoose/typegoose/lib/types'
|
||||
|
||||
import { isInDemoMode } from '~/app.config'
|
||||
import { RedisKeys } from '~/constants/cache.constant'
|
||||
import { EventBusEvents } from '~/constants/event-bus.constant'
|
||||
import { CacheService } from '~/processors/cache/cache.service'
|
||||
import { InjectModel } from '~/transformers/model.transformer'
|
||||
import { sleep } from '~/utils'
|
||||
import { banInDemo, sleep } from '~/utils'
|
||||
import { getRedisKey } from '~/utils/redis.util'
|
||||
|
||||
import * as optionDtos from '../configs/configs.dto'
|
||||
|
@ -61,14 +62,13 @@ const generateDefaultConfig: () => IConfig = () => ({
|
|||
mailOptions: {} as MailOptionsDto,
|
||||
commentOptions: { antiSpam: false },
|
||||
friendLinkOptions: { allowApply: true },
|
||||
backupOptions: { enable: true } as BackupOptionsDto,
|
||||
backupOptions: { enable: isInDemoMode ? false : true } as BackupOptionsDto,
|
||||
baiduSearchOptions: { enable: false },
|
||||
algoliaSearchOptions: { enable: false, apiKey: '', appId: '', indexName: '' },
|
||||
adminExtra: {
|
||||
enableAdminProxy: true,
|
||||
title: 'おかえり~',
|
||||
background:
|
||||
'https://gitee.com/xun7788/my-imagination/raw/master/images/88426823_p0.jpg',
|
||||
background: '',
|
||||
gaodemapKey: null!,
|
||||
},
|
||||
terminalOptions: {
|
||||
|
@ -209,6 +209,7 @@ export class ConfigsService {
|
|||
key: T,
|
||||
value: Partial<IConfig[T]>,
|
||||
) {
|
||||
banInDemo()
|
||||
value = camelcaseKeys(value, { deep: true }) as any
|
||||
|
||||
switch (key) {
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
import { resolve } from 'path'
|
||||
|
||||
import { Module } from '@nestjs/common'
|
||||
import { Cron, CronExpression } from '@nestjs/schedule'
|
||||
|
||||
import { AssetService } from '~/processors/helper/helper.asset.service'
|
||||
|
||||
import { BackupModule } from '../backup/backup.module'
|
||||
import { BackupService } from '../backup/backup.service'
|
||||
|
||||
@Module({
|
||||
imports: [BackupModule],
|
||||
})
|
||||
export class DemoModule {
|
||||
constructor(
|
||||
private readonly backupService: BackupService,
|
||||
private readonly assetService: AssetService,
|
||||
) {
|
||||
this.reset()
|
||||
}
|
||||
@Cron(CronExpression.EVERY_DAY_AT_1AM)
|
||||
reset() {
|
||||
this.backupService.restore(
|
||||
resolve(this.assetService.embedAssetPath, 'demo-data.zip'),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ import { Readable } from 'stream'
|
|||
import { BadRequestException, Injectable } from '@nestjs/common'
|
||||
|
||||
import { STATIC_FILE_DIR } from '~/constants/path.constant'
|
||||
import { banInDemo } from '~/utils'
|
||||
|
||||
import { ConfigsService } from '../configs/configs.service'
|
||||
import { FileType } from './file.type'
|
||||
|
@ -33,6 +34,7 @@ export class FileService {
|
|||
data: Readable,
|
||||
encoding?: BufferEncoding,
|
||||
) {
|
||||
banInDemo()
|
||||
// eslint-disable-next-line no-async-promise-executor
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const filePath = this.resolveFilePath(type, name)
|
||||
|
@ -58,6 +60,7 @@ export class FileService {
|
|||
}
|
||||
|
||||
deleteFile(type: FileType, name: string) {
|
||||
banInDemo()
|
||||
return fs.unlink(this.resolveFilePath(type, name)).catch(() => null)
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ import { Reflector } from '@nestjs/core'
|
|||
import { SchedulerRegistry } from '@nestjs/schedule'
|
||||
|
||||
import { Auth } from '~/common/decorator/auth.decorator'
|
||||
import { BanInDemo } from '~/common/decorator/demo.decorator'
|
||||
import { HTTPDecorators } from '~/common/decorator/http.decorator'
|
||||
import { ApiName } from '~/common/decorator/openapi.decorator'
|
||||
import { CRON_DESCRIPTION } from '~/constants/meta.constant'
|
||||
|
@ -73,6 +74,7 @@ export class HealthController {
|
|||
}
|
||||
|
||||
@Post('/cron/run/:name')
|
||||
@BanInDemo
|
||||
async runCron(@Param('name') name: string) {
|
||||
if (!isString(name)) {
|
||||
throw new UnprocessableEntityException('name must be string')
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
UnprocessableEntityException,
|
||||
} from '@nestjs/common'
|
||||
|
||||
import { BanInDemo } from '~/common/decorator/demo.decorator'
|
||||
import { HTTPDecorators } from '~/common/decorator/http.decorator'
|
||||
import { IConfig } from '~/modules/configs/configs.interface'
|
||||
import { ConfigsService } from '~/modules/configs/configs.service'
|
||||
|
@ -50,6 +51,7 @@ export class BaseOptionController {
|
|||
}
|
||||
|
||||
@Patch('/:key')
|
||||
@BanInDemo
|
||||
patch(@Param() params: ConfigKeyDto, @Body() body: Record<string, any>) {
|
||||
if (typeof body !== 'object') {
|
||||
throw new UnprocessableEntityException('body must be object')
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
WebSocketGateway,
|
||||
} from '@nestjs/websockets'
|
||||
|
||||
import { isInDemoMode } from '~/app.config'
|
||||
import { BusinessEvents } from '~/constants/business-event.constant'
|
||||
import { RedisKeys } from '~/constants/cache.constant'
|
||||
import { DATA_DIR } from '~/constants/path.constant'
|
||||
|
@ -42,6 +43,17 @@ export class PTYGateway
|
|||
client: Socket,
|
||||
data?: { password?: string; cols: number; rows: number },
|
||||
) {
|
||||
if (isInDemoMode) {
|
||||
client.send(
|
||||
this.gatewayMessageFormat(
|
||||
BusinessEvents.PTY_MESSAGE,
|
||||
'PTY 在演示模式下不可用',
|
||||
),
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const password = data?.password
|
||||
const terminalOptions = await this.configService.get('terminalOptions')
|
||||
if (!terminalOptions.enable) {
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
} from '@nestjs/common'
|
||||
|
||||
import { Auth } from '~/common/decorator/auth.decorator'
|
||||
import { BanInDemo } from '~/common/decorator/demo.decorator'
|
||||
import { HTTPDecorators } from '~/common/decorator/http.decorator'
|
||||
import { ApiName } from '~/common/decorator/openapi.decorator'
|
||||
import { IsMaster } from '~/common/decorator/role.decorator'
|
||||
|
@ -74,6 +75,7 @@ export class SnippetController {
|
|||
|
||||
@Post('/')
|
||||
@Auth()
|
||||
@BanInDemo
|
||||
async create(@Body() body: SnippetModel) {
|
||||
return await this.snippetService.create(body)
|
||||
}
|
||||
|
@ -146,6 +148,7 @@ export class SnippetController {
|
|||
|
||||
@Put('/:id')
|
||||
@Auth()
|
||||
@BanInDemo
|
||||
async update(@Param() param: MongoIdDto, @Body() body: SnippetModel) {
|
||||
const { id } = param
|
||||
|
||||
|
@ -154,6 +157,7 @@ export class SnippetController {
|
|||
|
||||
@Delete('/:id')
|
||||
@Auth()
|
||||
@BanInDemo
|
||||
async delete(@Param() param: MongoIdDto) {
|
||||
const { id } = param
|
||||
await this.snippetService.delete(id)
|
||||
|
|
|
@ -15,7 +15,7 @@ import { MasterLostException } from '~/common/exceptions/master-lost.exception'
|
|||
import { RedisKeys } from '~/constants/cache.constant'
|
||||
import { CacheService } from '~/processors/cache/cache.service'
|
||||
import { InjectModel } from '~/transformers/model.transformer'
|
||||
import { getAvatar, sleep } from '~/utils'
|
||||
import { banInDemo, getAvatar, sleep } from '~/utils'
|
||||
import { getRedisKey } from '~/utils/redis.util'
|
||||
|
||||
import { AuthService } from '../auth/auth.service'
|
||||
|
@ -95,6 +95,7 @@ export class UserService {
|
|||
* @param {Partial} data - 部分修改数据
|
||||
*/
|
||||
async patchUserData(user: UserDocument, data: Partial<UserModel>) {
|
||||
banInDemo()
|
||||
const { password } = data
|
||||
const doc = { ...data }
|
||||
if (password !== undefined) {
|
||||
|
|
|
@ -10,7 +10,7 @@ import { Inject, Injectable, Logger, forwardRef } from '@nestjs/common'
|
|||
import { OnEvent } from '@nestjs/event-emitter'
|
||||
import { Cron, CronExpression } from '@nestjs/schedule'
|
||||
|
||||
import { isMainCluster } from '~/app.config'
|
||||
import { isInDemoMode, isMainCluster } from '~/app.config'
|
||||
import { CronDescription } from '~/common/decorator/cron-description.decorator'
|
||||
import { RedisKeys } from '~/constants/cache.constant'
|
||||
import { EventBusEvents } from '~/constants/event-bus.constant'
|
||||
|
@ -82,6 +82,9 @@ export class CronService {
|
|||
@Cron(CronExpression.EVERY_DAY_AT_1AM, { name: 'backupDB' })
|
||||
@CronDescription('备份 DB 并上传 COS')
|
||||
async backupDB({ uploadCOS = true }: { uploadCOS?: boolean } = {}) {
|
||||
if (isInDemoMode) {
|
||||
return
|
||||
}
|
||||
const backup = await this.backupService.backup()
|
||||
if (!backup) {
|
||||
this.logger.log('没有开启备份')
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
import { isInDemoMode } from '~/app.config'
|
||||
import { BanInDemoExcpetion } from '~/common/exceptions/ban-in-demo.exception'
|
||||
|
||||
/**
|
||||
* 检查是否在 demo 模式下,禁用此功能
|
||||
*/
|
||||
export const banInDemo = () => {
|
||||
if (isInDemoMode) {
|
||||
throw new BanInDemoExcpetion()
|
||||
}
|
||||
}
|
|
@ -6,3 +6,4 @@ export * from './redis.util'
|
|||
export * from './system.util'
|
||||
export * from './time.util'
|
||||
export * from './tool.util'
|
||||
export * from './demo.util'
|
||||
|
|
Loading…
Reference in New Issue