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, toBooleanOrNull } 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'
17 import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
19 const listVideoCommentsValidator = [
22 .customSanitizer(toBooleanOrNull)
23 .custom(isBooleanValid)
24 .withMessage('Should have a valid is local boolean'),
28 .custom(exists).withMessage('Should have a valid search'),
30 query('searchAccount')
32 .custom(exists).withMessage('Should have a valid account search'),
36 .custom(exists).withMessage('Should have a valid video search'),
38 (req: express.Request, res: express.Response, next: express.NextFunction) => {
39 logger.debug('Checking listVideoCommentsValidator parameters.', { parameters: req.query })
41 if (areValidationErrors(req, res)) return
47 const listVideoCommentThreadsValidator = [
48 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
50 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
51 logger.debug('Checking listVideoCommentThreads parameters.', { parameters: req.params })
53 if (areValidationErrors(req, res)) return
54 if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return
60 const listVideoThreadCommentsValidator = [
61 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
62 param('threadId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid threadId'),
64 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
65 logger.debug('Checking listVideoThreadComments parameters.', { parameters: req.params })
67 if (areValidationErrors(req, res)) return
68 if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return
69 if (!await doesVideoCommentThreadExist(req.params.threadId, res.locals.onlyVideo, res)) return
75 const addVideoCommentThreadValidator = [
76 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
77 body('text').custom(isValidVideoCommentText).not().isEmpty().withMessage('Should have a valid comment text'),
79 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
80 logger.debug('Checking addVideoCommentThread parameters.', { parameters: req.params, body: req.body })
82 if (areValidationErrors(req, res)) return
83 if (!await doesVideoExist(req.params.videoId, res)) return
84 if (!isVideoCommentsEnabled(res.locals.videoAll, res)) return
85 if (!await isVideoCommentAccepted(req, res, res.locals.videoAll, false)) return
91 const addVideoCommentReplyValidator = [
92 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
93 param('commentId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid commentId'),
94 body('text').custom(isValidVideoCommentText).not().isEmpty().withMessage('Should have a valid comment text'),
96 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
97 logger.debug('Checking addVideoCommentReply parameters.', { parameters: req.params, body: req.body })
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
109 const videoCommentGetValidator = [
110 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
111 param('commentId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid commentId'),
113 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
114 logger.debug('Checking videoCommentGetValidator parameters.', { parameters: req.params })
116 if (areValidationErrors(req, res)) return
117 if (!await doesVideoExist(req.params.videoId, res, 'id')) return
118 if (!await doesVideoCommentExist(req.params.commentId, res.locals.videoId, res)) return
124 const removeVideoCommentValidator = [
125 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
126 param('commentId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid commentId'),
128 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
129 logger.debug('Checking removeVideoCommentValidator parameters.', { parameters: req.params })
131 if (areValidationErrors(req, res)) return
132 if (!await doesVideoExist(req.params.videoId, res)) return
133 if (!await doesVideoCommentExist(req.params.commentId, res.locals.videoAll, res)) return
135 // Check if the user who did the request is able to delete the video
136 if (!checkUserCanDeleteVideoComment(res.locals.oauth.token.User, res.locals.videoCommentFull, res)) return
142 // ---------------------------------------------------------------------------
145 listVideoCommentThreadsValidator,
146 listVideoThreadCommentsValidator,
147 addVideoCommentThreadValidator,
148 listVideoCommentsValidator,
149 addVideoCommentReplyValidator,
150 videoCommentGetValidator,
151 removeVideoCommentValidator
154 // ---------------------------------------------------------------------------
156 function isVideoCommentsEnabled (video: MVideo, res: express.Response) {
157 if (video.commentsEnabled !== true) {
158 res.status(HttpStatusCode.CONFLICT_409)
159 .json({ error: 'Video comments are disabled for this video.' })
167 function checkUserCanDeleteVideoComment (user: MUserAccountUrl, videoComment: MCommentOwnerVideoReply, res: express.Response) {
168 if (videoComment.isDeleted()) {
169 res.status(HttpStatusCode.CONFLICT_409)
170 .json({ error: 'This comment is already deleted' })
175 const userAccount = user.Account
178 user.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT) === false && // Not a moderator
179 videoComment.accountId !== userAccount.id && // Not the comment owner
180 videoComment.Video.VideoChannel.accountId !== userAccount.id // Not the video owner
182 res.status(HttpStatusCode.FORBIDDEN_403)
183 .json({ error: 'Cannot remove video comment of another user' })
191 async function isVideoCommentAccepted (req: express.Request, res: express.Response, video: MVideoFullLight, isReply: boolean) {
192 const acceptParameters = {
194 commentBody: req.body,
195 user: res.locals.oauth.token.User
198 let acceptedResult: AcceptResult
201 const acceptReplyParameters = Object.assign(acceptParameters, { parentComment: res.locals.videoCommentFull })
203 acceptedResult = await Hooks.wrapFun(
204 isLocalVideoCommentReplyAccepted,
205 acceptReplyParameters,
206 'filter:api.video-comment-reply.create.accept.result'
209 acceptedResult = await Hooks.wrapFun(
210 isLocalVideoThreadAccepted,
212 'filter:api.video-thread.create.accept.result'
216 if (!acceptedResult || acceptedResult.accepted !== true) {
217 logger.info('Refused local comment.', { acceptedResult, acceptParameters })
218 res.status(HttpStatusCode.FORBIDDEN_403)
219 .json({ error: acceptedResult?.errorMessage || 'Refused local comment' })