Split types and typings
[oweals/peertube.git] / server / lib / video-comment.ts
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, MCommentOwnerVideoReply, MVideoFullLight, MCommentOwnerVideo } from '../types/models'
9 import { sendCreateVideoComment, sendDeleteVideoComment } from './activitypub/send'
10 import { getVideoCommentActivityPubUrl } from './activitypub/url'
11 import { Hooks } from './plugins/hooks'
12
13 async function removeComment (videoCommentInstance: MCommentOwnerVideo) {
14   const videoCommentInstanceBefore = cloneDeep(videoCommentInstance)
15
16   await sequelizeTypescript.transaction(async t => {
17     if (videoCommentInstance.isOwned() || videoCommentInstance.Video.isOwned()) {
18       await sendDeleteVideoComment(videoCommentInstance, t)
19     }
20
21     markCommentAsDeleted(videoCommentInstance)
22
23     await videoCommentInstance.save()
24   })
25
26   logger.info('Video comment %d deleted.', videoCommentInstance.id)
27
28   Hooks.runAction('action:api.video-comment.deleted', { comment: videoCommentInstanceBefore })
29 }
30
31 async function createVideoComment (obj: {
32   text: string
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
39
40   if (obj.inReplyToComment && obj.inReplyToComment !== null) {
41     originCommentId = obj.inReplyToComment.originCommentId || obj.inReplyToComment.id
42     inReplyToCommentId = obj.inReplyToComment.id
43   }
44
45   const comment = await VideoCommentModel.create({
46     text: obj.text,
47     originCommentId,
48     inReplyToCommentId,
49     videoId: obj.video.id,
50     accountId: obj.account.id,
51     url: new Date().toISOString()
52   }, { transaction: t, validate: false })
53
54   comment.url = getVideoCommentActivityPubUrl(obj.video, comment)
55
56   const savedComment: MCommentOwnerVideoReply = await comment.save({ transaction: t })
57   savedComment.InReplyToVideoComment = obj.inReplyToComment
58   savedComment.Video = obj.video
59   savedComment.Account = obj.account
60
61   await sendCreateVideoComment(savedComment, t)
62
63   return savedComment
64 }
65
66 function buildFormattedCommentTree (resultList: ResultList<VideoCommentModel>): VideoCommentThreadTree {
67   // Comments are sorted by id ASC
68   const comments = resultList.data
69
70   const comment = comments.shift()
71   const thread: VideoCommentThreadTree = {
72     comment: comment.toFormattedJSON(),
73     children: []
74   }
75   const idx = {
76     [comment.id]: thread
77   }
78
79   while (comments.length !== 0) {
80     const childComment = comments.shift()
81
82     const childCommentThread: VideoCommentThreadTree = {
83       comment: childComment.toFormattedJSON(),
84       children: []
85     }
86
87     const parentCommentThread = idx[childComment.inReplyToCommentId]
88     // Maybe the parent comment was blocked by the admin/user
89     if (!parentCommentThread) continue
90
91     parentCommentThread.children.push(childCommentThread)
92     idx[childComment.id] = childCommentThread
93   }
94
95   return thread
96 }
97
98 function markCommentAsDeleted (comment: MComment): void {
99   comment.text = ''
100   comment.deletedAt = new Date()
101   comment.accountId = null
102 }
103
104 // ---------------------------------------------------------------------------
105
106 export {
107   removeComment,
108   createVideoComment,
109   buildFormattedCommentTree,
110   markCommentAsDeleted
111 }