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: []
|
||||
};
|
||||
|
||||
function ImportManager() {
|
||||
this.importers = [ImageImporter, DataImporter];
|
||||
this.handlers = [ImageHandler, JSONHandler, MarkdownHandler];
|
||||
// Keep track of file to cleanup at the end
|
||||
this.fileToDelete = null;
|
||||
}
|
||||
class ImportManager {
|
||||
constructor() {
|
||||
this.importers = [ImageImporter, DataImporter];
|
||||
this.handlers = [ImageHandler, JSONHandler, MarkdownHandler];
|
||||
|
||||
/**
|
||||
* A number, or a string containing a number.
|
||||
* @typedef {Object} ImportData
|
||||
* @property [Object] data
|
||||
* @property [Array] images
|
||||
*/
|
||||
// Keep track of file to cleanup at the end
|
||||
this.fileToDelete = null;
|
||||
}
|
||||
|
||||
_.extend(ImportManager.prototype, {
|
||||
/**
|
||||
* Get an array of all the file extensions for which we have handlers
|
||||
* @returns {string[]}
|
||||
*/
|
||||
getExtensions: function () {
|
||||
getExtensions() {
|
||||
return _.flatten(_.union(_.map(this.handlers, 'extensions'), defaults.extensions));
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of all the mime types for which we have handlers
|
||||
* @returns {string[]}
|
||||
*/
|
||||
getContentTypes: function () {
|
||||
getContentTypes() {
|
||||
return _.flatten(_.union(_.map(this.handlers, 'contentTypes'), defaults.contentTypes));
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of directories for which we have handlers
|
||||
* @returns {string[]}
|
||||
*/
|
||||
getDirectories: function () {
|
||||
getDirectories() {
|
||||
return _.flatten(_.union(_.map(this.handlers, 'directories'), defaults.directories));
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert items into a glob string
|
||||
* @param {String[]} items
|
||||
* @returns {String}
|
||||
*/
|
||||
getGlobPattern: function (items) {
|
||||
getGlobPattern(items) {
|
||||
return '+(' + _.reduce(items, function (memo, ext) {
|
||||
return memo !== '' ? memo + '|' + ext : ext;
|
||||
}, '') + ')';
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {String[]} extensions
|
||||
* @param {Number} level
|
||||
* @returns {String}
|
||||
*/
|
||||
getExtensionGlob: function (extensions, level) {
|
||||
getExtensionGlob(extensions, level) {
|
||||
const prefix = level === ALL_DIRS ? '**/*' :
|
||||
(level === ROOT_OR_SINGLE_DIR ? '{*/*,*}' : '*');
|
||||
|
||||
return prefix + this.getGlobPattern(extensions);
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {String[]} directories
|
||||
* @param {Number} level
|
||||
* @returns {String}
|
||||
*/
|
||||
getDirectoryGlob: function (directories, level) {
|
||||
getDirectoryGlob(directories, level) {
|
||||
const prefix = level === ALL_DIRS ? '**/' :
|
||||
(level === ROOT_OR_SINGLE_DIR ? '{*/,}' : '');
|
||||
|
||||
return prefix + this.getGlobPattern(directories);
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove files after we're done (abstracted into a function for easier testing)
|
||||
* @returns {Function}
|
||||
*/
|
||||
cleanUp: function () {
|
||||
cleanUp() {
|
||||
const self = this;
|
||||
|
||||
if (self.fileToDelete === null) {
|
||||
|
@ -120,14 +120,16 @@ _.extend(ImportManager.prototype, {
|
|||
|
||||
self.fileToDelete = null;
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the given file is a Zip
|
||||
* @returns Boolean
|
||||
*/
|
||||
isZip: function (ext) {
|
||||
isZip(ext) {
|
||||
return _.includes(defaults.extensions, ext);
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
@ -136,7 +138,7 @@ _.extend(ImportManager.prototype, {
|
|||
* @param {String} directory
|
||||
* @returns {Promise}
|
||||
*/
|
||||
isValidZip: function (directory) {
|
||||
isValidZip(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});
|
||||
|
||||
|
@ -166,20 +168,22 @@ _.extend(ImportManager.prototype, {
|
|||
}
|
||||
|
||||
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
|
||||
* @param {String} filePath
|
||||
* @returns {Promise[]} Files
|
||||
*/
|
||||
extractZip: function (filePath) {
|
||||
extractZip(filePath) {
|
||||
const tmpDir = path.join(os.tmpdir(), uuid.v4());
|
||||
this.fileToDelete = tmpDir;
|
||||
|
||||
return extract(filePath, tmpDir).then(function () {
|
||||
return tmpDir;
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
@ -187,18 +191,19 @@ _.extend(ImportManager.prototype, {
|
|||
* @param {String} directory
|
||||
* @returns [] Files
|
||||
*/
|
||||
getFilesFromZip: function (handler, directory) {
|
||||
getFilesFromZip(handler, directory) {
|
||||
const globPattern = this.getExtensionGlob(handler.extensions, ALL_DIRS);
|
||||
return _.map(glob.sync(globPattern, {cwd: directory}), function (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
|
||||
* @param {String} directory
|
||||
* @returns {Promise (String)}
|
||||
* @returns {String}
|
||||
*/
|
||||
getBaseDirectory: function (directory) {
|
||||
getBaseDirectory(directory) {
|
||||
// Globs match root level only
|
||||
const extMatches = glob.sync(this.getExtensionGlob(this.getExtensions(), ROOT_ONLY), {cwd: directory});
|
||||
|
||||
|
@ -218,7 +223,8 @@ _.extend(ImportManager.prototype, {
|
|||
}
|
||||
|
||||
return extMatchesAll[0].split('/')[0];
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Process Zip
|
||||
* 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
|
||||
* @returns {Promise(ImportData)}
|
||||
*/
|
||||
processZip: function (file) {
|
||||
processZip(file) {
|
||||
const self = this;
|
||||
|
||||
return this.extractZip(file.path).then(function (zipDirectory) {
|
||||
|
@ -268,7 +274,8 @@ _.extend(ImportManager.prototype, {
|
|||
return importData;
|
||||
});
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Process File
|
||||
* 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
|
||||
* @returns {Promise(ImportData)}
|
||||
*/
|
||||
processFile: function (file, ext) {
|
||||
processFile(file, ext) {
|
||||
const fileHandler = _.find(this.handlers, function (handler) {
|
||||
return _.includes(handler.extensions, ext);
|
||||
});
|
||||
|
@ -289,7 +296,8 @@ _.extend(ImportManager.prototype, {
|
|||
importData[fileHandler.type] = loadedData;
|
||||
return importData;
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Import Step 1:
|
||||
* Load the given file into usable importData in the format: {data: {}, images: []}, regardless of
|
||||
|
@ -297,11 +305,12 @@ _.extend(ImportManager.prototype, {
|
|||
* @param {File} file
|
||||
* @returns {Promise}
|
||||
*/
|
||||
loadFile: function (file) {
|
||||
loadFile(file) {
|
||||
const self = this;
|
||||
const ext = path.extname(file.name).toLowerCase();
|
||||
return this.isZip(ext) ? self.processZip(file) : self.processFile(file, ext);
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Import Step 2:
|
||||
* 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
|
||||
* @returns {Promise(ImportData)}
|
||||
*/
|
||||
preProcess: function (importData) {
|
||||
preProcess(importData) {
|
||||
const ops = [];
|
||||
_.each(this.importers, function (importer) {
|
||||
ops.push(function () {
|
||||
|
@ -318,16 +327,17 @@ _.extend(ImportManager.prototype, {
|
|||
});
|
||||
|
||||
return pipeline(ops);
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Import Step 3:
|
||||
* 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
|
||||
* @param {ImportData} importData
|
||||
* @param {importOptions} importOptions to allow override of certain import features such as locking a user
|
||||
* @returns {Promise(ImportData)}
|
||||
* @param {Object} importOptions to allow override of certain import features such as locking a user
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
doImport: function (importData, importOptions) {
|
||||
doImport(importData, importOptions) {
|
||||
importOptions = importOptions || {};
|
||||
const ops = [];
|
||||
_.each(this.importers, function (importer) {
|
||||
|
@ -341,24 +351,26 @@ _.extend(ImportManager.prototype, {
|
|||
return sequence(ops).then(function (importResult) {
|
||||
return importResult;
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Import Step 4:
|
||||
* Report on what was imported, currently a no-op
|
||||
* @param {ImportData} importData
|
||||
* @returns {Promise(ImportData)}
|
||||
* @returns {Promise<ImportData>}
|
||||
*/
|
||||
generateReport: function (importData) {
|
||||
generateReport(importData) {
|
||||
return Promise.resolve(importData);
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Import From File
|
||||
* The main method of the ImportManager, call this to kick everything off!
|
||||
* @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}
|
||||
*/
|
||||
importFromFile: function (file, importOptions = {}) {
|
||||
importFromFile(file, importOptions = {}) {
|
||||
const self = this;
|
||||
|
||||
// Step 1: Handle converting the file to usable data
|
||||
|
@ -374,6 +386,13 @@ _.extend(ImportManager.prototype, {
|
|||
return self.generateReport(importData);
|
||||
}).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();
|
||||
|
|
Loading…
Add table
Reference in a new issue