1 import * as express from 'express'
2 import { body, param } from 'express-validator'
3 import { UserRight } from '../../../../shared'
4 import { isIdOrUUIDValid, isIdValid } from '../../../helpers/custom-validators/misc'
5 import { isValidVideoCommentText } from '../../../helpers/custom-validators/video-comments'
6 import { logger } from '../../../helpers/logger'
7 import { VideoCommentModel } from '../../../models/video/video-comment'
8 import { areValidationErrors } from '../utils'
9 import { Hooks } from '../../../lib/plugins/hooks'
10 import { AcceptResult, isLocalVideoCommentReplyAccepted, isLocalVideoThreadAccepted } from '../../../lib/moderation'
11 import { doesVideoExist } from '../../../helpers/middlewares'
12 import { MCommentOwner, MVideo, MVideoFullLight, MVideoId } from '../../../typings/models/video'
13 import { MUser } from '@server/typings/models'
15 const listVideoCommentThreadsValidator = [
16 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
18 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
19 logger.debug('Checking listVideoCommentThreads parameters.', { parameters: req.params })
21 if (areValidationErrors(req, res)) return
22 if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return
28 const listVideoThreadCommentsValidator = [
29 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
30 param('threadId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid threadId'),
32 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
33 logger.debug('Checking listVideoThreadComments parameters.', { parameters: req.params })
35 if (areValidationErrors(req, res)) return
36 if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return
37 if (!await doesVideoCommentThreadExist(req.params.threadId, res.locals.onlyVideo, res)) return
43 const addVideoCommentThreadValidator = [
44 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
45 body('text').custom(isValidVideoCommentText).not().isEmpty().withMessage('Should have a valid comment text'),
47 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
48 logger.debug('Checking addVideoCommentThread parameters.', { parameters: req.params, body: req.body })
50 if (areValidationErrors(req, res)) return
51 if (!await doesVideoExist(req.params.videoId, res)) return
52 if (!isVideoCommentsEnabled(res.locals.videoAll, res)) return
53 if (!await isVideoCommentAccepted(req, res, res.locals.videoAll, false)) return
59 const addVideoCommentReplyValidator = [
60 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
61 param('commentId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid commentId'),
62 body('text').custom(isValidVideoCommentText).not().isEmpty().withMessage('Should have a valid comment text'),
64 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
65 logger.debug('Checking addVideoCommentReply parameters.', { parameters: req.params, body: req.body })
67 if (areValidationErrors(req, res)) return
68 if (!await doesVideoExist(req.params.videoId, res)) return
69 if (!isVideoCommentsEnabled(res.locals.videoAll, res)) return
70 if (!await doesVideoCommentExist(req.params.commentId, res.locals.videoAll, res)) return
71 if (!await isVideoCommentAccepted(req, res, res.locals.videoAll, true)) return
77 const videoCommentGetValidator = [
78 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
79 param('commentId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid commentId'),
81 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
82 logger.debug('Checking videoCommentGetValidator parameters.', { parameters: req.params })
84 if (areValidationErrors(req, res)) return
85 if (!await doesVideoExist(req.params.videoId, res, 'id')) return
86 if (!await doesVideoCommentExist(req.params.commentId, res.locals.videoId, res)) return
92 const removeVideoCommentValidator = [
93 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
94 param('commentId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid commentId'),
96 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
97 logger.debug('Checking removeVideoCommentValidator parameters.', { parameters: req.params })
99 if (areValidationErrors(req, res)) return
100 if (!await doesVideoExist(req.params.videoId, res)) return
101 if (!await doesVideoCommentExist(req.params.commentId, res.locals.videoAll, res)) return
103 // Check if the user who did the request is able to delete the video
104 if (!checkUserCanDeleteVideoComment(res.locals.oauth.token.User, res.locals.videoCommentFull, res)) return
110 // ---------------------------------------------------------------------------
113 listVideoCommentThreadsValidator,
114 listVideoThreadCommentsValidator,
115 addVideoCommentThreadValidator,
116 addVideoCommentReplyValidator,
117 videoCommentGetValidator,
118 removeVideoCommentValidator
121 // ---------------------------------------------------------------------------
123 async function doesVideoCommentThreadExist (idArg: number | string, video: MVideoId, res: express.Response) {
124 const id = parseInt(idArg + '', 10)
125 const videoComment = await VideoCommentModel.loadById(id)
129 .json({ error: 'Video comment thread not found' })
135 if (videoComment.videoId !== video.id) {
137 .json({ error: 'Video comment is not associated to this video.' })
143 if (videoComment.inReplyToCommentId !== null) {
145 .json({ error: 'Video comment is not a thread.' })
151 res.locals.videoCommentThread = videoComment
155 async function doesVideoCommentExist (idArg: number | string, video: MVideoId, res: express.Response) {
156 const id = parseInt(idArg + '', 10)
157 const videoComment = await VideoCommentModel.loadByIdAndPopulateVideoAndAccountAndReply(id)
161 .json({ error: 'Video comment thread not found' })
167 if (videoComment.videoId !== video.id) {
169 .json({ error: 'Video comment is not associated to this video.' })
175 res.locals.videoCommentFull = videoComment
179 function isVideoCommentsEnabled (video: MVideo, res: express.Response) {
180 if (video.commentsEnabled !== true) {
182 .json({ error: 'Video comments are disabled for this video.' })
191 function checkUserCanDeleteVideoComment (user: MUser, videoComment: MCommentOwner, res: express.Response) {
192 if (videoComment.isDeleted()) {
194 .json({ error: 'This comment is already deleted' })
199 const account = videoComment.Account
200 if (user.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT) === false && account.userId !== user.id) {
202 .json({ error: 'Cannot remove video comment of another user' })
210 async function isVideoCommentAccepted (req: express.Request, res: express.Response, video: MVideoFullLight, isReply: boolean) {
211 const acceptParameters = {
213 commentBody: req.body,
214 user: res.locals.oauth.token.User
217 let acceptedResult: AcceptResult
220 const acceptReplyParameters = Object.assign(acceptParameters, { parentComment: res.locals.videoCommentFull })
222 acceptedResult = await Hooks.wrapFun(
223 isLocalVideoCommentReplyAccepted,
224 acceptReplyParameters,
225 'filter:api.video-comment-reply.create.accept.result'
228 acceptedResult = await Hooks.wrapFun(
229 isLocalVideoThreadAccepted,
231 'filter:api.video-thread.create.accept.result'
235 if (!acceptedResult || acceptedResult.accepted !== true) {
236 logger.info('Refused local comment.', { acceptedResult, acceptParameters })
238 .json({ error: acceptedResult.errorMessage || 'Refused local comment' })