Taichi Masuyama 4 лет назад
Родитель
Сommit
82f1f32baf
1 измененных файлов с 66 добавлено и 35 удалено
  1. 66 35
      packages/app/src/server/service/page.ts

+ 66 - 35
packages/app/src/server/service/page.ts

@@ -26,7 +26,7 @@ const debug = require('debug')('growi:services:page');
 
 const logger = loggerFactory('growi:services:page');
 const {
-  isCreatablePage, isTrashPage, isTopPage, isDeletablePage, omitDuplicateAreaPathFromPaths, omitDuplicateAreaPageFromPages,
+  isCreatablePage, isTrashPage, isTopPage, isDeletablePage, omitDuplicateAreaPageFromPages,
 } = pagePathUtils;
 
 const BULK_REINDEX_SIZE = 100;
@@ -95,7 +95,7 @@ class PageCursorsForDescendantsFactory {
 
     const builder = new PageQueryBuilder(this.Page.find(), this.shouldIncludeEmpty);
     builder.addConditionToFilteringByParentId(page._id);
-    await this.Page.addConditionToFilteringByViewerToEdit(builder, this.user);
+    // await this.Page.addConditionToFilteringByViewerToEdit(builder, this.user);
 
     const cursor = builder.query.lean().cursor({ batchSize: BULK_REINDEX_SIZE }) as QueryCursor<any>;
 
@@ -317,25 +317,34 @@ class PageService {
 
   async renamePage(page, newPagePath, user, options) {
     /*
-     * Main Operation
+     * Common Operation
      */
-    const Page = this.crowi.model('Page');
-
     if (isTopPage(page.path)) {
       throw Error('It is forbidden to rename the top page');
     }
 
-    // 1. Separate v4 & v5 process
+    // Separate v4 & v5 process
     const shouldUseV4Process = this.shouldUseV4Process(page);
     if (shouldUseV4Process) {
       return this.renamePageV4(page, newPagePath, user, options);
     }
 
+    /*
+     * Resumable Operation
+     */
+    const renamedPage = await this.renameMainOperation(page, newPagePath, user, options);
+
+    return renamedPage;
+  }
+
+  async renameMainOperation(page, newPagePath: string, user, options) {
+    const Page = mongoose.model('Page') as unknown as PageModel;
+
     const updateMetadata = options.updateMetadata || false;
     // sanitize path
     newPagePath = this.crowi.xss.process(newPagePath); // eslint-disable-line no-param-reassign
 
-    // 2. UserGroup & Owner validation
+    // UserGroup & Owner validation
     // use the parent's grant when target page is an empty page
     let grant;
     let grantedUserIds;
@@ -371,7 +380,7 @@ class PageService {
       }
     }
 
-    // 3. Rename target (update parent attr)
+    // Rename target (update parent attr)
     const update: Partial<IPage> = {};
     // find or create parent
     const newParent = await Page.getParentAndFillAncestors(newPagePath);
@@ -388,12 +397,12 @@ class PageService {
     /*
      * Sub Operation
      */
-    this.renameDescendantsSubOperation(page, newPagePath, user, options, renamedPage);
+    this.renameSubOperation(page, newPagePath, user, options, renamedPage);
 
     return renamedPage;
   }
 
-  async renameDescendantsSubOperation(page, newPagePath: string, user, options, renamedPage): Promise<void> {
+  async renameSubOperation(page, newPagePath: string, user, options, renamedPage): Promise<void> {
     const exParentId = page.parent;
 
     // update descendants first
@@ -660,11 +669,15 @@ class PageService {
    */
   async duplicate(page, newPagePath, user, isRecursively) {
     /*
-     * Main Operation
+     * Common Operation
      */
     const Page = mongoose.model('Page') as unknown as PageModel;
     const PageTagRelation = mongoose.model('PageTagRelation') as any; // TODO: Typescriptize model
 
+    if (isRecursively && page.isEmpty) {
+      throw Error('Page not found.');
+    }
+
     newPagePath = this.crowi.xss.process(newPagePath); // eslint-disable-line no-param-reassign
 
     // 1. Separate v4 & v5 process
@@ -738,15 +751,10 @@ class PageService {
     }
 
     if (isRecursively) {
-      (async() => {
-        const nDuplicatedPages = await this.duplicateDescendantsWithStream(page, newPagePath, user, false);
-        // END Main Operation
-
-        /*
-         * Sub Operation
-         */
-        await this.duplicateDescendantsSubOperation(page, newPagePath, user, duplicatedTarget, nDuplicatedPages);
-      })();
+      /*
+       * Resumable Operation
+       */
+      this.duplicateRecursivelyMainOperation(page, newPagePath, user);
     }
 
     const result = serializePageSecurely(duplicatedTarget);
@@ -754,7 +762,9 @@ class PageService {
     return result;
   }
 
-  async duplicateDescendantsSubOperation(page, newPagePath: string, user, duplicatedTarget, nDuplicatedPages: number): Promise<void> {
+  async duplicateRecursivelyMainOperation(page, newPagePath: string, user): Promise<void> {
+    const nDuplicatedPages = await this.duplicateDescendantsWithStream(page, newPagePath, user, false);
+
     // normalize parent of descendant pages
     const shouldNormalize = this.shouldNormalizeParent(page);
     if (shouldNormalize) {
@@ -767,7 +777,21 @@ class PageService {
         throw err;
       }
     }
-    await this.updateDescendantCountOfAncestors(duplicatedTarget._id, nDuplicatedPages, false);
+
+    /*
+     * Sub Operation
+     */
+    await this.duplicateRecursivelySubOperation(newPagePath, nDuplicatedPages);
+  }
+
+  async duplicateRecursivelySubOperation(newPagePath: string, nDuplicatedPages: number): Promise<void> {
+    const Page = mongoose.model('Page');
+    const newTarget = await Page.findOne({ path: newPagePath }); // only one page will be found since duplicating to existing path is forbidden
+    if (newTarget == null) {
+      throw Error('No duplicated page found. Something might have gone wrong in duplicateRecursivelyMainOperation.');
+    }
+
+    await this.updateDescendantCountOfAncestors(newTarget._id, nDuplicatedPages, false);
   }
 
   async duplicateV4(page, newPagePath, user, isRecursively) {
@@ -1036,11 +1060,10 @@ class PageService {
    */
   async deletePage(page, user, options = {}, isRecursively = false) {
     /*
-     * Main Operation
+     * Common Operation
      */
     const Page = mongoose.model('Page') as PageModel;
     const PageTagRelation = mongoose.model('PageTagRelation') as any; // TODO: Typescriptize model
-    const Revision = mongoose.model('Revision') as any; // TODO: Typescriptize model
     const PageRedirect = mongoose.model('PageRedirect') as unknown as PageRedirectModel;
 
     // 1. Separate v4 & v5 process
@@ -1049,8 +1072,9 @@ class PageService {
       return this.deletePageV4(page, user, options, isRecursively);
     }
 
-    const newPath = Page.getDeletedPageName(page.path);
-
+    if (page.isEmpty && !isRecursively) {
+      throw Error('Page not found.');
+    }
     const isTrashed = isTrashPage(page.path);
     if (isTrashed) {
       throw new Error('This method does NOT support deleting trashed pages.');
@@ -1059,6 +1083,7 @@ class PageService {
       throw new Error('Page is not deletable.');
     }
 
+    // prepare before deletion when !isRecursively
     if (!isRecursively) {
       // replace with an empty page
       const shouldReplace = await Page.exists({ parent: page._id });
@@ -1074,9 +1099,8 @@ class PageService {
     }
 
     let deletedPage;
-    // update Revisions
     if (!page.isEmpty) {
-      await Revision.updateRevisionListByPageId(page._id, { pageId: page._id });
+      const newPath = Page.getDeletedPageName(page.path);
       deletedPage = await Page.findByIdAndUpdate(page._id, {
         $set: {
           path: newPath, status: Page.STATUS_DELETED, deleteUser: user._id, deletedAt: Date.now(), parent: null, descendantCount: 0, // set parent as null
@@ -1092,24 +1116,31 @@ class PageService {
       this.pageEvent.emit('delete', page, user);
       this.pageEvent.emit('create', deletedPage, user);
     }
+    else if (page.isEmtpy && isRecursively) {
+      // delete target (empty page)
+      await Page.deleteOne({ _id: page._id, isEmpty: true });
+
+      // delete leaf empty pages
+      const parent = await Page.findById(page.parent);
+      await this.removeLeafEmptyPages(parent);
+
+      deletedPage = page;
+    }
 
     if (isRecursively) {
       /*
        * Sub Operation
        */
-      this.deletePageDescendantsSubOperation(page, user);
+      this.deleteDescendantsMainOperation(page, user);
     }
 
     return deletedPage;
   }
 
-  async deletePageDescendantsSubOperation(page, user): Promise<void> {
-    const deletedDescendantCount = await this.deleteDescendantsWithStream(page, user, false);
+  async deleteDescendantsMainOperation(page, user): Promise<void> {
+    await this.deleteDescendantsWithStream(page, user, false);
 
-    // update descendantCount of ancestors'
-    if (page.parent != null) {
-      await this.updateDescendantCountOfAncestors(page.parent, (deletedDescendantCount + 1) * -1, true);
-    }
+    // no sub operation available
   }
 
   private async deletePageV4(page, user, options = {}, isRecursively = false) {