Explorar o código

Merge branch 'feat/page-rename-v5' into imprv/duplicate-single-page

Taichi Masuyama %!s(int64=4) %!d(string=hai) anos
pai
achega
e2cf4de13f

+ 107 - 0
packages/app/src/migrations/20211227060705-revision-path-to-page-id-schema-migration.js

@@ -0,0 +1,107 @@
+import mongoose from 'mongoose';
+import { Writable } from 'stream';
+import streamToPromise from 'stream-to-promise';
+
+import { getModelSafely, getMongoUri, mongoOptions } from '@growi/core';
+import loggerFactory from '~/utils/logger';
+import getPageModel from '~/server/models/page';
+import { createBatchStream } from '~/server/util/batch-stream';
+
+
+const logger = loggerFactory('growi:migrate:revision-path-to-page-id-schema-migration');
+
+const LIMIT = 300;
+
+module.exports = {
+  // path => pageId
+  async up(db, client) {
+    mongoose.connect(getMongoUri(), mongoOptions);
+    const Page = getModelSafely('Page') || getPageModel();
+    const Revision = getModelSafely('Revision') || require('~/server/models/revision')();
+
+    const pagesStream = await Page.find({ revision: { $ne: null } }, { _id: 1, revision: 1 }).cursor({ batch_size: LIMIT });
+    const batchStrem = createBatchStream(LIMIT);
+
+    const migratePagesStream = new Writable({
+      objectMode: true,
+      async write(pages, encoding, callback) {
+        const updateManyOperations = pages.map((page) => {
+          return {
+            updateMany: {
+              filter: { _id: page.revision },
+              update: [
+                {
+                  $unset: ['path'],
+                },
+                {
+                  $set: { pageId: page._id },
+                },
+              ],
+            },
+          };
+        });
+
+        await Revision.bulkWrite(updateManyOperations);
+
+        callback();
+      },
+      final(callback) {
+        callback();
+      },
+    });
+
+    pagesStream
+      .pipe(batchStrem)
+      .pipe(migratePagesStream);
+
+    await streamToPromise(migratePagesStream);
+
+    logger.info('Migration has successfully applied');
+  },
+
+  // pageId => path
+  async down(db, client) {
+    mongoose.connect(getMongoUri(), mongoOptions);
+    const Page = getModelSafely('Page') || getPageModel();
+    const Revision = getModelSafely('Revision') || require('~/server/models/revision')();
+
+    const pagesStream = await Page.find({ revision: { $ne: null } }, { _id: 1, revision: 1, path: 1 }).cursor({ batch_size: LIMIT });
+    const batchStrem = createBatchStream(LIMIT);
+
+    const migratePagesStream = new Writable({
+      objectMode: true,
+      async write(pages, encoding, callback) {
+        const updateManyOperations = pages.map((page) => {
+          return {
+            updateMany: {
+              filter: { _id: page.revision },
+              update: [
+                {
+                  $unset: ['pageId'],
+                },
+                {
+                  $set: { path: page.path },
+                },
+              ],
+            },
+          };
+        });
+
+        await Revision.bulkWrite(updateManyOperations);
+
+        callback();
+      },
+      final(callback) {
+        callback();
+      },
+    });
+
+    pagesStream
+      .pipe(batchStrem)
+      .pipe(migratePagesStream);
+
+    await streamToPromise(migratePagesStream);
+
+    logger.info('Migration down has successfully applied');
+  },
+};

+ 5 - 31
packages/app/src/server/models/revision.js

@@ -12,7 +12,8 @@ module.exports = function(crowi) {
 
   const ObjectId = mongoose.Schema.Types.ObjectId;
   const revisionSchema = new mongoose.Schema({
-    path: { type: String, required: true, index: true },
+    // OBSOLETE path: { type: String, required: true, index: true }
+    pageId: { type: ObjectId, required: true, index: true },
     body: {
       type: String,
       required: true,
@@ -36,18 +37,10 @@ module.exports = function(crowi) {
       .exec();
   };
 
-  revisionSchema.statics.updateRevisionListByPath = function(path, updateData, options) {
+  revisionSchema.statics.updateRevisionListByPath = async function(path, updateData) {
     const Revision = this;
 
-    return new Promise(((resolve, reject) => {
-      Revision.update({ path }, { $set: updateData }, { multi: true }, (err, data) => {
-        if (err) {
-          return reject(err);
-        }
-
-        return resolve(data);
-      });
-    }));
+    return Revision.updateMany({ path }, { $set: updateData });
   };
 
   revisionSchema.statics.prepareRevision = function(pageData, body, previousBody, user, options) {
@@ -64,7 +57,7 @@ module.exports = function(crowi) {
     }
 
     const newRevision = new Revision();
-    newRevision.path = pageData.path;
+    newRevision.pageId = pageData._id;
     newRevision.body = body;
     newRevision.format = format;
     newRevision.author = user._id;
@@ -76,24 +69,5 @@ module.exports = function(crowi) {
     return newRevision;
   };
 
-  revisionSchema.statics.removeRevisionsByPath = function(path) {
-    const Revision = this;
-
-    return new Promise(((resolve, reject) => {
-      Revision.remove({ path }, (err, data) => {
-        if (err) {
-          return reject(err);
-        }
-
-        return resolve(data);
-      });
-    }));
-  };
-
-  revisionSchema.statics.findAuthorsByPage = async function(page) {
-    const result = await this.distinct('author', { path: page.path }).exec();
-    return result;
-  };
-
   return mongoose.model('Revision', revisionSchema);
 };

+ 5 - 2
packages/app/src/server/service/page.ts

@@ -19,7 +19,7 @@ const debug = require('debug')('growi:services:page');
 
 const logger = loggerFactory('growi:services:page');
 const {
-  isCreatablePage, isDeletablePage, isTrashPage, collectAncestorPaths,
+  isCreatablePage, isDeletablePage, isTrashPage, collectAncestorPaths, isTopPage,
 } = pagePathUtils;
 
 const BULK_REINDEX_SIZE = 100;
@@ -199,7 +199,10 @@ class PageService {
     // v4 compatible process
     const isPageMigrated = page.parent != null;
     const isV5Compatible = this.crowi.configManager.getConfig('crowi', 'app:isV5Compatible');
-    if (!isV5Compatible || !isPageMigrated) {
+    const isRoot = isTopPage(page.path);
+    const isPageRestricted = page.grant === Page.GRANT_RESTRICTED;
+    const shouldUseV4Process = !isV5Compatible || !isPageMigrated || !isRoot || isPageRestricted;
+    if (shouldUseV4Process) {
       return this.renamePageV4(page, newPagePath, user, options);
     }