mizozobu 6 лет назад
Родитель
Сommit
91ae70a677
2 измененных файлов с 97 добавлено и 6 удалено
  1. 8 1
      src/server/routes/apiv3/import.js
  2. 89 5
      src/server/service/import.js

+ 8 - 1
src/server/routes/apiv3/import.js

@@ -41,7 +41,14 @@ module.exports = (crowi) => {
     const zipFilePath = path.join(file.destination, file.filename);
 
     try {
-      await importService.importFromZip(Page, zipFilePath);
+      let overwriteOption;
+      const overwriteOptionFn = importService.getOverwriteOption(Page);
+      // await in case overwriteOption is a Promise
+      if (overwriteOptionFn != null) {
+        overwriteOption = await overwriteOptionFn(req);
+      }
+
+      await importService.importFromZip(Page, zipFilePath, overwriteOption);
 
       // TODO: use res.apiv3
       return res.send({ status: 'OK' });

+ 89 - 5
src/server/service/import.js

@@ -1,6 +1,7 @@
 const logger = require('@alias/logger')('growi:services:ImportService'); // eslint-disable-line no-unused-vars
 const fs = require('fs');
 const path = require('path');
+const mongoose = require('mongoose');
 const JSONStream = require('JSONStream');
 const streamToPromise = require('stream-to-promise');
 
@@ -11,6 +12,44 @@ class ImportService {
     this.encoding = 'utf-8';
     this.per = 100;
     this.zlibLevel = 9; // 0(min) - 9(max)
+
+    const { ObjectId } = require('mongoose').Types;
+    const keepOriginal = v => v;
+    const toObjectId = v => ObjectId(v);
+    // each key accepts either function or hardcoded value
+    // to filter out an attribute, try "[key]: undefined" or unlisting the key
+    this.attrMap = {
+      pages: {
+        _id: toObjectId,
+        path: keepOriginal,
+        revision: toObjectId,
+        redirectTo: keepOriginal,
+        status: 'published', // FIXME when importing users and user groups
+        grant: 1, // FIXME when importing users and user groups
+        grantedUsers: [], // FIXME when importing users and user groups
+        grantedGroup: null, // FIXME when importing users and user groups
+        // creator: keepOriginal, // FIXME when importing users
+        // lastUpdateUser: keepOriginal, // FIXME when importing users
+        liker: [], // FIXME when importing users
+        seenUsers: [], // FIXME when importing users
+        commentCount: 0, // FIXME when importing comments
+        extended: {}, // FIXME when ?
+        // pageIdOnHackmd: keepOriginal, // FIXME when importing hackmd?
+        // revisionHackmdSynced: keepOriginal, // FIXME when importing hackmd?
+        // hasDraftOnHackmd: keepOriginal, // FIXME when importing hackmd?
+        createdAt: keepOriginal,
+        updatedAt: keepOriginal,
+      },
+    };
+
+    this.overwriteOption = {
+      pages: (req) => {
+        return {
+          creator: mongoose.Types.ObjectId(req.user._id),
+          lastUpdateUser: mongoose.Types.ObjectId(req.user._id),
+        };
+      },
+    };
   }
 
   /**
@@ -19,8 +58,9 @@ class ImportService {
    * @memberOf ImportService
    * @param {object} Model instance of mongoose model
    * @param {string} filePath path to zipped json
+   * @param {object} overwriteOption overwrite each document with unrelated value. e.g. { creator: req.user }
    */
-  async importFromZip(Model, filePath) {
+  async importFromZip(Model, filePath, overwriteOption = {}) {
     const { collectionName } = Model.collection;
     let counter = 0;
     let nInsertedTotal = 0;
@@ -28,13 +68,12 @@ class ImportService {
     let failedIds = [];
     let unorderedBulkOp = Model.collection.initializeUnorderedBulkOp();
 
-
-    const readStream = fs.createReadStream(path.join(this.baseDir, 'pages.json'));
+    const readStream = fs.createReadStream(path.join(this.baseDir, 'pages.json')); // FIXME
     const jsonStream = readStream.pipe(JSONStream.parse('*'));
 
     jsonStream.on('data', async(document) => {
       // documents are not persisted until unorderedBulkOp.execute()
-      unorderedBulkOp.insert(document);
+      unorderedBulkOp.insert(this.convertDocuments(Model, document, overwriteOption));
 
       counter++;
 
@@ -78,7 +117,6 @@ class ImportService {
    *
    * @memberOf ImportService
    * @param {object} unorderedBulkOp result of Model.collection.initializeUnorderedBulkOp()
-   * @param {string} filePath path to zipped json
    * @return {{nInserted: number, failed: string[]}} number of docuemnts inserted and failed
    */
   async execUnorderedBulkOpSafely(unorderedBulkOp) {
@@ -108,6 +146,52 @@ class ImportService {
     };
   }
 
+  /**
+   * execute unorderedBulkOp and ignore errors
+   *
+   * @memberOf ImportService
+   * @param {object} Model instance of mongoose model
+   * @param {object} _document document being imported
+   * @param {object} overwriteOption overwrite each document with unrelated value. e.g. { creator: req.user }
+   * @return {object} document to be persisted
+   */
+  convertDocuments(Model, _document, overwriteOption) {
+    const attrMap = this.attrMap[Model.collection.collectionName];
+    if (attrMap == null) {
+      throw new Error(`attribute map is not defined for ${Model.collection.collectionName}`);
+    }
+
+    const document = {};
+
+    // generate value from documents being imported
+    for (const entry of Object.entries(attrMap)) {
+      const key = entry[0];
+      const value = entry[1];
+
+      // distinguish between null and undefined
+      if (_document[key] !== undefined) {
+        document[key] = (typeof value === 'function') ? value(_document[key]) : value;
+      }
+    }
+
+    // overwrite documents
+    for (const entry of Object.entries(overwriteOption)) {
+      const key = entry[0];
+      const value = entry[1];
+
+      // distinguish between null and undefined
+      if (_document[key] !== undefined) {
+        document[key] = (typeof value === 'function') ? value(_document[key]) : value;
+      }
+    }
+
+    return document;
+  }
+
+  getOverwriteOption(Model) {
+    return this.overwriteOption[Model.collection.collectionName];
+  }
+
 }
 
 module.exports = ImportService;