fix: config readonly & clone deep

This commit is contained in:
Innei
2021-09-18 16:29:40 +08:00
parent a9724b09a8
commit 9563ff3c6a
8 changed files with 89 additions and 76 deletions

View File

@@ -62,6 +62,7 @@
"bcrypt": "5.0.1",
"cache-manager": "3.4.4",
"cache-manager-ioredis": "2.1.0",
"camelcase-keys": "^7.0.0",
"chalk": "*",
"class-transformer": "0.4.0",
"class-validator": "0.13.1",

23
pnpm-lock.yaml generated
View File

@@ -45,6 +45,7 @@ specifiers:
bcrypt: 5.0.1
cache-manager: 3.4.4
cache-manager-ioredis: 2.1.0
camelcase-keys: ^7.0.0
chalk: '*'
class-transformer: 0.4.0
class-validator: 0.13.1
@@ -125,6 +126,7 @@ dependencies:
bcrypt: 5.0.1
cache-manager: 3.4.4
cache-manager-ioredis: 2.1.0
camelcase-keys: 7.0.0
chalk: 4.1.2
class-transformer: 0.4.0
class-validator: 0.13.1
@@ -3104,6 +3106,16 @@ packages:
tslib: 2.3.1
dev: false
/camelcase-keys/7.0.0:
resolution: {integrity: sha512-qlQlECgDl5Ev+gkvONaiD4X4TF2gyZKuLBvzx0zLo2UwAxmz3hJP/841aaMHTeH1T7v5HRwoRq91daulXoYWvg==}
engines: {node: '>=12'}
dependencies:
camelcase: 6.2.0
map-obj: 4.2.1
quick-lru: 5.1.1
type-fest: 1.4.0
dev: false
/camelcase/5.3.1:
resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==}
engines: {node: '>=6'}
@@ -3112,7 +3124,6 @@ packages:
/camelcase/6.2.0:
resolution: {integrity: sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==}
engines: {node: '>=10'}
dev: true
/caniuse-lite/1.0.30001255:
resolution: {integrity: sha512-F+A3N9jTZL882f/fg/WWVnKSu6IOo3ueLz4zwaOPbPYHNmM/ZaDUyzyJwS1mZhX7Ex5jqTyW599Gdelh5PDYLQ==}
@@ -7073,6 +7084,11 @@ packages:
/quick-format-unescaped/4.0.3:
resolution: {integrity: sha512-MaL/oqh02mhEo5m5J2rwsVL23Iw2PEaGVHgT2vFt8AAsr0lfvQA5dpXo9TPu0rz7tSBdUPgkbam0j/fj5ZM8yg==}
/quick-lru/5.1.1:
resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==}
engines: {node: '>=10'}
dev: false
/randombytes/2.1.0:
resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
dependencies:
@@ -8235,6 +8251,11 @@ packages:
resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==}
engines: {node: '>=10'}
/type-fest/1.4.0:
resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==}
engines: {node: '>=10'}
dev: false
/typedarray-to-buffer/3.1.5:
resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==}
dependencies:

View File

