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 { exists, isBooleanValid, isIdOrUUIDValid, isIdValid } from '../../../helpers/custom-validators/misc'
8 doesVideoCommentThreadExist,
9 isValidVideoCommentText
10 } from '../../../helpers/custom-validators/video-comments'
11 import { logger } from '../../../helpers/logger'
12 import { doesVideoExist } from '../../../helpers/middlewares'
13 import { AcceptResult, isLocalVideoCommentReplyAccepted, isLocalVideoThreadAccepted } from '../../../lib/moderation'
14 import { Hooks } from '../../../lib/plugins/hooks'
15 import { MCommentOwnerVideoReply, MVideo, MVideoFullLight } from '../../../types/models/video'
16 import { areValidationErrors } from '../utils'
18 const listVideoCommentsValidator = [
21 .custom(isBooleanValid)
22 .withMessage('Should have a valid is local boolean'),
26 .custom(exists).withMessage('Should have a valid search'),
28 query('searchAccount')
30 .custom(exists).withMessage('Should have a valid account search'),
34 .custom(exists).withMessage('Should have a valid video search'),
36 (req: express.Request, res: express.Response, next: express.NextFunction) => {
37 logger.debug('Checking listVideoCommentsValidator parameters.', { parameters: req.query })
39 if (areValidationErrors(req, res)) return
45 const listVideoCommentThreadsValidator = [
46 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
48 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
49 logger.debug('Checking listVideoCommentThreads parameters.', { parameters: req.params })
51 if (areValidationErrors(req, res)) return
52 if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return
58 const listVideoThreadCommentsValidator = [
59 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
60 param('threadId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid threadId'),
62 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
63 logger.debug('Checking listVideoThreadComments parameters.', { parameters: req.params })
65 if (areValidationErrors(req, res)) return
66 if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return
67 if (!await doesVideoCommentThreadExist(req.params.threadId, res.locals.onlyVideo, res)) return
73 const addVideoCommentThreadValidator = [
74 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
75 body('text').custom(isValidVideoCommentText).not().isEmpty().withMessage('Should have a valid comment text'),
77 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
78 logger.debug('Checking addVideoCommentThread parameters.', { parameters: req.params, body: req.body })
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
89 const addVideoCommentReplyValidator = [
90 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
91 param('commentId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid commentId'),
92 body('text').custom(isValidVideoCommentText).not().isEmpty().withMessage('Should have a valid comment text'),
94 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
95 logger.debug('Checking addVideoCommentReply parameters.', { parameters: req.params, body: req.body })
97 if (areValidationErrors(req, res)) return
98 if (!await doesVideoExist(req.params.videoId, res)) return
99 if (!isVideoCommentsEnabled(res.locals.videoAll, res)) return
100 if (!await doesVideoCommentExist(req.params.commentId, res.locals.videoAll, res)) return
101 if (!await isVideoCommentAccepted(req, res, res.locals.videoAll, true)) return
107 const videoCommentGetValidator = [
108 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
109 param('commentId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid commentId'),
111 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
112 logger.debug('Checking videoCommentGetValidator parameters.', { parameters: req.params })
114 if (areValidationErrors(req, res)) return
115 if (!await doesVideoExist(req.params.videoId, res, 'id')) return
116 if (!await doesVideoCommentExist(req.params.commentId, res.locals.videoId, res)) return
122 const removeVideoCommentValidator = [
123 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
124 param('commentId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid commentId'),
126 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
127 logger.debug('Checking removeVideoCommentValidator parameters.', { parameters: req.params })
129 if (areValidationErrors(req, res)) return
130 if (!await doesVideoExist(req.params.videoId, res)) return
131 if (!await doesVideoCommentExist(req.params.commentId, res.locals.videoAll, res)) return
133 // Check if the user who did the request is able to delete the video
134 if (!checkUserCanDeleteVideoComment(res.locals.oauth.token.User, res.locals.videoCommentFull, res)) return
140 // ---------------------------------------------------------------------------
143 listVideoCommentThreadsValidator,
144 listVideoThreadCommentsValidator,
145 addVideoCommentThreadValidator,
146 listVideoCommentsValidator,
147 addVideoCommentReplyValidator,
148 videoCommentGetValidator,
149 removeVideoCommentValidator
152 // ---------------------------------------------------------------------------
154 function isVideoCommentsEnabled (video: MVideo, res: express.Response) {
155 if (video.commentsEnabled !== true) {
157 .json({ error: 'Video comments are disabled for this video.' })
165 function checkUserCanDeleteVideoComment (user: MUserAccountUrl, videoComment: MCommentOwnerVideoReply, res: express.Response) {
166 if (videoComment.isDeleted()) {
168 .json({ error: 'This comment is already deleted' })
173 const userAccount = user.Account
176 user.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT) === false && // Not a moderator
177 videoComment.accountId !== userAccount.id && // Not the comment owner
178 videoComment.Video.VideoChannel.accountId !== userAccount.id // Not the video owner
181 .json({ error: 'Cannot remove video comment of another user' })
189 async function isVideoCommentAccepted (req: express.Request, res: express.Response, video: MVideoFullLight, isReply: boolean) {
190 const acceptParameters = {
192 commentBody: req.body,
193 user: res.locals.oauth.token.User
196 let acceptedResult: AcceptResult
199 const acceptReplyParameters = Object.assign(acceptParameters, { parentComment: res.locals.videoCommentFull })
201 acceptedResult = await Hooks.wrapFun(
202 isLocalVideoCommentReplyAccepted,
203 acceptReplyParameters,
204 'filter:api.video-comment-reply.create.accept.result'
207 acceptedResult = await Hooks.wrapFun(
208 isLocalVideoThreadAccepted,
210 'filter:api.video-thread.create.accept.result'
214 if (!acceptedResult || acceptedResult.accepted !== true) {
215 logger.info('Refused local comment.', { acceptedResult, acceptParameters })
217 .json({ error: acceptedResult.errorMessage || 'Refused local comment' })