feat(subscribe): add feature list toggle
Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
7
pnpm-lock.yaml
generated
@@ -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:
|
||||
|
||||
@@ -63,4 +63,7 @@ export const generateDefaultConfig: () => IConfig = () => ({
|
||||
textOptions: {
|
||||
macros: true,
|
||||
},
|
||||
featureList: {
|
||||
emailSubscribe: false,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -323,5 +323,16 @@ export class BarkOptionsDto {
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
@JSONSchemaToggleField('开启评论通知')
|
||||
enableComment?: boolean
|
||||
enableComment: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* 特征开关
|
||||
*/
|
||||
@JSONSchema({ title: '特征开关设定' })
|
||||
export class FeatureListDto {
|
||||
@JSONSchemaToggleField('开启邮件推送订阅')
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
emailSubscribe: boolean
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user