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