Bläddra i källkod

Revert "refs 126902: fix PageService & PageGrantService"

This reverts commit bedaed74a35345e4eeefdca6327b94e471de3a75.
Futa Arai 2 år sedan
förälder
incheckning
4826a638b2

+ 1 - 1
apps/app/src/components/Sidebar/PageTree/Item.tsx

@@ -354,7 +354,7 @@ const Item: FC<ItemProps> = (props: ItemProps) => {
         path: newPagePath,
         path: newPagePath,
         body: undefined,
         body: undefined,
         grant: page.grant,
         grant: page.grant,
-        grantUserGroupIds: page.grantedGroups,
+        grantUserGroupId: page.grantedGroup,
       });
       });
 
 
       mutateChildren();
       mutateChildren();

+ 0 - 2
apps/app/src/features/external-user-group/server/models/external-user-group.ts

@@ -57,6 +57,4 @@ schema.statics.findGroupsWithAncestorsRecursively = UserGroup.findGroupsWithAnce
 
 
 schema.statics.findGroupsWithDescendantsRecursively = UserGroup.findGroupsWithDescendantsRecursively;
 schema.statics.findGroupsWithDescendantsRecursively = UserGroup.findGroupsWithDescendantsRecursively;
 
 
-schema.statics.findGroupsWithDescendantsById = UserGroup.findGroupsWithDescendantsById;
-
 export default getOrCreateModel<ExternalUserGroupDocument, ExternalUserGroupModel>('ExternalUserGroup', schema);
 export default getOrCreateModel<ExternalUserGroupDocument, ExternalUserGroupModel>('ExternalUserGroup', schema);

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

@@ -249,7 +249,7 @@ export const getPageSchema = (crowi) => {
     return this.populate('revision');
     return this.populate('revision');
   };
   };
 
 
-  pageSchema.methods.applyScope = function(user, grant, grantUserGroupIds) {
+  pageSchema.methods.applyScope = function(user, grant, grantUserGroupId) {
     // Reset
     // Reset
     this.grantedUsers = [];
     this.grantedUsers = [];
     this.grantedGroup = null;
     this.grantedGroup = null;
@@ -261,7 +261,7 @@ export const getPageSchema = (crowi) => {
     }
     }
 
 
     if (grant === GRANT_USER_GROUP) {
     if (grant === GRANT_USER_GROUP) {
-      this.grantedGroups = grantUserGroupIds;
+      this.grantedGroup = grantUserGroupId;
     }
     }
   };
   };
 
 

+ 2 - 5
apps/app/src/server/models/page-operation.ts

