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