feat(subscribe): add feature list toggle

Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
Innei
2023-02-13 22:50:00 +08:00
parent bfc2e25fab
commit 1b80adb02c
10 changed files with 95 additions and 10 deletions

View File

@@ -67,6 +67,7 @@
"@fastify/cookie": "8.3.0",
"@fastify/multipart": "7.4.0",
"@fastify/static": "6.8.0",
"@innei/next-async": "0.2.3",
"@nestjs/common": "9.3.2",
"@nestjs/core": "9.3.2",
"@nestjs/event-emitter": "1.4.1",

View File

@@ -27,6 +27,17 @@ export class SubscribeController<ResponseWrapper> implements IController {
return this.client.proxy(this.base)
}
/**
* 检查开启状态
*/
check() {
return this.proxy.status.get<{
enable: boolean
bitMap: Record<SubscribeType, number>
allowTypes: number[]
}>()
}
subscribe(email: string, types: SubscribeType[]) {
return this.proxy.post<never>({
params: {

View File

@@ -11,5 +11,6 @@ export * from './recently'
export * from './say'
export * from './setting'
export * from './snippet'
export * from './subscribe'
export * from './topic'
export * from './user'

View File

@@ -60,6 +60,7 @@
},
"scripts": {
"package": "rm -rf dist && tsup && node mod-dts.mjs",
"build": "npm run package",
"prepackage": "rm -rf dist",
"test": "vitest",
"dev": "vitest"

7
pnpm-lock.yaml generated
View File

@@ -19,6 +19,7 @@ importers:
'@fastify/multipart': 7.4.0
'@fastify/static': 6.8.0
'@innei/eslint-config-ts': 0.9.7
'@innei/next-async': 0.2.3
'@innei/prettier': 0.9.7
'@nestjs/cli': 9.2.0
'@nestjs/common': 9.3.2
@@ -141,6 +142,7 @@ importers:
'@fastify/cookie': 8.3.0
'@fastify/multipart': 7.4.0
'@fastify/static': 6.8.0
'@innei/next-async': 0.2.3
'@nestjs/common': 9.3.2_wfccvjmrxsdt24lpjkvzmt5igm
'@nestjs/core': 9.3.2_gbxbw37j2g4pb6cu3eyhp23y5a
'@nestjs/event-emitter': 1.4.1_dvs6bzzwlxted5pypaoawo6uwu
@@ -1783,6 +1785,11 @@ packages:
- supports-color
dev: true
/@innei/next-async/0.2.3:
resolution: {integrity: sha512-9Xn38sPORx8knSpjjOEPot0S2BszKRK6V8sqShmBIeOspKhQIypa7BCwy+2+rZnPLk1XrodVUl5mCqBmHwBDTg==}
engines: {pnpm: '>=7'}
dev: false
/@innei/prettier/0.9.7:
resolution: {integrity: sha512-CxUbNMsm23h3llui28siIPAyuv8WvSwRVrghZo1JYb+UV/+YcqLGlZNyhtsk0KiPKOIRhMcJI4T3CgMX2MSHPQ==}
dependencies:

View File

@@ -63,4 +63,7 @@ export const generateDefaultConfig: () => IConfig = () => ({
textOptions: {
macros: true,
},
featureList: {
emailSubscribe: false,
},
})

View File

@@ -323,5 +323,16 @@ export class BarkOptionsDto {
@IsOptional()
@IsBoolean()
@JSONSchemaToggleField('开启评论通知')
enableComment?: boolean
enableComment: boolean
}
/**
* 特征开关
*/
@JSONSchema({ title: '特征开关设定' })
export class FeatureListDto {
@JSONSchemaToggleField('开启邮件推送订阅')
@IsBoolean()
@IsOptional()
emailSubscribe: boolean
}

View File

@@ -9,6 +9,7 @@ import {
BaiduSearchOptionsDto,
BarkOptionsDto,
CommentOptionsDto,
FeatureListDto,
FriendLinkOptionsDto,
MailOptionsDto,
SeoDto,
@@ -67,6 +68,10 @@ export abstract class IConfig {
@Type(() => TerminalOptionsDto)
@ValidateNested()
terminalOptions: Required<TerminalOptionsDto>
@Type(() => FeatureListDto)
@ValidateNested()
featureList: Required<FeatureListDto>
}
export type IConfigKeys = keyof IConfig

View File

@@ -1,10 +1,15 @@
import { Body, Get, Post, Query } from '@nestjs/common'
import { BadRequestException, Body, Get, Post, Query } from '@nestjs/common'
import { ApiController } from '~/common/decorators/api-controller.decorator'
import { Auth } from '~/common/decorators/auth.decorator'
import { HTTPDecorators } from '~/common/decorators/http.decorator'
import { PagerDto } from '~/shared/dto/pager.dto'
import {
SubscribeNoteCreateBit,
SubscribePostCreateBit,
SubscribeTypeToBitMap,
} from './subscribe.constant'
import { CancelSubscribeDto, SubscribeDto } from './subscribe.dto'
import { SubscribeService } from './subscribe.service'
@@ -12,6 +17,18 @@ import { SubscribeService } from './subscribe.service'
export class SubscribeController {
constructor(private readonly service: SubscribeService) {}
@Get('/status')
// 检查特征是否开启
@HTTPDecorators.Bypass
async checkStatus() {
return {
enable: await this.service.checkEnable(),
bit_map: SubscribeTypeToBitMap,
// TODO move to service
allow_types: [SubscribeNoteCreateBit, SubscribePostCreateBit],
}
}
@Get('/')
@HTTPDecorators.Paginator
@Auth()
@@ -33,6 +50,9 @@ export class SubscribeController {
@Post('/')
async subscribe(@Body() body: SubscribeDto) {
if (!(await this.service.checkEnable())) {
throw new BadRequestException('订阅功能未开启')
}
const { email, types } = body
let bit = 0
for (const type of types) {

View File

@@ -2,6 +2,8 @@ import cluster from 'cluster'
import { render } from 'ejs'
import { nanoid } from 'nanoid'
import { Co } from '@innei/next-async'
import { CoAction } from '@innei/next-async/types/interface'
import { BadRequestException, Injectable, OnModuleInit } from '@nestjs/common'
import { BusinessEvents, EventScope } from '~/constants/business-event.constant'
@@ -67,20 +69,25 @@ export class SubscribeService implements OnModuleInit {
return `${serverUrl}/subscribe/unsubscribe?email=${email}&cancelToken=${document.cancelToken}`
}
const noteAndPostHandler = async (noteOrPost: NoteModel | PostModel) => {
const user = await this.configService.getMaster()
for (const [email, subscribe] of this.subscribeMap.entries()) {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const self = this
const noteAndPostHandler: CoAction<never> = async function (
noteOrPost: NoteModel | PostModel,
) {
const user = await self.configService.getMaster()
for (const [email, subscribe] of self.subscribeMap.entries()) {
const unsubscribeLink = await getUnsubscribeLink(email)
if (!unsubscribeLink) continue
const isNote = this.urlBuilderService.isNoteModel(noteOrPost)
const isNote = self.urlBuilderService.isNoteModel(noteOrPost)
if (
subscribe & (isNote ? SubscribeNoteCreateBit : SubscribePostCreateBit)
)
this.sendEmail(email, {
self.sendEmail(email, {
author: user.name,
detail_link: await this.urlBuilderService.buildWithBaseUrl(
detail_link: await self.urlBuilderService.buildWithBaseUrl(
noteOrPost,
),
text: `${noteOrPost.text.slice(0, 150)}...`,
@@ -90,15 +97,27 @@ export class SubscribeService implements OnModuleInit {
})
}
}
const precheck: CoAction<any> = async function () {
const enable = await self.checkEnable()
if (enable) {
await this.next()
return
}
this.abort()
}
// TODO 抽离逻辑
this.eventManager.on(
BusinessEvents.NOTE_CREATE,
noteAndPostHandler,
(e) => new Co().use(precheck, noteAndPostHandler).start(e),
scopeCfg,
)
this.eventManager.on(
BusinessEvents.POST_CREATE,
noteAndPostHandler,
(e) => new Co().use(precheck, noteAndPostHandler).start(e),
scopeCfg,
)
@@ -209,4 +228,10 @@ export class SubscribeService implements OnModuleInit {
await this.emailService.send(options)
}
async checkEnable() {
const { emailSubscribe } = await this.configService.get('featureList')
return emailSubscribe
}
}