]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - 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
CommitLineData
41fb13c3 1import express from 'express'
0f8d00e3 2import { body, param, query } from 'express-validator'
26d6bf65 3import { MUserAccountUrl } from '@server/types/models'
d17c7b4e 4import { HttpStatusCode, UserRight } from '@shared/models'
d4a8e7a6 5import { exists, isBooleanValid, isIdValid, toBooleanOrNull } from '../../../helpers/custom-validators/misc'
10363c74 6import { isValidVideoCommentText } from '../../../helpers/custom-validators/video-comments'
6e46de09 7import { logger } from '../../../helpers/logger'
ceba0e65
C
8import { AcceptResult, isLocalVideoCommentReplyAccepted, isLocalVideoThreadAccepted } from '../../../lib/moderation'
9import { Hooks } from '../../../lib/plugins/hooks'
57f6896f 10import { MCommentOwnerVideoReply, MVideo, MVideoFullLight } from '../../../types/models/video'
84c8d986
C
11import {
12 areValidationErrors,
13 checkCanSeeVideoIfPrivate,
14 doesVideoCommentExist,
15 doesVideoCommentThreadExist,
16 doesVideoExist,
17 isValidVideoIdParam
18} from '../shared'
bf1f6508 19
0f8d00e3
C
20const listVideoCommentsValidator = [
21 query('isLocal')
22 .optional()
f1273314 23 .customSanitizer(toBooleanOrNull)
0f8d00e3
C
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
bf1f6508 48const listVideoCommentThreadsValidator = [
d4a8e7a6 49 isValidVideoIdParam('videoId'),
bf1f6508
C
50
51 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
d3ea8975 52 logger.debug('Checking listVideoCommentThreads parameters.', { parameters: req.params })
bf1f6508
C
53
54 if (areValidationErrors(req, res)) return
0f6acda1 55 if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return
bf1f6508 56
c729caf6 57 if (!await checkCanSeeVideoIfPrivate(req, res, res.locals.onlyVideo)) return
84c8d986 58
bf1f6508
C
59 return next()
60 }
61]
62
63const listVideoThreadCommentsValidator = [
d4a8e7a6
C
64 isValidVideoIdParam('videoId'),
65
66 param('threadId')
67 .custom(isIdValid).not().isEmpty().withMessage('Should have a valid threadId'),
bf1f6508
C
68
69 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
d3ea8975 70 logger.debug('Checking listVideoThreadComments parameters.', { parameters: req.params })
bf1f6508
C
71
72 if (areValidationErrors(req, res)) return
0f6acda1 73 if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return
453e83ea 74 if (!await doesVideoCommentThreadExist(req.params.threadId, res.locals.onlyVideo, res)) return
bf1f6508 75
c729caf6 76 if (!await checkCanSeeVideoIfPrivate(req, res, res.locals.onlyVideo)) return
84c8d986 77
bf1f6508
C
78 return next()
79 }
80]
81
82const addVideoCommentThreadValidator = [
d4a8e7a6
C
83 isValidVideoIdParam('videoId'),
84
85 body('text')
86 .custom(isValidVideoCommentText).not().isEmpty().withMessage('Should have a valid comment text'),
bf1f6508
C
87
88 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
5de8a55a 89 logger.debug('Checking addVideoCommentThread parameters.', { parameters: req.params, body: req.body })
bf1f6508
C
90
91 if (areValidationErrors(req, res)) return
0f6acda1 92 if (!await doesVideoExist(req.params.videoId, res)) return
6ea9295b 93
f1c70a86 94 if (!await checkCanSeeVideoIfPrivate(req, res, res.locals.videoAll)) return
6ea9295b 95
453e83ea 96 if (!isVideoCommentsEnabled(res.locals.videoAll, res)) return
a1587156 97 if (!await isVideoCommentAccepted(req, res, res.locals.videoAll, false)) return
bf1f6508
C
98
99 return next()
100 }
101]
102
103const addVideoCommentReplyValidator = [
d4a8e7a6
C
104 isValidVideoIdParam('videoId'),
105
bf1f6508 106 param('commentId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid commentId'),
d4a8e7a6 107
bf1f6508
C
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) => {
5de8a55a 111 logger.debug('Checking addVideoCommentReply parameters.', { parameters: req.params, body: req.body })
bf1f6508
C
112
113 if (areValidationErrors(req, res)) return
0f6acda1 114 if (!await doesVideoExist(req.params.videoId, res)) return
6ea9295b 115
f1c70a86 116 if (!await checkCanSeeVideoIfPrivate(req, res, res.locals.videoAll)) return
6ea9295b 117
453e83ea
C
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
bf1f6508
C
121
122 return next()
123 }
124]
125
da854ddd 126const videoCommentGetValidator = [
d4a8e7a6
C
127 isValidVideoIdParam('videoId'),
128
129 param('commentId')
130 .custom(isIdValid).not().isEmpty().withMessage('Should have a valid commentId'),
da854ddd
C
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
0f6acda1 136 if (!await doesVideoExist(req.params.videoId, res, 'id')) return
453e83ea 137 if (!await doesVideoCommentExist(req.params.commentId, res.locals.videoId, res)) return
da854ddd
C
138
139 return next()
140 }
141]
142
4cb6d457 143const removeVideoCommentValidator = [
d4a8e7a6
C
144 isValidVideoIdParam('videoId'),
145
4cb6d457
C
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
0f6acda1 152 if (!await doesVideoExist(req.params.videoId, res)) return
453e83ea 153 if (!await doesVideoCommentExist(req.params.commentId, res.locals.videoAll, res)) return
4cb6d457
C
154
155 // Check if the user who did the request is able to delete the video
453e83ea 156 if (!checkUserCanDeleteVideoComment(res.locals.oauth.token.User, res.locals.videoCommentFull, res)) return
4cb6d457
C
157
158 return next()
159 }
160]
161
bf1f6508
C
162// ---------------------------------------------------------------------------
163
164export {
165 listVideoCommentThreadsValidator,
166 listVideoThreadCommentsValidator,
167 addVideoCommentThreadValidator,
0f8d00e3 168 listVideoCommentsValidator,
da854ddd 169 addVideoCommentReplyValidator,
4cb6d457
C
170 videoCommentGetValidator,
171 removeVideoCommentValidator
bf1f6508
C
172}
173
174// ---------------------------------------------------------------------------
175
453e83ea 176function isVideoCommentsEnabled (video: MVideo, res: express.Response) {
47564bbe 177 if (video.commentsEnabled !== true) {
76148b27
RK
178 res.fail({
179 status: HttpStatusCode.CONFLICT_409,
180 message: 'Video comments are disabled for this video.'
181 })
47564bbe
C
182 return false
183 }
184
185 return true
186}
4cb6d457 187
fde37dc9 188function checkUserCanDeleteVideoComment (user: MUserAccountUrl, videoComment: MCommentOwnerVideoReply, res: express.Response) {
c883db6d 189 if (videoComment.isDeleted()) {
76148b27
RK
190 res.fail({
191 status: HttpStatusCode.CONFLICT_409,
192 message: 'This comment is already deleted'
193 })
c883db6d
C
194 return false
195 }
196
fde37dc9
C
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 ) {
76148b27
RK
204 res.fail({
205 status: HttpStatusCode.FORBIDDEN_403,
206 message: 'Cannot remove video comment of another user'
207 })
4cb6d457
C
208 return false
209 }
210
211 return true
212}
b4055e1c 213
453e83ea 214async function isVideoCommentAccepted (req: express.Request, res: express.Response, video: MVideoFullLight, isReply: boolean) {
b4055e1c 215 const acceptParameters = {
453e83ea 216 video,
b4055e1c
C
217 commentBody: req.body,
218 user: res.locals.oauth.token.User
219 }
220
221 let acceptedResult: AcceptResult
222
223 if (isReply) {
453e83ea 224 const acceptReplyParameters = Object.assign(acceptParameters, { parentComment: res.locals.videoCommentFull })
b4055e1c 225
6691c522
C
226 acceptedResult = await Hooks.wrapFun(
227 isLocalVideoCommentReplyAccepted,
228 acceptReplyParameters,
b4055e1c
C
229 'filter:api.video-comment-reply.create.accept.result'
230 )
231 } else {
6691c522
C
232 acceptedResult = await Hooks.wrapFun(
233 isLocalVideoThreadAccepted,
234 acceptParameters,
b4055e1c
C
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 })
b4055e1c 241
76148b27
RK
242 res.fail({
243 status: HttpStatusCode.FORBIDDEN_403,
244 message: acceptedResult?.errorMessage || 'Refused local comment'
245 })
b4055e1c
C
246 return false
247 }
248
249 return true
250}