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

Merge branch 'imprv/refacter-rename-recursively' into imprv/refacter-recursively

itizawa 5 лет назад
Родитель
Сommit
745699df55

+ 3 - 0
src/server/events/page.js

@@ -15,5 +15,8 @@ PageEvent.prototype.onCreate = function(page, user) {
 PageEvent.prototype.onUpdate = function(page, user) {
 PageEvent.prototype.onUpdate = function(page, user) {
   debug('onUpdate event fired');
   debug('onUpdate event fired');
 };
 };
+PageEvent.prototype.onCreateMany = function(pages, user) {
+  debug('onCreateMany event fired');
+};
 
 
 module.exports = PageEvent;
 module.exports = PageEvent;

+ 49 - 12
src/server/models/page.js

@@ -293,6 +293,7 @@ module.exports = function(crowi) {
     pageEvent = crowi.event('page');
     pageEvent = crowi.event('page');
     pageEvent.on('create', pageEvent.onCreate);
     pageEvent.on('create', pageEvent.onCreate);
     pageEvent.on('update', pageEvent.onUpdate);
     pageEvent.on('update', pageEvent.onUpdate);
+    pageEvent.on('createMany', pageEvent.onCreateMany);
   }
   }
 
 
   function validateCrowi() {
   function validateCrowi() {
@@ -1128,17 +1129,16 @@ module.exports = function(crowi) {
 
 
   pageSchema.statics.deletePageRecursively = async function(targetPage, user, options = {}) {
   pageSchema.statics.deletePageRecursively = async function(targetPage, user, options = {}) {
     const isTrashed = checkIfTrashed(targetPage.path);
     const isTrashed = checkIfTrashed(targetPage.path);
-
+    const newPath = this.getDeletedPageName(targetPage.path);
     if (isTrashed) {
     if (isTrashed) {
       throw new Error('This method does NOT supports deleting trashed pages.');
       throw new Error('This method does NOT supports deleting trashed pages.');
     }
     }
 
 
-    // find manageable descendants (this array does not include GRANT_RESTRICTED)
-    const pages = await this.findManageableListWithDescendants(targetPage, user, options);
-
-    await Promise.all(pages.map((page) => {
-      return this.deletePage(page, user, options);
-    }));
+    const socketClientId = options.socketClientId || null;
+    if (this.isDeletableName(targetPage.path)) {
+      targetPage.status = STATUS_DELETED;
+    }
+    return await this.renameRecursively(targetPage, newPath, user, { socketClientId, createRedirectPage: true });
   };
   };
 
 
 
 
@@ -1201,7 +1201,7 @@ module.exports = function(crowi) {
       await Page.create(path, body, user, { redirectTo: newPagePath });
       await Page.create(path, body, user, { redirectTo: newPagePath });
     }
     }
 
 
-    pageEvent.emit('delete', pageData, user, socketClientId);
+    pageEvent.emit('delete', [pageData], user, socketClientId);
     pageEvent.emit('create', updatedPageData, user, socketClientId);
     pageEvent.emit('create', updatedPageData, user, socketClientId);
 
 
     return updatedPageData;
     return updatedPageData;
@@ -1210,8 +1210,12 @@ module.exports = function(crowi) {
   pageSchema.statics.renameRecursively = async function(targetPage, newPagePathPrefix, user, options) {
   pageSchema.statics.renameRecursively = async function(targetPage, newPagePathPrefix, user, options) {
     validateCrowi();
     validateCrowi();
 
 
+    const pageCollection = mongoose.connection.collection('pages');
+    const revisionCollection = mongoose.connection.collection('revisions');
+
     const path = targetPage.path;
     const path = targetPage.path;
     const pathRegExp = new RegExp(`^${escapeStringRegexp(path)}`, 'i');
     const pathRegExp = new RegExp(`^${escapeStringRegexp(path)}`, 'i');
+    const { updateMetadata, createRedirectPage } = options;
 
 
     // sanitize path
     // sanitize path
     newPagePathPrefix = crowi.xss.process(newPagePathPrefix); // eslint-disable-line no-param-reassign
     newPagePathPrefix = crowi.xss.process(newPagePathPrefix); // eslint-disable-line no-param-reassign
@@ -1219,13 +1223,46 @@ module.exports = function(crowi) {
     // find manageable descendants
     // find manageable descendants
     const pages = await this.findManageableListWithDescendants(targetPage, user, options);
     const pages = await this.findManageableListWithDescendants(targetPage, user, options);
 
 
-    // TODO GW-4634 use stream
-    const promise = pages.map((page) => {
+    const unorderedBulkOp = pageCollection.initializeUnorderedBulkOp();
+    const createRediectPageBulkOp = pageCollection.initializeUnorderedBulkOp();
+    const revisionUnorderedBulkOp = revisionCollection.initializeUnorderedBulkOp();
+
+    pages.forEach((page) => {
       const newPagePath = page.path.replace(pathRegExp, newPagePathPrefix);
       const newPagePath = page.path.replace(pathRegExp, newPagePathPrefix);
-      return this.rename(page, newPagePath, user, options);
+      if (updateMetadata) {
+        unorderedBulkOp.find({ _id: page._id }).update([{ $set: { path: newPagePath, lastUpdateUser: user._id, updatedAt: { $toDate: Date.now() } } }]);
+      }
+      else {
+        unorderedBulkOp.find({ _id: page._id }).update({ $set: { path: newPagePath } });
+      }
+      if (createRedirectPage) {
+        createRediectPageBulkOp.insert({
+          path: page.path, body: `redirect ${newPagePath}`, creator: user, lastUpdateUser: user, status: STATUS_PUBLISHED, redirectTo: newPagePath,
+        });
+      }
+      revisionUnorderedBulkOp.find({ path: page.path }).update({ $set: { path: newPagePath } }, { multi: true });
     });
     });
 
 
-    await Promise.allSettled(promise);
+    try {
+      await unorderedBulkOp.execute();
+      await revisionUnorderedBulkOp.execute();
+    }
+    catch (err) {
+      if (err.code !== 11000) {
+        throw new Error('Failed to rename pages: ', err);
+      }
+    }
+
+    const newParentPath = path.replace(pathRegExp, newPagePathPrefix);
+    const newParentPage = await this.findByPath(newParentPath);
+    const renamedPages = await this.findManageableListWithDescendants(newParentPage, user, options);
+
+    pageEvent.emit('createMany', renamedPages, user, newParentPage);
+
+    // Execute after unorderedBulkOp to prevent duplication
+    if (createRedirectPage) {
+      await createRediectPageBulkOp.execute();
+    }
 
 
     targetPage.path = newPagePathPrefix;
     targetPage.path = newPagePathPrefix;
     return targetPage;
     return targetPage;

+ 31 - 0
src/server/service/search-delegator/elasticsearch.js

@@ -352,6 +352,14 @@ class ElasticsearchDelegator {
     return this.updateOrInsertPages(() => Page.findById(pageId));
     return this.updateOrInsertPages(() => Page.findById(pageId));
   }
   }
 
 
+  updateOrInsertDescendantsPagesById(page, user) {
+    const Page = mongoose.model('Page');
+    const { PageQueryBuilder } = Page;
+    const builder = new PageQueryBuilder(Page.find());
+    builder.addConditionToListWithDescendants(page.path);
+    return this.updateOrInsertPages(() => builder.query);
+  }
+
   /**
   /**
    * @param {function} queryFactory factory method to generate a Mongoose Query instance
    * @param {function} queryFactory factory method to generate a Mongoose Query instance
    */
    */
@@ -959,6 +967,29 @@ class ElasticsearchDelegator {
     return this.updateOrInsertPageById(page._id);
     return this.updateOrInsertPageById(page._id);
   }
   }
 
 
+  async syncDescendantsPagesUpdated(pages, user, parentPage) {
+
+    const shoudDeletePages = [];
+    pages.forEach((page) => {
+      logger.debug('SearchClient.syncPageUpdated', page.path);
+      if (!this.shouldIndexed(page)) {
+        shoudDeletePages.append(page);
+      }
+    });
+
+    // delete if page should not indexed
+    try {
+      if (shoudDeletePages.length !== 0) {
+        await this.deletePages(shoudDeletePages);
+      }
+    }
+    catch (err) {
+      logger.error('deletePages:ES Error', err);
+    }
+
+    return this.updateOrInsertDescendantsPagesById(parentPage, user);
+  }
+
   async syncPagesDeletedCompletely(pages, user) {
   async syncPagesDeletedCompletely(pages, user) {
     for (let i = 0; i < pages.length; i++) {
     for (let i = 0; i < pages.length; i++) {
       logger.debug('SearchClient.syncPageDeleted', pages[i].path);
       logger.debug('SearchClient.syncPageDeleted', pages[i].path);

+ 1 - 0
src/server/service/search.js

@@ -60,6 +60,7 @@ class SearchService {
     pageEvent.on('update', this.delegator.syncPageUpdated.bind(this.delegator));
     pageEvent.on('update', this.delegator.syncPageUpdated.bind(this.delegator));
     pageEvent.on('deleteCompletely', this.delegator.syncPagesDeletedCompletely.bind(this.delegator));
     pageEvent.on('deleteCompletely', this.delegator.syncPagesDeletedCompletely.bind(this.delegator));
     pageEvent.on('delete', this.delegator.syncPageDeleted.bind(this.delegator));
     pageEvent.on('delete', this.delegator.syncPageDeleted.bind(this.delegator));
+    pageEvent.on('createMany', this.delegator.syncDescendantsPagesUpdated.bind(this.delegator));
 
 
     const bookmarkEvent = this.crowi.event('bookmark');
     const bookmarkEvent = this.crowi.event('bookmark');
     bookmarkEvent.on('create', this.delegator.syncBookmarkChanged.bind(this.delegator));
     bookmarkEvent.on('create', this.delegator.syncBookmarkChanged.bind(this.delegator));