]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/middlewares/validators/videos/video-comments.ts
1e3e42833c8628d566503a08721a18e722b94051
[github/Chocobozzz/PeerTube.git] / server / middlewares / validators / videos / video-comments.ts
1 import * as express from 'express'
2 import { body, param } from 'express-validator/check'
3 import { UserRight } from '../../../../shared'
4 import { isIdOrUUIDValid, isIdValid } from '../../../helpers/custom-validators/misc'
5 import { isValidVideoCommentText } from '../../../helpers/custom-validators/video-comments'
6 import { logger } from '../../../helpers/logger'
7 import { UserModel } from '../../../models/account/user'
8 import { VideoModel } from '../../../models/video/video'
9 import { VideoCommentModel } from '../../../models/video/video-comment'
10 import { areValidationErrors } from '../utils'
11 import { Hooks } from '../../../lib/plugins/hooks'
12 import { isLocalVideoThreadAccepted, isLocalVideoCommentReplyAccepted, AcceptResult } from '../../../lib/moderation'
13 import { doesVideoExist } from '../../../helpers/middlewares'
14
15 const listVideoCommentThreadsValidator = [
16 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
17
18 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
19 logger.debug('Checking listVideoCommentThreads parameters.', { parameters: req.params })
20
21 if (areValidationErrors(req, res)) return
22 if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return
23
24 return next()
25 }
26 ]
27
28 const listVideoThreadCommentsValidator = [
29 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
30 param('threadId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid threadId'),
31
32 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
33 logger.debug('Checking listVideoThreadComments parameters.', { parameters: req.params })
34
35 if (areValidationErrors(req, res)) return
36 if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return
37 if (!await doesVideoCommentThreadExist(req.params.threadId, res.locals.video, res)) return
38
39 return next()
40 }
41 ]
42
43 const addVideoCommentThreadValidator = [
44 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
45 body('text').custom(isValidVideoCommentText).not().isEmpty().withMessage('Should have a valid comment text'),
46
47 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
48 logger.debug('Checking addVideoCommentThread parameters.', { parameters: req.params, body: req.body })
49
50 if (areValidationErrors(req, res)) return
51 if (!await doesVideoExist(req.params.videoId, res)) return
52 if (!isVideoCommentsEnabled(res.locals.video, res)) return
53 if (!await isVideoCommentAccepted(req, res, false)) return
54
55 return next()
56 }
57 ]
58
59 const addVideoCommentReplyValidator = [
60 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
61 param('commentId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid commentId'),
62 body('text').custom(isValidVideoCommentText).not().isEmpty().withMessage('Should have a valid comment text'),
63
64 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
65 logger.debug('Checking addVideoCommentReply parameters.', { parameters: req.params, body: req.body })
66
67 if (areValidationErrors(req, res)) return
68 if (!await doesVideoExist(req.params.videoId, res)) return
69 if (!isVideoCommentsEnabled(res.locals.video, res)) return
70 if (!await doesVideoCommentExist(req.params.commentId, res.locals.video, res)) return
71 if (!await isVideoCommentAccepted(req, res, true)) return
72
73 return next()
74 }
75 ]
76
77 const videoCommentGetValidator = [
78 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
79 param('commentId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid commentId'),
80
81 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
82 logger.debug('Checking videoCommentGetValidator parameters.', { parameters: req.params })
83
84 if (areValidationErrors(req, res)) return
85 if (!await doesVideoExist(req.params.videoId, res, 'id')) return
86 if (!await doesVideoCommentExist(req.params.commentId, res.locals.video, res)) return
87
88 return next()
89 }
90 ]
91
92 const removeVideoCommentValidator = [
93 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
94 param('commentId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid commentId'),
95
96 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
97 logger.debug('Checking removeVideoCommentValidator parameters.', { parameters: req.params })
98
99 if (areValidationErrors(req, res)) return
100 if (!await doesVideoExist(req.params.videoId, res)) return
101 if (!await doesVideoCommentExist(req.params.commentId, res.locals.video, res)) return
102
103 // Check if the user who did the request is able to delete the video
104 if (!checkUserCanDeleteVideoComment(res.locals.oauth.token.User, res.locals.videoComment, res)) return
105
106 return next()
107 }
108 ]
109
110 // ---------------------------------------------------------------------------
111
112 export {
113 listVideoCommentThreadsValidator,
114 listVideoThreadCommentsValidator,
115 addVideoCommentThreadValidator,
116 addVideoCommentReplyValidator,
117 videoCommentGetValidator,
118 removeVideoCommentValidator
119 }
120
121 // ---------------------------------------------------------------------------
122
123 async function doesVideoCommentThreadExist (id: number, video: VideoModel, res: express.Response) {
124 const videoComment = await VideoCommentModel.loadById(id)
125
126 if (!videoComment) {
127 res.status(404)
128 .json({ error: 'Video comment thread not found' })
129 .end()
130
131 return false
132 }
133
134 if (videoComment.videoId !== video.id) {
135 res.status(400)
136 .json({ error: 'Video comment is associated to this video.' })
137 .end()
138
139 return false
140 }
141
142 if (videoComment.inReplyToCommentId !== null) {
143 res.status(400)
144 .json({ error: 'Video comment is not a thread.' })
145 .end()
146
147 return false
148 }
149
150 res.locals.videoCommentThread = videoComment
151 return true
152 }
153
154 async function doesVideoCommentExist (id: number, video: VideoModel, res: express.Response) {
155 const videoComment = await VideoCommentModel.loadByIdAndPopulateVideoAndAccountAndReply(id)
156
157 if (!videoComment) {
158 res.status(404)
159 .json({ error: 'Video comment thread not found' })
160 .end()
161
162 return false
163 }
164
165 if (videoComment.videoId !== video.id) {
166 res.status(400)
167 .json({ error: 'Video comment is associated to this video.' })
168 .end()
169
170 return false
171 }
172
173 res.locals.videoComment = videoComment
174 return true
175 }
176
177 function isVideoCommentsEnabled (video: VideoModel, res: express.Response) {
178 if (video.commentsEnabled !== true) {
179 res.status(409)
180 .json({ error: 'Video comments are disabled for this video.' })
181 .end()
182
183 return false
184 }
185
186 return true
187 }
188
189 function checkUserCanDeleteVideoComment (user: UserModel, videoComment: VideoCommentModel, res: express.Response) {
190 const account = videoComment.Account
191 if (user.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT) === false && account.userId !== user.id) {
192 res.status(403)
193 .json({ error: 'Cannot remove video comment of another user' })
194 .end()
195 return false
196 }
197
198 return true
199 }
200
201 async function isVideoCommentAccepted (req: express.Request, res: express.Response, isReply: boolean) {
202 const acceptParameters = {
203 video: res.locals.video,
204 commentBody: req.body,
205 user: res.locals.oauth.token.User
206 }
207
208 let acceptedResult: AcceptResult
209
210 if (isReply) {
211 const acceptReplyParameters = Object.assign(acceptParameters, { parentComment: res.locals.videoComment })
212
213 acceptedResult = await Hooks.wrapFun(
214 isLocalVideoCommentReplyAccepted,
215 acceptReplyParameters,
216 'filter:api.video-comment-reply.create.accept.result'
217 )
218 } else {
219 acceptedResult = await Hooks.wrapFun(
220 isLocalVideoThreadAccepted,
221 acceptParameters,
222 'filter:api.video-thread.create.accept.result'
223 )
224 }
225
226 if (!acceptedResult || acceptedResult.accepted !== true) {
227 logger.info('Refused local comment.', { acceptedResult, acceptParameters })
228 res.status(403)
229 .json({ error: acceptedResult.errorMessage || 'Refused local comment' })
230
231 return false
232 }
233
234 return true
235 }