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

Merge branch 'feat/page-rename-v5' into imprv/typescriptize-page-service

Taichi Masuyama 4 лет назад
Родитель
Сommit
d8de0613e0
2 измененных файлов с 67 добавлено и 37 удалено
  1. 23 13
      packages/app/src/server/models/page.ts
  2. 44 24
      packages/app/src/server/service/page-grant.ts

+ 23 - 13
packages/app/src/server/models/page.ts

@@ -370,8 +370,8 @@ export default (crowi: Crowi): any => {
     }
 
     const isV5Compatible = crowi.configManager.getConfig('crowi', 'app:isV5Compatible');
+    // v4 compatible process
     if (!isV5Compatible) {
-      // v4 compatible process
       return this.createV4(path, body, user, options);
     }
 
@@ -380,28 +380,27 @@ export default (crowi: Crowi): any => {
     const {
       format = 'markdown', redirectTo, grantUserGroupId,
     } = options;
+    let grant = options.grant;
 
     // sanitize path
     path = crowi.xss.process(path); // eslint-disable-line no-param-reassign
-
-    let grant = options.grant;
-    // force public
-    if (isTopPage(path)) {
-      grant = GRANT_PUBLIC;
-    }
-
+    // throw if exists
     const isExist = (await this.count({ path, isEmpty: false })) > 0; // not validate empty page
     if (isExist) {
       throw new Error('Cannot create new page to existed path');
     }
+    // force public
+    if (isTopPage(path)) {
+      grant = GRANT_PUBLIC;
+    }
 
-    // find existing empty page at target path
+    // find an existing empty page
     const emptyPage = await Page.findOne({ path, isEmpty: true });
 
+    /*
+     * UserGroup & Owner validation
+     */
     if (grant !== GRANT_RESTRICTED) {
-      /*
-       * UserGroup & Owner validation
-       */
       let isGrantNormalized = false;
       try {
         // It must check descendants as well if emptyTarget is not null
@@ -443,11 +442,22 @@ export default (crowi: Crowi): any => {
     page.lastUpdateUser = user;
     page.redirectTo = redirectTo;
     page.status = STATUS_PUBLISHED;
-    page.parent = options.grant === GRANT_RESTRICTED ? null : parentId;
+
+    // set parent to null when GRANT_RESTRICTED
+    if (grant === GRANT_RESTRICTED) {
+      page.parent = null;
+    }
+    else {
+      page.parent = parentId;
+    }
 
     page.applyScope(user, grant, grantUserGroupId);
 
     let savedPage = await page.save();
+
+    /*
+     * After save
+     */
     const newRevision = Revision.prepareRevision(savedPage, body, null, user, { format });
     const revision = await pushRevision(savedPage, newRevision, user);
     savedPage = await this.findByPath(revision.path);

+ 44 - 24
packages/app/src/server/service/page-grant.ts

@@ -1,11 +1,13 @@
 import mongoose from 'mongoose';
-import { pagePathUtils } from '@growi/core';
+import { pagePathUtils, pathUtils } from '@growi/core';
+import escapeStringRegexp from 'escape-string-regexp';
 
 import UserGroup from '~/server/models/user-group';
 import { PageModel } from '~/server/models/page';
 import { PageQueryBuilder } from '../models/obsolete-page';
 import { isIncludesObjectId, removeDuplicates, excludeTestIdsFromTargetIds } from '~/server/util/compare-objectId';
 
+const { addTrailingSlash } = pathUtils;
 const { isTopPage } = pagePathUtils;
 
 type ObjectId = mongoose.Types.ObjectId;
@@ -254,32 +256,50 @@ class PageGrantService {
     /*
      * make granted users list of descendant's
      */
-    // find all descendants excluding empty pages
-    const builderForDescendants = new PageQueryBuilder(Page.find({}, {
-      _id: 0, grant: 1, grantedUsers: 1, grantedGroup: 1,
-    }), false);
-    const descendants = await builderForDescendants
-      .addConditionToListOnlyDescendants(targetPath)
-      .query
-      .exec();
+    const pathWithTrailingSlash = addTrailingSlash(targetPath);
+    const startsPattern = escapeStringRegexp(pathWithTrailingSlash);
+
+    const result = await Page.aggregate([
+      { // match to descendants excluding empty pages
+        $match: {
+          path: new RegExp(`^${startsPattern}`),
+          isEmpty: { $ne: true },
+        },
+      },
+      {
+        $project: {
+          _id: 0,
+          grant: 1,
+          grantedUsers: 1,
+          grantedGroup: 1,
+        },
+      },
+      { // remove duplicates from pipeline
+        $group: {
+          _id: '$grant',
+          grantedGroupSet: { $addToSet: '$grantedGroup' },
+          grantedUsersSet: { $addToSet: '$grantedUsers' },
+        },
+      },
+      { // flatten granted user set
+        $unwind: {
+          path: '$grantedUsersSet',
+        },
+      },
+    ]);
+
+    // GRANT_PUBLIC group
+    const isPublicExist = result.some(r => r._id === Page.GRANT_PUBLIC);
+    // GRANT_OWNER group
+    const grantOwnerResult = result.filter(r => r._id === Page.GRANT_OWNER)[0]; // users of GRANT_OWNER
+    const grantedUserIds: ObjectId[] = grantOwnerResult?.grantedUsersSet ?? [];
+    // GRANT_USER_GROUP group
+    const grantUserGroupResult = result.filter(r => r._id === Page.GRANT_USER_GROUP)[0]; // users of GRANT_OWNER
+    const grantedGroupIds = grantUserGroupResult?.grantedGroupSet ?? [];
 
-    const isPublicExist = descendants.some(d => d.grant === Page.GRANT_PUBLIC);
-
-    let grantedUsersOfGrantOwner: ObjectId[] = []; // users of GRANT_OWNER
-    const grantedGroups: ObjectId[] = [];
-    descendants.forEach((d) => {
-      if (d.grantedUsers != null) {
-        grantedUsersOfGrantOwner = grantedUsersOfGrantOwner.concat(d.grantedUsers);
-      }
-      if (d.grantedGroup != null) {
-        grantedGroups.push(d.grantedGroup);
-      }
-    });
-
-    const grantedGroupIds = removeDuplicates(grantedGroups);
     return {
       isPublicExist,
-      grantedUserIds: grantedUsersOfGrantOwner,
+      grantedUserIds,
       grantedGroupIds,
     };
   }