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/models/http/http-error-codes'
6 import { exists, isBooleanValid, 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, isValidVideoIdParam } 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 isValidVideoIdParam('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 isValidVideoIdParam('videoId'),
59 .custom(isIdValid).not().isEmpty().withMessage('Should have a valid threadId'),
61 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
62 logger.debug('Checking listVideoThreadComments parameters.', { parameters: req.params })
64 if (areValidationErrors(req, res)) return
65 if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return
66 if (!await doesVideoCommentThreadExist(req.params.threadId, res.locals.onlyVideo, res)) return
72 const addVideoCommentThreadValidator = [
73 isValidVideoIdParam('videoId'),
76 .custom(isValidVideoCommentText).not().isEmpty().withMessage('Should have a valid comment text'),
78 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
79 logger.debug('Checking addVideoCommentThread parameters.', { parameters: req.params, body: req.body })
81 if (areValidationErrors(req, res)) return
82 if (!await doesVideoExist(req.params.videoId, res)) return
83 if (!isVideoCommentsEnabled(res.locals.videoAll, res)) return
84 if (!await isVideoCommentAccepted(req, res, res.locals.videoAll, false)) return
90 const addVideoCommentReplyValidator = [
91 isValidVideoIdParam('videoId'),
93 param('commentId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid commentId'),
95 body('text').custom(isValidVideoCommentText).not().isEmpty().withMessage('Should have a valid comment text'),
97 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
98 logger.debug('Checking addVideoCommentReply parameters.', { parameters: req.params, body: req.body })
100 if (areValidationErrors(req, res)) return
101 if (!await doesVideoExist(req.params.videoId, res)) return
102 if (!isVideoCommentsEnabled(res.locals.videoAll, res)) return
103 if (!await doesVideoCommentExist(req.params.commentId, res.locals.videoAll, res)) return
104 if (!await isVideoCommentAccepted(req, res, res.locals.videoAll, true)) return
110 const videoCommentGetValidator = [
111 isValidVideoIdParam('videoId'),
114 .custom(isIdValid).not().isEmpty().withMessage('Should have a valid commentId'),
116 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
117 logger.debug('Checking videoCommentGetValidator parameters.', { parameters: req.params })
119 if (areValidationErrors(req, res)) return
120 if (!await doesVideoExist(req.params.videoId, res, 'id')) return
121 if (!await doesVideoCommentExist(req.params.commentId, res.locals.videoId, res)) return
127 const removeVideoCommentValidator = [
128 isValidVideoIdParam('videoId'),
130 param('commentId').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 removeVideoCommentValidator parameters.', { parameters: req.params })
135 if (areValidationErrors(req, res)) return
136 if (!await doesVideoExist(req.params.videoId, res)) return
137 if (!await doesVideoCommentExist(req.params.commentId, res.locals.videoAll, res)) return
139 // Check if the user who did the request is able to delete the video
140 if (!checkUserCanDeleteVideoComment(res.locals.oauth.token.User, res.locals.videoCommentFull, res)) return
146 // ---------------------------------------------------------------------------
149 listVideoCommentThreadsValidator,
150 listVideoThreadCommentsValidator,
151 addVideoCommentThreadValidator,
152 listVideoCommentsValidator,
153 addVideoCommentReplyValidator,
154 videoCommentGetValidator,
155 removeVideoCommentValidator
158 // ---------------------------------------------------------------------------
160 function isVideoCommentsEnabled (video: MVideo, res: express.Response) {
161 if (video.commentsEnabled !== true) {
163 status: HttpStatusCode.CONFLICT_409,
164 message: 'Video comments are disabled for this video.'
172 function checkUserCanDeleteVideoComment (user: MUserAccountUrl, videoComment: MCommentOwnerVideoReply, res: express.Response) {
173 if (videoComment.isDeleted()) {
175 status: HttpStatusCode.CONFLICT_409,
176 message: 'This comment is already deleted'
181 const userAccount = user.Account
184 user.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT) === false && // Not a moderator
185 videoComment.accountId !== userAccount.id && // Not the comment owner
186 videoComment.Video.VideoChannel.accountId !== userAccount.id // Not the video owner
189 status: HttpStatusCode.FORBIDDEN_403,
190 message: 'Cannot remove video comment of another user'
198 async function isVideoCommentAccepted (req: express.Request, res: express.Response, video: MVideoFullLight, isReply: boolean) {
199 const acceptParameters = {
201 commentBody: req.body,
202 user: res.locals.oauth.token.User
205 let acceptedResult: AcceptResult
208 const acceptReplyParameters = Object.assign(acceptParameters, { parentComment: res.locals.videoCommentFull })
210 acceptedResult = await Hooks.wrapFun(
211 isLocalVideoCommentReplyAccepted,
212 acceptReplyParameters,
213 'filter:api.video-comment-reply.create.accept.result'
216 acceptedResult = await Hooks.wrapFun(
217 isLocalVideoThreadAccepted,
219 'filter:api.video-thread.create.accept.result'
223 if (!acceptedResult || acceptedResult.accepted !== true) {
224 logger.info('Refused local comment.', { acceptedResult, acceptParameters })
227 status: HttpStatusCode.FORBIDDEN_403,
228 message: acceptedResult?.errorMessage || 'Refused local comment'