فهرست منبع

specify user related group grant update for tree grant update

Futa Arai 2 سال پیش
والد
کامیت
93e4cc6e69

+ 1 - 1
apps/app/src/components/SavePageControls.tsx

@@ -89,7 +89,7 @@ export const SavePageControls = (props: SavePageControlsProps): JSX.Element | nu
         )
       }
 
-      <UncontrolledButtonDropdown direction="up">
+      <UncontrolledButtonDropdown direction="up" isOpen>
         <Button
           id="caret"
           data-testid="save-page-btn"

+ 12 - 5
apps/app/src/pages/[[...path]].page.tsx

@@ -3,8 +3,9 @@ import React, { ReactNode, useEffect } from 'react';
 
 import EventEmitter from 'events';
 
-import { isIPageInfoForEntity } from '@growi/core';
+import { isIPageInfoForEntity, isPopulated } from '@growi/core';
 import type {
+  GroupType,
   IDataWithMeta, IPageInfoForEntity, IPagePopulatedToShowRevision,
 } from '@growi/core';
 import {
@@ -29,6 +30,7 @@ import type { IPageGrantData } from '~/interfaces/page';
 import type { RendererConfig } from '~/interfaces/services/renderer';
 import type { PageModel, PageDocument } from '~/server/models/page';
 import type { PageRedirectModel } from '~/server/models/page-redirect';
+import PageService from '~/server/service/page';
 import {
   useCurrentUser,
   useIsForbidden, useIsSharedUser,
@@ -460,16 +462,21 @@ async function injectPageData(context: GetServerSidePropsContext, props: Props):
     // apply parent page grant, without groups that user isn't related to
     const ancestor = await Page.findAncestorByPathAndViewer(currentPathname, user);
     if (ancestor != null) {
+      ancestor.populate('grantedGroups.item');
       const userRelatedGrantedGroups = await pageService.getUserRelatedGrantedGroups(ancestor, user);
-      props.grantData = {
-        grant: ancestor.grant,
-        grantedGroups: userRelatedGrantedGroups.map((group) => {
+      const grantedGroupsInfo = userRelatedGrantedGroups.map((group) => {
+        if (isPopulated(group.item)) {
           return {
             id: group.item._id,
             name: group.item.name,
             type: group.type,
           };
-        }),
+        }
+        return null;
+      }).filter((info): info is NonNullable<{id: string, name: string, type: GroupType}> => info != null);
+      props.grantData = {
+        grant: ancestor.grant,
+        grantedGroups: grantedGroupsInfo,
       };
     }
   }

+ 0 - 24
apps/app/src/server/models/obsolete-page.js

@@ -638,30 +638,6 @@ export const getPageSchema = (crowi) => {
     return { templateBody, templateTags };
   };
 
-  pageSchema.statics.applyScopesToDescendantsAsyncronously = async function(parentPage, user, isV4 = false) {
-    const builder = new this.PageQueryBuilder(this.find());
-    builder.addConditionToListOnlyDescendants(parentPage.path);
-
-    if (isV4) {
-      builder.addConditionAsRootOrNotOnTree();
-    }
-    else {
-      builder.addConditionAsOnTree();
-    }
-
-    // add grant conditions
-    await addConditionToFilteringByViewerToEdit(builder, user);
-
-    const grant = parentPage.grant;
-
-    await builder.query.updateMany({}, {
-      grant,
-      grantedGroups: grant === PageGrant.GRANT_USER_GROUP ? parentPage.grantedGroups : null,
-      grantedUsers: grant === PageGrant.GRANT_OWNER ? [user._id] : null,
-    });
-
-  };
-
   pageSchema.statics.findListByPathsArray = async function(paths, includeEmpty = false) {
     const queryBuilder = new this.PageQueryBuilder(this.find(), includeEmpty);
     queryBuilder.addConditionToListByPathsArray(paths);

+ 61 - 11
apps/app/src/server/service/page.ts

@@ -5,7 +5,10 @@ import type {
   Ref, HasObjectId, IUserHasId, IUser,
   IPage, IPageInfo, IPageInfoAll, IPageInfoForEntity, IPageWithMeta, IGrantedGroup,
 } from '@growi/core';
-import { PageGrant, PageStatus, getIdForRef } from '@growi/core';
+import {
+  GroupType,
+  PageGrant, PageStatus, getIdForRef, isPopulated,
+} from '@growi/core';
 import {
   pagePathUtils, pathUtils,
 } from '@growi/core/dist/utils';
@@ -14,6 +17,7 @@ import mongoose, { ObjectId, Cursor } from 'mongoose';
 import streamToPromise from 'stream-to-promise';
 
 import { Comment } from '~/features/comment/server';
+import ExternalUserGroup from '~/features/external-user-group/server/models/external-user-group';
 import ExternalUserGroupRelation from '~/features/external-user-group/server/models/external-user-group-relation';
 import { SupportedAction } from '~/interfaces/activity';
 import { V5ConversionErrCode } from '~/interfaces/errors/v5-conversion-error';
@@ -41,6 +45,7 @@ import type { PageRedirectModel } from '../models/page-redirect';
 import { serializePageSecurely } from '../models/serializers/page-serializer';
 import ShareLink from '../models/share-link';
 import Subscription from '../models/subscription';
+import UserGroup from '../models/user-group';
 import UserGroupRelation from '../models/user-group-relation';
 import { V5ConversionError } from '../models/vo/v5-conversion-error';
 import { divideByType } from '../util/granted-group';
@@ -2360,15 +2365,19 @@ class PageService {
   }
 
   /*
- * get all groups of Page that user is related to
- */
-  async getUserRelatedGrantedGroups(page: PageDocument, user): Promise<PopulatedGrantedGroup[]> {
-    const populatedPage = await page.populate<{grantedGroups: PopulatedGrantedGroup[] | null}>('grantedGroups.item');
-    const userRelatedGroupIds = [
+   * get all groups of Page that user is related to
+   */
+  async getUserRelatedGrantedGroups(page: PageDocument, user): Promise<IGrantedGroup[]> {
+    const userRelatedGroupIds: string[] = [
       ...(await UserGroupRelation.findAllGroupsForUser(user)).map(ugr => ugr._id.toString()),
       ...(await ExternalUserGroupRelation.findAllGroupsForUser(user)).map(eugr => eugr._id.toString()),
     ];
-    return populatedPage.grantedGroups?.filter(group => userRelatedGroupIds.includes(group.item._id.toString())) || [];
+    return page.grantedGroups?.filter((group) => {
+      if (isPopulated(group.item)) {
+        return userRelatedGroupIds.includes(group.item._id.toString());
+      }
+      return userRelatedGroupIds.includes(group.item);
+    }) || [];
   }
 
   private async revertDeletedPageV4(page, user, options = {}, isRecursively = false) {
@@ -2400,6 +2409,47 @@ class PageService {
     return updatedPage;
   }
 
+  private async applyScopesToDescendantsAsyncronously(parentPage, user, isV4 = false) {
+    const Page = this.crowi.model('Page');
+    const builder = new Page.PageQueryBuilder(Page.find());
+    builder.addConditionToListOnlyDescendants(parentPage.path);
+
+    if (isV4) {
+      builder.addConditionAsRootOrNotOnTree();
+    }
+    else {
+      builder.addConditionAsOnTree();
+    }
+
+    // add grant conditions
+    await Page.addConditionToFilteringByViewerToEdit(builder, user);
+
+    const grant = parentPage.grant;
+
+    if (grant === PageGrant.GRANT_USER_GROUP) {
+      const userRelatedParentGrantedGroups = await this.getUserRelatedGrantedGroups(parentPage, user);
+      const childPages = await builder.query;
+      const childPageUpdatePromises = childPages.map((childPage) => {
+        return (async() => {
+          const childGrantedGroups = childPage.grantedGroups || [];
+          const userRelatedChildGrantedGroupIds = (await this.getUserRelatedGrantedGroups(childPage, user)).map(g => getIdForRef(g.item));
+          const userUnrelatedChildGrantedGroups = childGrantedGroups.filter(g => userRelatedChildGrantedGroupIds.includes(getIdForRef(g.item)));
+          const newChildGrantedGroups = [...userUnrelatedChildGrantedGroups, ...userRelatedParentGrantedGroups];
+          childPage.grantedGroups = newChildGrantedGroups;
+          childPage.save();
+        })();
+      });
+      await Promise.all(childPageUpdatePromises);
+    }
+    else {
+      await builder.query.updateMany({}, {
+        grant,
+        grantedGroups: null,
+        grantedUsers: grant === PageGrant.GRANT_OWNER ? [user._id] : null,
+      });
+    }
+  }
+
   /**
    * Create revert stream
    */
@@ -3844,7 +3894,7 @@ class PageService {
 
     // update scopes for descendants
     if (options.overwriteScopesOfDescendants) {
-      await Page.applyScopesToDescendantsAsyncronously(page, user);
+      await this.applyScopesToDescendantsAsyncronously(page, user);
     }
 
     await PageOperation.findByIdAndDelete(pageOpId);
@@ -3896,7 +3946,7 @@ class PageService {
 
     // update scopes for descendants
     if (options.overwriteScopesOfDescendants) {
-      Page.applyScopesToDescendantsAsyncronously(savedPage, user, true);
+      this.applyScopesToDescendantsAsyncronously(savedPage, user, true);
     }
 
     return savedPage;
@@ -4049,7 +4099,7 @@ class PageService {
 
     // 3. Update scopes for descendants
     if (options.overwriteScopesOfDescendants) {
-      await Page.applyScopesToDescendantsAsyncronously(currentPage, user);
+      await this.applyScopesToDescendantsAsyncronously(currentPage, user);
     }
 
     await PageOperation.findByIdAndDelete(pageOpId);
@@ -4214,7 +4264,7 @@ class PageService {
 
     // update scopes for descendants
     if (options.overwriteScopesOfDescendants) {
-      Page.applyScopesToDescendantsAsyncronously(savedPage, user, true);
+      this.applyScopesToDescendantsAsyncronously(savedPage, user, true);
     }