comment.js 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. module.exports = function(crowi, app) {
  2. const logger = require('@alias/logger')('growi:routes:comment');
  3. const Comment = crowi.model('Comment');
  4. const User = crowi.model('User');
  5. const Page = crowi.model('Page');
  6. const GlobalNotificationSetting = crowi.model('GlobalNotificationSetting');
  7. const ApiResponse = require('../util/apiResponse');
  8. const globalNotificationService = crowi.getGlobalNotificationService();
  9. const { body } = require('express-validator/check');
  10. const mongoose = require('mongoose');
  11. const ObjectId = mongoose.Types.ObjectId;
  12. const actions = {};
  13. const api = {};
  14. actions.api = api;
  15. api.validators = {};
  16. /**
  17. * @api {get} /comments.get Get comments of the page of the revision
  18. * @apiName GetComments
  19. * @apiGroup Comment
  20. *
  21. * @apiParam {String} page_id Page Id.
  22. * @apiParam {String} revision_id Revision Id.
  23. */
  24. api.get = async function(req, res) {
  25. const pageId = req.query.page_id;
  26. const revisionId = req.query.revision_id;
  27. // check whether accessible
  28. const isAccessible = await Page.isAccessiblePageByViewer(pageId, req.user);
  29. if (!isAccessible) {
  30. return res.json(ApiResponse.error('Current user is not accessible to this page.'));
  31. }
  32. let fetcher = null;
  33. try {
  34. if (revisionId) {
  35. fetcher = Comment.getCommentsByRevisionId(revisionId);
  36. }
  37. else {
  38. fetcher = Comment.getCommentsByPageId(pageId);
  39. }
  40. }
  41. catch (err) {
  42. return res.json(ApiResponse.error(err));
  43. }
  44. const comments = await fetcher.populate(
  45. { path: 'creator', select: User.USER_PUBLIC_FIELDS, populate: User.IMAGE_POPULATION },
  46. );
  47. res.json(ApiResponse.success({ comments }));
  48. };
  49. api.validators.add = function() {
  50. const validator = [
  51. body('commentForm.page_id').exists(),
  52. body('commentForm.revision_id').exists(),
  53. body('commentForm.comment').exists(),
  54. body('commentForm.comment_position').isInt(),
  55. body('commentForm.is_markdown').isBoolean(),
  56. body('commentForm.replyTo').exists().custom((value) => {
  57. if (value === '') {
  58. return undefined;
  59. }
  60. return ObjectId(value);
  61. }),
  62. body('slackNotificationForm.isSlackEnabled').isBoolean().exists(),
  63. ];
  64. return validator;
  65. };
  66. /**
  67. * @api {post} /comments.add Post comment for the page
  68. * @apiName PostComment
  69. * @apiGroup Comment
  70. *
  71. * @apiParam {String} page_id Page Id.
  72. * @apiParam {String} revision_id Revision Id.
  73. * @apiParam {String} comment Comment body
  74. * @apiParam {Number} comment_position=-1 Line number of the comment
  75. */
  76. api.add = async function(req, res) {
  77. const { commentForm, slackNotificationForm } = req.body;
  78. const { validationResult } = require('express-validator/check');
  79. const errors = validationResult(req.body);
  80. if (!errors.isEmpty()) {
  81. // return res.json(ApiResponse.error('Invalid comment.'));
  82. // return res.status(422).json({ errors: errors.array() });
  83. return res.json(ApiResponse.error('コメントを入力してください。'));
  84. }
  85. const pageId = commentForm.page_id;
  86. const revisionId = commentForm.revision_id;
  87. const comment = commentForm.comment;
  88. const position = commentForm.comment_position || -1;
  89. const isMarkdown = commentForm.is_markdown;
  90. const replyTo = commentForm.replyTo;
  91. // check whether accessible
  92. const isAccessible = await Page.isAccessiblePageByViewer(pageId, req.user);
  93. if (!isAccessible) {
  94. return res.json(ApiResponse.error('Current user is not accessible to this page.'));
  95. }
  96. let createdComment;
  97. try {
  98. createdComment = await Comment.create(pageId, req.user._id, revisionId, comment, position, isMarkdown, replyTo);
  99. await Comment.populate(createdComment, [
  100. { path: 'creator', model: 'User', select: User.USER_PUBLIC_FIELDS },
  101. ]);
  102. }
  103. catch (err) {
  104. logger.error(err);
  105. return res.json(ApiResponse.error(err));
  106. }
  107. // update page
  108. const page = await Page.findOneAndUpdate({ _id: pageId }, {
  109. lastUpdateUser: req.user,
  110. updatedAt: new Date(),
  111. });
  112. res.json(ApiResponse.success({ comment: createdComment }));
  113. const path = page.path;
  114. // global notification
  115. globalNotificationService.fire(GlobalNotificationSetting.EVENT.COMMENT, path, req.user, {
  116. comment: createdComment,
  117. });
  118. // slack notification
  119. if (slackNotificationForm.isSlackEnabled) {
  120. const user = await User.findUserByUsername(req.user.username);
  121. const channelsStr = slackNotificationForm.slackChannels || null;
  122. page.updateSlackChannel(channelsStr).catch((err) => {
  123. logger.error('Error occured in updating slack channels: ', err);
  124. });
  125. const channels = channelsStr != null ? channelsStr.split(',') : [null];
  126. const promises = channels.map((chan) => {
  127. return crowi.slack.postComment(createdComment, user, chan, path);
  128. });
  129. Promise.all(promises)
  130. .catch((err) => {
  131. logger.error('Error occured in sending slack notification: ', err);
  132. });
  133. }
  134. };
  135. /**
  136. * @api {post} /comments.update Update comment dody
  137. * @apiName UpdateComment
  138. * @apiGroup Comment
  139. */
  140. api.update = async function(req, res) {
  141. const { commentForm } = req.body;
  142. const pageId = commentForm.page_id;
  143. const revisionId = commentForm.revision_id;
  144. const comment = commentForm.comment;
  145. const isMarkdown = commentForm.is_markdown;
  146. const commentId = commentForm.comment_id;
  147. const author = commentForm.author;
  148. if (comment === '') {
  149. return res.json(ApiResponse.error('Comment text is required'));
  150. }
  151. if (commentId == null) {
  152. return res.json(ApiResponse.error('\'comment_id\' is undefined'));
  153. }
  154. if (author !== req.user.username) {
  155. return res.json(ApiResponse.error('Only the author can edit'));
  156. }
  157. // check whether accessible
  158. const isAccessible = await Page.isAccessiblePageByViewer(pageId, req.user._id, revisionId, comment, isMarkdown, req.user);
  159. if (!isAccessible) {
  160. return res.json(ApiResponse.error('Current user is not accessible to this page.'));
  161. }
  162. try {
  163. const updatedComment = await Comment.updateCommentsByPageId(comment, isMarkdown, commentId);
  164. const page = await Page.findOneAndUpdate({ _id: pageId }, {
  165. lastUpdateUser: req.user,
  166. updatedAt: new Date(),
  167. });
  168. res.json(ApiResponse.success({ comment: updatedComment }));
  169. const path = page.path;
  170. // global notification
  171. globalNotificationService.notifyComment(updatedComment, path);
  172. }
  173. catch (err) {
  174. return res.json(ApiResponse.error(err));
  175. }
  176. };
  177. /**
  178. * @api {post} /comments.remove Remove specified comment
  179. * @apiName RemoveComment
  180. * @apiGroup Comment
  181. *
  182. * @apiParam {String} comment_id Comment Id.
  183. */
  184. api.remove = async function(req, res) {
  185. const commentId = req.body.comment_id;
  186. if (!commentId) {
  187. return Promise.resolve(res.json(ApiResponse.error('\'comment_id\' is undefined')));
  188. }
  189. try {
  190. const comment = await Comment.findById(commentId).exec();
  191. if (comment == null) {
  192. throw new Error('This comment does not exist.');
  193. }
  194. // check whether accessible
  195. const pageId = comment.page;
  196. const isAccessible = await Page.isAccessiblePageByViewer(pageId, req.user);
  197. if (!isAccessible) {
  198. throw new Error('Current user is not accessible to this page.');
  199. }
  200. await comment.removeWithReplies();
  201. await Page.updateCommentCount(comment.page);
  202. }
  203. catch (err) {
  204. return res.json(ApiResponse.error(err));
  205. }
  206. return res.json(ApiResponse.success({}));
  207. };
  208. return actions;
  209. };