2
0
Эх сурвалжийг харах

Merge pull request #5952 from weseek/feat/96655-add-activity-middleware

feat: add-activity express middleware
Yuki Takei 3 жил өмнө
parent
commit
c42d5384c1

+ 21 - 3
packages/app/src/interfaces/activity.ts

@@ -5,6 +5,7 @@ import { IUser } from './user';
 const MODEL_PAGE = 'Page';
 
 // Action
+const ACTION_UNSETTLED = 'UNSETTLED';
 const ACTION_PAGE_LIKE = 'PAGE_LIKE';
 const ACTION_PAGE_BOOKMARK = 'PAGE_BOOKMARK';
 const ACTION_PAGE_CREATE = 'PAGE_CREATE';
@@ -23,6 +24,7 @@ export const SUPPORTED_TARGET_MODEL_TYPE = {
 } as const;
 
 export const SUPPORTED_ACTION_TYPE = {
+  ACTION_UNSETTLED,
   ACTION_PAGE_LIKE,
   ACTION_PAGE_BOOKMARK,
   ACTION_PAGE_CREATE,
@@ -36,9 +38,24 @@ export const SUPPORTED_ACTION_TYPE = {
   ACTION_COMMENT_UPDATE,
 } as const;
 
+export const SUPPORTED_ACTION_TO_NOTIFIED_TYPE = {
+  ACTION_PAGE_LIKE,
+  ACTION_PAGE_BOOKMARK,
+  ACTION_PAGE_UPDATE,
+  ACTION_PAGE_RENAME,
+  ACTION_PAGE_DUPLICATE,
+  ACTION_PAGE_DELETE,
+  ACTION_PAGE_DELETE_COMPLETELY,
+  ACTION_PAGE_REVERT,
+  ACTION_COMMENT_CREATE,
+  ACTION_COMMENT_UPDATE,
+} as const;
+
 
 export const AllSupportedTargetModelType = Object.values(SUPPORTED_TARGET_MODEL_TYPE);
 export const AllSupportedActionType = Object.values(SUPPORTED_ACTION_TYPE);
+export const AllSupportedActionToNotifiedType = Object.values(SUPPORTED_ACTION_TO_NOTIFIED_TYPE);
+
 
 /*
  * For AuditLogManagement.tsx
@@ -62,7 +79,6 @@ export const CommentActions = Object.values({
 
 
 export type SupportedTargetModelType = typeof SUPPORTED_TARGET_MODEL_TYPE[keyof typeof SUPPORTED_TARGET_MODEL_TYPE];
-// 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];
 
 
@@ -70,8 +86,10 @@ export type ISnapshot = Partial<Pick<IUser, 'username'>>
 
 export type IActivity = {
   user?: IUser
-  targetModel: SupportedTargetModelType
-  target: string
+  ip?: string
+  endpoint?: string
+  targetModel?: SupportedTargetModelType
+  target?: string
   action: SupportedActionType
   createdAt: Date
   snapshot?: ISnapshot

+ 34 - 0
packages/app/src/server/middlewares/add-activity.ts

@@ -0,0 +1,34 @@
+import { NextFunction, Request, Response } from 'express';
+
+import { SUPPORTED_ACTION_TYPE } from '~/interfaces/activity';
+import { IUserHasId } from '~/interfaces/user';
+import loggerFactory from '~/utils/logger';
+
+
+const logger = loggerFactory('growi:middlewares:add-activity');
+
+interface AuthorizedRequest extends Request {
+  user?: IUserHasId
+}
+
+export const generateAddActivityMiddleware = crowi => async(req: AuthorizedRequest, res: Response, next: NextFunction): Promise<void> => {
+  const parameter = {
+    ip:  req.ip,
+    endpoint: req.originalUrl,
+    action: SUPPORTED_ACTION_TYPE.ACTION_UNSETTLED,
+    user: req.user?._id,
+    snapshot: {
+      username: req.user?.username,
+    },
+  };
+
+  try {
+    const activity = await crowi.activityService.createByParameters(parameter);
+    res.locals.activity = activity;
+  }
+  catch (err) {
+    logger.error('Create activity failed', err);
+  }
+
+  return next();
+};

+ 13 - 6
packages/app/src/server/models/activity.ts

@@ -4,7 +4,9 @@ import {
 } from 'mongoose';
 import mongoosePaginate from 'mongoose-paginate-v2';
 
-import { AllSupportedTargetModelType, AllSupportedActionType, ISnapshot } from '~/interfaces/activity';
+import {
+  AllSupportedTargetModelType, AllSupportedActionType, SupportedActionType, ISnapshot,
+} from '~/interfaces/activity';
 
 import loggerFactory from '../../utils/logger';
 import activityEvent from '../events/activity';
@@ -16,9 +18,11 @@ const logger = loggerFactory('growi:models:activity');
 export interface ActivityDocument extends Document {
   _id: Types.ObjectId
   user: Types.ObjectId | any
+  ip: string
+  endpoint: string
   targetModel: string
   target: Types.ObjectId
-  action: string
+  action: SupportedActionType
   snapshot: ISnapshot
 
   getNotificationTargetUsers(): Promise<any[]>
@@ -39,22 +43,25 @@ const activitySchema = new Schema<ActivityDocument, ActivityModel>({
     type: Schema.Types.ObjectId,
     ref: 'User',
     index: true,
-    required: true,
+  },
+  ip: {
+    type: String,
+  },
+  endpoint: {
+    type: String,
   },
   targetModel: {
     type: String,
-    required: true,
     enum: AllSupportedTargetModelType,
   },
   target: {
     type: Schema.Types.ObjectId,
     refPath: 'targetModel',
-    required: true,
   },
   action: {
     type: String,
-    required: true,
     enum: AllSupportedActionType,
+    required: true,
   },
   snapshot: snapshotSchema,
 }, {

+ 11 - 0
packages/app/src/server/service/activity.ts

@@ -1,7 +1,13 @@
 import { getModelSafely } from '@growi/core';
 
+import { IActivity } from '~/interfaces/activity';
+import Activity from '~/server/models/activity';
+
 import Crowi from '../crowi';
 
+
+type ParameterType = Omit<IActivity, 'createdAt'>
+
 class ActivityService {
 
   crowi!: Crowi;
@@ -24,6 +30,11 @@ class ActivityService {
     return Activity.create(parameters);
   };
 
+  updateByParameters = async function(activityId: string, parameters: ParameterType): Promise<IActivity> {
+    const activity = await Activity.findOneAndUpdate({ _id: activityId }, parameters, { new: true }) as unknown as IActivity;
+
+    return activity;
+  };
 
   /**
    * @param {User} user

+ 28 - 9
packages/app/src/server/service/in-app-notification.ts

@@ -1,22 +1,25 @@
-import { Types } from 'mongoose';
 import { subDays } from 'date-fns';
+import { Types } from 'mongoose';
+
+import { AllSupportedActionToNotifiedType } from '~/interfaces/activity';
+import { HasObjectId } from '~/interfaces/has-object-id';
 import { InAppNotificationStatuses, PaginateResult } from '~/interfaces/in-app-notification';
-import Crowi from '../crowi';
+import { IPage } from '~/interfaces/page';
+import { SubscriptionStatusType } from '~/interfaces/subscription';
+import { IUser } from '~/interfaces/user';
+import { stringifySnapshot } from '~/models/serializers/in-app-notification-snapshot/page';
+import { ActivityDocument } from '~/server/models/activity';
 import {
   InAppNotification,
   InAppNotificationDocument,
 } from '~/server/models/in-app-notification';
-
-import { ActivityDocument } from '~/server/models/activity';
 import InAppNotificationSettings from '~/server/models/in-app-notification-settings';
 import Subscription from '~/server/models/subscription';
-
-import { IUser } from '~/interfaces/user';
-
-import { HasObjectId } from '~/interfaces/has-object-id';
 import loggerFactory from '~/utils/logger';
+
+import Crowi from '../crowi';
 import { RoomPrefix, getRoomNameWithId } from '../util/socket-io-helpers';
-import { SubscriptionStatusType } from '~/interfaces/subscription';
+
 
 const { STATUS_UNREAD, STATUS_UNOPENED, STATUS_OPENED } = InAppNotificationStatuses;
 
@@ -36,6 +39,8 @@ export default class InAppNotificationService {
     this.crowi = crowi;
     this.socketIoService = crowi.socketIoService;
 
+    this.emitSocketIo = this.emitSocketIo.bind(this);
+    this.upsertByActivity = this.upsertByActivity.bind(this);
     this.getUnreadCountByUser = this.getUnreadCountByUser.bind(this);
   }
 
@@ -175,6 +180,20 @@ export default class InAppNotificationService {
     return;
   };
 
+  createInAppNotification = async function(activity: ActivityDocument, target: IPage): Promise<void> {
+    const shouldNotification = activity != null && target != null && (AllSupportedActionToNotifiedType as ReadonlyArray<string>).includes(activity.action);
+    if (shouldNotification) {
+      const snapshot = stringifySnapshot(target as IPage);
+      const notificationTargetUsers = await activity?.getNotificationTargetUsers();
+      await this.upsertByActivity(notificationTargetUsers, activity, snapshot);
+      await this.emitSocketIo(notificationTargetUsers);
+    }
+    else {
+      throw Error('No activity to notify');
+    }
+    return;
+  };
+
 }
 
 module.exports = InAppNotificationService;