|
|
@@ -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;
|