diff --git a/src/modules/comment/comment.controller.ts b/src/modules/comment/comment.controller.ts index a09b3e73..a4bc4b01 100644 --- a/src/modules/comment/comment.controller.ts +++ b/src/modules/comment/comment.controller.ts @@ -1,4 +1,5 @@ import { isUndefined } from 'lodash' +import { Document, FilterQuery } from 'mongoose' import { Body, @@ -30,6 +31,7 @@ import { MongoIdDto } from '~/shared/dto/id.dto' import { PagerDto } from '~/shared/dto/pager.dto' import { transformDataToPaginate } from '~/transformers/paginate.transformer' +import { ConfigsService } from '../configs/configs.service' import { UserModel } from '../user/user.model' import { CommentDto, @@ -50,6 +52,7 @@ export class CommentController { constructor( private readonly commentService: CommentService, private readonly eventManager: EventManagerService, + private readonly configsService: ConfigsService, ) {} @Get('/') @@ -63,16 +66,23 @@ export class CommentController { @Get('/:id') @ApiOperation({ summary: '根据 comment id 获取评论, 包括子评论' }) - async getComments(@Param() params: MongoIdDto) { + async getComments( + @Param() params: MongoIdDto, + @IsMaster() isMaster: boolean, + ) { const { id } = params const data = await this.commentService.model .findOne({ _id: id, }) .populate('parent') + if (!data) { throw new CannotFindException() } + if (data.isWhispers && !isMaster) { + throw new CannotFindException() + } return data } @@ -83,19 +93,58 @@ export class CommentController { async getCommentsByRefId( @Param() params: MongoIdDto, @Query() query: PagerDto, + @IsMaster() isMaster: boolean, ) { const { id } = params const { page = 1, size = 10 } = query - const comments = await this.commentService.model.paginate( + + const configs = await this.configsService.get('commentOptions') + const { commentShouldAudit } = configs + + const $and: FilterQuery>[] = [ { parent: undefined, ref: id, + }, + { + $or: commentShouldAudit + ? [ + { + state: CommentState.Read, + }, + ] + : [ + { + state: CommentState.Read, + }, + { state: CommentState.Unread }, + ], + }, + ] + + if (isMaster) { + $and.push({ $or: [ + { isWhispers: true }, + { isWhispers: false }, { - state: CommentState.Read, + isWhispers: { $exists: false }, }, - { state: CommentState.Unread }, ], + }) + } else { + $and.push({ + $or: [ + { isWhispers: false }, + { + isWhispers: { $exists: false }, + }, + ], + }) + } + const comments = await this.commentService.model.paginate( + { + $and, }, { limit: size, @@ -138,6 +187,8 @@ export class CommentController { const comment = await this.commentService.createComment(id, model, ref) process.nextTick(async () => { + const configs = await this.configsService.get('commentOptions') + const { commentShouldAudit } = configs if (await this.commentService.checkSpam(comment)) { comment.state = CommentState.Junk await comment.save() @@ -146,11 +197,27 @@ export class CommentController { this.commentService.sendEmail(comment, ReplyMailType.Owner) } + if (commentShouldAudit) { + await this.eventManager.broadcast( + BusinessEvents.COMMENT_CREATE, + comment, + { + scope: EventScope.TO_SYSTEM_ADMIN, + }, + ) + + return + } + await this.eventManager.broadcast( BusinessEvents.COMMENT_CREATE, comment, { - scope: isMaster ? EventScope.TO_SYSTEM_VISITOR : EventScope.ALL, + scope: isMaster + ? EventScope.TO_SYSTEM_VISITOR + : comment.isWhispers + ? EventScope.TO_SYSTEM_ADMIN + : EventScope.ALL, }, ) }) @@ -222,6 +289,16 @@ export class CommentController { scope: EventScope.TO_SYSTEM_VISITOR, }) } else { + const configs = await this.configsService.get('commentOptions') + const { commentShouldAudit } = configs + + if (commentShouldAudit) { + this.eventManager.broadcast(BusinessEvents.COMMENT_CREATE, comment, { + scope: EventScope.TO_SYSTEM_ADMIN, + }) + return + } + this.commentService.sendEmail(comment, ReplyMailType.Owner) this.eventManager.broadcast(BusinessEvents.COMMENT_CREATE, comment, { scope: EventScope.ALL, diff --git a/src/modules/comment/comment.dto.ts b/src/modules/comment/comment.dto.ts index 36195618..7f36b2a7 100644 --- a/src/modules/comment/comment.dto.ts +++ b/src/modules/comment/comment.dto.ts @@ -40,6 +40,10 @@ export class CommentDto { @ApiProperty({ example: 'http://example.com' }) @MaxLength(50, { message: '地址不得大于 50 个字符' }) url?: string + + @IsOptional() + @IsBoolean() + isWhispers?: boolean } export class TextOnlyDto { diff --git a/src/modules/comment/comment.model.ts b/src/modules/comment/comment.model.ts index 84982184..b65aa888 100644 --- a/src/modules/comment/comment.model.ts +++ b/src/modules/comment/comment.model.ts @@ -126,6 +126,10 @@ export class CommentModel extends BaseModel { @prop() public location?: string + // 悄悄话 + @prop({ default: false }) + isWhispers?: boolean + public get avatar() { return getAvatar(this.mail) } diff --git a/src/modules/comment/comment.service.ts b/src/modules/comment/comment.service.ts index 9f71924a..a754221c 100644 --- a/src/modules/comment/comment.service.ts +++ b/src/modules/comment/comment.service.ts @@ -1,7 +1,12 @@ import { LeanDocument, Types } from 'mongoose' import { URL } from 'url' -import { BadRequestException, Injectable, Logger } from '@nestjs/common' +import { + BadRequestException, + Injectable, + Logger, + NotFoundException, +} from '@nestjs/common' import { DocumentType } from '@typegoose/typegoose' import { BeAnObject, ReturnModelType } from '@typegoose/typegoose/lib/types' @@ -24,7 +29,7 @@ import { PostModel } from '../post/post.model' import { ToolService } from '../tool/tool.service' import { UserService } from '../user/user.service' import BlockedKeywords from './block-keywords.json' -import { CommentModel, CommentRefTypes } from './comment.model' +import { CommentModel, CommentRefTypes, CommentState } from './comment.model' @Injectable() export class CommentService { @@ -126,12 +131,13 @@ export class CommentService { type = type_ as any } if (!ref) { - throw new CannotFindException() + throw new NotFoundException('评论文章不存在') } const commentIndex = ref.commentsIndex || 0 doc.key = `#${commentIndex + 1}` const comment = await this.commentModel.create({ ...doc, + state: CommentState.Unread, ref: new Types.ObjectId(id), refType: type, }) diff --git a/src/modules/configs/configs.default.ts b/src/modules/configs/configs.default.ts index d5d61f5e..5b587eda 100644 --- a/src/modules/configs/configs.default.ts +++ b/src/modules/configs/configs.default.ts @@ -31,6 +31,7 @@ export const generateDefaultConfig: () => IConfig = () => ({ fetchLocationTimeout: 3000, recordIpLocation: true, spamKeywords: [], + commentShouldAudit: false, }, barkOptions: { enable: false, diff --git a/src/modules/configs/configs.dto.ts b/src/modules/configs/configs.dto.ts index 31ec63c8..066ee8a9 100644 --- a/src/modules/configs/configs.dto.ts +++ b/src/modules/configs/configs.dto.ts @@ -133,6 +133,11 @@ export class CommentOptionsDto { @JSONSchemaToggleField('禁止非中文评论') disableNoChinese?: boolean + @IsOptional() + @IsBoolean() + @JSONSchemaToggleField('只展示已读评论') + commentShouldAudit?: boolean + @IsBoolean() @IsOptional() @JSONSchemaToggleField('评论公开归属地')