comment.ts 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. import { getModelSafely } from '@growi/core';
  2. import { Types } from 'mongoose';
  3. import { SUPPORTED_TARGET_MODEL_TYPE, SUPPORTED_EVENT_MODEL_TYPE, SUPPORTED_ACTION_TYPE } from '~/interfaces/activity';
  4. import { stringifySnapshot } from '~/models/serializers/in-app-notification-snapshot/page';
  5. import loggerFactory from '../../utils/logger';
  6. import Crowi from '../crowi';
  7. // https://regex101.com/r/Ztxj2j/1
  8. const USERNAME_PATTERN = new RegExp(/\B@[\w@.-]+/g);
  9. const logger = loggerFactory('growi:service:CommentService');
  10. class CommentService {
  11. crowi!: Crowi;
  12. activityService!: any;
  13. inAppNotificationService!: any;
  14. commentEvent!: any;
  15. constructor(crowi: Crowi) {
  16. this.crowi = crowi;
  17. this.activityService = crowi.activityService;
  18. this.inAppNotificationService = crowi.inAppNotificationService;
  19. this.commentEvent = crowi.event('comment');
  20. // init
  21. this.initCommentEventListeners();
  22. }
  23. initCommentEventListeners(): void {
  24. // create
  25. this.commentEvent.on('create', async(savedComment) => {
  26. try {
  27. const Page = getModelSafely('Page') || require('../models/page')(this.crowi);
  28. await Page.updateCommentCount(savedComment.page);
  29. const page = await Page.findById(savedComment.page);
  30. if (page == null) {
  31. logger.error('Page is not found');
  32. return;
  33. }
  34. const activity = await this.createActivity(savedComment, SUPPORTED_ACTION_TYPE.ACTION_COMMENT_CREATE);
  35. await this.createAndSendNotifications(activity, page);
  36. }
  37. catch (err) {
  38. logger.error('Error occurred while handling the comment create event:\n', err);
  39. }
  40. });
  41. // update
  42. this.commentEvent.on('update', async(updatedComment) => {
  43. try {
  44. this.commentEvent.onUpdate();
  45. await this.createActivity(updatedComment, SUPPORTED_ACTION_TYPE.ACTION_COMMENT_UPDATE);
  46. }
  47. catch (err) {
  48. logger.error('Error occurred while handling the comment update event:\n', err);
  49. }
  50. });
  51. // remove
  52. this.commentEvent.on('remove', async(comment) => {
  53. this.commentEvent.onRemove();
  54. try {
  55. const Page = getModelSafely('Page') || require('../models/page')(this.crowi);
  56. await Page.updateCommentCount(comment.page);
  57. }
  58. catch (err) {
  59. logger.error('Error occurred while updating the comment count:\n', err);
  60. }
  61. });
  62. }
  63. private createActivity = async function(comment, action) {
  64. const parameters = {
  65. user: comment.creator,
  66. targetModel: SUPPORTED_TARGET_MODEL_TYPE.MODEL_PAGE,
  67. target: comment.page,
  68. eventModel: SUPPORTED_EVENT_MODEL_TYPE.MODEL_COMMENT,
  69. event: comment._id,
  70. action,
  71. };
  72. const activity = await this.activityService.createByParameters(parameters);
  73. return activity;
  74. };
  75. private createAndSendNotifications = async function(activity, page) {
  76. const snapshot = stringifySnapshot(page);
  77. // Get user to be notified
  78. let targetUsers: Types.ObjectId[] = [];
  79. targetUsers = await activity.getNotificationTargetUsers();
  80. // Add mentioned users to targetUsers
  81. const mentionedUsers = await this.getMentionedUsers(activity.event);
  82. targetUsers = targetUsers.concat(mentionedUsers);
  83. await this.inAppNotificationService.upsertByActivity(targetUsers, activity, snapshot);
  84. await this.inAppNotificationService.emitSocketIo(targetUsers);
  85. };
  86. getMentionedUsers = async(commentId: Types.ObjectId): Promise<Types.ObjectId[]> => {
  87. const Comment = getModelSafely('Comment') || require('../models/comment')(this.crowi);
  88. const User = getModelSafely('User') || require('../models/user')(this.crowi);
  89. // Get comment by comment ID
  90. const commentData = await Comment.findOne({ _id: commentId });
  91. const { comment } = commentData;
  92. const usernamesFromComment = comment.match(USERNAME_PATTERN);
  93. // Get username from comment and remove duplicate username
  94. const mentionedUsernames = [...new Set(usernamesFromComment?.map((username) => {
  95. return username.slice(1);
  96. }))];
  97. // Get mentioned users ID
  98. const mentionedUserIDs = await User.find({ username: { $in: mentionedUsernames } });
  99. return mentionedUserIDs?.map((user) => {
  100. return user._id;
  101. });
  102. }
  103. }
  104. module.exports = CommentService;