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