Taichi Masuyama 4 лет назад
Родитель
Сommit
9a0ecc457c
2 измененных файлов с 150 добавлено и 28 удалено
  1. 146 6
      packages/app/src/server/models/page.js
  2. 4 22
      packages/app/src/server/service/page.js

+ 146 - 6
packages/app/src/server/models/page.js

@@ -1,4 +1,5 @@
 import { templateChecker, pagePathUtils } from '@growi/core';
+import { constants } from 'crypto';
 import loggerFactory from '~/utils/logger';
 
 // disable no-return-await for model functions
@@ -961,14 +962,10 @@ module.exports = function(crowi) {
     }
   }
 
-  pageSchema.statics.create = async function(path, body, user, options = {}) {
-    validateCrowi();
-
+  pageSchema.statics._createV4 = async function(path, body, user, options = {}) {
     const Page = this;
     const Revision = crowi.model('Revision');
-    const format = options.format || 'markdown';
-    const redirectTo = options.redirectTo || null;
-    const grantUserGroupId = options.grantUserGroupId || null;
+    const { format = 'markdown', redirectTo, grantUserGroupId } = options;
 
     // sanitize path
     path = crowi.xss.process(path); // eslint-disable-line no-param-reassign
@@ -1006,6 +1003,149 @@ module.exports = function(crowi) {
     return savedPage;
   };
 
+  const generateAncestorPaths = (path, ancestorPaths = []) => {
+    const parentPath = nodePath.dirname(path);
+    ancestorPaths.push(parentPath);
+
+    if (path !== '/') return generateAncestorPaths(parentPath, ancestorPaths);
+
+    return ancestorPaths;
+  };
+
+  pageSchema.statics.createEmptyPagesByPaths = async function(paths) {
+    const Page = this;
+
+    // find existing parents
+    const builder = new PageQueryBuilder(Page.find({}, { _id: 0, path: 1 }));
+    const existingPages = await builder
+      .addConditionToListByPathsArray(paths)
+      .query
+      .lean()
+      .exec();
+    const existingPagePaths = existingPages.map(page => page.path);
+
+    // paths to create empty pages
+    const notExistingPagePaths = paths.filter(path => !existingPagePaths.includes(path));
+
+    // insertMany empty pages
+    try {
+      await Page.insertMany(notExistingPagePaths.map(path => ({ path, isEmpty: true })));
+    }
+    catch (err) {
+      logger.error('Failed to insert empty pages.', err);
+      throw err;
+    }
+  };
+
+  pageSchema.statics._getParentIdAndFillAncestors = async function(path) {
+    const Page = this;
+    const parentPath = nodePath.dirname(path);
+
+    const parent = await Page.findOne({ path: parentPath }); // find the oldest parent which must always be the true parent
+    if (parent != null) { // fill parents if parent is null
+      return parent._id;
+    }
+
+    const ancestorPaths = generateAncestorPaths(path); // paths of parents need to be created
+
+    // just create ancestors with empty pages
+    await Page.createEmptyPagesByPaths(ancestorPaths);
+
+    // find ancestors
+    const builder = new PageQueryBuilder(Page.find({}, { _id: 1, path: 1 }));
+    const ancestors = await builder
+      .addConditionToListByPathsArray(ancestorPaths)
+      .query
+      .lean()
+      .exec();
+
+
+    const ancestorsMap = new Map(); // Map<path, _id>
+    ancestors.forEach(page => ancestorsMap.set(page.path, page._id));
+
+    // bulkWrite to update ancestors
+    const nonRootAncestors = ancestors.filter(page => page.path !== '/');
+    const operations = nonRootAncestors.map((page) => {
+      const { path } = page;
+      const parentPath = nodePath.dirname(path);
+      return {
+        updateOne: {
+          filter: {
+            path,
+          },
+          update: {
+            parent: ancestorsMap.get(parentPath),
+          },
+        },
+      };
+    });
+    await Page.bulkWrite(operations);
+
+    const parentId = ancestorsMap.get(parentPath);
+    return parentId;
+  };
+
+  pageSchema.statics._createV5 = async function(path, body, user, options = {}) {
+    const Page = this;
+    const Revision = crowi.model('Revision');
+    const {
+      format = 'markdown', redirectTo, grantUserGroupId, parentId,
+    } = options;
+
+    // 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;
+    }
+
+    const page = new Page();
+    page.path = path;
+    page.creator = user;
+    page.lastUpdateUser = user;
+    page.redirectTo = redirectTo;
+    page.status = STATUS_PUBLISHED;
+
+    await validateAppliedScope(user, grant, grantUserGroupId);
+    page.applyScope(user, grant, grantUserGroupId);
+
+    /*
+     * Fill parent before saving the page
+     */
+    page.parent = parentId;
+    if (parentId == null) {
+      page.parent = await Page._getParentIdAndFillAncestors(path);
+    }
+
+    /*
+     * Save page
+     */
+    let savedPage = await page.save();
+    const newRevision = Revision.prepareRevision(savedPage, body, null, user, { format });
+    const revision = await pushRevision(savedPage, newRevision, user);
+    savedPage = await this.findByPath(revision.path);
+    await savedPage.populateDataToShowRevision();
+
+    pageEvent.emit('create', savedPage, user);
+
+    return savedPage;
+  };
+
+  pageSchema.statics.create = async function(path, body, user, options = {}) {
+    validateCrowi();
+
+    const Page = this;
+
+    const isV5Compatible = crowi.configManager.getConfig('crowi', 'app:isV5Compatible');
+
+    if (isV5Compatible) {
+      return Page._createV5(path, body, user, options);
+    }
+    return Page._createV4(path, body, user, options);
+  };
+
   pageSchema.statics.updatePage = async function(pageData, body, previousBody, user, options = {}) {
     validateCrowi();
 

+ 4 - 22
packages/app/src/server/service/page.js

@@ -796,30 +796,12 @@ class PageService {
         const parentPathsSet = new Set(pages.map(page => pathlib.dirname(page.path)));
         const parentPaths = Array.from(parentPathsSet);
 
-        // find existing parents
-        const builder1 = new PageQueryBuilder(Page.find({}, { _id: 0, path: 1 }));
-        const existingParents = await builder1
-          .addConditionToListByPathsArray(parentPaths)
-          .query
-          .lean()
-          .exec();
-        const existingParentPaths = existingParents.map(parent => parent.path);
-
-        // paths to create empty pages
-        const notExistingParentPaths = parentPaths.filter(path => !existingParentPaths.includes(path));
-
-        // insertMany empty pages
-        try {
-          await Page.insertMany(notExistingParentPaths.map(path => ({ path, isEmpty: true })));
-        }
-        catch (err) {
-          logger.error('Failed to insert empty pages.', err);
-          throw err;
-        }
+        // fill parents with empty pages
+        await Page.createEmptyPagesByPaths(parentPaths);
 
         // find parents again
-        const builder2 = new PageQueryBuilder(Page.find({}, { _id: 1, path: 1 }));
-        const parents = await builder2
+        const builder = new PageQueryBuilder(Page.find({}, { _id: 1, path: 1 }));
+        const parents = await builder
           .addConditionToListByPathsArray(parentPaths)
           .query
           .lean()