1 import express from 'express'
2 import { body, param, query } from 'express-validator'
3 import { MUserAccountUrl } from '@server/types/models'
4 import { HttpStatusCode, UserRight } from '@shared/models'
5 import { exists, isBooleanValid, isIdValid, toBooleanOrNull } from '../../../helpers/custom-validators/misc'
6 import { isValidVideoCommentText } from '../../../helpers/custom-validators/video-comments'
7 import { logger } from '../../../helpers/logger'
8 import { AcceptResult, isLocalVideoCommentReplyAccepted, isLocalVideoThreadAccepted } from '../../../lib/moderation'
9 import { Hooks } from '../../../lib/plugins/hooks'
10 import { MCommentOwnerVideoReply, MVideo, MVideoFullLight } from '../../../types/models/video'
13 checkCanSeeVideoIfPrivate,
14 doesVideoCommentExist,
15 doesVideoCommentThreadExist,
20 const listVideoCommentsValidator = [
23 .customSanitizer(toBooleanOrNull)
24 .custom(isBooleanValid)
25 .withMessage('Should have a valid is local boolean'),
29 .custom(exists).withMessage('Should have a valid search'),
31 query('searchAccount')
33 .custom(exists).withMessage('Should have a valid account search'),
37 .custom(exists).withMessage('Should have a valid video search'),
39 (req: express.Request, res: express.Response, next: express.NextFunction) => {
40 logger.debug('Checking listVideoCommentsValidator parameters.', { parameters: req.query })
42 if (areValidationErrors(req, res)) return
48 const listVideoCommentThreadsValidator = [
49 isValidVideoIdParam('videoId'),
51 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
52 logger.debug('Checking listVideoCommentThreads parameters.', { parameters: req.params })
54 if (areValidationErrors(req, res)) return
55 if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return
57 if (!await checkCanSeeVideoIfPrivate(req, res, res.locals.onlyVideo)) return
63 const listVideoThreadCommentsValidator = [
64 isValidVideoIdParam('videoId'),
67 .custom(isIdValid).not().isEmpty().withMessage('Should have a valid threadId'),
69 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
70 logger.debug('Checking listVideoThreadComments parameters.', { parameters: req.params })
72 if (areValidationErrors(req, res)) return
73 if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return
74 if (!await doesVideoCommentThreadExist(req.params.threadId, res.locals.onlyVideo, res)) return
76 if (!await checkCanSeeVideoIfPrivate(req, res, res.locals.onlyVideo)) return
82 const addVideoCommentThreadValidator = [
83 isValidVideoIdParam('videoId'),
86 .custom(isValidVideoCommentText).not().isEmpty().withMessage('Should have a valid comment text'),
88 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
89 logger.debug('Checking addVideoCommentThread parameters.', { parameters: req.params, body: req.body })
91 if (areValidationErrors(req, res)) return
92 if (!await doesVideoExist(req.params.videoId, res)) return
94 if (!await checkCanSeeVideoIfPrivate(req, res, res.locals.videoAll)) return
96 if (!isVideoCommentsEnabled(res.locals.videoAll, res)) return
97 if (!await isVideoCommentAccepted(req, res, res.locals.videoAll, false)) return
103 const addVideoCommentReplyValidator = [
104 isValidVideoIdParam('videoId'),
106 param('commentId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid commentId'),
108 body('text').custom(isValidVideoCommentText).not().isEmpty().withMessage('Should have a valid comment text'),
110 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
111 logger.debug('Checking addVideoCommentReply parameters.', { parameters: req.params, body: req.body })
113 if (areValidationErrors(req, res)) return
114 if (!await doesVideoExist(req.params.videoId, res)) return
116 if (!await checkCanSeeVideoIfPrivate(req, res, res.locals.videoAll)) return
118 if (!isVideoCommentsEnabled(res.locals.videoAll, res)) return
119 if (!await doesVideoCommentExist(req.params.commentId, res.locals.videoAll, res)) return
120 if (!await isVideoCommentAccepted(req, res, res.locals.videoAll, true)) return
126 const videoCommentGetValidator = [
127 isValidVideoIdParam('videoId'),
130 .custom(isIdValid).not().isEmpty().withMessage('Should have a valid commentId'),
132 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
133 logger.debug('Checking videoCommentGetValidator parameters.', { parameters: req.params })
135 if (areValidationErrors(req, res)) return
136 if (!await doesVideoExist(req.params.videoId, res, 'id')) return
137 if (!await doesVideoCommentExist(req.params.commentId, res.locals.videoId, res)) return
143 const removeVideoCommentValidator = [
144 isValidVideoIdParam('videoId'),
146 param('commentId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid commentId'),
148 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
149 logger.debug('Checking removeVideoCommentValidator parameters.', { parameters: req.params })
151 if (areValidationErrors(req, res)) return
152 if (!await doesVideoExist(req.params.videoId, res)) return
153 if (!await doesVideoCommentExist(req.params.commentId, res.locals.videoAll, res)) return
155 // Check if the user who did the request is able to delete the video
156 if (!checkUserCanDeleteVideoComment(res.locals.oauth.token.User, res.locals.videoCommentFull, res)) return
162 // ---------------------------------------------------------------------------
165 listVideoCommentThreadsValidator,
166 listVideoThreadCommentsValidator,
167 addVideoCommentThreadValidator,
168 listVideoCommentsValidator,
169 addVideoCommentReplyValidator,
170 videoCommentGetValidator,
171 removeVideoCommentValidator
174 // ---------------------------------------------------------------------------
176 function isVideoCommentsEnabled (video: MVideo, res: express.Response) {
177 if (video.commentsEnabled !== true) {
179 status: HttpStatusCode.CONFLICT_409,
180 message: 'Video comments are disabled for this video.'
188 function checkUserCanDeleteVideoComment (user: MUserAccountUrl, videoComment: MCommentOwnerVideoReply, res: express.Response) {
189 if (videoComment.isDeleted()) {
191 status: HttpStatusCode.CONFLICT_409,
192 message: 'This comment is already deleted'
197 const userAccount = user.Account
200 user.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT) === false && // Not a moderator
201 videoComment.accountId !== userAccount.id && // Not the comment owner
202 videoComment.Video.VideoChannel.accountId !== userAccount.id // Not the video owner
205 status: HttpStatusCode.FORBIDDEN_403,
206 message: 'Cannot remove video comment of another user'
214 async function isVideoCommentAccepted (req: express.Request, res: express.Response, video: MVideoFullLight, isReply: boolean) {
215 const acceptParameters = {
217 commentBody: req.body,
218 user: res.locals.oauth.token.User
221 let acceptedResult: AcceptResult
224 const acceptReplyParameters = Object.assign(acceptParameters, { parentComment: res.locals.videoCommentFull })
226 acceptedResult = await Hooks.wrapFun(
227 isLocalVideoCommentReplyAccepted,
228 acceptReplyParameters,
229 'filter:api.video-comment-reply.create.accept.result'
232 acceptedResult = await Hooks.wrapFun(
233 isLocalVideoThreadAccepted,
235 'filter:api.video-thread.create.accept.result'
239 if (!acceptedResult || acceptedResult.accepted !== true) {
240 logger.info('Refused local comment.', { acceptedResult, acceptParameters })
243 status: HttpStatusCode.FORBIDDEN_403,
244 message: acceptedResult?.errorMessage || 'Refused local comment'