mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-10 23:36:14 -05:00
Converted import-manager module to a class
refs https://github.com/TryGhost/Team/issues/694 refs https://linear.app/tryghost/issue/CORE-16/tackle-importersdataindexjs - The codebase uses class syntax instead of extending/instantiating a native function (this is a very old of doing pseudo OOP in JS). Updated the old syntax in a very one-to-one brainless way with intention to improve the file again when touched again
This commit is contained in:
parent
c39d1996a4
commit
7589218abc
1 changed files with 75 additions and 56 deletions
|
@ -29,80 +29,80 @@ defaults = {
|
||||||
directories: []
|
directories: []
|
||||||
};
|
};
|
||||||
|
|
||||||
function ImportManager() {
|
class ImportManager {
|
||||||
this.importers = [ImageImporter, DataImporter];
|
constructor() {
|
||||||
this.handlers = [ImageHandler, JSONHandler, MarkdownHandler];
|
this.importers = [ImageImporter, DataImporter];
|
||||||
// Keep track of file to cleanup at the end
|
this.handlers = [ImageHandler, JSONHandler, MarkdownHandler];
|
||||||
this.fileToDelete = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// Keep track of file to cleanup at the end
|
||||||
* A number, or a string containing a number.
|
this.fileToDelete = null;
|
||||||
* @typedef {Object} ImportData
|
}
|
||||||
* @property [Object] data
|
|
||||||
* @property [Array] images
|
|
||||||
*/
|
|
||||||
|
|
||||||
_.extend(ImportManager.prototype, {
|
|
||||||
/**
|
/**
|
||||||
* Get an array of all the file extensions for which we have handlers
|
* Get an array of all the file extensions for which we have handlers
|
||||||
* @returns {string[]}
|
* @returns {string[]}
|
||||||
*/
|
*/
|
||||||
getExtensions: function () {
|
getExtensions() {
|
||||||
return _.flatten(_.union(_.map(this.handlers, 'extensions'), defaults.extensions));
|
return _.flatten(_.union(_.map(this.handlers, 'extensions'), defaults.extensions));
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get an array of all the mime types for which we have handlers
|
* Get an array of all the mime types for which we have handlers
|
||||||
* @returns {string[]}
|
* @returns {string[]}
|
||||||
*/
|
*/
|
||||||
getContentTypes: function () {
|
getContentTypes() {
|
||||||
return _.flatten(_.union(_.map(this.handlers, 'contentTypes'), defaults.contentTypes));
|
return _.flatten(_.union(_.map(this.handlers, 'contentTypes'), defaults.contentTypes));
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get an array of directories for which we have handlers
|
* Get an array of directories for which we have handlers
|
||||||
* @returns {string[]}
|
* @returns {string[]}
|
||||||
*/
|
*/
|
||||||
getDirectories: function () {
|
getDirectories() {
|
||||||
return _.flatten(_.union(_.map(this.handlers, 'directories'), defaults.directories));
|
return _.flatten(_.union(_.map(this.handlers, 'directories'), defaults.directories));
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert items into a glob string
|
* Convert items into a glob string
|
||||||
* @param {String[]} items
|
* @param {String[]} items
|
||||||
* @returns {String}
|
* @returns {String}
|
||||||
*/
|
*/
|
||||||
getGlobPattern: function (items) {
|
getGlobPattern(items) {
|
||||||
return '+(' + _.reduce(items, function (memo, ext) {
|
return '+(' + _.reduce(items, function (memo, ext) {
|
||||||
return memo !== '' ? memo + '|' + ext : ext;
|
return memo !== '' ? memo + '|' + ext : ext;
|
||||||
}, '') + ')';
|
}, '') + ')';
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {String[]} extensions
|
* @param {String[]} extensions
|
||||||
* @param {Number} level
|
* @param {Number} level
|
||||||
* @returns {String}
|
* @returns {String}
|
||||||
*/
|
*/
|
||||||
getExtensionGlob: function (extensions, level) {
|
getExtensionGlob(extensions, level) {
|
||||||
const prefix = level === ALL_DIRS ? '**/*' :
|
const prefix = level === ALL_DIRS ? '**/*' :
|
||||||
(level === ROOT_OR_SINGLE_DIR ? '{*/*,*}' : '*');
|
(level === ROOT_OR_SINGLE_DIR ? '{*/*,*}' : '*');
|
||||||
|
|
||||||
return prefix + this.getGlobPattern(extensions);
|
return prefix + this.getGlobPattern(extensions);
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {String[]} directories
|
* @param {String[]} directories
|
||||||
* @param {Number} level
|
* @param {Number} level
|
||||||
* @returns {String}
|
* @returns {String}
|
||||||
*/
|
*/
|
||||||
getDirectoryGlob: function (directories, level) {
|
getDirectoryGlob(directories, level) {
|
||||||
const prefix = level === ALL_DIRS ? '**/' :
|
const prefix = level === ALL_DIRS ? '**/' :
|
||||||
(level === ROOT_OR_SINGLE_DIR ? '{*/,}' : '');
|
(level === ROOT_OR_SINGLE_DIR ? '{*/,}' : '');
|
||||||
|
|
||||||
return prefix + this.getGlobPattern(directories);
|
return prefix + this.getGlobPattern(directories);
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove files after we're done (abstracted into a function for easier testing)
|
* Remove files after we're done (abstracted into a function for easier testing)
|
||||||
* @returns {Function}
|
* @returns {Function}
|
||||||
*/
|
*/
|
||||||
cleanUp: function () {
|
cleanUp() {
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
if (self.fileToDelete === null) {
|
if (self.fileToDelete === null) {
|
||||||
|
@ -120,14 +120,16 @@ _.extend(ImportManager.prototype, {
|
||||||
|
|
||||||
self.fileToDelete = null;
|
self.fileToDelete = null;
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true if the given file is a Zip
|
* Return true if the given file is a Zip
|
||||||
* @returns Boolean
|
* @returns Boolean
|
||||||
*/
|
*/
|
||||||
isZip: function (ext) {
|
isZip(ext) {
|
||||||
return _.includes(defaults.extensions, ext);
|
return _.includes(defaults.extensions, ext);
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks the content of a zip folder to see if it is valid.
|
* Checks the content of a zip folder to see if it is valid.
|
||||||
* Importable content includes any files or directories which the handlers can process
|
* Importable content includes any files or directories which the handlers can process
|
||||||
|
@ -136,7 +138,7 @@ _.extend(ImportManager.prototype, {
|
||||||
* @param {String} directory
|
* @param {String} directory
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
isValidZip: function (directory) {
|
isValidZip(directory) {
|
||||||
// Globs match content in the root or inside a single directory
|
// Globs match content in the root or inside a single directory
|
||||||
const extMatchesBase = glob.sync(this.getExtensionGlob(this.getExtensions(), ROOT_OR_SINGLE_DIR), {cwd: directory});
|
const extMatchesBase = glob.sync(this.getExtensionGlob(this.getExtensions(), ROOT_OR_SINGLE_DIR), {cwd: directory});
|
||||||
|
|
||||||
|
@ -166,20 +168,22 @@ _.extend(ImportManager.prototype, {
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new errors.UnsupportedMediaTypeError({message: i18n.t('errors.data.importer.index.invalidZipStructure')});
|
throw new errors.UnsupportedMediaTypeError({message: i18n.t('errors.data.importer.index.invalidZipStructure')});
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use the extract module to extract the given zip file to a temp directory & return the temp directory path
|
* Use the extract module to extract the given zip file to a temp directory & return the temp directory path
|
||||||
* @param {String} filePath
|
* @param {String} filePath
|
||||||
* @returns {Promise[]} Files
|
* @returns {Promise[]} Files
|
||||||
*/
|
*/
|
||||||
extractZip: function (filePath) {
|
extractZip(filePath) {
|
||||||
const tmpDir = path.join(os.tmpdir(), uuid.v4());
|
const tmpDir = path.join(os.tmpdir(), uuid.v4());
|
||||||
this.fileToDelete = tmpDir;
|
this.fileToDelete = tmpDir;
|
||||||
|
|
||||||
return extract(filePath, tmpDir).then(function () {
|
return extract(filePath, tmpDir).then(function () {
|
||||||
return tmpDir;
|
return tmpDir;
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use the handler extensions to get a globbing pattern, then use that to fetch all the files from the zip which
|
* Use the handler extensions to get a globbing pattern, then use that to fetch all the files from the zip which
|
||||||
* are relevant to the given handler, and return them as a name and path combo
|
* are relevant to the given handler, and return them as a name and path combo
|
||||||
|
@ -187,18 +191,19 @@ _.extend(ImportManager.prototype, {
|
||||||
* @param {String} directory
|
* @param {String} directory
|
||||||
* @returns [] Files
|
* @returns [] Files
|
||||||
*/
|
*/
|
||||||
getFilesFromZip: function (handler, directory) {
|
getFilesFromZip(handler, directory) {
|
||||||
const globPattern = this.getExtensionGlob(handler.extensions, ALL_DIRS);
|
const globPattern = this.getExtensionGlob(handler.extensions, ALL_DIRS);
|
||||||
return _.map(glob.sync(globPattern, {cwd: directory}), function (file) {
|
return _.map(glob.sync(globPattern, {cwd: directory}), function (file) {
|
||||||
return {name: file, path: path.join(directory, file)};
|
return {name: file, path: path.join(directory, file)};
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the name of the single base directory if there is one, else return an empty string
|
* Get the name of the single base directory if there is one, else return an empty string
|
||||||
* @param {String} directory
|
* @param {String} directory
|
||||||
* @returns {Promise (String)}
|
* @returns {String}
|
||||||
*/
|
*/
|
||||||
getBaseDirectory: function (directory) {
|
getBaseDirectory(directory) {
|
||||||
// Globs match root level only
|
// Globs match root level only
|
||||||
const extMatches = glob.sync(this.getExtensionGlob(this.getExtensions(), ROOT_ONLY), {cwd: directory});
|
const extMatches = glob.sync(this.getExtensionGlob(this.getExtensions(), ROOT_ONLY), {cwd: directory});
|
||||||
|
|
||||||
|
@ -218,7 +223,8 @@ _.extend(ImportManager.prototype, {
|
||||||
}
|
}
|
||||||
|
|
||||||
return extMatchesAll[0].split('/')[0];
|
return extMatchesAll[0].split('/')[0];
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process Zip
|
* Process Zip
|
||||||
* Takes a reference to a zip file, extracts it, sends any relevant files from inside to the right handler, and
|
* Takes a reference to a zip file, extracts it, sends any relevant files from inside to the right handler, and
|
||||||
|
@ -228,7 +234,7 @@ _.extend(ImportManager.prototype, {
|
||||||
* @param {File} file
|
* @param {File} file
|
||||||
* @returns {Promise(ImportData)}
|
* @returns {Promise(ImportData)}
|
||||||
*/
|
*/
|
||||||
processZip: function (file) {
|
processZip(file) {
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
return this.extractZip(file.path).then(function (zipDirectory) {
|
return this.extractZip(file.path).then(function (zipDirectory) {
|
||||||
|
@ -268,7 +274,8 @@ _.extend(ImportManager.prototype, {
|
||||||
return importData;
|
return importData;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process File
|
* Process File
|
||||||
* Takes a reference to a single file, sends it to the relevant handler to be loaded and returns an object in the
|
* Takes a reference to a single file, sends it to the relevant handler to be loaded and returns an object in the
|
||||||
|
@ -278,7 +285,7 @@ _.extend(ImportManager.prototype, {
|
||||||
* @param {File} file
|
* @param {File} file
|
||||||
* @returns {Promise(ImportData)}
|
* @returns {Promise(ImportData)}
|
||||||
*/
|
*/
|
||||||
processFile: function (file, ext) {
|
processFile(file, ext) {
|
||||||
const fileHandler = _.find(this.handlers, function (handler) {
|
const fileHandler = _.find(this.handlers, function (handler) {
|
||||||
return _.includes(handler.extensions, ext);
|
return _.includes(handler.extensions, ext);
|
||||||
});
|
});
|
||||||
|
@ -289,7 +296,8 @@ _.extend(ImportManager.prototype, {
|
||||||
importData[fileHandler.type] = loadedData;
|
importData[fileHandler.type] = loadedData;
|
||||||
return importData;
|
return importData;
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Import Step 1:
|
* Import Step 1:
|
||||||
* Load the given file into usable importData in the format: {data: {}, images: []}, regardless of
|
* Load the given file into usable importData in the format: {data: {}, images: []}, regardless of
|
||||||
|
@ -297,11 +305,12 @@ _.extend(ImportManager.prototype, {
|
||||||
* @param {File} file
|
* @param {File} file
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
loadFile: function (file) {
|
loadFile(file) {
|
||||||
const self = this;
|
const self = this;
|
||||||
const ext = path.extname(file.name).toLowerCase();
|
const ext = path.extname(file.name).toLowerCase();
|
||||||
return this.isZip(ext) ? self.processZip(file) : self.processFile(file, ext);
|
return this.isZip(ext) ? self.processZip(file) : self.processFile(file, ext);
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Import Step 2:
|
* Import Step 2:
|
||||||
* Pass the prepared importData through the preProcess function of the various importers, so that the importers can
|
* Pass the prepared importData through the preProcess function of the various importers, so that the importers can
|
||||||
|
@ -309,7 +318,7 @@ _.extend(ImportManager.prototype, {
|
||||||
* @param {ImportData} importData
|
* @param {ImportData} importData
|
||||||
* @returns {Promise(ImportData)}
|
* @returns {Promise(ImportData)}
|
||||||
*/
|
*/
|
||||||
preProcess: function (importData) {
|
preProcess(importData) {
|
||||||
const ops = [];
|
const ops = [];
|
||||||
_.each(this.importers, function (importer) {
|
_.each(this.importers, function (importer) {
|
||||||
ops.push(function () {
|
ops.push(function () {
|
||||||
|
@ -318,16 +327,17 @@ _.extend(ImportManager.prototype, {
|
||||||
});
|
});
|
||||||
|
|
||||||
return pipeline(ops);
|
return pipeline(ops);
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Import Step 3:
|
* Import Step 3:
|
||||||
* Each importer gets passed the data from importData which has the key matching its type - i.e. it only gets the
|
* Each importer gets passed the data from importData which has the key matching its type - i.e. it only gets the
|
||||||
* data that it should import. Each importer then handles actually importing that data into Ghost
|
* data that it should import. Each importer then handles actually importing that data into Ghost
|
||||||
* @param {ImportData} importData
|
* @param {ImportData} importData
|
||||||
* @param {importOptions} importOptions to allow override of certain import features such as locking a user
|
* @param {Object} importOptions to allow override of certain import features such as locking a user
|
||||||
* @returns {Promise(ImportData)}
|
* @returns {Promise<any>}
|
||||||
*/
|
*/
|
||||||
doImport: function (importData, importOptions) {
|
doImport(importData, importOptions) {
|
||||||
importOptions = importOptions || {};
|
importOptions = importOptions || {};
|
||||||
const ops = [];
|
const ops = [];
|
||||||
_.each(this.importers, function (importer) {
|
_.each(this.importers, function (importer) {
|
||||||
|
@ -341,24 +351,26 @@ _.extend(ImportManager.prototype, {
|
||||||
return sequence(ops).then(function (importResult) {
|
return sequence(ops).then(function (importResult) {
|
||||||
return importResult;
|
return importResult;
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Import Step 4:
|
* Import Step 4:
|
||||||
* Report on what was imported, currently a no-op
|
* Report on what was imported, currently a no-op
|
||||||
* @param {ImportData} importData
|
* @param {ImportData} importData
|
||||||
* @returns {Promise(ImportData)}
|
* @returns {Promise<ImportData>}
|
||||||
*/
|
*/
|
||||||
generateReport: function (importData) {
|
generateReport(importData) {
|
||||||
return Promise.resolve(importData);
|
return Promise.resolve(importData);
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Import From File
|
* Import From File
|
||||||
* The main method of the ImportManager, call this to kick everything off!
|
* The main method of the ImportManager, call this to kick everything off!
|
||||||
* @param {File} file
|
* @param {File} file
|
||||||
* @param {importOptions} importOptions to allow override of certain import features such as locking a user
|
* @param {Object} importOptions to allow override of certain import features such as locking a user
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
importFromFile: function (file, importOptions = {}) {
|
importFromFile(file, importOptions = {}) {
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
// Step 1: Handle converting the file to usable data
|
// Step 1: Handle converting the file to usable data
|
||||||
|
@ -374,6 +386,13 @@ _.extend(ImportManager.prototype, {
|
||||||
return self.generateReport(importData);
|
return self.generateReport(importData);
|
||||||
}).finally(() => self.cleanUp()); // Step 5: Cleanup any files
|
}).finally(() => self.cleanUp()); // Step 5: Cleanup any files
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A number, or a string containing a number.
|
||||||
|
* @typedef {Object} ImportData
|
||||||
|
* @property [Object] data
|
||||||
|
* @property [Array] images
|
||||||
|
*/
|
||||||
|
|
||||||
module.exports = new ImportManager();
|
module.exports = new ImportManager();
|
||||||
|
|
Loading…
Add table
Reference in a new issue