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