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