@@ -11,7 +11,7 @@ import {
ValidateNested,
} from 'class-validator'
export class SEODto {
export class SeoDto {
@IsString({ message: '标题必须是字符串' })
@IsNotEmpty({ message: '不能为空!!' })
@IsOptional()
@@ -80,7 +80,7 @@ export class MailOptionsDto {
options?: MailOption
}
export class CommentOptions {
export class CommentOptionsDto {
@IsBoolean()
@IsOptional()
antiSpam: boolean
@@ -97,7 +97,7 @@ export class CommentOptions {
disableNoChinese?: boolean
}
export class BackupOptions {
export class BackupOptionsDto {
@IsBoolean()
@IsOptional()
enable: boolean
@@ -119,7 +119,7 @@ export class BackupOptions {
region: string
}
export class BaiduSearchOptions {
export class BaiduSearchOptionsDto {
@IsOptional()
@IsBoolean()
enable: boolean
@@ -130,7 +130,7 @@ export class BaiduSearchOptions {
token?: string
}
export class AlgoliaSearchOptions {
export class AlgoliaSearchOptionsDto {
@IsBoolean()
@IsOptional()
enable: boolean

View File

@@ -1,21 +1,21 @@
import {
AlgoliaSearchOptions,
BackupOptions,
BaiduSearchOptions,
CommentOptions,
AlgoliaSearchOptionsDto,
BackupOptionsDto,
BaiduSearchOptionsDto,
CommentOptionsDto,
MailOptionsDto,
SEODto,
SeoDto,
UrlDto,
} from './configs.dto'
export interface IConfig {
seo: SEODto
seo: SeoDto
url: UrlDto
mailOptions: MailOptionsDto
commentOptions: CommentOptions
backupOptions: BackupOptions
baiduSearchOptions: BaiduSearchOptions
algoliaSearchOptions: AlgoliaSearchOptions
commentOptions: CommentOptionsDto
backupOptions: BackupOptionsDto
baiduSearchOptions: BaiduSearchOptionsDto
algoliaSearchOptions: AlgoliaSearchOptionsDto
}
export type IConfigKeys = keyof IConfig

View File

@@ -1,13 +1,14 @@
import { Injectable, Logger } from '@nestjs/common'
import { ReturnModelType } from '@typegoose/typegoose'
import { cloneDeep } from 'lodash'
import { InjectModel } from 'nestjs-typegoose'
import { sleep } from '~/utils/index.util'
import { UserService } from '../user/user.service'
import { BackupOptions, MailOptionsDto } from './configs.dto'
import { BackupOptionsDto, MailOptionsDto } from './configs.dto'
import { IConfig } from './configs.interface'
import { OptionModel } from './configs.model'
const defaultConfig: IConfig = {
const generateDefaultConfig: () => IConfig = () => ({
seo: {
title: 'mx-space',
description: 'Hello World~',
@@ -20,14 +21,14 @@ const defaultConfig: IConfig = {
},
mailOptions: {} as MailOptionsDto,
commentOptions: { antiSpam: false },
backupOptions: { enable: false } as BackupOptions,
backupOptions: { enable: false } as BackupOptionsDto,
baiduSearchOptions: { enable: false },
algoliaSearchOptions: { enable: false, apiKey: '', appId: '', indexName: '' },
}
})
@Injectable()
export class ConfigsService {
private config: IConfig = defaultConfig
private config: IConfig = generateDefaultConfig()
private logger: Logger
constructor(
@InjectModel(OptionModel)
@@ -75,19 +76,11 @@ export class ConfigsService {
this.logger.log('Config 已经加载完毕!')
}
public get seo() {
return this.config.seo
}
public get url() {
return this.config.url
}
public get<T extends keyof IConfig>(key: T): Readonly<IConfig[T]> {
return this.config[key] as Readonly<IConfig[T]>
return cloneDeep(this.config[key]) as Readonly<IConfig[T]>
}
public getConfig(): Readonly<IConfig> {
return this.config
return cloneDeep(this.config)
}
public async patch<T extends keyof IConfig>(key: T, data: IConfig[T]) {
@@ -98,9 +91,10 @@ export class ConfigsService {
)
const newData = (await this.optionModel.findOne({ name: key as string }))
.value
this.config[key] = newData
return this.config[key]
return cloneDeep(this.config[key])
}
get getMaster() {

View File

@@ -7,27 +7,26 @@
* @Coding with Love
*/
import {
Injectable,
UnprocessableEntityException,
ValidationPipe,
} from '@nestjs/common'
import { BadRequestException, Injectable, ValidationPipe } from '@nestjs/common'
import camelcaseKeys from 'camelcase-keys'
import { ClassConstructor, plainToClass } from 'class-transformer'
import { validateSync, ValidatorOptions } from 'class-validator'
import { CronService } from '~/processors/helper/helper.cron.service'
import { EmailService } from '~/processors/helper/helper.email.service'
import {
AlgoliaSearchOptions,
BackupOptions,
BaiduSearchOptions,
CommentOptions,
MailOptionsDto,
SEODto,
UrlDto,
} from '../configs/configs.dto'
import * as optionDtos from '../configs/configs.dto'
import { AlgoliaSearchOptionsDto, MailOptionsDto } from '../configs/configs.dto'
import { IConfig } from '../configs/configs.interface'
import { ConfigsService } from '../configs/configs.service'
const map: Record<string, any> = Object.entries(optionDtos).reduce(
(obj, [key, value]) => ({
...obj,
[`${key.charAt(0).toLowerCase() + key.slice(1).replace(/Dto$/, '')}`]:
value,
}),
{},
)
@Injectable()
export class OptionService {
constructor(
@@ -42,16 +41,9 @@ export class OptionService {
}
validate = new ValidationPipe(this.validOptions)
patchAndValid(key: keyof IConfig, value: any) {
switch (key) {
case 'url': {
this.validWithDto(UrlDto, value)
return this.configs.patch('url', value)
}
case 'commentOptions': {
this.validWithDto(CommentOptions, value)
return this.configs.patch('commentOptions', value)
}
value = camelcaseKeys(value, { deep: true })
switch (key) {
case 'mailOptions': {
this.validWithDto(MailOptionsDto, value)
const task = this.configs.patch('mailOptions', value)
@@ -61,29 +53,24 @@ export class OptionService {
})
return task
}
case 'seo': {
this.validWithDto(SEODto, value)
return this.configs.patch('seo', value)
}
case 'backupOptions': {
this.validWithDto(BackupOptions, value)
return this.configs.patch('backupOptions', value)
}
case 'baiduSearchOptions': {
this.validWithDto(BaiduSearchOptions, value)
return this.configs.patch('baiduSearchOptions', value)
}
case 'algoliaSearchOptions': {
this.validWithDto(AlgoliaSearchOptions, value)
return this.configs.patch('algoliaSearchOptions', value).then((r) => {
this.cronService.pushToAlgoliaSearch()
return r
})
return this.configs
.patch(
'algoliaSearchOptions',
this.validWithDto(AlgoliaSearchOptionsDto, value),
)
.then((r) => {
this.cronService.pushToAlgoliaSearch()
return r
})
}
default: {
throw new UnprocessableEntityException('设置不存在')
const dto = map[key]
if (!dto) {
throw new BadRequestException('设置不存在')
}
return this.configs.patch(key, this.validWithDto(dto, value))
}
}
}
@@ -95,6 +82,6 @@ export class OptionService {
const error = this.validate.createExceptionFactory()(errors as any[])
throw error
}
return true
return validModel
}
}

View File

@@ -265,6 +265,7 @@ export class CronService {
.lean()
.then((list) => {
return list.map((data) => {
Reflect.set(data, 'objectID', data._id)
Reflect.deleteProperty(data, '_id')
return {
...data,
@@ -277,6 +278,7 @@ export class CronService {
.lean()
.then((list) => {
return list.map((data) => {
Reflect.set(data, 'objectID', data._id)
Reflect.deleteProperty(data, '_id')
return {
...data,
@@ -300,6 +302,7 @@ export class CronService {
.then((list) => {
return list.map((data) => {
const id = data.nid.toString()
Reflect.set(data, 'objectID', data._id)
Reflect.deleteProperty(data, '_id')
Reflect.deleteProperty(data, 'nid')
return {
@@ -314,8 +317,9 @@ export class CronService {
documents.push(...documents_)
})
try {
await index.clearObjects()
await index.saveObjects(documents, {
autoGenerateObjectIDIfNotExist: true,
autoGenerateObjectIDIfNotExist: false,
})
this.logger.log('--> 推送到 algoliasearch 成功')
} catch {

View File

@@ -4,3 +4,9 @@ import { isDev } from './index.util'
Object.assign(globalThis, {
isDev: isDev,
})
console.debug = (...rest) => {
if (isDev) {
console.log.call(console, ...rest)
}
}