@@ -1,4 +1,3 @@
-import { GroupType } from '@growi/core';
 import { addSeconds } from 'date-fns';
 import { addSeconds } from 'date-fns';
 import mongoose, {
 import mongoose, {
   Schema, Model, Document, QueryOptions, FilterQuery,
   Schema, Model, Document, QueryOptions, FilterQuery,
@@ -63,9 +62,8 @@ const pageSchemaForResuming = new Schema<IPageForResuming>({
   grantedGroups: [{
   grantedGroups: [{
     type: {
     type: {
       type: String,
       type: String,
-      enum: Object.values(GroupType),
+      enum: ['UserGroup', 'ExternalUserGroup'],
       required: true,
       required: true,
-      default: 'UserGroup',
     },
     },
     item: {
     item: {
       type: ObjectId, refPath: 'grantedGroups.type', required: true,
       type: ObjectId, refPath: 'grantedGroups.type', required: true,
@@ -87,9 +85,8 @@ const optionsSchemaForResuming = new Schema<IOptionsForResuming>({
   grantUserGroupIds: [{
   grantUserGroupIds: [{
     type: {
     type: {
       type: String,
       type: String,
-      enum: Object.values(GroupType),
+      enum: ['UserGroup', 'ExternalUserGroup'],
       required: true,
       required: true,
-      default: 'UserGroup',
     },
     },
     item: {
     item: {
       type: ObjectId, refPath: 'grantedGroups.type', required: true,
       type: ObjectId, refPath: 'grantedGroups.type', required: true,

+ 3 - 9
apps/app/src/server/models/page.ts

@@ -2,9 +2,7 @@
 
 
 import nodePath from 'path';
 import nodePath from 'path';
 
 
-import {
-  GroupType, HasObjectId, pagePathUtils, pathUtils,
-} from '@growi/core';
+import { HasObjectId, pagePathUtils, pathUtils } from '@growi/core';
 import { collectAncestorPaths } from '@growi/core/dist/utils/page-path-utils/collect-ancestor-paths';
 import { collectAncestorPaths } from '@growi/core/dist/utils/page-path-utils/collect-ancestor-paths';
 import escapeStringRegexp from 'escape-string-regexp';
 import escapeStringRegexp from 'escape-string-regexp';
 import mongoose, {
 import mongoose, {
@@ -102,9 +100,8 @@ const schema = new Schema<PageDocument, PageModel>({
   grantedGroups: [{
   grantedGroups: [{
     type: {
     type: {
       type: String,
       type: String,
-      enum: Object.values(GroupType),
+      enum: ['UserGroup', 'ExternalUserGroup'],
       required: true,
       required: true,
-      default: 'UserGroup',
     },
     },
     item: {
     item: {
       type: ObjectId, refPath: 'grantedGroups.type', required: true, index: true,
       type: ObjectId, refPath: 'grantedGroups.type', required: true, index: true,
@@ -974,10 +971,7 @@ schema.statics.findNonEmptyClosestAncestor = async function(path: string): Promi
 
 
 export type PageCreateOptions = {
 export type PageCreateOptions = {
   format?: string
   format?: string
-  grantUserGroupIds?: {
-    type: string,
-    item: ObjectIdLike
-  }[],
+  grantUserGroupId?: ObjectIdLike
   grant?: number
   grant?: number
   overwriteScopesOfDescendants?: boolean
   overwriteScopesOfDescendants?: boolean
 }
 }

+ 23 - 43
apps/app/src/server/service/page-grant.ts

@@ -1,19 +1,14 @@
 import {
 import {
-  GrantedGroup,
   pagePathUtils, pathUtils, pageUtils,
   pagePathUtils, pathUtils, pageUtils,
-  PageGrant, PageGrantCanBeOnTree, GroupType,
+  PageGrant, PageGrantCanBeOnTree,
 } from '@growi/core';
 } from '@growi/core';
 import escapeStringRegexp from 'escape-string-regexp';
 import escapeStringRegexp from 'escape-string-regexp';
 import mongoose from 'mongoose';
 import mongoose from 'mongoose';
 
 
-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 { IRecordApplicableGrant } from '~/interfaces/page-grant';
 import { IRecordApplicableGrant } from '~/interfaces/page-grant';
 import { PageDocument, PageModel } from '~/server/models/page';
 import { PageDocument, PageModel } from '~/server/models/page';
 import UserGroup from '~/server/models/user-group';
 import UserGroup from '~/server/models/user-group';
-import { isIncludesObjectId, excludeTestIdsFromTargetIds, hasIntersection } from '~/server/util/compare-objectId';
-
-import { divideByType } from '../util/granted-group';
+import { isIncludesObjectId, excludeTestIdsFromTargetIds } from '~/server/util/compare-objectId';
 
 
 const { addTrailingSlash } = pathUtils;
 const { addTrailingSlash } = pathUtils;
 const { isTopPage } = pagePathUtils;
 const { isTopPage } = pagePathUtils;
@@ -25,10 +20,7 @@ type ObjectIdLike = mongoose.Types.ObjectId | string;
 type ComparableTarget = {
 type ComparableTarget = {
   grant: number,
   grant: number,
   grantedUserIds?: ObjectIdLike[],
   grantedUserIds?: ObjectIdLike[],
-  grantedGroupIds?: {
-    type: GroupType,
-    item: ObjectIdLike,
-  }[],
+  grantedGroupId?: ObjectIdLike,
   applicableUserIds?: ObjectIdLike[],
   applicableUserIds?: ObjectIdLike[],
   applicableGroupIds?: ObjectIdLike[],
   applicableGroupIds?: ObjectIdLike[],
 };
 };
@@ -90,13 +82,13 @@ class PageGrantService {
   private validateComparableTarget(comparable: ComparableTarget) {
   private validateComparableTarget(comparable: ComparableTarget) {
     const Page = mongoose.model('Page') as unknown as PageModel;
     const Page = mongoose.model('Page') as unknown as PageModel;
 
 
-    const { grant, grantedUserIds, grantedGroupIds } = comparable;
+    const { grant, grantedUserIds, grantedGroupId } = comparable;
 
 
     if (grant === Page.GRANT_OWNER && (grantedUserIds == null || grantedUserIds.length !== 1)) {
     if (grant === Page.GRANT_OWNER && (grantedUserIds == null || grantedUserIds.length !== 1)) {
       throw Error('grantedUserIds must not be null and must have 1 length');
       throw Error('grantedUserIds must not be null and must have 1 length');
     }
     }
-    if (grant === Page.GRANT_USER_GROUP && grantedGroupIds == null) {
-      throw Error('grantedGroupIds is not specified');
+    if (grant === Page.GRANT_USER_GROUP && grantedGroupId == null) {
+      throw Error('grantedGroupId is not specified');
     }
     }
   }
   }
 
 
@@ -151,11 +143,11 @@ class PageGrantService {
       }
       }
 
 
       if (target.grant === Page.GRANT_USER_GROUP) {
       if (target.grant === Page.GRANT_USER_GROUP) {
-        if (target.grantedGroupIds == null) {
+        if (target.grantedGroupId == null) {
           throw Error('grantedGroupId must not be null');
           throw Error('grantedGroupId must not be null');
         }
         }
 
 
-        if (!hasIntersection(ancestor.applicableGroupIds, target.grantedGroupIds.map(e => e.item))) { // only child groups or the same group can exist under GRANT_USER_GROUP page
+        if (!isIncludesObjectId(ancestor.applicableGroupIds, target.grantedGroupId)) { // only child groups or the same group can exist under GRANT_USER_GROUP page
           return false;
           return false;
         }
         }
       }
       }
@@ -215,7 +207,7 @@ class PageGrantService {
    * @returns Promise<ComparableAncestor>
    * @returns Promise<ComparableAncestor>
    */
    */
   private async generateComparableTarget(
   private async generateComparableTarget(
-      grant, grantedUserIds: ObjectIdLike[] | undefined, grantedGroupIds: GrantedGroup[] | undefined, includeApplicable: boolean,
+      grant, grantedUserIds: ObjectIdLike[] | undefined, grantedGroupId: ObjectIdLike | undefined, includeApplicable: boolean,
   ): Promise<ComparableTarget> {
   ): Promise<ComparableTarget> {
     if (includeApplicable) {
     if (includeApplicable) {
       const Page = mongoose.model('Page') as unknown as PageModel;
       const Page = mongoose.model('Page') as unknown as PageModel;
@@ -225,34 +217,22 @@ class PageGrantService {
       let applicableGroupIds: ObjectIdLike[] | undefined;
       let applicableGroupIds: ObjectIdLike[] | undefined;
 
 
       if (grant === Page.GRANT_USER_GROUP) {
       if (grant === Page.GRANT_USER_GROUP) {
-        if (grantedGroupIds == null || grantedGroupIds.length === 0) {
-          throw Error('Target user group is not given');
-        }
-
-        const { grantedUserGroups: grantedUserGroupIds, grantedExternalUserGroups: grantedExternalUserGroupIds } = divideByType(grantedGroupIds);
-        const targetUserGroups = await UserGroup.find({ _id: { $in: grantedUserGroupIds } });
-        const targetExternalUserGroups = await ExternalUserGroup.find({ _id: { $in: grantedExternalUserGroupIds } });
-        if (targetUserGroups.length === 0 && targetExternalUserGroups.length === 0) {
+        const targetUserGroup = await UserGroup.findOne({ _id: grantedGroupId });
+        if (targetUserGroup == null) {
           throw Error('Target user group does not exist');
           throw Error('Target user group does not exist');
         }
         }
 
 
-        const userGroupRelations = await UserGroupRelation.find({ relatedGroup: { $in: targetUserGroups.map(g => g._id) } });
-        const externalUserGroupRelations = await ExternalUserGroupRelation.find({ relatedGroup: { $in: targetUserGroups.map(g => g._id) } });
-        applicableUserIds = [...userGroupRelations, ...externalUserGroupRelations].map(u => u.relatedUser);
-
-        const applicableUserGroups = (await Promise.all(targetUserGroups.map((group) => {
-          return UserGroup.findGroupsWithDescendantsById(group._id);
-        }))).flat();
-        const applicableExternalUserGroups = (await Promise.all(targetExternalUserGroups.map((group) => {
-          return ExternalUserGroup.findGroupsWithDescendantsById(group._id);
-        }))).flat();
-        applicableGroupIds = [...applicableUserGroups, ...applicableExternalUserGroups].map(g => g._id) || null;
+        const relatedUsers = await UserGroupRelation.find({ relatedGroup: targetUserGroup._id });
+        applicableUserIds = relatedUsers.map(u => u.relatedUser);
+
+        const applicableGroups = grantedGroupId != null ? await UserGroup.findGroupsWithDescendantsById(grantedGroupId) : null;
+        applicableGroupIds = applicableGroups?.map(g => g._id) || null;
       }
       }
 
 
       return {
       return {
         grant,
         grant,
         grantedUserIds,
         grantedUserIds,
-        grantedGroupIds,
+        grantedGroupId,
         applicableUserIds,
         applicableUserIds,
         applicableGroupIds,
         applicableGroupIds,
       };
       };
@@ -261,7 +241,7 @@ class PageGrantService {
     return {
     return {
       grant,
       grant,
       grantedUserIds,
       grantedUserIds,
-      grantedGroupIds,
+      grantedGroupId,
     };
     };
   }
   }
 
 
@@ -403,7 +383,7 @@ class PageGrantService {
    */
    */
   async isGrantNormalized(
   async isGrantNormalized(
       // eslint-disable-next-line max-len
       // eslint-disable-next-line max-len
-      user, targetPath: string, grant, grantedUserIds?: ObjectIdLike[], grantedGroupIds?: GrantedGroup[], shouldCheckDescendants = false, includeNotMigratedPages = false,
+      user, targetPath: string, grant, grantedUserIds?: ObjectIdLike[], grantedGroupId?: ObjectIdLike, shouldCheckDescendants = false, includeNotMigratedPages = false,
   ): Promise<boolean> {
   ): Promise<boolean> {
     if (isTopPage(targetPath)) {
     if (isTopPage(targetPath)) {
       return true;
       return true;
@@ -412,11 +392,11 @@ class PageGrantService {
     const comparableAncestor = await this.generateComparableAncestor(targetPath, includeNotMigratedPages);
     const comparableAncestor = await this.generateComparableAncestor(targetPath, includeNotMigratedPages);
 
 
     if (!shouldCheckDescendants) { // checking the parent is enough
     if (!shouldCheckDescendants) { // checking the parent is enough
-      const comparableTarget = await this.generateComparableTarget(grant, grantedUserIds, grantedGroupIds, false);
+      const comparableTarget = await this.generateComparableTarget(grant, grantedUserIds, grantedGroupId, false);
       return this.processValidation(comparableTarget, comparableAncestor);
       return this.processValidation(comparableTarget, comparableAncestor);
     }
     }
 
 
-    const comparableTarget = await this.generateComparableTarget(grant, grantedUserIds, grantedGroupIds, true);
+    const comparableTarget = await this.generateComparableTarget(grant, grantedUserIds, grantedGroupId, true);
     const comparableDescendants = await this.generateComparableDescendants(targetPath, user, includeNotMigratedPages);
     const comparableDescendants = await this.generateComparableDescendants(targetPath, user, includeNotMigratedPages);
 
 
     return this.processValidation(comparableTarget, comparableAncestor, comparableDescendants);
     return this.processValidation(comparableTarget, comparableAncestor, comparableDescendants);
@@ -441,7 +421,7 @@ class PageGrantService {
 
 
     for await (const page of pages) {
     for await (const page of pages) {
       const {
       const {
-        path, grant, grantedUsers: grantedUserIds, grantedGroups,
+        path, grant, grantedUsers: grantedUserIds, grantedGroup: grantedGroupId,
       } = page;
       } = page;
 
 
       if (!pageUtils.isPageNormalized(page)) {
       if (!pageUtils.isPageNormalized(page)) {
@@ -449,7 +429,7 @@ class PageGrantService {
         continue;
         continue;
       }
       }
 
 
-      if (await this.isGrantNormalized(user, path, grant, grantedUserIds, grantedGroups, shouldCheckDescendants, shouldIncludeNotMigratedPages)) {
+      if (await this.isGrantNormalized(user, path, grant, grantedUserIds, grantedGroupId, shouldCheckDescendants, shouldIncludeNotMigratedPages)) {
         normalizable.push(page);
         normalizable.push(page);
       }
       }
       else {
       else {

+ 34 - 40
apps/app/src/server/service/page.ts

@@ -3,7 +3,7 @@ import { Readable, Writable } from 'stream';
 
 
 import type {
 import type {
   Ref, HasObjectId, IUserHasId,
   Ref, HasObjectId, IUserHasId,
-  IPage, IPageInfo, IPageInfoAll, IPageInfoForEntity, IPageWithMeta, GrantedGroup,
+  IPage, IPageInfo, IPageInfoAll, IPageInfoForEntity, IPageWithMeta,
 } from '@growi/core';
 } from '@growi/core';
 import {
 import {
   pagePathUtils, pathUtils, PageGrant, PageStatus,
   pagePathUtils, pathUtils, PageGrant, PageStatus,
@@ -452,7 +452,7 @@ class PageService {
     // use the parent's grant when target page is an empty page
     // use the parent's grant when target page is an empty page
     let grant;
     let grant;
     let grantedUserIds;
     let grantedUserIds;
-    let grantedGroupIds;
+    let grantedGroupId;
     if (page.isEmpty) {
     if (page.isEmpty) {
       const parent = await Page.findOne({ _id: page.parent });
       const parent = await Page.findOne({ _id: page.parent });
       if (parent == null) {
       if (parent == null) {
@@ -460,18 +460,18 @@ class PageService {
       }
       }
       grant = parent.grant;
       grant = parent.grant;
       grantedUserIds = parent.grantedUsers;
       grantedUserIds = parent.grantedUsers;
-      grantedGroupIds = parent.grantedGroups;
+      grantedGroupId = parent.grantedGroup;
     }
     }
     else {
     else {
       grant = page.grant;
       grant = page.grant;
       grantedUserIds = page.grantedUsers;
       grantedUserIds = page.grantedUsers;
-      grantedGroupIds = page.grantedGroups;
+      grantedGroupId = page.grantedGroup;
     }
     }
 
 
     if (grant !== Page.GRANT_RESTRICTED) {
     if (grant !== Page.GRANT_RESTRICTED) {
       let isGrantNormalized = false;
       let isGrantNormalized = false;
       try {
       try {
-        isGrantNormalized = await this.crowi.pageGrantService.isGrantNormalized(user, newPagePath, grant, grantedUserIds, grantedGroupIds, false);
+        isGrantNormalized = await this.crowi.pageGrantService.isGrantNormalized(user, newPagePath, grant, grantedUserIds, grantedGroupId, false);
       }
       }
       catch (err) {
       catch (err) {
         logger.error(`Failed to validate grant of page at "${newPagePath}" when renaming`, err);
         logger.error(`Failed to validate grant of page at "${newPagePath}" when renaming`, err);
@@ -959,7 +959,7 @@ class PageService {
     // use the parent's grant when target page is an empty page
     // use the parent's grant when target page is an empty page
     let grant;
     let grant;
     let grantedUserIds;
     let grantedUserIds;
-    let grantedGroupIds;
+    let grantedGroupId;
     if (page.isEmpty) {
     if (page.isEmpty) {
       const parent = await Page.findOne({ _id: page.parent });
       const parent = await Page.findOne({ _id: page.parent });
       if (parent == null) {
       if (parent == null) {
@@ -967,18 +967,18 @@ class PageService {
       }
       }
       grant = parent.grant;
       grant = parent.grant;
       grantedUserIds = parent.grantedUsers;
       grantedUserIds = parent.grantedUsers;
-      grantedGroupIds = parent.grantedGroups;
+      grantedGroupId = parent.grantedGroup;
     }
     }
     else {
     else {
       grant = page.grant;
       grant = page.grant;
       grantedUserIds = page.grantedUsers;
       grantedUserIds = page.grantedUsers;
-      grantedGroupIds = page.grantedGroups;
+      grantedGroupId = page.grantedGroup;
     }
     }
 
 
     if (grant !== Page.GRANT_RESTRICTED) {
     if (grant !== Page.GRANT_RESTRICTED) {
       let isGrantNormalized = false;
       let isGrantNormalized = false;
       try {
       try {
-        isGrantNormalized = await this.crowi.pageGrantService.isGrantNormalized(user, newPagePath, grant, grantedUserIds, grantedGroupIds, false);
+        isGrantNormalized = await this.crowi.pageGrantService.isGrantNormalized(user, newPagePath, grant, grantedUserIds, grantedGroupId, false);
       }
       }
       catch (err) {
       catch (err) {
         logger.error(`Failed to validate grant of page at "${newPagePath}" when duplicating`, err);
         logger.error(`Failed to validate grant of page at "${newPagePath}" when duplicating`, err);
@@ -995,7 +995,7 @@ class PageService {
     // 3. Duplicate target
     // 3. Duplicate target
     const options: PageCreateOptions = {
     const options: PageCreateOptions = {
       grant: page.grant,
       grant: page.grant,
-      grantUserGroupIds: page.grantedGroups,
+      grantUserGroupId: page.grantedGroup,
     };
     };
     let duplicatedTarget;
     let duplicatedTarget;
     if (page.isEmpty) {
     if (page.isEmpty) {
@@ -1107,7 +1107,7 @@ class PageService {
     // create option
     // create option
     const options: any = { page };
     const options: any = { page };
     options.grant = page.grant;
     options.grant = page.grant;
-    options.grantUserGroupIds = page.grantedGroups;
+    options.grantUserGroupId = page.grantedGroup;
     options.grantedUserIds = page.grantedUsers;
     options.grantedUserIds = page.grantedUsers;
 
 
     newPagePath = this.crowi.xss.process(newPagePath); // eslint-disable-line no-param-reassign
     newPagePath = this.crowi.xss.process(newPagePath); // eslint-disable-line no-param-reassign
@@ -1208,7 +1208,7 @@ class PageService {
           path: newPagePath,
           path: newPagePath,
           creator: user._id,
           creator: user._id,
           grant: page.grant,
           grant: page.grant,
-          grantedGroups: page.grantedGroups,
+          grantedGroup: page.grantedGroup,
           grantedUsers: page.grantedUsers,
           grantedUsers: page.grantedUsers,
           lastUpdateUser: user._id,
           lastUpdateUser: user._id,
           revision: revisionId,
           revision: revisionId,
@@ -1254,7 +1254,7 @@ class PageService {
         path: newPagePath,
         path: newPagePath,
         creator: user._id,
         creator: user._id,
         grant: page.grant,
         grant: page.grant,
-        grantedGroups: page.grantedGroups,
+        grantedGroup: page.grantedGroup,
         grantedUsers: page.grantedUsers,
         grantedUsers: page.grantedUsers,
         lastUpdateUser: user._id,
         lastUpdateUser: user._id,
         revision: revisionId,
         revision: revisionId,
@@ -2268,13 +2268,7 @@ class PageService {
 
 
   async handlePrivatePagesForGroupsToDelete(groupsToDelete, action, transferToUserGroupId, user) {
   async handlePrivatePagesForGroupsToDelete(groupsToDelete, action, transferToUserGroupId, user) {
     const Page = this.crowi.model('Page');
     const Page = this.crowi.model('Page');
-    const pages = await Page.find({
-      grantedGroups: {
-        $elemMatch: {
-          item: { $in: groupsToDelete },
-        },
-      },
-    });
+    const pages = await Page.find({ grantedGroup: { $in: groupsToDelete } });
 
 
     switch (action) {
     switch (action) {
       case 'public':
       case 'public':
@@ -2445,7 +2439,7 @@ class PageService {
 
 
       const options: PageCreateOptions & { grantedUsers?: ObjectIdLike[] | undefined } = {
       const options: PageCreateOptions & { grantedUsers?: ObjectIdLike[] | undefined } = {
         grant: notEmptyParent.grant,
         grant: notEmptyParent.grant,
-        grantUserGroupIds: notEmptyParent.grantedGroups,
+        grantUserGroupId: notEmptyParent.grantedGroup,
         grantedUsers: notEmptyParent.grantedUsers,
         grantedUsers: notEmptyParent.grantedUsers,
       };
       };
 
 
@@ -2462,7 +2456,7 @@ class PageService {
 
 
     const grant = page.grant;
     const grant = page.grant;
     const grantedUserIds = page.grantedUsers;
     const grantedUserIds = page.grantedUsers;
-    const grantedGroupIds = page.grantedGroups;
+    const grantedGroupId = page.grantedGroup;
 
 
     /*
     /*
      * UserGroup & Owner validation
      * UserGroup & Owner validation
@@ -2471,7 +2465,7 @@ class PageService {
     try {
     try {
       const shouldCheckDescendants = true;
       const shouldCheckDescendants = true;
 
 
-      isGrantNormalized = await this.crowi.pageGrantService.isGrantNormalized(user, path, grant, grantedUserIds, grantedGroupIds, shouldCheckDescendants);
+      isGrantNormalized = await this.crowi.pageGrantService.isGrantNormalized(user, path, grant, grantedUserIds, grantedGroupId, shouldCheckDescendants);
     }
     }
     catch (err) {
     catch (err) {
       logger.error(`Failed to validate grant of page at "${path}"`, err);
       logger.error(`Failed to validate grant of page at "${path}"`, err);
@@ -2571,7 +2565,7 @@ class PageService {
     const Page = mongoose.model('Page') as unknown as PageModel;
     const Page = mongoose.model('Page') as unknown as PageModel;
 
 
     const {
     const {
-      path, grant, grantedUsers: grantedUserIds, grantedGroups: grantedGroupIds,
+      path, grant, grantedUsers: grantedUserIds, grantedGroup: grantedGroupId,
     } = page;
     } = page;
 
 
     // check if any page exists at target path already
     // check if any page exists at target path already
@@ -2588,7 +2582,7 @@ class PageService {
       try {
       try {
         const shouldCheckDescendants = true;
         const shouldCheckDescendants = true;
 
 
-        isGrantNormalized = await this.crowi.pageGrantService.isGrantNormalized(user, path, grant, grantedUserIds, grantedGroupIds, shouldCheckDescendants);
+        isGrantNormalized = await this.crowi.pageGrantService.isGrantNormalized(user, path, grant, grantedUserIds, grantedGroupId, shouldCheckDescendants);
       }
       }
       catch (err) {
       catch (err) {
         logger.error(`Failed to validate grant of page at "${path}"`, err);
         logger.error(`Failed to validate grant of page at "${path}"`, err);
@@ -3460,7 +3454,7 @@ class PageService {
       grantData: {
       grantData: {
         grant: number,
         grant: number,
         grantedUserIds?: ObjectIdLike[],
         grantedUserIds?: ObjectIdLike[],
-        grantUserGroupIds?: GrantedGroup[],
+        grantUserGroupId?: ObjectIdLike,
       },
       },
       shouldValidateGrant: boolean,
       shouldValidateGrant: boolean,
       user?,
       user?,
@@ -3483,7 +3477,7 @@ class PageService {
     }
     }
 
 
     // UserGroup & Owner validation
     // UserGroup & Owner validation
-    const { grant, grantedUserIds, grantUserGroupIds } = grantData;
+    const { grant, grantedUserIds, grantUserGroupId } = grantData;
     if (shouldValidateGrant) {
     if (shouldValidateGrant) {
       if (user == null) {
       if (user == null) {
         throw Error('user is required to validate grant');
         throw Error('user is required to validate grant');
@@ -3495,7 +3489,7 @@ class PageService {
         const isEmptyPageAlreadyExist = await Page.count({ path, isEmpty: true }) > 0;
         const isEmptyPageAlreadyExist = await Page.count({ path, isEmpty: true }) > 0;
         const shouldCheckDescendants = isEmptyPageAlreadyExist && !options?.overwriteScopesOfDescendants;
         const shouldCheckDescendants = isEmptyPageAlreadyExist && !options?.overwriteScopesOfDescendants;
 
 
-        isGrantNormalized = await this.crowi.pageGrantService.isGrantNormalized(user, path, grant, grantedUserIds, grantUserGroupIds, shouldCheckDescendants);
+        isGrantNormalized = await this.crowi.pageGrantService.isGrantNormalized(user, path, grant, grantedUserIds, grantUserGroupId, shouldCheckDescendants);
       }
       }
       catch (err) {
       catch (err) {
         logger.error(`Failed to validate grant of page at "${path}" of grant ${grant}:`, err);
         logger.error(`Failed to validate grant of page at "${path}" of grant ${grant}:`, err);
@@ -3506,7 +3500,7 @@ class PageService {
       }
       }
 
 
       if (options?.overwriteScopesOfDescendants) {
       if (options?.overwriteScopesOfDescendants) {
-        const updateGrantInfo = await this.crowi.pageGrantService.generateUpdateGrantInfoToOverwriteDescendants(user, grant, options.grantUserGroupIds);
+        const updateGrantInfo = await this.crowi.pageGrantService.generateUpdateGrantInfoToOverwriteDescendants(user, grant, options.grantUserGroupId);
         const canOverwriteDescendants = await this.crowi.pageGrantService.canOverwriteDescendants(path, user, updateGrantInfo);
         const canOverwriteDescendants = await this.crowi.pageGrantService.canOverwriteDescendants(path, user, updateGrantInfo);
 
 
         if (!canOverwriteDescendants) {
         if (!canOverwriteDescendants) {
@@ -3535,13 +3529,13 @@ class PageService {
     // eslint-disable-next-line no-param-reassign
     // eslint-disable-next-line no-param-reassign
     path = this.crowi.xss.process(path); // sanitize path
     path = this.crowi.xss.process(path); // sanitize path
     const {
     const {
-      format = 'markdown', grantUserGroupIds,
+      format = 'markdown', grantUserGroupId,
     } = options;
     } = options;
     const grant = isTopPage(path) ? Page.GRANT_PUBLIC : options.grant;
     const grant = isTopPage(path) ? Page.GRANT_PUBLIC : options.grant;
     const grantData = {
     const grantData = {
       grant,
       grant,
       grantedUserIds: grant === Page.GRANT_OWNER ? [user._id] : undefined,
       grantedUserIds: grant === Page.GRANT_OWNER ? [user._id] : undefined,
-      grantUserGroupIds,
+      grantUserGroupId,
     };
     };
 
 
     const isGrantRestricted = grant === Page.GRANT_RESTRICTED;
     const isGrantRestricted = grant === Page.GRANT_RESTRICTED;
@@ -3561,7 +3555,7 @@ class PageService {
     this.setFieldExceptForGrantRevisionParent(page, path, user);
     this.setFieldExceptForGrantRevisionParent(page, path, user);
 
 
     // Apply scope
     // Apply scope
-    page.applyScope(user, grant, grantUserGroupIds);
+    page.applyScope(user, grant, grantUserGroupId);
 
 
     // Set parent
     // Set parent
     if (isTopPage(path) || isGrantRestricted) { // set parent to null when GRANT_RESTRICTED
     if (isTopPage(path) || isGrantRestricted) { // set parent to null when GRANT_RESTRICTED
@@ -3722,7 +3716,7 @@ class PageService {
     path = this.crowi.xss.process(path); // sanitize path
     path = this.crowi.xss.process(path); // sanitize path
 
 
     const {
     const {
-      format = 'markdown', grantUserGroupIds, grantedUsers,
+      format = 'markdown', grantUserGroupId, grantedUsers,
     } = options;
     } = options;
     const grant = isTopPage(path) ? Page.GRANT_PUBLIC : options.grant;
     const grant = isTopPage(path) ? Page.GRANT_PUBLIC : options.grant;
 
 
@@ -3732,7 +3726,7 @@ class PageService {
     const grantData = {
     const grantData = {
       grant,
       grant,
       grantedUserIds: isGrantOwner ? grantedUsers : undefined,
       grantedUserIds: isGrantOwner ? grantedUsers : undefined,
-      grantUserGroupIds,
+      grantUserGroupId,
     };
     };
 
 
     // Validate
     // Validate
@@ -3752,7 +3746,7 @@ class PageService {
     this.setFieldExceptForGrantRevisionParent(page, path);
     this.setFieldExceptForGrantRevisionParent(page, path);
 
 
     // Apply scope
     // Apply scope
-    page.applyScope({ _id: grantedUsers?.[0] }, grant, grantUserGroupIds);
+    page.applyScope({ _id: grantedUsers?.[0] }, grant, grantUserGroupId);
 
 
     // Set parent
     // Set parent
     if (isTopPage(path) || isGrantRestricted) { // set parent to null when GRANT_RESTRICTED
     if (isTopPage(path) || isGrantRestricted) { // set parent to null when GRANT_RESTRICTED
@@ -3792,12 +3786,12 @@ class PageService {
    * @param {UserDocument} user
    * @param {UserDocument} user
    * @param options
    * @param options
    */
    */
-  async updateGrant(page, user, grantData: {grant: PageGrant, grantedGroups: GrantedGroup[]}): Promise<PageDocument> {
-    const { grant, grantedGroups } = grantData;
+  async updateGrant(page, user, grantData: {grant: PageGrant, grantedGroup: ObjectIdLike}): Promise<PageDocument> {
+    const { grant, grantedGroup } = grantData;
 
 
     const options = {
     const options = {
       grant,
       grant,
-      grantUserGroupIds: grantedGroups,
+      grantUserGroupId: grantedGroup,
       isSyncRevisionToHackmd: false,
       isSyncRevisionToHackmd: false,
     };
     };
 
 
@@ -3867,7 +3861,7 @@ class PageService {
     const newPageData = pageData;
     const newPageData = pageData;
 
 
     const grant = options.grant ?? clonedPageData.grant; // use the previous data if absence
     const grant = options.grant ?? clonedPageData.grant; // use the previous data if absence
-    const grantUserGroupId: undefined | ObjectIdLike = options.grantUserGroupIds ?? clonedPageData.grantedGroup?._id.toString();
+    const grantUserGroupId: undefined | ObjectIdLike = options.grantUserGroupId ?? clonedPageData.grantedGroup?._id.toString();
 
 
     const grantedUserIds = clonedPageData.grantedUserIds || [user._id];
     const grantedUserIds = clonedPageData.grantedUserIds || [user._id];
     const shouldBeOnTree = grant !== PageGrant.GRANT_RESTRICTED;
     const shouldBeOnTree = grant !== PageGrant.GRANT_RESTRICTED;
@@ -3891,7 +3885,7 @@ class PageService {
       }
       }
 
 
       if (options.overwriteScopesOfDescendants) {
       if (options.overwriteScopesOfDescendants) {
-        const updateGrantInfo = await pageGrantService.generateUpdateGrantInfoToOverwriteDescendants(user, grant, options.grantUserGroupIds);
+        const updateGrantInfo = await pageGrantService.generateUpdateGrantInfoToOverwriteDescendants(user, grant, options.grantUserGroupId);
         const canOverwriteDescendants = await pageGrantService.canOverwriteDescendants(clonedPageData.path, user, updateGrantInfo);
         const canOverwriteDescendants = await pageGrantService.canOverwriteDescendants(clonedPageData.path, user, updateGrantInfo);
 
 
         if (!canOverwriteDescendants) {
         if (!canOverwriteDescendants) {

+ 0 - 7
apps/app/src/server/util/compare-objectId.ts

@@ -12,13 +12,6 @@ export const isIncludesObjectId = (arr: ObjectIdLike[], id: ObjectIdLike): boole
   return _arr.includes(_id);
   return _arr.includes(_id);
 };
 };
 
 
-// Check if two arrays have an intersection
-export const hasIntersection = (arr: ObjectIdLike[], arr2: ObjectIdLike[]): boolean => {
-  const _arr = arr.map(i => i.toString());
-  const _arr2 = arr2.map(i => i.toString());
-  return _arr.some(item => _arr2.includes(item));
-};
-
 /**
 /**
  * Exclude ObjectIds which exist in testIds from targetIds
  * Exclude ObjectIds which exist in testIds from targetIds
  * @param targetIds Array of mongoose.Types.ObjectId
  * @param targetIds Array of mongoose.Types.ObjectId

+ 0 - 22
apps/app/src/server/util/granted-group.ts

@@ -1,22 +0,0 @@
-import { GrantedGroup, GroupType } from '@growi/core';
-
-import { ObjectIdLike } from '../interfaces/mongoose-utils';
-
-export const divideByType = (grantedGroups: GrantedGroup[]): {
-  grantedUserGroups: ObjectIdLike[];
-  grantedExternalUserGroups: ObjectIdLike[];
-} => {
-  const grantedUserGroups: ObjectIdLike[] = [];
-  const grantedExternalUserGroups: ObjectIdLike[] = [];
-
-  grantedGroups.forEach((group) => {
-    if (group.type === GroupType.userGroup) {
-      grantedUserGroups.push(group.item);
-    }
-    else {
-      grantedExternalUserGroups.push(group.item);
-    }
-  });
-
-  return { grantedUserGroups, grantedExternalUserGroups };
-};

+ 54 - 74
apps/app/test/integration/service/v5.non-public-page.test.ts

@@ -343,7 +343,7 @@ describe('PageService page operations with non-public pages', () => {
         _id: pageIdRename2,
         _id: pageIdRename2,
         path: '/np_rename2',
         path: '/np_rename2',
         grant: Page.GRANT_USER_GROUP,
         grant: Page.GRANT_USER_GROUP,
-        grantedGroups: [{ item: groupIdB }],
+        grantedGroup: groupIdB,
         creator: npDummyUser2._id,
         creator: npDummyUser2._id,
         lastUpdateUser: npDummyUser2._id,
         lastUpdateUser: npDummyUser2._id,
         parent: rootPage._id,
         parent: rootPage._id,
@@ -352,7 +352,7 @@ describe('PageService page operations with non-public pages', () => {
         _id: pageIdRename3,
         _id: pageIdRename3,
         path: '/np_rename2/np_rename3',
         path: '/np_rename2/np_rename3',
         grant: Page.GRANT_USER_GROUP,
         grant: Page.GRANT_USER_GROUP,
-        grantedGroups: [{ item: groupIdC }],
+        grantedGroup: groupIdC,
         creator: npDummyUser3._id,
         creator: npDummyUser3._id,
         lastUpdateUser: npDummyUser3._id,
         lastUpdateUser: npDummyUser3._id,
         parent: pageIdRename2._id,
         parent: pageIdRename2._id,
@@ -361,7 +361,7 @@ describe('PageService page operations with non-public pages', () => {
         _id: pageIdRename4,
         _id: pageIdRename4,
         path: '/np_rename4_destination',
         path: '/np_rename4_destination',
         grant: Page.GRANT_USER_GROUP,
         grant: Page.GRANT_USER_GROUP,
-        grantedGroups: [{ item: groupIdIsolate }],
+        grantedGroup: groupIdIsolate,
         creator: npDummyUser3._id,
         creator: npDummyUser3._id,
         lastUpdateUser: npDummyUser3._id,
         lastUpdateUser: npDummyUser3._id,
         parent: rootPage._id,
         parent: rootPage._id,
@@ -370,7 +370,7 @@ describe('PageService page operations with non-public pages', () => {
         _id: pageIdRename5,
         _id: pageIdRename5,
         path: '/np_rename5',
         path: '/np_rename5',
         grant: Page.GRANT_USER_GROUP,
         grant: Page.GRANT_USER_GROUP,
-        grantedGroups: [{ item: groupIdB }],
+        grantedGroup: groupIdB,
         creator: npDummyUser2._id,
         creator: npDummyUser2._id,
         lastUpdateUser: npDummyUser2._id,
         lastUpdateUser: npDummyUser2._id,
         parent: rootPage._id,
         parent: rootPage._id,
@@ -379,7 +379,7 @@ describe('PageService page operations with non-public pages', () => {
         _id: pageIdRename6,
         _id: pageIdRename6,
         path: '/np_rename5/np_rename6',
         path: '/np_rename5/np_rename6',
         grant: Page.GRANT_USER_GROUP,
         grant: Page.GRANT_USER_GROUP,
-        grantedGroups: [{ item: groupIdB }],
+        grantedGroup: groupIdB,
         creator: npDummyUser2._id,
         creator: npDummyUser2._id,
         lastUpdateUser: npDummyUser2._id,
         lastUpdateUser: npDummyUser2._id,
         parent: pageIdRename5,
         parent: pageIdRename5,
@@ -388,7 +388,7 @@ describe('PageService page operations with non-public pages', () => {
         _id: pageIdRename7,
         _id: pageIdRename7,
         path: '/np_rename7_destination',
         path: '/np_rename7_destination',
         grant: Page.GRANT_USER_GROUP,
         grant: Page.GRANT_USER_GROUP,
-        grantedGroups: { item: groupIdIsolate },
+        grantedGroup: groupIdIsolate,
         creator: npDummyUser2._id,
         creator: npDummyUser2._id,
         lastUpdateUser: npDummyUser2._id,
         lastUpdateUser: npDummyUser2._id,
         parent: pageIdRename5,
         parent: pageIdRename5,
@@ -424,7 +424,7 @@ describe('PageService page operations with non-public pages', () => {
         _id: pageIdDuplicate2,
         _id: pageIdDuplicate2,
         path: '/np_duplicate2',
         path: '/np_duplicate2',
         grant: Page.GRANT_USER_GROUP,
         grant: Page.GRANT_USER_GROUP,
-        grantedGroups: { item: groupIdA },
+        grantedGroup: groupIdA,
         creator: npDummyUser1._id,
         creator: npDummyUser1._id,
         lastUpdateUser: npDummyUser1._id,
         lastUpdateUser: npDummyUser1._id,
         revision: revisionIdDuplicate2,
         revision: revisionIdDuplicate2,
@@ -434,7 +434,7 @@ describe('PageService page operations with non-public pages', () => {
         _id: pageIdDuplicate3,
         _id: pageIdDuplicate3,
         path: '/np_duplicate2/np_duplicate3',
         path: '/np_duplicate2/np_duplicate3',
         grant: Page.GRANT_USER_GROUP,
         grant: Page.GRANT_USER_GROUP,
-        grantedGroups: { item: groupIdB },
+        grantedGroup: groupIdB,
         creator: npDummyUser2._id,
         creator: npDummyUser2._id,
         lastUpdateUser: npDummyUser2._id,
         lastUpdateUser: npDummyUser2._id,
         revision: revisionIdDuplicate3,
         revision: revisionIdDuplicate3,
@@ -531,7 +531,7 @@ describe('PageService page operations with non-public pages', () => {
         _id: pageIdDelete2,
         _id: pageIdDelete2,
         path: '/npdel2_ug',
         path: '/npdel2_ug',
         grant: Page.GRANT_USER_GROUP,
         grant: Page.GRANT_USER_GROUP,
-        grantedGroups: { item: groupIdA },
+        grantedGroup: groupIdA,
         status: Page.STATUS_PUBLISHED,
         status: Page.STATUS_PUBLISHED,
         isEmpty: false,
         isEmpty: false,
         parent: rootPage._id,
         parent: rootPage._id,
@@ -541,7 +541,7 @@ describe('PageService page operations with non-public pages', () => {
         _id: pageIdDelete3,
         _id: pageIdDelete3,
         path: '/npdel3_top',
         path: '/npdel3_top',
         grant: Page.GRANT_USER_GROUP,
         grant: Page.GRANT_USER_GROUP,
-        grantedGroups: { item: groupIdA },
+        grantedGroup: groupIdA,
         status: Page.STATUS_PUBLISHED,
         status: Page.STATUS_PUBLISHED,
         isEmpty: false,
         isEmpty: false,
         parent: rootPage._id,
         parent: rootPage._id,
@@ -551,7 +551,7 @@ describe('PageService page operations with non-public pages', () => {
         _id: pageIdDelete4,
         _id: pageIdDelete4,
         path: '/npdel3_top/npdel4_ug',
         path: '/npdel3_top/npdel4_ug',
         grant: Page.GRANT_USER_GROUP,
         grant: Page.GRANT_USER_GROUP,
-        grantedGroups: { item: groupIdB },
+        grantedGroup: groupIdB,
         status: Page.STATUS_PUBLISHED,
         status: Page.STATUS_PUBLISHED,
         isEmpty: false,
         isEmpty: false,
         parent: pageIdDelete3._id,
         parent: pageIdDelete3._id,
@@ -566,7 +566,7 @@ describe('PageService page operations with non-public pages', () => {
       {
       {
         path: '/npdel3_top/npdel4_ug/npdel5_ug',
         path: '/npdel3_top/npdel4_ug/npdel5_ug',
         grant: Page.GRANT_USER_GROUP,
         grant: Page.GRANT_USER_GROUP,
-        grantedGroups: { item: groupIdC },
+        grantedGroup: groupIdC,
         status: Page.STATUS_PUBLISHED,
         status: Page.STATUS_PUBLISHED,
         isEmpty: false,
         isEmpty: false,
         parent: pageIdDelete4._id,
         parent: pageIdDelete4._id,
@@ -589,7 +589,7 @@ describe('PageService page operations with non-public pages', () => {
       {
       {
         path: '/npdc2_ug',
         path: '/npdc2_ug',
         grant: Page.GRANT_USER_GROUP,
         grant: Page.GRANT_USER_GROUP,
-        grantedGroups: { item: groupIdA },
+        grantedGroup: groupIdA,
         status: Page.STATUS_PUBLISHED,
         status: Page.STATUS_PUBLISHED,
         isEmpty: false,
         isEmpty: false,
         parent: rootPage._id,
         parent: rootPage._id,
@@ -598,7 +598,7 @@ describe('PageService page operations with non-public pages', () => {
         _id: pageIdDeleteComp1,
         _id: pageIdDeleteComp1,
         path: '/npdc3_ug',
         path: '/npdc3_ug',
         grant: Page.GRANT_USER_GROUP,
         grant: Page.GRANT_USER_GROUP,
-        grantedGroups: { item: groupIdA },
+        grantedGroup: groupIdA,
         status: Page.STATUS_PUBLISHED,
         status: Page.STATUS_PUBLISHED,
         isEmpty: false,
         isEmpty: false,
         parent: rootPage._id,
         parent: rootPage._id,
@@ -607,7 +607,7 @@ describe('PageService page operations with non-public pages', () => {
         _id: pageIdDeleteComp2,
         _id: pageIdDeleteComp2,
         path: '/npdc3_ug/npdc4_ug',
         path: '/npdc3_ug/npdc4_ug',
         grant: Page.GRANT_USER_GROUP,
         grant: Page.GRANT_USER_GROUP,
-        grantedGroups: { item: groupIdB },
+        grantedGroup: groupIdB,
         status: Page.STATUS_PUBLISHED,
         status: Page.STATUS_PUBLISHED,
         isEmpty: false,
         isEmpty: false,
         parent: pageIdDeleteComp1,
         parent: pageIdDeleteComp1,
@@ -615,7 +615,7 @@ describe('PageService page operations with non-public pages', () => {
       {
       {
         path: '/npdc3_ug/npdc4_ug/npdc5_ug',
         path: '/npdc3_ug/npdc4_ug/npdc5_ug',
         grant: Page.GRANT_USER_GROUP,
         grant: Page.GRANT_USER_GROUP,
-        grantedGroups: { item: groupIdC },
+        grantedGroup: groupIdC,
         status: Page.STATUS_PUBLISHED,
         status: Page.STATUS_PUBLISHED,
         isEmpty: false,
         isEmpty: false,
         parent: pageIdDeleteComp2,
         parent: pageIdDeleteComp2,
@@ -643,7 +643,7 @@ describe('PageService page operations with non-public pages', () => {
         _id: pageIdRevert2,
         _id: pageIdRevert2,
         path: '/trash/np_revert2',
         path: '/trash/np_revert2',
         grant: Page.GRANT_USER_GROUP,
         grant: Page.GRANT_USER_GROUP,
-        grantedGroups: { item: groupIdA },
+        grantedGroup: groupIdA,
         revision: revisionIdRevert2,
         revision: revisionIdRevert2,
         status: Page.STATUS_DELETED,
         status: Page.STATUS_DELETED,
       },
       },
@@ -665,7 +665,7 @@ describe('PageService page operations with non-public pages', () => {
         _id: pageIdRevert5,
         _id: pageIdRevert5,
         path: '/trash/np_revert5',
         path: '/trash/np_revert5',
         grant: Page.GRANT_USER_GROUP,
         grant: Page.GRANT_USER_GROUP,
-        grantedGroups: { item: groupIdA },
+        grantedGroup: groupIdA,
         revision: revisionIdRevert5,
         revision: revisionIdRevert5,
         status: Page.STATUS_DELETED,
         status: Page.STATUS_DELETED,
       },
       },
@@ -673,7 +673,7 @@ describe('PageService page operations with non-public pages', () => {
         _id: pageIdRevert6,
         _id: pageIdRevert6,
         path: '/trash/np_revert5/middle/np_revert6',
         path: '/trash/np_revert5/middle/np_revert6',
         grant: Page.GRANT_USER_GROUP,
         grant: Page.GRANT_USER_GROUP,
-        grantedGroups: { item: groupIdB },
+        grantedGroup: groupIdB,
         revision: revisionIdRevert6,
         revision: revisionIdRevert6,
         status: Page.STATUS_DELETED,
         status: Page.STATUS_DELETED,
       },
       },
@@ -897,8 +897,8 @@ describe('PageService page operations with non-public pages', () => {
       const _path2 = '/np_rename2';
       const _path2 = '/np_rename2';
       const _path3 = '/np_rename2/np_rename3';
       const _path3 = '/np_rename2/np_rename3';
       const _propertiesD = { grant: Page.GRANT_PUBLIC };
       const _propertiesD = { grant: Page.GRANT_PUBLIC };
-      const _properties2 = { grant: Page.GRANT_USER_GROUP, grantedGroups: { $elemMatch: { item: groupIdB } } };
-      const _properties3 = { grant: Page.GRANT_USER_GROUP, grantedGroups: { $elemMatch: { item: groupIdC } } };
+      const _properties2 = { grant: Page.GRANT_USER_GROUP, grantedGroup: groupIdB };
+      const _properties3 = { grant: Page.GRANT_USER_GROUP, grantedGroup: groupIdC };
       const _pageD = await Page.findOne({ path: _pathD, ..._propertiesD });
       const _pageD = await Page.findOne({ path: _pathD, ..._propertiesD });
       const _page2 = await Page.findOne({ path: _path2, ..._properties2 });
       const _page2 = await Page.findOne({ path: _path2, ..._properties2 });
       const _page3 = await Page.findOne({ path: _path3, ..._properties3, parent: _page2._id });
       const _page3 = await Page.findOne({ path: _path3, ..._properties3, parent: _page2._id });
@@ -934,9 +934,9 @@ describe('PageService page operations with non-public pages', () => {
       const _pathD = '/np_rename4_destination';
       const _pathD = '/np_rename4_destination';
       const _path2 = '/np_rename5';
       const _path2 = '/np_rename5';
       const _path3 = '/np_rename5/np_rename6';
       const _path3 = '/np_rename5/np_rename6';
-      const _propertiesD = { grant: Page.GRANT_USER_GROUP, grantedGroups: { $elemMatch: { item: groupIdIsolate } } };
-      const _properties2 = { grant: Page.GRANT_USER_GROUP, grantedGroups: { $elemMatch: { item: groupIdB } } };
-      const _properties3 = { grant: Page.GRANT_USER_GROUP, grantedGroups: { $elemMatch: { item: groupIdB } } };
+      const _propertiesD = { grant: Page.GRANT_USER_GROUP, grantedGroup: groupIdIsolate };
+      const _properties2 = { grant: Page.GRANT_USER_GROUP, grantedGroup: groupIdB };
+      const _properties3 = { grant: Page.GRANT_USER_GROUP, grantedGroup: groupIdB };
       const _pageD = await Page.findOne({ path: _pathD, ..._propertiesD });// isolate
       const _pageD = await Page.findOne({ path: _pathD, ..._propertiesD });// isolate
       const _page2 = await Page.findOne({ path: _path2, ..._properties2 });// groupIdB
       const _page2 = await Page.findOne({ path: _path2, ..._properties2 });// groupIdB
       const _page3 = await Page.findOne({ path: _path3, ..._properties3, parent: _page2 });// groupIdB
       const _page3 = await Page.findOne({ path: _path3, ..._properties3, parent: _page2 });// groupIdB
@@ -971,7 +971,7 @@ describe('PageService page operations with non-public pages', () => {
       const _pathD = '/np_rename7_destination';
       const _pathD = '/np_rename7_destination';
       const _path2 = '/np_rename8';
       const _path2 = '/np_rename8';
       const _path3 = '/np_rename8/np_rename9';
       const _path3 = '/np_rename8/np_rename9';
-      const _pageD = await Page.findOne({ path: _pathD, grant: Page.GRANT_USER_GROUP, grantedGroups: { $elemMatch: { item: groupIdIsolate } } });
+      const _pageD = await Page.findOne({ path: _pathD, grant: Page.GRANT_USER_GROUP, grantedGroup: groupIdIsolate });
       const _page2 = await Page.findOne({ path: _path2, grant: Page.GRANT_RESTRICTED });
       const _page2 = await Page.findOne({ path: _path2, grant: Page.GRANT_RESTRICTED });
       const _page3 = await Page.findOne({ path: _path3, grant: Page.GRANT_RESTRICTED });
       const _page3 = await Page.findOne({ path: _path3, grant: Page.GRANT_RESTRICTED });
       expect(_pageD).toBeTruthy();
       expect(_pageD).toBeTruthy();
@@ -1042,9 +1042,9 @@ describe('PageService page operations with non-public pages', () => {
     test('Should duplicate multiple pages with GRANT_USER_GROUP', async() => {
     test('Should duplicate multiple pages with GRANT_USER_GROUP', async() => {
       const _path1 = '/np_duplicate2';
       const _path1 = '/np_duplicate2';
       const _path2 = '/np_duplicate2/np_duplicate3';
       const _path2 = '/np_duplicate2/np_duplicate3';
-      const _page1 = await Page.findOne({ path: _path1, parent: rootPage._id, grantedGroups: { $elemMatch: { item: groupIdA } } })
+      const _page1 = await Page.findOne({ path: _path1, parent: rootPage._id, grantedGroup: groupIdA })
         .populate({ path: 'revision', model: 'Revision', grantedPage: groupIdA._id });
         .populate({ path: 'revision', model: 'Revision', grantedPage: groupIdA._id });
-      const _page2 = await Page.findOne({ path: _path2, parent: _page1._id, grantedGroups: { $elemMatch: { item: groupIdB } } })
+      const _page2 = await Page.findOne({ path: _path2, parent: _page1._id, grantedGroup: groupIdB })
         .populate({ path: 'revision', model: 'Revision', grantedPage: groupIdB._id });
         .populate({ path: 'revision', model: 'Revision', grantedPage: groupIdB._id });
       const _revision1 = _page1.revision;
       const _revision1 = _page1.revision;
       const _revision2 = _page2.revision;
       const _revision2 = _page2.revision;
@@ -1065,16 +1065,8 @@ describe('PageService page operations with non-public pages', () => {
       expect(duplicatedPage2).toBeTruthy();
       expect(duplicatedPage2).toBeTruthy();
       expect(duplicatedRevision1).toBeTruthy();
       expect(duplicatedRevision1).toBeTruthy();
       expect(duplicatedRevision2).toBeTruthy();
       expect(duplicatedRevision2).toBeTruthy();
-      const grantedGroups1 = JSON.parse(JSON.stringify(duplicatedPage1.grantedGroups)).map((group) => {
-        delete group._id;
-        return group;
-      });
-      const grantedGroups2 = JSON.parse(JSON.stringify(duplicatedPage2.grantedGroups)).map((group) => {
-        delete group._id;
-        return group;
-      });
-      expect(grantedGroups1).toStrictEqual([{ item: groupIdA._id.toString(), type: 'UserGroup' }]);
-      expect(grantedGroups2).toStrictEqual([{ item: groupIdB._id.toString(), type: 'UserGroup' }]);
+      expect(duplicatedPage1.grantedGroup).toStrictEqual(groupIdA._id);
+      expect(duplicatedPage2.grantedGroup).toStrictEqual(groupIdB._id);
       expect(duplicatedPage1.parent).toStrictEqual(_page1.parent);
       expect(duplicatedPage1.parent).toStrictEqual(_page1.parent);
       expect(duplicatedPage2.parent).toStrictEqual(duplicatedPage1._id);
       expect(duplicatedPage2.parent).toStrictEqual(duplicatedPage1._id);
       expect(duplicatedRevision1.body).toBe(_revision1.body);
       expect(duplicatedRevision1.body).toBe(_revision1.body);
@@ -1164,7 +1156,7 @@ describe('PageService page operations with non-public pages', () => {
     describe('Delete single page with grant USER_GROUP', () => {
     describe('Delete single page with grant USER_GROUP', () => {
       test('should be able to delete', async() => {
       test('should be able to delete', async() => {
         const _path = '/npdel2_ug';
         const _path = '/npdel2_ug';
-        const _page1 = await Page.findOne({ path: _path, grantedGroups: { $elemMatch: { item: groupIdA } } });
+        const _page1 = await Page.findOne({ path: _path, grantedGroup: groupIdA });
         expect(_page1).toBeTruthy();
         expect(_page1).toBeTruthy();
 
 
         const isRecursively = false;
         const isRecursively = false;
@@ -1173,8 +1165,8 @@ describe('PageService page operations with non-public pages', () => {
           endpoint: '/_api/v3/pages/rename',
           endpoint: '/_api/v3/pages/rename',
         });
         });
 
 
-        const pageN = await Page.findOne({ path: _path, grantedGroups: { $elemMatch: { item: groupIdA } } });
-        const page1 = await Page.findOne({ path: `/trash${_path}`, grantedGroups: { $elemMatch: { item: groupIdA } } });
+        const pageN = await Page.findOne({ path: _path, grantedGroup: groupIdA });
+        const page1 = await Page.findOne({ path: `/trash${_path}`, grantedGroup: groupIdA });
         expect(pageN).toBeNull();
         expect(pageN).toBeNull();
         expect(page1).toBeTruthy();
         expect(page1).toBeTruthy();
         expect(page1.status).toBe(Page.STATUS_DELETED);
         expect(page1.status).toBe(Page.STATUS_DELETED);
@@ -1187,9 +1179,9 @@ describe('PageService page operations with non-public pages', () => {
         const _pathT = '/npdel3_top';
         const _pathT = '/npdel3_top';
         const _path1 = '/npdel3_top/npdel4_ug';
         const _path1 = '/npdel3_top/npdel4_ug';
         const _path2 = '/npdel3_top/npdel4_ug/npdel5_ug';
         const _path2 = '/npdel3_top/npdel4_ug/npdel5_ug';
-        const _pageT = await Page.findOne({ path: _pathT, grant: Page.GRANT_USER_GROUP, grantedGroups: { $elemMatch: { item: groupIdA } } }); // A
-        const _page1 = await Page.findOne({ path: _path1, grant: Page.GRANT_USER_GROUP, grantedGroups: { $elemMatch: { item: groupIdB } } }); // B
-        const _page2 = await Page.findOne({ path: _path2, grant: Page.GRANT_USER_GROUP, grantedGroups: { $elemMatch: { item: groupIdC } } }); // C
+        const _pageT = await Page.findOne({ path: _pathT, grant: Page.GRANT_USER_GROUP, grantedGroup: groupIdA }); // A
+        const _page1 = await Page.findOne({ path: _path1, grant: Page.GRANT_USER_GROUP, grantedGroup: groupIdB }); // B
+        const _page2 = await Page.findOne({ path: _path2, grant: Page.GRANT_USER_GROUP, grantedGroup: groupIdC }); // C
         const _pageR = await Page.findOne({ path: _path1, grant: Page.GRANT_RESTRICTED }); // Restricted
         const _pageR = await Page.findOne({ path: _path1, grant: Page.GRANT_RESTRICTED }); // Restricted
         expect(_pageT).toBeTruthy();
         expect(_pageT).toBeTruthy();
         expect(_page1).toBeTruthy();
         expect(_page1).toBeTruthy();
@@ -1202,12 +1194,12 @@ describe('PageService page operations with non-public pages', () => {
           endpoint: '/_api/v3/pages/rename',
           endpoint: '/_api/v3/pages/rename',
         });
         });
 
 
-        const pageTNotExist = await Page.findOne({ path: _pathT, grant: Page.GRANT_USER_GROUP, grantedGroups: { $elemMatch: { item: groupIdA } } }); // A should not exist
-        const page1NotExist = await Page.findOne({ path: _path1, grant: Page.GRANT_USER_GROUP, grantedGroups: { $elemMatch: { item: groupIdB } } }); // B should not exist
-        const page2NotExist = await Page.findOne({ path: _path2, grant: Page.GRANT_USER_GROUP, grantedGroups: { $elemMatch: { item: groupIdC } } }); // C should not exist
-        const pageT = await Page.findOne({ path: `/trash${_pathT}`, grant: Page.GRANT_USER_GROUP, grantedGroups: { $elemMatch: { item: groupIdA } } }); // A
-        const page1 = await Page.findOne({ path: `/trash${_path1}`, grant: Page.GRANT_USER_GROUP, grantedGroups: { $elemMatch: { item: groupIdB } } }); // B
-        const page2 = await Page.findOne({ path: `/trash${_path2}`, grant: Page.GRANT_USER_GROUP, grantedGroups: { $elemMatch: { item: groupIdC } } }); // C
+        const pageTNotExist = await Page.findOne({ path: _pathT, grant: Page.GRANT_USER_GROUP, grantedGroup: groupIdA }); // A should not exist
+        const page1NotExist = await Page.findOne({ path: _path1, grant: Page.GRANT_USER_GROUP, grantedGroup: groupIdB }); // B should not exist
+        const page2NotExist = await Page.findOne({ path: _path2, grant: Page.GRANT_USER_GROUP, grantedGroup: groupIdC }); // C should not exist
+        const pageT = await Page.findOne({ path: `/trash${_pathT}`, grant: Page.GRANT_USER_GROUP, grantedGroup: groupIdA }); // A
+        const page1 = await Page.findOne({ path: `/trash${_path1}`, grant: Page.GRANT_USER_GROUP, grantedGroup: groupIdB }); // B
+        const page2 = await Page.findOne({ path: `/trash${_path2}`, grant: Page.GRANT_USER_GROUP, grantedGroup: groupIdC }); // C
         const pageR = await Page.findOne({ path: _path1, grant: Page.GRANT_RESTRICTED }); // Restricted
         const pageR = await Page.findOne({ path: _path1, grant: Page.GRANT_RESTRICTED }); // Restricted
         expect(page1NotExist).toBeNull();
         expect(page1NotExist).toBeNull();
         expect(pageTNotExist).toBeNull();
         expect(pageTNotExist).toBeNull();
@@ -1264,7 +1256,7 @@ describe('PageService page operations with non-public pages', () => {
     describe('Delete single page with grant USER_GROUP', () => {
     describe('Delete single page with grant USER_GROUP', () => {
       test('should be able to delete completely', async() => {
       test('should be able to delete completely', async() => {
         const _path = '/npdc2_ug';
         const _path = '/npdc2_ug';
-        const _page = await Page.findOne({ path: _path, grant: Page.GRANT_USER_GROUP, grantedGroups: { $elemMatch: { item: groupIdA } } });
+        const _page = await Page.findOne({ path: _path, grant: Page.GRANT_USER_GROUP, grantedGroup: groupIdA });
         expect(_page).toBeTruthy();
         expect(_page).toBeTruthy();
 
 
         await deleteCompletely(_page, npDummyUser1, {}, false, false, {
         await deleteCompletely(_page, npDummyUser1, {}, false, false, {
@@ -1272,7 +1264,7 @@ describe('PageService page operations with non-public pages', () => {
           endpoint: '/_api/v3/pages/rename',
           endpoint: '/_api/v3/pages/rename',
         });
         });
 
 
-        const page = await Page.findOne({ path: _path, grant: Page.GRANT_USER_GROUP, grantedGroups: { $elemMatch: { item: groupIdA } } });
+        const page = await Page.findOne({ path: _path, grant: Page.GRANT_USER_GROUP, grantedGroup: groupIdA });
         expect(page).toBeNull();
         expect(page).toBeNull();
       });
       });
     });
     });
@@ -1281,9 +1273,9 @@ describe('PageService page operations with non-public pages', () => {
         const _path1 = '/npdc3_ug';
         const _path1 = '/npdc3_ug';
         const _path2 = '/npdc3_ug/npdc4_ug';
         const _path2 = '/npdc3_ug/npdc4_ug';
         const _path3 = '/npdc3_ug/npdc4_ug/npdc5_ug';
         const _path3 = '/npdc3_ug/npdc4_ug/npdc5_ug';
-        const _page1 = await Page.findOne({ path: _path1, grant: Page.GRANT_USER_GROUP, grantedGroups: { $elemMatch: { item: groupIdA } } });
-        const _page2 = await Page.findOne({ path: _path2, grant: Page.GRANT_USER_GROUP, grantedGroups: { $elemMatch: { item: groupIdB } } });
-        const _page3 = await Page.findOne({ path: _path3, grant: Page.GRANT_USER_GROUP, grantedGroups: { $elemMatch: { item: groupIdC } } });
+        const _page1 = await Page.findOne({ path: _path1, grant: Page.GRANT_USER_GROUP, grantedGroup: groupIdA });
+        const _page2 = await Page.findOne({ path: _path2, grant: Page.GRANT_USER_GROUP, grantedGroup: groupIdB });
+        const _page3 = await Page.findOne({ path: _path3, grant: Page.GRANT_USER_GROUP, grantedGroup: groupIdC });
         const _page4 = await Page.findOne({ path: _path2, grant: Page.GRANT_RESTRICTED });
         const _page4 = await Page.findOne({ path: _path2, grant: Page.GRANT_RESTRICTED });
         expect(_page1).toBeTruthy();
         expect(_page1).toBeTruthy();
         expect(_page2).toBeTruthy();
         expect(_page2).toBeTruthy();
@@ -1295,9 +1287,9 @@ describe('PageService page operations with non-public pages', () => {
           endpoint: '/_api/v3/pages/rename',
           endpoint: '/_api/v3/pages/rename',
         });
         });
 
 
-        const page1 = await Page.findOne({ path: _path1, grant: Page.GRANT_USER_GROUP, grantedGroups: { $elemMatch: { item: groupIdA } } });
-        const page2 = await Page.findOne({ path: _path2, grant: Page.GRANT_USER_GROUP, grantedGroups: { $elemMatch: { item: groupIdB } } });
-        const page3 = await Page.findOne({ path: _path3, grant: Page.GRANT_USER_GROUP, grantedGroups: { $elemMatch: { item: groupIdC } } });
+        const page1 = await Page.findOne({ path: _path1, grant: Page.GRANT_USER_GROUP, grantedGroup: groupIdA });
+        const page2 = await Page.findOne({ path: _path2, grant: Page.GRANT_USER_GROUP, grantedGroup: groupIdB });
+        const page3 = await Page.findOne({ path: _path3, grant: Page.GRANT_USER_GROUP, grantedGroup: groupIdC });
         const page4 = await Page.findOne({ path: _path2, grant: Page.GRANT_RESTRICTED });
         const page4 = await Page.findOne({ path: _path2, grant: Page.GRANT_RESTRICTED });
 
 
         expect(page1).toBeNull();
         expect(page1).toBeNull();
@@ -1379,11 +1371,7 @@ describe('PageService page operations with non-public pages', () => {
       expect(revertedPage.parent).toStrictEqual(rootPage._id);
       expect(revertedPage.parent).toStrictEqual(rootPage._id);
       expect(revertedPage.status).toBe(Page.STATUS_PUBLISHED);
       expect(revertedPage.status).toBe(Page.STATUS_PUBLISHED);
       expect(revertedPage.grant).toBe(Page.GRANT_USER_GROUP);
       expect(revertedPage.grant).toBe(Page.GRANT_USER_GROUP);
-      const grantedGroups = JSON.parse(JSON.stringify(revertedPage.grantedGroups)).map((group) => {
-        delete group._id;
-        return group;
-      });
-      expect(grantedGroups).toStrictEqual([{ item: groupIdA.toString(), type: 'UserGroup' }]);
+      expect(revertedPage.grantedGroup).toStrictEqual(groupIdA);
       expect(pageTagRelation.isPageTrashed).toBe(false);
       expect(pageTagRelation.isPageTrashed).toBe(false);
     });
     });
     test(`revert multiple pages: only target page should be reverted.
     test(`revert multiple pages: only target page should be reverted.
@@ -1429,8 +1417,8 @@ describe('PageService page operations with non-public pages', () => {
       const beforeRevertPath1 = '/trash/np_revert5';
       const beforeRevertPath1 = '/trash/np_revert5';
       const beforeRevertPath2 = '/trash/np_revert5/middle/np_revert6';
       const beforeRevertPath2 = '/trash/np_revert5/middle/np_revert6';
       const beforeRevertPath3 = '/trash/np_revert5/middle';
       const beforeRevertPath3 = '/trash/np_revert5/middle';
-      const trashedPage1 = await Page.findOne({ path: beforeRevertPath1, status: Page.STATUS_DELETED, grantedGroups: { $elemMatch: { item: groupIdA } } });
-      const trashedPage2 = await Page.findOne({ path: beforeRevertPath2, status: Page.STATUS_DELETED, grantedGroups: { $elemMatch: { item: groupIdB } } });
+      const trashedPage1 = await Page.findOne({ path: beforeRevertPath1, status: Page.STATUS_DELETED, grantedGroup: groupIdA });
+      const trashedPage2 = await Page.findOne({ path: beforeRevertPath2, status: Page.STATUS_DELETED, grantedGroup: groupIdB });
       const nonExistantPage3 = await Page.findOne({ path: beforeRevertPath3 }); // not exist
       const nonExistantPage3 = await Page.findOne({ path: beforeRevertPath3 }); // not exist
       const revision1 = await Revision.findOne({ pageId: trashedPage1._id });
       const revision1 = await Revision.findOne({ pageId: trashedPage1._id });
       const revision2 = await Revision.findOne({ pageId: trashedPage2._id });
       const revision2 = await Revision.findOne({ pageId: trashedPage2._id });
@@ -1465,16 +1453,8 @@ describe('PageService page operations with non-public pages', () => {
       expect(revertedPage1.status).toBe(Page.STATUS_PUBLISHED);
       expect(revertedPage1.status).toBe(Page.STATUS_PUBLISHED);
       expect(revertedPage2.status).toBe(Page.STATUS_PUBLISHED);
       expect(revertedPage2.status).toBe(Page.STATUS_PUBLISHED);
       expect(newlyCreatedPage.status).toBe(Page.STATUS_PUBLISHED);
       expect(newlyCreatedPage.status).toBe(Page.STATUS_PUBLISHED);
-      const grantedGroups1 = JSON.parse(JSON.stringify(revertedPage1.grantedGroups)).map((group) => {
-        delete group._id;
-        return group;
-      });
-      const grantedGroups2 = JSON.parse(JSON.stringify(revertedPage2.grantedGroups)).map((group) => {
-        delete group._id;
-        return group;
-      });
-      expect(grantedGroups1).toStrictEqual([{ item: groupIdA.toString(), type: 'UserGroup' }]);
-      expect(grantedGroups2).toStrictEqual([{ item: groupIdB.toString(), type: 'UserGroup' }]);
+      expect(revertedPage1.grantedGroup).toStrictEqual(groupIdA);
+      expect(revertedPage2.grantedGroup).toStrictEqual(groupIdB);
       expect(newlyCreatedPage.grant).toBe(Page.GRANT_PUBLIC);
       expect(newlyCreatedPage.grant).toBe(Page.GRANT_PUBLIC);
 
 
     });
     });

+ 5 - 8
packages/core/src/interfaces/page.ts

@@ -5,13 +5,7 @@ import type { SubscriptionStatusType } from './subscription';
 import type { ITag } from './tag';
 import type { ITag } from './tag';
 import type { IUser, IUserGroupHasId, IUserHasId } from './user';
 import type { IUser, IUserGroupHasId, IUserHasId } from './user';
 
 
-export const GroupType = { userGroup: 'UserGroup', externalUserGroup: 'ExternalUserGroup' } as const;
-export type GroupType = typeof GroupType[keyof typeof GroupType];
-
-export type GrantedGroup = {
-  type: GroupType,
-  item: Ref<any>,
-}
+export type GroupType = 'UserGroup' | 'ExternalUserGroup'
 
 
 export type IPage = {
 export type IPage = {
   path: string,
   path: string,
@@ -27,7 +21,10 @@ export type IPage = {
   isEmpty: boolean,
   isEmpty: boolean,
   grant: PageGrant,
   grant: PageGrant,
   grantedUsers: Ref<IUser>[],
   grantedUsers: Ref<IUser>[],
-  grantedGroups: GrantedGroup[],
+  grantedGroups: {
+    type: GroupType,
+    item: Ref<any>,
+  }[],
   lastUpdateUser: Ref<IUser>,
   lastUpdateUser: Ref<IUser>,
   liker: Ref<IUser>[],
   liker: Ref<IUser>[],
   commentCount: number
   commentCount: number