0
Fork 0
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:
Naz 2021-09-21 18:00:40 +02:00 committed by naz
parent c39d1996a4
commit 7589218abc

View file

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