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'
14 const listVideoCommentsValidator = [
17 .customSanitizer(toBooleanOrNull)
18 .custom(isBooleanValid)
19 .withMessage('Should have a valid is local boolean'),
23 .custom(exists).withMessage('Should have a valid search'),
25 query('searchAccount')
27 .custom(exists).withMessage('Should have a valid account search'),
31 .custom(exists).withMessage('Should have a valid video search'),
33 (req: express.Request, res: express.Response, next: express.NextFunction) => {
34 logger.debug('Checking listVideoCommentsValidator parameters.', { parameters: req.query })
36 if (areValidationErrors(req, res)) return
42 const listVideoCommentThreadsValidator = [
43 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
45 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
46 logger.debug('Checking listVideoCommentThreads parameters.', { parameters: req.params })
48 if (areValidationErrors(req, res)) return
49 if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return
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'),
59 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
60 logger.debug('Checking listVideoThreadComments parameters.', { parameters: req.params })
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
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'),
74 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
75 logger.debug('Checking addVideoCommentThread parameters.', { parameters: req.params, body: req.body })
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
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'),
91 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
92 logger.debug('Checking addVideoCommentReply parameters.', { parameters: req.params, body: req.body })
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
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'),
108 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
109 logger.debug('Checking videoCommentGetValidator parameters.', { parameters: req.params })
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
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'),
123 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
124 logger.debug('Checking removeVideoCommentValidator parameters.', { parameters: req.params })
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
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
137 // ---------------------------------------------------------------------------
140 listVideoCommentThreadsValidator,
141 listVideoThreadCommentsValidator,
142 addVideoCommentThreadValidator,
143 listVideoCommentsValidator,
144 addVideoCommentReplyValidator,
145 videoCommentGetValidator,
146 removeVideoCommentValidator
149 // ---------------------------------------------------------------------------
151 function isVideoCommentsEnabled (video: MVideo, res: express.Response) {
152 if (video.commentsEnabled !== true) {
154 status: HttpStatusCode.CONFLICT_409,
155 message: 'Video comments are disabled for this video.'
163 function checkUserCanDeleteVideoComment (user: MUserAccountUrl, videoComment: MCommentOwnerVideoReply, res: express.Response) {
164 if (videoComment.isDeleted()) {
166 status: HttpStatusCode.CONFLICT_409,
167 message: 'This comment is already deleted'
172 const userAccount = user.Account
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
180 status: HttpStatusCode.FORBIDDEN_403,
181 message: '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 })
218 status: HttpStatusCode.FORBIDDEN_403,
219 message: acceptedResult?.errorMessage || 'Refused local comment'