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