Browse Source

Merge pull request #7811 from weseek/imprv/105325-124592-can-be-deleted-even-on-the-grant-pages

imprv: Can be deleted even on the grant pages
Yuki Takei 2 years ago
parent
commit
f02af3ccb1

+ 1 - 1
apps/app/src/server/events/user.ts

@@ -27,7 +27,7 @@ class UserEvent extends EventEmitter {
 
     let page = await Page.findByPath(userHomepagePath, true);
 
-    if (page !== null && page.creator.toString() !== user._id.toString()) {
+    if (page != null && page.creator != null && page.creator.toString() !== user._id.toString()) {
       await this.crowi.pageService.deleteCompletelyUserHomeBySystem(userHomepagePath);
       page = null;
     }

+ 23 - 0
apps/app/src/server/models/page.ts

@@ -377,6 +377,12 @@ export class PageQueryBuilder {
     return this;
   }
 
+  addConditionForSystemDeletion(): PageQueryBuilder {
+    const condition = generateGrantConditionForSystemDeletion();
+    this.query = this.query.and(condition);
+    return this;
+  }
+
   addConditionToPagenate(offset, limit, sortOpt?): PageQueryBuilder {
     this.query = this.query
       .sort(sortOpt).skip(offset).limit(limit); // eslint-disable-line newline-per-chained-call
@@ -943,6 +949,23 @@ export function generateGrantCondition(
 
 schema.statics.generateGrantCondition = generateGrantCondition;
 
+function generateGrantConditionForSystemDeletion(): { $or: any[] } {
+  const grantCondition: AnyObject[] = [
+    { grant: null },
+    { grant: GRANT_PUBLIC },
+    { grant: GRANT_RESTRICTED },
+    { grant: GRANT_SPECIFIED },
+    { grant: GRANT_OWNER },
+    { grant: GRANT_USER_GROUP },
+  ];
+
+  return {
+    $or: grantCondition,
+  };
+}
+
+schema.statics.generateGrantConditionForSystemDeletion = generateGrantConditionForSystemDeletion;
+
 // find ancestor page with isEmpty: false. If parameter path is '/', return undefined
 schema.statics.findNonEmptyClosestAncestor = async function(path: string): Promise<PageDocument | undefined> {
   if (path === '/') {

+ 60 - 25
apps/app/src/server/service/page.ts

@@ -1962,8 +1962,6 @@ class PageService {
     }
   }
 
-  // TODO: Can be deleted even on the grant pages
-  // see: https://redmine.weseek.co.jp/issues/124592
   /**
    * @description This function is intended to be used exclusively for forcibly deleting the user homepage by the system.
    * It should only be called from within the appropriate context and with caution as it performs a system-level operation.
@@ -1975,46 +1973,83 @@ class PageService {
   async deleteCompletelyUserHomeBySystem(userHomepagePath: string): Promise<void> {
     const Page = this.crowi.model('Page');
     const userHomepage = await Page.findByPath(userHomepagePath, true);
-    const options = {};
 
     if (userHomepage == null) {
       logger.error('user homepage is not found.');
       return;
     }
 
+    const shouldUseV4Process = this.shouldUseV4Process(userHomepage);
+
     const ids = [userHomepage._id];
     const paths = [userHomepage.path];
 
-    let pageOp;
     try {
-      // 1. update descendantCount
-      const inc = userHomepage.isEmpty ? -userHomepage.descendantCount : -(userHomepage.descendantCount + 1);
-      await this.updateDescendantCountOfAncestors(userHomepage.parent, inc, true);
-      // 2. delete target completely
+      if (!shouldUseV4Process) {
+        // Ensure consistency of ancestors
+        const inc = userHomepage.isEmpty ? -userHomepage.descendantCount : -(userHomepage.descendantCount + 1);
+        await this.updateDescendantCountOfAncestors(userHomepage.parent, inc, true);
+      }
+
+      // Delete the user's homepage
       await this.deleteCompletelyOperation(ids, paths);
-      // 3. delete leaf empty pages
-      await Page.removeLeafEmptyPagesRecursively(userHomepage.parent);
 
-      // if (!userHomepage.isEmpty) {
-      //   this.pageEvent.emit('deleteCompletely', userHomepage, user);
-      // }
+      if (!shouldUseV4Process) {
+        // Remove leaf empty pages
+        await Page.removeLeafEmptyPagesRecursively(userHomepage.parent);
+      }
+
+      if (!userHomepage.isEmpty) {
+        // Emit an event for the search service
+        this.pageEvent.emit('deleteCompletely', userHomepage);
+      }
+
+      const { PageQueryBuilder } = Page;
+
+      // Find descendant pages with system deletion condition
+      const builder = new PageQueryBuilder(Page.find(), true)
+        .addConditionForSystemDeletion()
+        .addConditionToListOnlyDescendants(userHomepage.path);
+
+      // Stream processing to delete descendant pages
+      // ────────┤ start │─────────
+      const readStream = await builder
+        .query
+        .lean()
+        .cursor({ batchSize: BULK_REINDEX_SIZE });
+
+      let count = 0;
+
+      const deleteMultipleCompletely = this.deleteMultipleCompletely.bind(this);
+      const writeStream = new Writable({
+        objectMode: true,
+        async write(batch, encoding, callback) {
+          try {
+            count += batch.length;
+            // Delete multiple pages completely
+            await deleteMultipleCompletely(batch, null, {});
+            logger.debug(`Adding pages progressing: (count=${count})`);
+          }
+          catch (err) {
+            logger.error('addAllPages error on add anyway: ', err);
+          }
+          callback();
+        },
+        final(callback) {
+          logger.debug(`Adding pages has completed: (totalCount=${count})`);
+          callback();
+        },
+      });
 
-      // pageOp = await PageOperation.create({
-      //   actionType: PageActionType.DeleteCompletely,
-      //   actionStage: PageActionStage.Main,
-      //   page: userHomepage,
-      //   user,
-      //   fromPath: userHomepage.path,
-      //   options,
-      // });
+      readStream
+        .pipe(createBatchStream(BULK_REINDEX_SIZE))
+        .pipe(writeStream);
 
-      // await this.deleteCompletelyRecursivelyMainOperation(userHomepage, user, options, pageOp._id);
+      await streamToPromise(writeStream);
+      // ────────┤ end │─────────
     }
     catch (err) {
       logger.error('Error occurred while deleting user homepage and subpages.', err);
-      // if (pageOp != null) {
-      //   await PageOperation.deleteOne({ _id: pageOp._id });
-      // }
       throw err;
     }
   }