Parcourir la source

Added descendant page notification features

Shunm634-source il y a 3 ans
Parent
commit
fcd645d6d4

+ 8 - 0
packages/app/src/interfaces/activity.ts

@@ -308,6 +308,10 @@ export const EssentialActionGroup = {
   ACTION_PAGE_DELETE,
   ACTION_PAGE_DELETE_COMPLETELY,
   ACTION_PAGE_REVERT,
+  ACTION_PAGE_RECURSIVELY_RENAME,
+  ACTION_PAGE_RECURSIVELY_DELETE,
+  ACTION_PAGE_RECURSIVELY_DELETE_COMPLETELY,
+  ACTION_PAGE_RECURSIVELY_REVERT,
   ACTION_COMMENT_CREATE,
 } as const;
 
@@ -355,6 +359,10 @@ export const MediumActionGroup = {
   ACTION_PAGE_DELETE,
   ACTION_PAGE_DELETE_COMPLETELY,
   ACTION_PAGE_REVERT,
+  ACTION_PAGE_RECURSIVELY_RENAME,
+  ACTION_PAGE_RECURSIVELY_DELETE,
+  ACTION_PAGE_RECURSIVELY_DELETE_COMPLETELY,
+  ACTION_PAGE_RECURSIVELY_REVERT,
   ACTION_PAGE_EMPTY_TRASH,
   ACTION_PAGE_SUBSCRIBE,
   ACTION_PAGE_UNSUBSCRIBE,

+ 10 - 2
packages/app/src/server/routes/apiv3/pages.js

@@ -516,14 +516,22 @@ module.exports = (crowi) => {
 
     let page;
     let renamedPage;
+    let descendantPages;
 
     try {
       page = await Page.findByIdAndViewer(pageId, req.user, null, true);
+      const pages = await Page.findListWithDescendants(page.path, req.user);
+      descendantPages = pages.pages;
+      descendantPages.pop();
 
       if (page == null) {
         return res.apiv3Err(new ErrorV3(`Page '${pageId}' is not found or forbidden`, 'notfound_or_forbidden'), 401);
       }
 
+      if (descendantPages == null && options.isRecursively) {
+        return res.apiv3Err(new ErrorV3(`Page '${pageId}' has no descendant pages`, 'notfound_or_forbidden'), 401);
+      }
+
       // empty page does not require revisionId validation
       if (!page.isEmpty && revisionId == null) {
         return res.apiv3Err(new ErrorV3('revisionId must be a mongoId', 'invalid_body'), 400);
@@ -553,9 +561,9 @@ module.exports = (crowi) => {
     const parameters = {
       targetModel: SupportedTargetModel.MODEL_PAGE,
       target: page,
-      action: SupportedAction.ACTION_PAGE_RENAME,
+      action: options.isRecursively ? SupportedAction.ACTION_PAGE_RECURSIVELY_RENAME : SupportedAction.ACTION_PAGE_RENAME,
     };
-    activityEvent.emit('update', activityId, parameters, page);
+    activityEvent.emit('update', activityId, parameters, page, descendantPages);
 
     return res.apiv3(result);
   });

+ 21 - 4
packages/app/src/server/routes/page.js

@@ -1287,15 +1287,23 @@ module.exports = function(crowi, app) {
 
     debug('Delete page', page._id, page.path);
 
+    let deleteAction;
+    let descendantPages;
+
     try {
+      const pages = await Page.findListWithDescendants(page.path, req.user);
+      descendantPages = pages.pages;
+      descendantPages.pop();
       if (isCompletely) {
         if (!crowi.pageService.canDeleteCompletely(page.path, creator, req.user, isRecursively)) {
           return res.json(ApiResponse.error('You can not delete this page completely', 'user_not_admin'));
         }
+        deleteAction = isRecursively ? SupportedAction.ACTION_PAGE_RECURSIVELY_DELETE_COMPLETELY : SupportedAction.ACTION_PAGE_DELETE_COMPLETELY;
         await crowi.pageService.deleteCompletely(page, req.user, options, isRecursively);
       }
       else {
         // behave like not found
+        deleteAction = isRecursively ? SupportedAction.ACTION_PAGE_RECURSIVELY_DELETE : SupportedAction.ACTION_PAGE_DELETE;
         const notRecursivelyAndEmpty = page.isEmpty && !isRecursively;
         if (notRecursivelyAndEmpty) {
           return res.json(ApiResponse.error(`Page '${pageId}' is not found.`, 'notfound'));
@@ -1326,9 +1334,10 @@ module.exports = function(crowi, app) {
     const parameters = {
       targetModel: SupportedTargetModel.MODEL_PAGE,
       target: page,
-      action: isCompletely ? SupportedAction.ACTION_PAGE_DELETE_COMPLETELY : SupportedAction.ACTION_PAGE_DELETE,
+      action: deleteAction,
     };
-    activityEvent.emit('update', res.locals.activity._id, parameters, page);
+
+    activityEvent.emit('update', res.locals.activity._id, parameters, page, descendantPages);
 
     res.json(ApiResponse.success(result));
 
@@ -1362,11 +1371,19 @@ module.exports = function(crowi, app) {
     const isRecursively = req.body.recursively;
 
     let page;
+    let descendantPages;
     try {
       page = await Page.findByIdAndViewer(pageId, req.user);
+      const pages = await Page.findListWithDescendants(page.path, req.user);
+      descendantPages = pages.pages;
+      console.log('What are the descendant pages\n', descendantPages);
+      descendantPages.pop();
       if (page == null) {
         throw new Error(`Page '${pageId}' is not found or forbidden`, 'notfound_or_forbidden');
       }
+      if (descendantPages == null && isRecursively) {
+        throw new Error(`Page '${pageId}' has no descendant pages`, 'notfound_or_forbidden');
+      }
       page = await crowi.pageService.revertDeletedPage(page, req.user, {}, isRecursively);
     }
     catch (err) {
@@ -1384,9 +1401,9 @@ module.exports = function(crowi, app) {
     const parameters = {
       targetModel: SupportedTargetModel.MODEL_PAGE,
       target: page,
-      action: SupportedAction.ACTION_PAGE_REVERT,
+      action: isRecursively ? SupportedAction.ACTION_PAGE_RECURSIVELY_REVERT : SupportedAction.ACTION_PAGE_REVERT,
     };
-    activityEvent.emit('update', res.locals.activity._id, parameters, page);
+    activityEvent.emit('update', res.locals.activity._id, parameters, page, descendantPages);
 
     return res.json(ApiResponse.success(result));
   };

+ 3 - 2
packages/app/src/server/service/activity.ts

@@ -9,6 +9,7 @@ import Activity from '~/server/models/activity';
 
 import loggerFactory from '../../utils/logger';
 import Crowi from '../crowi';
+import { PageDocument } from '../models/page';
 
 
 const logger = loggerFactory('growi:service:ActivityService');
@@ -39,7 +40,7 @@ class ActivityService {
   }
 
   initActivityEventListeners(): void {
-    this.activityEvent.on('update', async(activityId: string, parameters, target?: IPage) => {
+    this.activityEvent.on('update', async(activityId: string, parameters, target?: IPage, descendantPages?: PageDocument[]) => {
       let activity: IActivity;
       const shoudUpdate = this.shoudUpdateActivity(parameters.action);
 
@@ -52,7 +53,7 @@ class ActivityService {
           return;
         }
 
-        this.activityEvent.emit('updated', activity, target);
+        this.activityEvent.emit('updated', activity, target, descendantPages);
       }
     });
   }

+ 16 - 4
packages/app/src/server/service/in-app-notification.ts

@@ -18,6 +18,7 @@ import Subscription from '~/server/models/subscription';
 import loggerFactory from '~/utils/logger';
 
 import Crowi from '../crowi';
+import { PageDocument } from '../models/page';
 import { RoomPrefix, getRoomNameWithId } from '../util/socket-io-helpers';
 
 
@@ -51,11 +52,11 @@ export default class InAppNotificationService {
   }
 
   initActivityEventListeners(): void {
-    this.activityEvent.on('updated', async(activity: ActivityDocument, target: IPage) => {
+    this.activityEvent.on('updated', async(activity: ActivityDocument, target: IPage, descendantPages?: PageDocument[]) => {
       try {
         const shouldNotification = activity != null && target != null && (AllEssentialActions as ReadonlyArray<string>).includes(activity.action);
         if (shouldNotification) {
-          await this.createInAppNotification(activity, target);
+          await this.createInAppNotification(activity, target, descendantPages);
         }
       }
       catch (err) {
@@ -199,14 +200,25 @@ export default class InAppNotificationService {
     return;
   };
 
-  createInAppNotification = async function(activity: ActivityDocument, target: IPage): Promise<void> {
+  createInAppNotification = async function(activity: ActivityDocument, target: IPage, descendantPages?: PageDocument[]): Promise<void> {
     const shouldNotification = activity != null && target != null && (AllEssentialActions as ReadonlyArray<string>).includes(activity.action);
     if (shouldNotification) {
       let mentionedUsers: IUser[] = [];
       if (activity.action === SupportedAction.ACTION_COMMENT_CREATE) {
         mentionedUsers = await this.crowi.commentService.getMentionedUsers(activity.event);
       }
-      const notificationTargetUsers = await activity?.getNotificationTargetUsers();
+      let notificationTargetUsers = await activity?.getNotificationTargetUsers();
+      console.log('What are the descendant pages and target users\n', descendantPages, notificationTargetUsers);
+      if (descendantPages != null && descendantPages.length > 0) {
+        const User = this.crowi.model('User');
+        const targetDescendantsUsers = await Subscription.getSubscriptions(descendantPages);
+        const descendantsUsers = targetDescendantsUsers.filter(item => (item.toString() !== activity.user._id.toString()));
+        notificationTargetUsers = notificationTargetUsers.concat(await User.find({
+          _id: { $in: descendantsUsers },
+          status: User.STATUS_ACTIVE,
+        }).distinct('_id'));
+      }
+      console.log('Who are the target users?\n', notificationTargetUsers);
       const snapshot = stringifySnapshot(target as IPage);
       await this.upsertByActivity([...notificationTargetUsers, ...mentionedUsers], activity, snapshot);
       await this.emitSocketIo(notificationTargetUsers);