video-comments.ts 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. import * as express from 'express'
  2. import { body, param } from 'express-validator/check'
  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 { doesVideoExist } from '../../../helpers/custom-validators/videos'
  7. import { logger } from '../../../helpers/logger'
  8. import { UserModel } from '../../../models/account/user'
  9. import { VideoModel } from '../../../models/video/video'
  10. import { VideoCommentModel } from '../../../models/video/video-comment'
  11. import { areValidationErrors } from '../utils'
  12. import { Hooks } from '../../../lib/plugins/hooks'
  13. import { isLocalVideoThreadAccepted, isLocalVideoCommentReplyAccepted, AcceptResult } from '../../../lib/moderation'
  14. const listVideoCommentThreadsValidator = [
  15. param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
  16. async (req: express.Request, res: express.Response, next: express.NextFunction) => {
  17. logger.debug('Checking listVideoCommentThreads parameters.', { parameters: req.params })
  18. if (areValidationErrors(req, res)) return
  19. if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return
  20. return next()
  21. }
  22. ]
  23. const listVideoThreadCommentsValidator = [
  24. param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
  25. param('threadId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid threadId'),
  26. async (req: express.Request, res: express.Response, next: express.NextFunction) => {
  27. logger.debug('Checking listVideoThreadComments parameters.', { parameters: req.params })
  28. if (areValidationErrors(req, res)) return
  29. if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return
  30. if (!await doesVideoCommentThreadExist(req.params.threadId, res.locals.video, res)) return
  31. return next()
  32. }
  33. ]
  34. const addVideoCommentThreadValidator = [
  35. param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
  36. body('text').custom(isValidVideoCommentText).not().isEmpty().withMessage('Should have a valid comment text'),
  37. async (req: express.Request, res: express.Response, next: express.NextFunction) => {
  38. logger.debug('Checking addVideoCommentThread parameters.', { parameters: req.params, body: req.body })
  39. if (areValidationErrors(req, res)) return
  40. if (!await doesVideoExist(req.params.videoId, res)) return
  41. if (!isVideoCommentsEnabled(res.locals.video, res)) return
  42. if (!await isVideoCommentAccepted(req, res, false)) return
  43. return next()
  44. }
  45. ]
  46. const addVideoCommentReplyValidator = [
  47. param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
  48. param('commentId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid commentId'),
  49. body('text').custom(isValidVideoCommentText).not().isEmpty().withMessage('Should have a valid comment text'),
  50. async (req: express.Request, res: express.Response, next: express.NextFunction) => {
  51. logger.debug('Checking addVideoCommentReply parameters.', { parameters: req.params, body: req.body })
  52. if (areValidationErrors(req, res)) return
  53. if (!await doesVideoExist(req.params.videoId, res)) return
  54. if (!isVideoCommentsEnabled(res.locals.video, res)) return
  55. if (!await doesVideoCommentExist(req.params.commentId, res.locals.video, res)) return
  56. if (!await isVideoCommentAccepted(req, res, true)) return
  57. return next()
  58. }
  59. ]
  60. const videoCommentGetValidator = [
  61. param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
  62. param('commentId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid commentId'),
  63. async (req: express.Request, res: express.Response, next: express.NextFunction) => {
  64. logger.debug('Checking videoCommentGetValidator parameters.', { parameters: req.params })
  65. if (areValidationErrors(req, res)) return
  66. if (!await doesVideoExist(req.params.videoId, res, 'id')) return
  67. if (!await doesVideoCommentExist(req.params.commentId, res.locals.video, res)) return
  68. return next()
  69. }
  70. ]
  71. const removeVideoCommentValidator = [
  72. param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
  73. param('commentId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid commentId'),
  74. async (req: express.Request, res: express.Response, next: express.NextFunction) => {
  75. logger.debug('Checking removeVideoCommentValidator parameters.', { parameters: req.params })
  76. if (areValidationErrors(req, res)) return
  77. if (!await doesVideoExist(req.params.videoId, res)) return
  78. if (!await doesVideoCommentExist(req.params.commentId, res.locals.video, res)) return
  79. // Check if the user who did the request is able to delete the video
  80. if (!checkUserCanDeleteVideoComment(res.locals.oauth.token.User, res.locals.videoComment, res)) return
  81. return next()
  82. }
  83. ]
  84. // ---------------------------------------------------------------------------
  85. export {
  86. listVideoCommentThreadsValidator,
  87. listVideoThreadCommentsValidator,
  88. addVideoCommentThreadValidator,
  89. addVideoCommentReplyValidator,
  90. videoCommentGetValidator,
  91. removeVideoCommentValidator
  92. }
  93. // ---------------------------------------------------------------------------
  94. async function doesVideoCommentThreadExist (id: number, video: VideoModel, res: express.Response) {
  95. const videoComment = await VideoCommentModel.loadById(id)
  96. if (!videoComment) {
  97. res.status(404)
  98. .json({ error: 'Video comment thread not found' })
  99. .end()
  100. return false
  101. }
  102. if (videoComment.videoId !== video.id) {
  103. res.status(400)
  104. .json({ error: 'Video comment is associated to this video.' })
  105. .end()
  106. return false
  107. }
  108. if (videoComment.inReplyToCommentId !== null) {
  109. res.status(400)
  110. .json({ error: 'Video comment is not a thread.' })
  111. .end()
  112. return false
  113. }
  114. res.locals.videoCommentThread = videoComment
  115. return true
  116. }
  117. async function doesVideoCommentExist (id: number, video: VideoModel, res: express.Response) {
  118. const videoComment = await VideoCommentModel.loadByIdAndPopulateVideoAndAccountAndReply(id)
  119. if (!videoComment) {
  120. res.status(404)
  121. .json({ error: 'Video comment thread not found' })
  122. .end()
  123. return false
  124. }
  125. if (videoComment.videoId !== video.id) {
  126. res.status(400)
  127. .json({ error: 'Video comment is associated to this video.' })
  128. .end()
  129. return false
  130. }
  131. res.locals.videoComment = videoComment
  132. return true
  133. }
  134. function isVideoCommentsEnabled (video: VideoModel, res: express.Response) {
  135. if (video.commentsEnabled !== true) {
  136. res.status(409)
  137. .json({ error: 'Video comments are disabled for this video.' })
  138. .end()
  139. return false
  140. }
  141. return true
  142. }
  143. function checkUserCanDeleteVideoComment (user: UserModel, videoComment: VideoCommentModel, res: express.Response) {
  144. const account = videoComment.Account
  145. if (user.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT) === false && account.userId !== user.id) {
  146. res.status(403)
  147. .json({ error: 'Cannot remove video comment of another user' })
  148. .end()
  149. return false
  150. }
  151. return true
  152. }
  153. async function isVideoCommentAccepted (req: express.Request, res: express.Response, isReply: boolean) {
  154. const acceptParameters = {
  155. video: res.locals.video,
  156. commentBody: req.body,
  157. user: res.locals.oauth.token.User
  158. }
  159. let acceptedResult: AcceptResult
  160. if (isReply) {
  161. const acceptReplyParameters = Object.assign(acceptParameters, { parentComment: res.locals.videoComment })
  162. acceptedResult = await Hooks.wrapObject(
  163. isLocalVideoCommentReplyAccepted(acceptReplyParameters),
  164. 'filter:api.video-comment-reply.create.accept.result'
  165. )
  166. } else {
  167. acceptedResult = await Hooks.wrapObject(
  168. isLocalVideoThreadAccepted(acceptParameters),
  169. 'filter:api.video-thread.create.accept.result'
  170. )
  171. }
  172. if (!acceptedResult || acceptedResult.accepted !== true) {
  173. logger.info('Refused local comment.', { acceptedResult, acceptParameters })
  174. res.status(403)
  175. .json({ error: acceptedResult.errorMessage || 'Refused local comment' })
  176. return false
  177. }
  178. return true
  179. }