1 import { cloneDeep } from 'lodash'
2 import * as Sequelize from 'sequelize'
3 import { logger } from '@server/helpers/logger'
4 import { sequelizeTypescript } from '@server/initializers/database'
5 import { ResultList } from '../../shared/models'
6 import { VideoCommentThreadTree } from '../../shared/models/videos/video-comment.model'
7 import { VideoCommentModel } from '../models/video/video-comment'
8 import { MAccountDefault, MComment, MCommentOwnerVideo, MCommentOwnerVideoReply, MVideoFullLight } from '../types/models'
9 import { sendCreateVideoComment, sendDeleteVideoComment } from './activitypub/send'
10 import { getLocalVideoCommentActivityPubUrl } from './activitypub/url'
11 import { Hooks } from './plugins/hooks'
13 async function removeComment (videoCommentInstance: MCommentOwnerVideo) {
14 const videoCommentInstanceBefore = cloneDeep(videoCommentInstance)
16 await sequelizeTypescript.transaction(async t => {
17 if (videoCommentInstance.isOwned() || videoCommentInstance.Video.isOwned()) {
18 await sendDeleteVideoComment(videoCommentInstance, t)
21 markCommentAsDeleted(videoCommentInstance)
23 await videoCommentInstance.save()
26 logger.info('Video comment %d deleted.', videoCommentInstance.id)
28 Hooks.runAction('action:api.video-comment.deleted', { comment: videoCommentInstanceBefore })
31 async function createVideoComment (obj: {
33 inReplyToComment: MComment | null
34 video: MVideoFullLight
35 account: MAccountDefault
36 }, t: Sequelize.Transaction) {
37 let originCommentId: number | null = null
38 let inReplyToCommentId: number | null = null
40 if (obj.inReplyToComment && obj.inReplyToComment !== null) {
41 originCommentId = obj.inReplyToComment.originCommentId || obj.inReplyToComment.id
42 inReplyToCommentId = obj.inReplyToComment.id
45 const comment = await VideoCommentModel.create({
49 videoId: obj.video.id,
50 accountId: obj.account.id,
51 url: new Date().toISOString()
52 }, { transaction: t, validate: false })
54 comment.url = getLocalVideoCommentActivityPubUrl(obj.video, comment)
56 const savedComment: MCommentOwnerVideoReply = await comment.save({ transaction: t })
57 savedComment.InReplyToVideoComment = obj.inReplyToComment
58 savedComment.Video = obj.video
59 savedComment.Account = obj.account
61 await sendCreateVideoComment(savedComment, t)
66 function buildFormattedCommentTree (resultList: ResultList<VideoCommentModel>): VideoCommentThreadTree {
67 // Comments are sorted by id ASC
68 const comments = resultList.data
70 const comment = comments.shift()
71 const thread: VideoCommentThreadTree = {
72 comment: comment.toFormattedJSON(),
79 while (comments.length !== 0) {
80 const childComment = comments.shift()
82 const childCommentThread: VideoCommentThreadTree = {
83 comment: childComment.toFormattedJSON(),
87 const parentCommentThread = idx[childComment.inReplyToCommentId]
88 // Maybe the parent comment was blocked by the admin/user
89 if (!parentCommentThread) continue
91 parentCommentThread.children.push(childCommentThread)
92 idx[childComment.id] = childCommentThread
98 function markCommentAsDeleted (comment: MComment): void {
100 comment.deletedAt = new Date()
101 comment.accountId = null
104 // ---------------------------------------------------------------------------
109 buildFormattedCommentTree,