Files
core/src/modules/comment/comment.service.ts
2021-09-13 20:46:04 +08:00

246 lines
7.0 KiB
TypeScript

import { BadRequestException, Injectable, Logger } from '@nestjs/common'
import { DocumentType } from '@typegoose/typegoose'
import { BeAnObject } from '@typegoose/typegoose/lib/types'
import { LeanDocument, Types } from 'mongoose'
import { InjectModel } from 'nestjs-typegoose'
import { CannotFindException } from '~/common/exceptions/cant-find.exception'
import { DatabaseService } from '~/processors/database/database.service'
import {
EmailService,
ReplyMailType,
} from '~/processors/helper/helper.email.service'
import { WriteBaseModel } from '~/shared/model/base.model'
import { hasChinese, isDev } from '~/utils/index.util'
import { ConfigsService } from '../configs/configs.service'
import { UserService } from '../user/user.service'
import BlockedKeywords from './block-keywords.json'
import { CommentModel, CommentRefTypes } from './comment.model'
@Injectable()
export class CommentService {
private readonly logger: Logger = new Logger(CommentService.name)
constructor(
@InjectModel(CommentModel)
private readonly commentModel: MongooseModel<CommentModel>,
private readonly databaseService: DatabaseService,
private readonly configs: ConfigsService,
private readonly userService: UserService,
private readonly mailService: EmailService,
) {}
public get model() {
return this.commentModel
}
private getModelByRefType(type: CommentRefTypes) {
switch (type) {
case CommentRefTypes.Note:
return this.databaseService.getModelByRefType('Note')
case CommentRefTypes.Page:
return this.databaseService.getModelByRefType('Page')
case CommentRefTypes.Post:
return this.databaseService.getModelByRefType('Post')
}
}
async checkSpam(doc: Partial<CommentModel>) {
const res = await (async () => {
const commentOptions = this.configs.get('commentOptions')
if (!commentOptions.antiSpam) {
return false
}
const master = await this.userService.getMaster()
if (doc.author === master.username) {
return false
}
if (commentOptions.blockIps) {
const isBlock = commentOptions.blockIps.some((ip) =>
new RegExp(ip, 'ig').test(doc.ip),
)
if (isBlock) {
return true
}
}
const customKeywords = commentOptions.spamKeywords || []
const isBlock = [...customKeywords, ...BlockedKeywords].some((keyword) =>
new RegExp(keyword, 'ig').test(doc.text),
)
if (isBlock) {
return true
}
if (commentOptions.disableNoChinese && !hasChinese(doc.text)) {
return true
}
return false
})()
if (res) {
this.logger.warn(
'--> 检测到一条垃圾评论: ' +
`作者: ${doc.author}, IP: ${doc.ip}, 内容为: ${doc.text}`,
)
}
return res
}
async createComment(
id: string,
doc: Partial<CommentModel>,
type?: CommentRefTypes,
) {
let ref: LeanDocument<DocumentType<WriteBaseModel, BeAnObject>>
if (type) {
const model = this.getModelByRefType(type)
ref = await model.findById(id).lean()
} else {
const { type: type_, document } =
await this.databaseService.findGlobalById(id)
ref = document
type = type_ as any
}
if (!ref) {
throw new CannotFindException()
}
const commentIndex = ref.commentsIndex
doc.key = `#${commentIndex + 1}`
const comment = await this.commentModel.create({
...doc,
ref: Types.ObjectId(id),
refType: type,
})
await this.databaseService.getModelByRefType(type as any).updateOne(
{ _id: ref._id },
{
$inc: {
commentsIndex: 1,
},
},
)
return comment
}
async ValidAuthorName(author: string): Promise<void> {
const isExist = await this.userService.model.findOne({
name: author,
})
if (isExist) {
throw new BadRequestException(
'用户名与主人重名啦, 但是你好像并不是我的主人唉',
)
}
}
async deleteComments(id: string) {
const comment = await this.commentModel.findOneAndDelete({ _id: id })
if (!comment) {
throw new CannotFindException()
}
const { children, parent } = comment
if (children && children.length > 0) {
await Promise.all(
children.map(async (id) => {
await this.deleteComments(id as any as string)
}),
)
}
if (parent) {
const parent = await this.commentModel.findById(comment.parent)
if (parent) {
await parent.updateOne({
$pull: {
children: comment._id,
},
})
}
}
return { message: '删除成功' }
}
async allowComment(id: string, type?: CommentRefTypes) {
if (type) {
const model = this.getModelByRefType(type)
const doc = await model.findById(id)
return doc.allowComment ?? true
} else {
const { document: doc } = await this.databaseService.findGlobalById(id)
return doc.allowComment ?? true
}
}
async getComments({ page, size, state } = { page: 1, size: 10, state: 0 }) {
const queryList = await this.commentModel.paginate(
{ state },
{
select: '+ip +agent -children',
page,
limit: size,
populate: [
{ path: 'parent', select: '-children' },
{ path: 'ref', select: 'title _id slug nid' },
],
sort: { created: -1 },
},
)
return queryList
}
async sendEmail(
model: DocumentType<CommentModel>,
type: ReplyMailType,
debug?: true,
) {
const enable = this.configs.get('mailOptions').enable
if (!enable || (isDev && !debug)) {
return
}
this.userService.model.findOne().then(async (master) => {
const refType = model.refType
const refModel = this.getModelByRefType(refType)
const ref = await refModel.findById(model.ref).populate('category')
const time = new Date(model.created)
const parent = await this.commentModel.findOne({ _id: model.parent })
const parsedTime = `${time.getDate()}/${
time.getMonth() + 1
}/${time.getFullYear()}`
this.mailService.sendCommentNotificationMail({
to: type === ReplyMailType.Owner ? master.mail : parent.mail,
type,
source: {
title: ref.title,
text: model.text,
author: type === ReplyMailType.Guest ? parent.author : model.author,
master: master.name,
link: this.resolveUrlByType(refType, ref),
time: parsedTime,
mail: ReplyMailType.Owner === type ? model.mail : master.mail,
ip: model.ip || '',
},
})
})
}
resolveUrlByType(type: CommentRefTypes, model: any) {
const base = this.configs.get('url').webUrl
switch (type) {
case CommentRefTypes.Note: {
return new URL('/notes/' + model.nid, base).toString()
}
case CommentRefTypes.Page: {
return new URL(`/${model.slug}`, base).toString()
}
case CommentRefTypes.Post: {
return new URL(`/${model.category.slug}/${model.slug}`, base).toString()
}
}
}
}