Просмотр исходного кода

use activityEventEmitter with comment event

Shun Miyazawa 3 лет назад
Родитель
Сommit
8df4905366

+ 10 - 2
packages/app/src/interfaces/activity.ts

@@ -3,6 +3,7 @@ import { IUser } from './user';
 
 
 // Model
 // Model
 const MODEL_PAGE = 'Page';
 const MODEL_PAGE = 'Page';
+const MODEL_COMMENT = 'Comment';
 
 
 // Action
 // Action
 const ACTION_UNSETTLED = 'UNSETTLED';
 const ACTION_UNSETTLED = 'UNSETTLED';
@@ -28,6 +29,10 @@ export const SUPPORTED_TARGET_MODEL_TYPE = {
   MODEL_PAGE,
   MODEL_PAGE,
 } as const;
 } as const;
 
 
+export const SUPPORTED_EVENT_MODEL_TYPE = {
+  MODEL_COMMENT,
+} as const;
+
 export const SUPPORTED_ACTION_TYPE = {
 export const SUPPORTED_ACTION_TYPE = {
   ACTION_LOGIN_SUCCESS,
   ACTION_LOGIN_SUCCESS,
   ACTION_LOGIN_FAILURE,
   ACTION_LOGIN_FAILURE,
@@ -58,11 +63,11 @@ export const SUPPORTED_ACTION_TO_NOTIFIED_TYPE = {
   ACTION_PAGE_DELETE_COMPLETELY,
   ACTION_PAGE_DELETE_COMPLETELY,
   ACTION_PAGE_REVERT,
   ACTION_PAGE_REVERT,
   ACTION_COMMENT_CREATE,
   ACTION_COMMENT_CREATE,
-  ACTION_COMMENT_UPDATE,
 } as const;
 } as const;
 
 
 
 
 export const AllSupportedTargetModelType = Object.values(SUPPORTED_TARGET_MODEL_TYPE);
 export const AllSupportedTargetModelType = Object.values(SUPPORTED_TARGET_MODEL_TYPE);
+export const AllSupportedEventModelType = Object.values(SUPPORTED_EVENT_MODEL_TYPE);
 export const AllSupportedActionType = Object.values(SUPPORTED_ACTION_TYPE);
 export const AllSupportedActionType = Object.values(SUPPORTED_ACTION_TYPE);
 export const AllSupportedActionToNotifiedType = Object.values(SUPPORTED_ACTION_TO_NOTIFIED_TYPE);
 export const AllSupportedActionToNotifiedType = Object.values(SUPPORTED_ACTION_TO_NOTIFIED_TYPE);
 
 
@@ -89,17 +94,20 @@ export const CommentActions = Object.values({
 
 
 
 
 export type SupportedTargetModelType = typeof SUPPORTED_TARGET_MODEL_TYPE[keyof typeof SUPPORTED_TARGET_MODEL_TYPE];
 export type SupportedTargetModelType = typeof SUPPORTED_TARGET_MODEL_TYPE[keyof typeof SUPPORTED_TARGET_MODEL_TYPE];
+export type SupportedEventModelType = typeof SUPPORTED_EVENT_MODEL_TYPE[keyof typeof SUPPORTED_EVENT_MODEL_TYPE];
 export type SupportedActionType = typeof SUPPORTED_ACTION_TYPE[keyof typeof SUPPORTED_ACTION_TYPE];
 export type SupportedActionType = typeof SUPPORTED_ACTION_TYPE[keyof typeof SUPPORTED_ACTION_TYPE];
 
 
 
 
 export type ISnapshot = Partial<Pick<IUser, 'username'>>
 export type ISnapshot = Partial<Pick<IUser, 'username'>>
 
 
 export type IActivity = {
 export type IActivity = {
-  user?: IUser
+  user?: string
   ip?: string
   ip?: string
   endpoint?: string
   endpoint?: string
   targetModel?: SupportedTargetModelType
   targetModel?: SupportedTargetModelType
   target?: string
   target?: string
+  eventModel?: SupportedEventModelType
+  event?: string
   action: SupportedActionType
   action: SupportedActionType
   createdAt: Date
   createdAt: Date
   snapshot?: ISnapshot
   snapshot?: ISnapshot

+ 14 - 3
packages/app/src/server/models/activity.ts

@@ -5,7 +5,9 @@ import {
 import mongoosePaginate from 'mongoose-paginate-v2';
 import mongoosePaginate from 'mongoose-paginate-v2';
 
 
 import {
 import {
-  AllSupportedTargetModelType, AllSupportedActionType, SupportedActionType, ISnapshot,
+  ISnapshot, AllSupportedActionType, SupportedActionType,
+  AllSupportedTargetModelType, SupportedTargetModelType,
+  AllSupportedEventModelType, SupportedEventModelType,
 } from '~/interfaces/activity';
 } from '~/interfaces/activity';
 
 
 import loggerFactory from '../../utils/logger';
 import loggerFactory from '../../utils/logger';
@@ -16,11 +18,13 @@ const logger = loggerFactory('growi:models:activity');
 
 
 export interface ActivityDocument extends Document {
 export interface ActivityDocument extends Document {
   _id: Types.ObjectId
   _id: Types.ObjectId
-  user: Types.ObjectId | any
+  user: Types.ObjectId
   ip: string
   ip: string
   endpoint: string
   endpoint: string
-  targetModel: string
+  targetModel: SupportedTargetModelType
   target: Types.ObjectId
   target: Types.ObjectId
+  eventModel: SupportedEventModelType
+  event: Types.ObjectId
   action: SupportedActionType
   action: SupportedActionType
   snapshot: ISnapshot
   snapshot: ISnapshot
 
 
@@ -57,6 +61,13 @@ const activitySchema = new Schema<ActivityDocument, ActivityModel>({
     type: Schema.Types.ObjectId,
     type: Schema.Types.ObjectId,
     refPath: 'targetModel',
     refPath: 'targetModel',
   },
   },
+  eventModel: {
+    type: String,
+    enum: AllSupportedEventModelType,
+  },
+  event: {
+    type: Schema.Types.ObjectId,
+  },
   action: {
   action: {
     type: String,
     type: String,
     enum: AllSupportedActionType,
     enum: AllSupportedActionType,

+ 16 - 1
packages/app/src/server/routes/comment.js

@@ -1,3 +1,4 @@
+import { SUPPORTED_ACTION_TYPE, SUPPORTED_TARGET_MODEL_TYPE, SUPPORTED_EVENT_MODEL_TYPE } from '~/interfaces/activity';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 /**
 /**
@@ -60,6 +61,8 @@ module.exports = function(crowi, app) {
   const mongoose = require('mongoose');
   const mongoose = require('mongoose');
   const ObjectId = mongoose.Types.ObjectId;
   const ObjectId = mongoose.Types.ObjectId;
 
 
+  const activityEvent = crowi.event('activity');
+
   const actions = {};
   const actions = {};
   const api = {};
   const api = {};
 
 
@@ -248,7 +251,6 @@ module.exports = function(crowi, app) {
       logger.error(err);
       logger.error(err);
       return res.json(ApiResponse.error(err));
       return res.json(ApiResponse.error(err));
     }
     }
-
     // update page
     // update page
     const page = await Page.findOneAndUpdate(
     const page = await Page.findOneAndUpdate(
       { _id: pageId },
       { _id: pageId },
@@ -258,6 +260,16 @@ module.exports = function(crowi, app) {
       },
       },
     );
     );
 
 
+    const activityId = res.locals.activity._id;
+    const parameters = {
+      targetModel: SUPPORTED_TARGET_MODEL_TYPE.MODEL_PAGE,
+      target: page,
+      eventModel: SUPPORTED_EVENT_MODEL_TYPE.MODEL_COMMENT,
+      event: createdComment,
+      action: SUPPORTED_ACTION_TYPE.ACTION_COMMENT_CREATE,
+    };
+    activityEvent.emit('update', activityId, parameters, page);
+
     res.json(ApiResponse.success({ comment: createdComment }));
     res.json(ApiResponse.success({ comment: createdComment }));
 
 
     // global notification
     // global notification
@@ -386,6 +398,9 @@ module.exports = function(crowi, app) {
       return res.json(ApiResponse.error(err));
       return res.json(ApiResponse.error(err));
     }
     }
 
 
+    const parameters = { action: SUPPORTED_ACTION_TYPE.ACTION_COMMENT_UPDATE };
+    activityEvent.emit('update', res.locals.activity._id, parameters);
+
     res.json(ApiResponse.success({ comment: updatedComment }));
     res.json(ApiResponse.success({ comment: updatedComment }));
 
 
     // process notification if needed
     // process notification if needed

+ 2 - 2
packages/app/src/server/routes/index.js

@@ -186,8 +186,8 @@ module.exports = function(crowi, app) {
   apiV1Router.get('/tags.search'         , accessTokenParser, loginRequired, tag.api.search);
   apiV1Router.get('/tags.search'         , accessTokenParser, loginRequired, tag.api.search);
   apiV1Router.post('/tags.update'        , accessTokenParser, loginRequiredStrictly, tag.api.update);
   apiV1Router.post('/tags.update'        , accessTokenParser, loginRequiredStrictly, tag.api.update);
   apiV1Router.get('/comments.get'        , accessTokenParser , loginRequired , comment.api.get);
   apiV1Router.get('/comments.get'        , accessTokenParser , loginRequired , comment.api.get);
-  apiV1Router.post('/comments.add'       , comment.api.validators.add(), accessTokenParser , loginRequiredStrictly , csrf, comment.api.add);
-  apiV1Router.post('/comments.update'    , comment.api.validators.add(), accessTokenParser , loginRequiredStrictly , csrf, comment.api.update);
+  apiV1Router.post('/comments.add'       , comment.api.validators.add(), accessTokenParser , loginRequiredStrictly , csrf, addActivity, comment.api.add);
+  apiV1Router.post('/comments.update'    , comment.api.validators.add(), accessTokenParser , loginRequiredStrictly , csrf, addActivity, comment.api.update);
   apiV1Router.post('/comments.remove'    , accessTokenParser , loginRequiredStrictly , csrf, comment.api.remove);
   apiV1Router.post('/comments.remove'    , accessTokenParser , loginRequiredStrictly , csrf, comment.api.remove);
   apiV1Router.post('/attachments.add'                  , uploads.single('file'), autoReap, accessTokenParser, loginRequiredStrictly ,csrf, attachment.api.add);
   apiV1Router.post('/attachments.add'                  , uploads.single('file'), autoReap, accessTokenParser, loginRequiredStrictly ,csrf, attachment.api.add);
   apiV1Router.post('/attachments.uploadProfileImage'   , uploads.single('file'), autoReap, accessTokenParser, loginRequiredStrictly ,csrf, attachment.api.uploadProfileImage);
   apiV1Router.post('/attachments.uploadProfileImage'   , uploads.single('file'), autoReap, accessTokenParser, loginRequiredStrictly ,csrf, attachment.api.uploadProfileImage);

+ 1 - 11
packages/app/src/server/service/comment.ts

@@ -44,15 +44,6 @@ class CommentService {
       try {
       try {
         const Page = getModelSafely('Page') || require('../models/page')(this.crowi);
         const Page = getModelSafely('Page') || require('../models/page')(this.crowi);
         await Page.updateCommentCount(savedComment.page);
         await Page.updateCommentCount(savedComment.page);
-
-        const page = await Page.findById(savedComment.page);
-        if (page == null) {
-          logger.error('Page is not found');
-          return;
-        }
-
-        const activity = await this.createActivity(user, savedComment.page, SUPPORTED_ACTION_TYPE.ACTION_COMMENT_CREATE);
-        await this.createAndSendNotifications(activity, page);
       }
       }
       catch (err) {
       catch (err) {
         logger.error('Error occurred while handling the comment create event:\n', err);
         logger.error('Error occurred while handling the comment create event:\n', err);
@@ -64,7 +55,6 @@ class CommentService {
     this.commentEvent.on('update', async(user, updatedComment) => {
     this.commentEvent.on('update', async(user, updatedComment) => {
       try {
       try {
         this.commentEvent.onUpdate();
         this.commentEvent.onUpdate();
-        await this.createActivity(user, updatedComment.page, SUPPORTED_ACTION_TYPE.ACTION_COMMENT_UPDATE);
       }
       }
       catch (err) {
       catch (err) {
         logger.error('Error occurred while handling the comment update event:\n', err);
         logger.error('Error occurred while handling the comment update event:\n', err);
@@ -114,7 +104,7 @@ class CommentService {
     await this.inAppNotificationService.emitSocketIo(targetUsers);
     await this.inAppNotificationService.emitSocketIo(targetUsers);
   };
   };
 
 
-  getMentionedUsers = async(commentId: Types.ObjectId): Promise<Types.ObjectId[]> => {
+  getMentionedUsers = async function(commentId: Types.ObjectId): Promise<Types.ObjectId[]> {
     const Comment = getModelSafely('Comment') || require('../models/comment')(this.crowi);
     const Comment = getModelSafely('Comment') || require('../models/comment')(this.crowi);
     const User = getModelSafely('User') || require('../models/user')(this.crowi);
     const User = getModelSafely('User') || require('../models/user')(this.crowi);
 
 

+ 7 - 3
packages/app/src/server/service/in-app-notification.ts

@@ -1,7 +1,7 @@
 import { subDays } from 'date-fns';
 import { subDays } from 'date-fns';
 import { Types } from 'mongoose';
 import { Types } from 'mongoose';
 
 
-import { AllSupportedActionToNotifiedType } from '~/interfaces/activity';
+import { AllSupportedActionToNotifiedType, SUPPORTED_ACTION_TYPE } from '~/interfaces/activity';
 import { HasObjectId } from '~/interfaces/has-object-id';
 import { HasObjectId } from '~/interfaces/has-object-id';
 import { InAppNotificationStatuses, PaginateResult } from '~/interfaces/in-app-notification';
 import { InAppNotificationStatuses, PaginateResult } from '~/interfaces/in-app-notification';
 import { IPage } from '~/interfaces/page';
 import { IPage } from '~/interfaces/page';
@@ -183,9 +183,13 @@ export default class InAppNotificationService {
   createInAppNotification = async function(activity: ActivityDocument, target: IPage): Promise<void> {
   createInAppNotification = async function(activity: ActivityDocument, target: IPage): Promise<void> {
     const shouldNotification = activity != null && target != null && (AllSupportedActionToNotifiedType as ReadonlyArray<string>).includes(activity.action);
     const shouldNotification = activity != null && target != null && (AllSupportedActionToNotifiedType as ReadonlyArray<string>).includes(activity.action);
     if (shouldNotification) {
     if (shouldNotification) {
-      const snapshot = stringifySnapshot(target as IPage);
+      let mentionedUsers: IUser[] = [];
+      if (activity.action === SUPPORTED_ACTION_TYPE.ACTION_COMMENT_CREATE) {
+        mentionedUsers = await this.crowi.commentService.getMentionedUsers(activity.event);
+      }
       const notificationTargetUsers = await activity?.getNotificationTargetUsers();
       const notificationTargetUsers = await activity?.getNotificationTargetUsers();
-      await this.upsertByActivity(notificationTargetUsers, activity, snapshot);
+      const snapshot = stringifySnapshot(target as IPage);
+      await this.upsertByActivity([...notificationTargetUsers, ...mentionedUsers], activity, snapshot);
       await this.emitSocketIo(notificationTargetUsers);
       await this.emitSocketIo(notificationTargetUsers);
     }
     }
     else {
     else {