diff --git a/packages/rocketchat-importer/client/admin/adminImport.coffee b/packages/rocketchat-importer/client/admin/adminImport.coffee deleted file mode 100644 index c4a8506248e4..000000000000 --- a/packages/rocketchat-importer/client/admin/adminImport.coffee +++ /dev/null @@ -1,24 +0,0 @@ -Template.adminImport.helpers - isAdmin: -> - return RocketChat.authz.hasRole(Meteor.userId(), 'admin') - isImporters: -> - return Object.keys(Importer.Importers).length > 0 - getDescription: (importer) -> - return TAPi18n.__('Importer_From_Description', { from: importer.name }) - importers: -> - importers = [] - _.each Importer.Importers, (importer, key) -> - importer.key = key - importers.push importer - return importers - -Template.adminImport.events - 'click .start-import': (event) -> - importer = @ - - Meteor.call 'setupImporter', importer.key, (error, data) -> - if error - console.log t('importer_setup_error'), importer.key, error - return handleError(error) - else - FlowRouter.go '/admin/import/prepare/' + importer.key diff --git a/packages/rocketchat-importer/client/admin/adminImport.js b/packages/rocketchat-importer/client/admin/adminImport.js new file mode 100644 index 000000000000..9bfd1e34a4c7 --- /dev/null +++ b/packages/rocketchat-importer/client/admin/adminImport.js @@ -0,0 +1,33 @@ +/* globals Importer */ +Template.adminImport.helpers({ + isAdmin() { + return RocketChat.authz.hasRole(Meteor.userId(), 'admin'); + }, + isImporters() { + return Object.keys(Importer.Importers).length > 0; + }, + getDescription(importer) { + return TAPi18n.__('Importer_From_Description', { from: importer.name }); + }, + importers() { + const importers = []; + _.each(Importer.Importers, function(importer, key) { + importer.key = key; + return importers.push(importer); + }); + return importers; + } +}); + +Template.adminImport.events({ + 'click .start-import'() { + const importer = this; + return Meteor.call('setupImporter', importer.key, function(error) { + if (error) { + console.log(t('importer_setup_error'), importer.key, error); + return handleError(error); + } + return FlowRouter.go(`/admin/import/prepare/${ importer.key }`); + }); + } +}); diff --git a/packages/rocketchat-importer/client/admin/adminImportPrepare.coffee b/packages/rocketchat-importer/client/admin/adminImportPrepare.coffee deleted file mode 100644 index 823418efed04..000000000000 --- a/packages/rocketchat-importer/client/admin/adminImportPrepare.coffee +++ /dev/null @@ -1,152 +0,0 @@ -import toastr from 'toastr' -Template.adminImportPrepare.helpers - isAdmin: -> - return RocketChat.authz.hasRole(Meteor.userId(), 'admin') - importer: -> - importerKey = FlowRouter.getParam('importer') - importer = undefined - _.each Importer.Importers, (i, key) -> - i.key = key - if key == importerKey - importer = i - - return importer - isLoaded: -> - return Template.instance().loaded.get() - isPreparing: -> - return Template.instance().preparing.get() - users: -> - return Template.instance().users.get() - channels: -> - return Template.instance().channels.get() - -Template.adminImportPrepare.events - 'change .import-file-input': (event, template) -> - importer = @ - return if not importer.key - - e = event.originalEvent or event - files = e.target.files - if not files or files.length is 0 - files = e.dataTransfer?.files or [] - - for blob in files - template.preparing.set true - - reader = new FileReader() - reader.readAsDataURL(blob) - reader.onloadend = -> - Meteor.call 'prepareImport', importer.key, reader.result, blob.type, blob.name, (error, data) -> - if error - toastr.error t('Invalid_Import_File_Type') - template.preparing.set false - return - - if !data - console.warn 'The importer ' + importer.key + ' is not set up correctly, as it did not return any data.' - toastr.error t('Importer_not_setup') - template.preparing.set false - return - - if data.step - console.warn 'Invalid file, contains `data.step`.', data - toastr.error t('Invalid_Export_File', importer.key) - template.preparing.set false - return - - template.users.set data.users - template.channels.set data.channels - template.loaded.set true - template.preparing.set false - - 'click .button.start': (event, template) -> - btn = this - $(btn).prop "disabled", true - importer = @ - for user in template.users.get() - user.do_import = $("[name=#{user.user_id}]").is(':checked') - - for channel in template.channels.get() - channel.do_import = $("[name=#{channel.channel_id}]").is(':checked') - - Meteor.call 'startImport', FlowRouter.getParam('importer'), { users: template.users.get(), channels: template.channels.get() }, (error, data) -> - if error - console.warn 'Error on starting the import:', error - return handleError(error) - else - FlowRouter.go '/admin/import/progress/' + FlowRouter.getParam('importer') - - 'click .button.restart': (event, template) -> - Meteor.call 'restartImport', FlowRouter.getParam('importer'), (error, data) -> - if error - console.warn 'Error while restarting the import:', error - handleError(error) - return - - template.users.set [] - template.channels.set [] - template.loaded.set false - - 'click .button.uncheck-deleted-users': (event, template) -> - for user in template.users.get() when user.is_deleted - $("[name=#{user.user_id}]").attr('checked', false); - - 'click .button.uncheck-archived-channels': (event, template) -> - for channel in template.channels.get() when channel.is_archived - $("[name=#{channel.channel_id}]").attr('checked', false); - - -Template.adminImportPrepare.onCreated -> - instance = @ - @preparing = new ReactiveVar true - @loaded = new ReactiveVar false - @users = new ReactiveVar [] - @channels = new ReactiveVar [] - - loadSelection = (progress) -> - if progress?.step - switch progress.step - #When the import is running, take the user to the progress page - when 'importer_importing_started', 'importer_importing_users', 'importer_importing_channels', 'importer_importing_messages', 'importer_finishing' - FlowRouter.go '/admin/import/progress/' + FlowRouter.getParam('importer') - # when the import is done, restart it (new instance) - when 'importer_user_selection' - Meteor.call 'getSelectionData', FlowRouter.getParam('importer'), (error, data) -> - if error - handleError error - instance.users.set data.users - instance.channels.set data.channels - instance.loaded.set true - instance.preparing.set false - when 'importer_new' - instance.preparing.set false - else - Meteor.call 'restartImport', FlowRouter.getParam('importer'), (error, progress) -> - if error - handleError(error) - loadSelection(progress) - else - console.warn 'Invalid progress information.', progress - - # Load the initial progress to determine what we need to do - if FlowRouter.getParam('importer') - Meteor.call 'getImportProgress', FlowRouter.getParam('importer'), (error, progress) -> - if error - console.warn 'Error while getting the import progress:', error - handleError error - return - - # if the progress isnt defined, that means there currently isn't an instance - # of the importer, so we need to create it - if progress is undefined - Meteor.call 'setupImporter', FlowRouter.getParam('importer'), (err, data) -> - if err - handleError(err) - instance.preparing.set false - loadSelection(data) - else - # Otherwise, we might need to do something based upon the current step - # of the import - loadSelection(progress) - else - FlowRouter.go '/admin/import' diff --git a/packages/rocketchat-importer/client/admin/adminImportPrepare.js b/packages/rocketchat-importer/client/admin/adminImportPrepare.js new file mode 100644 index 000000000000..fe7e00ee045f --- /dev/null +++ b/packages/rocketchat-importer/client/admin/adminImportPrepare.js @@ -0,0 +1,195 @@ +/* globals Importer */ +import toastr from 'toastr'; +Template.adminImportPrepare.helpers({ + isAdmin() { + return RocketChat.authz.hasRole(Meteor.userId(), 'admin'); + }, + importer() { + const importerKey = FlowRouter.getParam('importer'); + let importer = undefined; + _.each(Importer.Importers, function(i, key) { + i.key = key; + if (key === importerKey) { + return importer = i; + } + }); + + return importer; + }, + isLoaded() { + return Template.instance().loaded.get(); + }, + isPreparing() { + return Template.instance().preparing.get(); + }, + users() { + return Template.instance().users.get(); + }, + channels() { + return Template.instance().channels.get(); + } +}); + +Template.adminImportPrepare.events({ + 'change .import-file-input'(event, template) { + const importer = this; + if (!importer.key) { return; } + + const e = event.originalEvent || event; + let { files } = e.target; + if (!files || (files.length === 0)) { + files = (e.dataTransfer != null ? e.dataTransfer.files : undefined) || []; + } + + return Array.from(files).map((blob) => { + template.preparing.set(true); + + const reader = new FileReader(); + reader.readAsDataURL(blob); + reader.onloadend = () => { + Meteor.call('prepareImport', importer.key, reader.result, blob.type, blob.name, function(error, data) { + if (error) { + toastr.error(t('Invalid_Import_File_Type')); + template.preparing.set(false); + return; + } + + if (!data) { + console.warn(`The importer ${ importer.key } is not set up correctly, as it did not return any data.`); + toastr.error(t('Importer_not_setup')); + template.preparing.set(false); + return; + } + + if (data.step) { + console.warn('Invalid file, contains `data.step`.', data); + toastr.error(t('Invalid_Export_File', importer.key)); + template.preparing.set(false); + return; + } + + template.users.set(data.users); + template.channels.set(data.channels); + template.loaded.set(true); + template.preparing.set(false); + }); + }; + }); + }, + + 'click .button.start'(event, template) { + const btn = this; + $(btn).prop('disabled', true); + // const importer = this; + for (const user of Array.from(template.users.get())) { + user.do_import = $(`[name=${ user.user_id }]`).is(':checked'); + } + + for (const channel of Array.from(template.channels.get())) { + channel.do_import = $(`[name=${ channel.channel_id }]`).is(':checked'); + } + + return Meteor.call('startImport', FlowRouter.getParam('importer'), { users: template.users.get(), channels: template.channels.get() }, function(error) { + if (error) { + console.warn('Error on starting the import:', error); + return handleError(error); + } else { + return FlowRouter.go(`/admin/import/progress/${ FlowRouter.getParam('importer') }`); + } + }); + }, + + 'click .button.restart'(event, template) { + Meteor.call('restartImport', FlowRouter.getParam('importer'), function(error) { + if (error) { + console.warn('Error while restarting the import:', error); + handleError(error); + return; + } + + template.users.set([]); + template.channels.set([]); + template.loaded.set(false); + }); + }, + + 'click .button.uncheck-deleted-users'(event, template) { + Array.from(template.users.get()).filter((user) => user.is_deleted).map((user) => + $(`[name=${ user.user_id }]`).attr('checked', false)); + }, + + 'click .button.uncheck-archived-channels'(event, template) { + Array.from(template.channels.get()).filter((channel) => channel.is_archived).map((channel) => + $(`[name=${ channel.channel_id }]`).attr('checked', false)); + } +}); + + +Template.adminImportPrepare.onCreated(function() { + const instance = this; + this.preparing = new ReactiveVar(true); + this.loaded = new ReactiveVar(false); + this.users = new ReactiveVar([]); + this.channels = new ReactiveVar([]); + + function loadSelection(progress) { + if ((progress != null ? progress.step : undefined)) { + switch (progress.step) { + //When the import is running, take the user to the progress page + case 'importer_importing_started': case 'importer_importing_users': case 'importer_importing_channels': case 'importer_importing_messages': case 'importer_finishing': + return FlowRouter.go(`/admin/import/progress/${ FlowRouter.getParam('importer') }`); + // when the import is done, restart it (new instance) + case 'importer_user_selection': + return Meteor.call('getSelectionData', FlowRouter.getParam('importer'), function(error, data) { + if (error) { + handleError(error); + } + instance.users.set(data.users); + instance.channels.set(data.channels); + instance.loaded.set(true); + return instance.preparing.set(false); + }); + case 'importer_new': + return instance.preparing.set(false); + default: + return Meteor.call('restartImport', FlowRouter.getParam('importer'), function(error, progress) { + if (error) { + handleError(error); + } + return loadSelection(progress); + }); + } + } else { + return console.warn('Invalid progress information.', progress); + } + } + + // Load the initial progress to determine what we need to do + if (FlowRouter.getParam('importer')) { + return Meteor.call('getImportProgress', FlowRouter.getParam('importer'), function(error, progress) { + if (error) { + console.warn('Error while getting the import progress:', error); + handleError(error); + return; + } + + // if the progress isnt defined, that means there currently isn't an instance + // of the importer, so we need to create it + if (progress === undefined) { + return Meteor.call('setupImporter', FlowRouter.getParam('importer'), function(err, data) { + if (err) { + handleError(err); + } + instance.preparing.set(false); + return loadSelection(data); + }); + } else { + // Otherwise, we might need to do something based upon the current step + // of the import + return loadSelection(progress); + } + }); + } else { + return FlowRouter.go('/admin/import'); + } +}); diff --git a/packages/rocketchat-importer/client/admin/adminImportProgress.coffee b/packages/rocketchat-importer/client/admin/adminImportProgress.coffee deleted file mode 100644 index d7e9fe7511f4..000000000000 --- a/packages/rocketchat-importer/client/admin/adminImportProgress.coffee +++ /dev/null @@ -1,41 +0,0 @@ -import toastr from 'toastr' -Template.adminImportProgress.helpers - step: -> - return Template.instance().step.get() - completed: -> - return Template.instance().completed.get() - total: -> - return Template.instance().total.get() - -Template.adminImportProgress.onCreated -> - instance = @ - @step = new ReactiveVar t('Loading...') - @completed = new ReactiveVar 0 - @total = new ReactiveVar 0 - @updateProgress = -> - if FlowRouter.getParam('importer') isnt '' - Meteor.call 'getImportProgress', FlowRouter.getParam('importer'), (error, progress) -> - if error - console.warn 'Error on getting the import progress:', error - handleError error - return - - if progress - if progress.step is 'importer_done' - toastr.success t(progress.step[0].toUpperCase() + progress.step.slice(1)) - FlowRouter.go '/admin/import' - else if progress.step is 'importer_import_failed' - toastr.error t(progress.step[0].toUpperCase() + progress.step.slice(1)) - FlowRouter.go '/admin/import/prepare/' + FlowRouter.getParam('importer') - else - instance.step.set t(progress.step[0].toUpperCase() + progress.step.slice(1)) - instance.completed.set progress.count.completed - instance.total.set progress.count.total - setTimeout(() -> - instance.updateProgress() - , 100) - else - toastr.warning t('Importer_not_in_progress') - FlowRouter.go '/admin/import/prepare/' + FlowRouter.getParam('importer') - - instance.updateProgress() diff --git a/packages/rocketchat-importer/client/admin/adminImportProgress.js b/packages/rocketchat-importer/client/admin/adminImportProgress.js new file mode 100644 index 000000000000..4d22c3a46a72 --- /dev/null +++ b/packages/rocketchat-importer/client/admin/adminImportProgress.js @@ -0,0 +1,51 @@ +import toastr from 'toastr'; +Template.adminImportProgress.helpers({ + step() { + return Template.instance().step.get(); + }, + completed() { + return Template.instance().completed.get(); + }, + total() { + return Template.instance().total.get(); + } +}); + +Template.adminImportProgress.onCreated(function() { + const instance = this; + this.step = new ReactiveVar(t('Loading...')); + this.completed = new ReactiveVar(0); + this.total = new ReactiveVar(0); + this.updateProgress = function() { + if (FlowRouter.getParam('importer') !== '') { + return Meteor.call('getImportProgress', FlowRouter.getParam('importer'), function(error, progress) { + if (error) { + console.warn('Error on getting the import progress:', error); + handleError(error); + return; + } + + if (progress) { + if (progress.step === 'importer_done') { + toastr.success(t(progress.step[0].toUpperCase() + progress.step.slice(1))); + return FlowRouter.go('/admin/import'); + } else if (progress.step === 'importer_import_failed') { + toastr.error(t(progress.step[0].toUpperCase() + progress.step.slice(1))); + return FlowRouter.go(`/admin/import/prepare/${ FlowRouter.getParam('importer') }`); + } else { + instance.step.set(t(progress.step[0].toUpperCase() + progress.step.slice(1))); + instance.completed.set(progress.count.completed); + instance.total.set(progress.count.total); + return setTimeout(() => instance.updateProgress() + , 100); + } + } else { + toastr.warning(t('Importer_not_in_progress')); + return FlowRouter.go(`/admin/import/prepare/${ FlowRouter.getParam('importer') }`); + } + }); + } + }; + + return instance.updateProgress(); +}); diff --git a/packages/rocketchat-importer/lib/_importer.coffee b/packages/rocketchat-importer/lib/_importer.coffee deleted file mode 100644 index 625541b61604..000000000000 --- a/packages/rocketchat-importer/lib/_importer.coffee +++ /dev/null @@ -1 +0,0 @@ -Importer = {} diff --git a/packages/rocketchat-importer/lib/_importer.js b/packages/rocketchat-importer/lib/_importer.js new file mode 100644 index 000000000000..8e7c3f9971cf --- /dev/null +++ b/packages/rocketchat-importer/lib/_importer.js @@ -0,0 +1,3 @@ +/* globals Importer */ +Importer = {}; +export default Importer; diff --git a/packages/rocketchat-importer/lib/importTool.coffee b/packages/rocketchat-importer/lib/importTool.coffee deleted file mode 100644 index 99514e045e9a..000000000000 --- a/packages/rocketchat-importer/lib/importTool.coffee +++ /dev/null @@ -1,9 +0,0 @@ -Importer.Importers = {} - -Importer.addImporter = (name, importer, options) -> - if not Importer.Importers[name]? - Importer.Importers[name] = - name: options.name - importer: importer - mimeType: options.mimeType - warnings: options.warnings diff --git a/packages/rocketchat-importer/lib/importTool.js b/packages/rocketchat-importer/lib/importTool.js new file mode 100644 index 000000000000..94315ab36bd5 --- /dev/null +++ b/packages/rocketchat-importer/lib/importTool.js @@ -0,0 +1,13 @@ +/* globals Importer */ +Importer.Importers = {}; + +Importer.addImporter = function(name, importer, options) { + if (Importer.Importers[name] == null) { + return Importer.Importers[name] = { + name: options.name, + importer, + mimeType: options.mimeType, + warnings: options.warnings + }; + } +}; diff --git a/packages/rocketchat-importer/package.js b/packages/rocketchat-importer/package.js index 5820b21e2ac7..0f9dffc9a96b 100644 --- a/packages/rocketchat-importer/package.js +++ b/packages/rocketchat-importer/package.js @@ -9,7 +9,6 @@ Package.onUse(function(api) { api.use([ 'ecmascript', 'templating', - 'coffeescript', 'check', 'rocketchat:lib' ]); @@ -18,37 +17,37 @@ Package.onUse(function(api) { api.use('templating', 'client'); //Import Framework - api.addFiles('lib/_importer.coffee'); - api.addFiles('lib/importTool.coffee'); - api.addFiles('server/classes/ImporterBase.coffee', 'server'); - api.addFiles('server/classes/ImporterProgress.coffee', 'server'); - api.addFiles('server/classes/ImporterProgressStep.coffee', 'server'); - api.addFiles('server/classes/ImporterSelection.coffee', 'server'); - api.addFiles('server/classes/ImporterSelectionChannel.coffee', 'server'); - api.addFiles('server/classes/ImporterSelectionUser.coffee', 'server'); + api.addFiles('lib/_importer.js'); + api.addFiles('lib/importTool.js'); + api.addFiles('server/classes/ImporterBase.js', 'server'); + api.addFiles('server/classes/ImporterProgress.js', 'server'); + api.addFiles('server/classes/ImporterProgressStep.js', 'server'); + api.addFiles('server/classes/ImporterSelection.js', 'server'); + api.addFiles('server/classes/ImporterSelectionChannel.js', 'server'); + api.addFiles('server/classes/ImporterSelectionUser.js', 'server'); //Database models - api.addFiles('server/models/Imports.coffee', 'server'); - api.addFiles('server/models/RawImports.coffee', 'server'); + api.addFiles('server/models/Imports.js', 'server'); + api.addFiles('server/models/RawImports.js', 'server'); //Server methods - api.addFiles('server/methods/getImportProgress.coffee', 'server'); - api.addFiles('server/methods/getSelectionData.coffee', 'server'); + api.addFiles('server/methods/getImportProgress.js', 'server'); + api.addFiles('server/methods/getSelectionData.js', 'server'); api.addFiles('server/methods/prepareImport.js', 'server'); - api.addFiles('server/methods/restartImport.coffee', 'server'); - api.addFiles('server/methods/setupImporter.coffee', 'server'); - api.addFiles('server/methods/startImport.coffee', 'server'); + api.addFiles('server/methods/restartImport.js', 'server'); + api.addFiles('server/methods/setupImporter.js', 'server'); + api.addFiles('server/methods/startImport.js', 'server'); //Client api.addFiles('client/admin/adminImport.html', 'client'); - api.addFiles('client/admin/adminImport.coffee', 'client'); + api.addFiles('client/admin/adminImport.js', 'client'); api.addFiles('client/admin/adminImportPrepare.html', 'client'); - api.addFiles('client/admin/adminImportPrepare.coffee', 'client'); + api.addFiles('client/admin/adminImportPrepare.js', 'client'); api.addFiles('client/admin/adminImportProgress.html', 'client'); - api.addFiles('client/admin/adminImportProgress.coffee', 'client'); + api.addFiles('client/admin/adminImportProgress.js', 'client'); //Imports database records cleanup, mark all as not valid. - api.addFiles('server/startup/setImportsToInvalid.coffee', 'server'); + api.addFiles('server/startup/setImportsToInvalid.js', 'server'); api.export('Importer'); }); diff --git a/packages/rocketchat-importer/server/classes/ImporterBase.coffee b/packages/rocketchat-importer/server/classes/ImporterBase.coffee deleted file mode 100644 index 197e76d48ee8..000000000000 --- a/packages/rocketchat-importer/server/classes/ImporterBase.coffee +++ /dev/null @@ -1,207 +0,0 @@ -# Base class for all Importers. -# -# @example How to subclass an importer -# class ExampleImporter extends RocketChat.importTool._baseImporter -# constructor: -> -# super('Name of Importer', 'Description of the importer, use i18n string.', new RegExp('application\/.*?zip')) -# prepare: (uploadedFileData, uploadedFileContentType, uploadedFileName) => -# super -# startImport: (selectedUsersAndChannels) => -# super -# getProgress: => -# #return the progress report, tbd what is expected -# @version 1.0.0 -Importer.Base = class Importer.Base - @MaxBSONSize = 8000000 - @http = Npm.require 'http' - @https = Npm.require 'https' - - @getBSONSize: (object) -> - # The max BSON object size we can store in MongoDB is 16777216 bytes - # but for some reason the mongo instanace which comes with meteor - # errors out for anything close to that size. So, we are rounding it - # down to 8000000 bytes. - BSON = require('bson').native().BSON - bson = new BSON() - bson.calculateObjectSize object - - @getBSONSafeArraysFromAnArray: (theArray) -> - BSONSize = Importer.Base.getBSONSize theArray - maxSize = Math.floor(theArray.length / (Math.ceil(BSONSize / Importer.Base.MaxBSONSize))) - safeArrays = [] - i = 0 - while i < theArray.length - safeArrays.push(theArray.slice(i, i += maxSize)) - return safeArrays - - # Constructs a new importer, adding an empty collection, AdmZip property, and empty users & channels - # - # @param [String] name the name of the Importer - # @param [String] description the i18n string which describes the importer - # @param [String] mimeType the of the expected file type - # - constructor: (@name, @description, @mimeType) -> - @logger = new Logger("#{@name} Importer", {}); - @progress = new Importer.Progress @name - @collection = Importer.RawImports - @AdmZip = Npm.require 'adm-zip' - @getFileType = Npm.require 'file-type' - importId = Importer.Imports.insert { 'type': @name, 'ts': Date.now(), 'status': @progress.step, 'valid': true, 'user': Meteor.user()._id } - @importRecord = Importer.Imports.findOne importId - @users = {} - @channels = {} - @messages = {} - - # Takes the uploaded file and extracts the users, channels, and messages from it. - # - # @param [String] dataURI a base64 string of the uploaded file - # @param [String] sentContentType the file type - # @param [String] fileName the name of the uploaded file - # - # @return [Importer.Selection] Contains two properties which are arrays of objects, `channels` and `users`. - # - prepare: (dataURI, sentContentType, fileName) => - fileType = @getFileType(new Buffer(dataURI.split(',')[1], 'base64')) - @logger.debug 'Uploaded file information is:', fileType - @logger.debug 'Expected file type is:', @mimeType - - if not fileType or fileType.mime isnt @mimeType - @logger.warn "Invalid file uploaded for the #{@name} importer." - throw new Meteor.Error('error-invalid-file-uploaded', "Invalid file uploaded to import #{@name} data from.", { step: 'prepare' }) - - @updateProgress Importer.ProgressStep.PREPARING_STARTED - @updateRecord { 'file': fileName } - - # Starts the import process. The implementing method should defer as soon as the selection is set, so the user who started the process - # doesn't end up with a "locked" ui while meteor waits for a response. The returned object should be the progress. - # - # @param [Importer.Selection] selectedUsersAndChannels an object with `channels` and `users` which contains information about which users and channels to import - # - # @return [Importer.Progress] the progress of the import - # - startImport: (importSelection) => - if importSelection is undefined - throw new Error "No selected users and channel data provided to the #{@name} importer." #TODO: Make translatable - else if importSelection.users is undefined - throw new Error "Users in the selected data wasn't found, it must but at least an empty array for the #{@name} importer." #TODO: Make translatable - else if importSelection.channels is undefined - throw new Error "Channels in the selected data wasn't found, it must but at least an empty array for the #{@name} importer." #TODO: Make translatable - - @updateProgress Importer.ProgressStep.IMPORTING_STARTED - - # Gets the Importer.Selection object for the import. - # - # @return [Importer.Selection] the users and channels selection - getSelection: () => - throw new Error "Invalid 'getSelection' called on #{@name}, it must be overridden and super can not be called." - - # Gets the progress of this importer. - # - # @return [Importer.Progress] the progress of the import - # - getProgress: => - return @progress - - # Updates the progress step of this importer. - # - # @return [Importer.Progress] the progress of the import - # - updateProgress: (step) => - @progress.step = step - - @logger.debug "#{@name} is now at #{step}." - @updateRecord { 'status': @progress.step } - - return @progress - - # Adds the passed in value to the total amount of items needed to complete. - # - # @return [Importer.Progress] the progress of the import - # - addCountToTotal: (count) => - @progress.count.total = @progress.count.total + count - @updateRecord { 'count.total': @progress.count.total } - - return @progress - - # Adds the passed in value to the total amount of items completed. - # - # @return [Importer.Progress] the progress of the import - # - addCountCompleted: (count) => - @progress.count.completed = @progress.count.completed + count - - #Only update the database every 500 records - #Or the completed is greater than or equal to the total amount - if (@progress.count.completed % 500 == 0) or @progress.count.completed >= @progress.count.total - @updateRecord { 'count.completed': @progress.count.completed } - - return @progress - - # Updates the import record with the given fields being `set` - # - # @return [Importer.Imports] the import record object - # - updateRecord: (fields) => - Importer.Imports.update { _id: @importRecord._id }, { $set: fields } - @importRecord = Importer.Imports.findOne @importRecord._id - - return @importRecord - - # Uploads the file to the storage. - # - # @param [Object] details an object with details about the upload. name, size, type, and rid - # @param [String] fileUrl url of the file to download/import - # @param [Object] user the Rocket.Chat user - # @param [Object] room the Rocket.Chat room - # @param [Date] timeStamp the timestamp the file was uploaded - # - uploadFile: (details, fileUrl, user, room, timeStamp) => - @logger.debug "Uploading the file #{details.name} from #{fileUrl}." - requestModule = if /https/i.test(fileUrl) then Importer.Base.https else Importer.Base.http - - requestModule.get fileUrl, Meteor.bindEnvironment((stream) -> - fileId = Meteor.fileStore.create details - if fileId - Meteor.fileStore.write stream, fileId, (err, file) -> - if err - throw new Error(err) - else - url = file.url.replace(Meteor.absoluteUrl(), '/') - - attachment = - title: "File Uploaded: #{file.name}" - title_link: url - - if /^image\/.+/.test file.type - attachment.image_url = url - attachment.image_type = file.type - attachment.image_size = file.size - attachment.image_dimensions = file.identify?.size - - if /^audio\/.+/.test file.type - attachment.audio_url = url - attachment.audio_type = file.type - attachment.audio_size = file.size - - if /^video\/.+/.test file.type - attachment.video_url = url - attachment.video_type = file.type - attachment.video_size = file.size - - msg = - rid: details.rid - ts: timeStamp - msg: '' - file: - _id: file._id - groupable: false - attachments: [attachment] - - if details.message_id? and (typeof details.message_id is 'string') - msg['_id'] = details.message_id - - RocketChat.sendMessage user, msg, room, true - else - @logger.error "Failed to create the store for #{fileUrl}!!!" - ) diff --git a/packages/rocketchat-importer/server/classes/ImporterBase.js b/packages/rocketchat-importer/server/classes/ImporterBase.js new file mode 100644 index 000000000000..9e3f98c4011c --- /dev/null +++ b/packages/rocketchat-importer/server/classes/ImporterBase.js @@ -0,0 +1,248 @@ +/* globals Importer */ +// Base class for all Importers. +// +// @example How to subclass an importer +// class ExampleImporter extends RocketChat.importTool._baseImporter +// constructor: -> +// super('Name of Importer', 'Description of the importer, use i18n string.', new RegExp('application\/.*?zip')) +// prepare: (uploadedFileData, uploadedFileContentType, uploadedFileName) => +// super +// startImport: (selectedUsersAndChannels) => +// super +// getProgress: => +// #return the progress report, tbd what is expected +// @version 1.0.0 +Importer.Base = class Base { + static getBSONSize(object) { + // The max BSON object size we can store in MongoDB is 16777216 bytes + // but for some reason the mongo instanace which comes with meteor + // errors out for anything close to that size. So, we are rounding it + // down to 8000000 bytes. + const { BSON } = require('bson').native(); + const bson = new BSON(); + return bson.calculateObjectSize(object); + } + + static getBSONSafeArraysFromAnArray(theArray) { + const BSONSize = Importer.Base.getBSONSize(theArray); + const maxSize = Math.floor(theArray.length / (Math.ceil(BSONSize / Importer.Base.MaxBSONSize))); + const safeArrays = []; + let i = 0; + while (i < theArray.length) { + safeArrays.push(theArray.slice(i, (i += maxSize))); + } + return safeArrays; + } + + // Constructs a new importer, adding an empty collection, AdmZip property, and empty users & channels + // + // @param [String] name the name of the Importer + // @param [String] description the i18n string which describes the importer + // @param [String] mimeType the of the expected file type + // + constructor(name, description, mimeType) { + this.MaxBSONSize = 8000000; + this.http = Npm.require('http'); + this.https = Npm.require('https'); + + + this.prepare = this.prepare.bind(this); + this.startImport = this.startImport.bind(this); + this.getSelection = this.getSelection.bind(this); + this.getProgress = this.getProgress.bind(this); + this.updateProgress = this.updateProgress.bind(this); + this.addCountToTotal = this.addCountToTotal.bind(this); + this.addCountCompleted = this.addCountCompleted.bind(this); + this.updateRecord = this.updateRecord.bind(this); + this.uploadFile = this.uploadFile.bind(this); + this.name = name; + this.description = description; + this.mimeType = mimeType; + this.logger = new Logger(`${ this.name } Importer`, {}); + this.progress = new Importer.Progress(this.name); + this.collection = Importer.RawImports; + this.AdmZip = Npm.require('adm-zip'); + this.getFileType = Npm.require('file-type'); + const importId = Importer.Imports.insert({ 'type': this.name, 'ts': Date.now(), 'status': this.progress.step, 'valid': true, 'user': Meteor.user()._id }); + this.importRecord = Importer.Imports.findOne(importId); + this.users = {}; + this.channels = {}; + this.messages = {}; + } + + // Takes the uploaded file and extracts the users, channels, and messages from it. + // + // @param [String] dataURI a base64 string of the uploaded file + // @param [String] sentContentType the file type + // @param [String] fileName the name of the uploaded file + // + // @return [Importer.Selection] Contains two properties which are arrays of objects, `channels` and `users`. + // + prepare(dataURI, sentContentType, fileName) { + const fileType = this.getFileType(new Buffer(dataURI.split(',')[1], 'base64')); + this.logger.debug('Uploaded file information is:', fileType); + this.logger.debug('Expected file type is:', this.mimeType); + + if (!fileType || (fileType.mime !== this.mimeType)) { + this.logger.warn(`Invalid file uploaded for the ${ this.name } importer.`); + throw new Meteor.Error('error-invalid-file-uploaded', `Invalid file uploaded to import ${ this.name } data from.`, { step: 'prepare' }); + } + + this.updateProgress(Importer.ProgressStep.PREPARING_STARTED); + return this.updateRecord({ 'file': fileName }); + } + + // Starts the import process. The implementing method should defer as soon as the selection is set, so the user who started the process + // doesn't end up with a "locked" ui while meteor waits for a response. The returned object should be the progress. + // + // @param [Importer.Selection] selectedUsersAndChannels an object with `channels` and `users` which contains information about which users and channels to import + // + // @return [Importer.Progress] the progress of the import + // + startImport(importSelection) { + if (importSelection === undefined) { + throw new Error(`No selected users and channel data provided to the ${ this.name } importer.`); //TODO: Make translatable + } else if (importSelection.users === undefined) { + throw new Error(`Users in the selected data wasn't found, it must but at least an empty array for the ${ this.name } importer.`); //TODO: Make translatable + } else if (importSelection.channels === undefined) { + throw new Error(`Channels in the selected data wasn't found, it must but at least an empty array for the ${ this.name } importer.`); //TODO: Make translatable + } + + return this.updateProgress(Importer.ProgressStep.IMPORTING_STARTED); + } + + // Gets the Importer.Selection object for the import. + // + // @return [Importer.Selection] the users and channels selection + getSelection() { + throw new Error(`Invalid 'getSelection' called on ${ this.name }, it must be overridden and super can not be called.`); + } + + // Gets the progress of this importer. + // + // @return [Importer.Progress] the progress of the import + // + getProgress() { + return this.progress; + } + + // Updates the progress step of this importer. + // + // @return [Importer.Progress] the progress of the import + // + updateProgress(step) { + this.progress.step = step; + + this.logger.debug(`${ this.name } is now at ${ step }.`); + this.updateRecord({ 'status': this.progress.step }); + + return this.progress; + } + + // Adds the passed in value to the total amount of items needed to complete. + // + // @return [Importer.Progress] the progress of the import + // + addCountToTotal(count) { + this.progress.count.total = this.progress.count.total + count; + this.updateRecord({ 'count.total': this.progress.count.total }); + + return this.progress; + } + + // Adds the passed in value to the total amount of items completed. + // + // @return [Importer.Progress] the progress of the import + // + addCountCompleted(count) { + this.progress.count.completed = this.progress.count.completed + count; + + //Only update the database every 500 records + //Or the completed is greater than or equal to the total amount + if (((this.progress.count.completed % 500) === 0) || (this.progress.count.completed >= this.progress.count.total)) { + this.updateRecord({ 'count.completed': this.progress.count.completed }); + } + + return this.progress; + } + + // Updates the import record with the given fields being `set` + // + // @return [Importer.Imports] the import record object + // + updateRecord(fields) { + Importer.Imports.update({ _id: this.importRecord._id }, { $set: fields }); + this.importRecord = Importer.Imports.findOne(this.importRecord._id); + + return this.importRecord; + } + + // Uploads the file to the storage. + // + // @param [Object] details an object with details about the upload. name, size, type, and rid + // @param [String] fileUrl url of the file to download/import + // @param [Object] user the Rocket.Chat user + // @param [Object] room the Rocket.Chat room + // @param [Date] timeStamp the timestamp the file was uploaded + // + uploadFile(details, fileUrl, user, room, timeStamp) { + this.logger.debug(`Uploading the file ${ details.name } from ${ fileUrl }.`); + const requestModule = /https/i.test(fileUrl) ? Importer.Base.https : Importer.Base.http; + + return requestModule.get(fileUrl, Meteor.bindEnvironment(function(stream) { + const fileId = Meteor.fileStore.create(details); + if (fileId) { + return Meteor.fileStore.write(stream, fileId, function(err, file) { + if (err) { + throw new Error(err); + } else { + const url = file.url.replace(Meteor.absoluteUrl(), '/'); + + const attachment = { + title: `File Uploaded: ${ file.name }`, + title_link: url + }; + + if (/^image\/.+/.test(file.type)) { + attachment.image_url = url; + attachment.image_type = file.type; + attachment.image_size = file.size; + attachment.image_dimensions = file.identify != null ? file.identify.size : undefined; + } + + if (/^audio\/.+/.test(file.type)) { + attachment.audio_url = url; + attachment.audio_type = file.type; + attachment.audio_size = file.size; + } + + if (/^video\/.+/.test(file.type)) { + attachment.video_url = url; + attachment.video_type = file.type; + attachment.video_size = file.size; + } + + const msg = { + rid: details.rid, + ts: timeStamp, + msg: '', + file: { + _id: file._id + }, + groupable: false, + attachments: [attachment] + }; + + if ((details.message_id != null) && (typeof details.message_id === 'string')) { + msg['_id'] = details.message_id; + } + + return RocketChat.sendMessage(user, msg, room, true); + } + }); + } else { + return this.logger.error(`Failed to create the store for ${ fileUrl }!!!`); + } + })); + } +}; diff --git a/packages/rocketchat-importer/server/classes/ImporterProgress.coffee b/packages/rocketchat-importer/server/classes/ImporterProgress.coffee deleted file mode 100644 index fcc5cd398390..000000000000 --- a/packages/rocketchat-importer/server/classes/ImporterProgress.coffee +++ /dev/null @@ -1,9 +0,0 @@ -# Class for all the progress of the importers to use. -Importer.Progress = class Importer.Progress - # Constructs a new progress object. - # - # @param [String] name the name of the Importer - # - constructor: (@name) -> - @step = Importer.ProgressStep.NEW - @count = { completed: 0, total: 0 } diff --git a/packages/rocketchat-importer/server/classes/ImporterProgress.js b/packages/rocketchat-importer/server/classes/ImporterProgress.js new file mode 100644 index 000000000000..f1cf9e370677 --- /dev/null +++ b/packages/rocketchat-importer/server/classes/ImporterProgress.js @@ -0,0 +1,13 @@ +/* globals Importer */ +// Class for all the progress of the importers to use. +Importer.Progress = (Importer.Progress = class Progress { + // Constructs a new progress object. + // + // @param [String] name the name of the Importer + // + constructor(name) { + this.name = name; + this.step = Importer.ProgressStep.NEW; + this.count = { completed: 0, total: 0 }; + } +}); diff --git a/packages/rocketchat-importer/server/classes/ImporterProgressStep.coffee b/packages/rocketchat-importer/server/classes/ImporterProgressStep.coffee deleted file mode 100644 index e3972dd24db9..000000000000 --- a/packages/rocketchat-importer/server/classes/ImporterProgressStep.coffee +++ /dev/null @@ -1,16 +0,0 @@ -# "ENUM" of the import step, the value is the translation string -Importer.ProgressStep = Object.freeze - NEW: 'importer_new' - PREPARING_STARTED: 'importer_preparing_started' - PREPARING_USERS: 'importer_preparing_users' - PREPARING_CHANNELS: 'importer_preparing_channels' - PREPARING_MESSAGES: 'importer_preparing_messages' - USER_SELECTION: 'importer_user_selection' - IMPORTING_STARTED: 'importer_importing_started' - IMPORTING_USERS: 'importer_importing_users' - IMPORTING_CHANNELS: 'importer_importing_channels' - IMPORTING_MESSAGES: 'importer_importing_messages' - FINISHING: 'importer_finishing' - DONE: 'importer_done' - ERROR: 'importer_import_failed' - CANCELLED: 'importer_import_cancelled' diff --git a/packages/rocketchat-importer/server/classes/ImporterProgressStep.js b/packages/rocketchat-importer/server/classes/ImporterProgressStep.js new file mode 100644 index 000000000000..542383ce100c --- /dev/null +++ b/packages/rocketchat-importer/server/classes/ImporterProgressStep.js @@ -0,0 +1,18 @@ +/* globals Importer */ +// "ENUM" of the import step, the value is the translation string +Importer.ProgressStep = Object.freeze({ + NEW: 'importer_new', + PREPARING_STARTED: 'importer_preparing_started', + PREPARING_USERS: 'importer_preparing_users', + PREPARING_CHANNELS: 'importer_preparing_channels', + PREPARING_MESSAGES: 'importer_preparing_messages', + USER_SELECTION: 'importer_user_selection', + IMPORTING_STARTED: 'importer_importing_started', + IMPORTING_USERS: 'importer_importing_users', + IMPORTING_CHANNELS: 'importer_importing_channels', + IMPORTING_MESSAGES: 'importer_importing_messages', + FINISHING: 'importer_finishing', + DONE: 'importer_done', + ERROR: 'importer_import_failed', + CANCELLED: 'importer_import_cancelled' +}); diff --git a/packages/rocketchat-importer/server/classes/ImporterSelection.coffee b/packages/rocketchat-importer/server/classes/ImporterSelection.coffee deleted file mode 100644 index 314a0c44b29d..000000000000 --- a/packages/rocketchat-importer/server/classes/ImporterSelection.coffee +++ /dev/null @@ -1,9 +0,0 @@ -# Class for all the selection of users and channels for the importers -Importer.Selection = class Importer.Selection - # Constructs a new importer selection object. - # - # @param [String] name the name of the Importer - # @param [Array] users the array of users - # @param [Array] channels the array of channels - # - constructor: (@name, @users, @channels) -> diff --git a/packages/rocketchat-importer/server/classes/ImporterSelection.js b/packages/rocketchat-importer/server/classes/ImporterSelection.js new file mode 100644 index 000000000000..070af9a70be6 --- /dev/null +++ b/packages/rocketchat-importer/server/classes/ImporterSelection.js @@ -0,0 +1,15 @@ +/* globals Importer */ +// Class for all the selection of users and channels for the importers +Importer.Selection = (Importer.Selection = class Selection { + // Constructs a new importer selection object. + // + // @param [String] name the name of the Importer + // @param [Array] users the array of users + // @param [Array] channels the array of channels + // + constructor(name, users, channels) { + this.name = name; + this.users = users; + this.channels = channels; + } +}); diff --git a/packages/rocketchat-importer/server/classes/ImporterSelectionChannel.coffee b/packages/rocketchat-importer/server/classes/ImporterSelectionChannel.coffee deleted file mode 100644 index 0ff26ef39d54..000000000000 --- a/packages/rocketchat-importer/server/classes/ImporterSelectionChannel.coffee +++ /dev/null @@ -1,12 +0,0 @@ -# Class for the selection channels for ImporterSelection -Importer.SelectionChannel = class Importer.SelectionChannel - # Constructs a new selection channel. - # - # @param [String] channel_id the unique identifier of the channel - # @param [String] name the name of the channel - # @param [Boolean] is_archived whether the channel was archived or not - # @param [Boolean] do_import whether we will be importing the channel or not - # @param [Boolean] is_private whether the channel is private or public - # - constructor: (@channel_id, @name, @is_archived, @do_import, @is_private) -> - #TODO: Add some verification? diff --git a/packages/rocketchat-importer/server/classes/ImporterSelectionChannel.js b/packages/rocketchat-importer/server/classes/ImporterSelectionChannel.js new file mode 100644 index 000000000000..60262cb0e99c --- /dev/null +++ b/packages/rocketchat-importer/server/classes/ImporterSelectionChannel.js @@ -0,0 +1,20 @@ +/* globals Importer */ +// Class for the selection channels for ImporterSelection +Importer.SelectionChannel = (Importer.SelectionChannel = class SelectionChannel { + // Constructs a new selection channel. + // + // @param [String] channel_id the unique identifier of the channel + // @param [String] name the name of the channel + // @param [Boolean] is_archived whether the channel was archived or not + // @param [Boolean] do_import whether we will be importing the channel or not + // @param [Boolean] is_private whether the channel is private or public + // + constructor(channel_id, name, is_archived, do_import, is_private) { + this.channel_id = channel_id; + this.name = name; + this.is_archived = is_archived; + this.do_import = do_import; + this.is_private = is_private; + } +}); + //TODO: Add some verification? diff --git a/packages/rocketchat-importer/server/classes/ImporterSelectionUser.coffee b/packages/rocketchat-importer/server/classes/ImporterSelectionUser.coffee deleted file mode 100644 index 1d46e1df90af..000000000000 --- a/packages/rocketchat-importer/server/classes/ImporterSelectionUser.coffee +++ /dev/null @@ -1,13 +0,0 @@ -# Class for the selection users for ImporterSelection -Importer.SelectionUser = class Importer.SelectionUser - # Constructs a new selection user. - # - # @param [String] user_id the unique user identifier - # @param [String] username the user's username - # @param [String] email the user's email - # @param [Boolean] is_deleted whether the user was deleted or not - # @param [Boolean] is_bot whether the user is a bot or not - # @param [Boolean] do_import whether we are going to import this user or not - # - constructor: (@user_id, @username, @email, @is_deleted, @is_bot, @do_import) -> - #TODO: Add some verification? diff --git a/packages/rocketchat-importer/server/classes/ImporterSelectionUser.js b/packages/rocketchat-importer/server/classes/ImporterSelectionUser.js new file mode 100644 index 000000000000..1e1afffda545 --- /dev/null +++ b/packages/rocketchat-importer/server/classes/ImporterSelectionUser.js @@ -0,0 +1,22 @@ +/* globals Importer */ +// Class for the selection users for ImporterSelection +Importer.SelectionUser = (Importer.SelectionUser = class SelectionUser { + // Constructs a new selection user. + // + // @param [String] user_id the unique user identifier + // @param [String] username the user's username + // @param [String] email the user's email + // @param [Boolean] is_deleted whether the user was deleted or not + // @param [Boolean] is_bot whether the user is a bot or not + // @param [Boolean] do_import whether we are going to import this user or not + // + constructor(user_id, username, email, is_deleted, is_bot, do_import) { + this.user_id = user_id; + this.username = username; + this.email = email; + this.is_deleted = is_deleted; + this.is_bot = is_bot; + this.do_import = do_import; + } +}); + //TODO: Add some verification? diff --git a/packages/rocketchat-importer/server/methods/getImportProgress.coffee b/packages/rocketchat-importer/server/methods/getImportProgress.coffee deleted file mode 100644 index 89ba39919b0e..000000000000 --- a/packages/rocketchat-importer/server/methods/getImportProgress.coffee +++ /dev/null @@ -1,12 +0,0 @@ -Meteor.methods - getImportProgress: (name) -> - if not Meteor.userId() - throw new Meteor.Error 'error-invalid-user', 'Invalid user', { method: 'getImportProgress' } - - if not RocketChat.authz.hasPermission(Meteor.userId(), 'run-import') - throw new Meteor.Error('error-action-not-allowed', 'Importing is not allowed', { method: 'setupImporter'}); - - if Importer.Importers[name]? - return Importer.Importers[name].importerInstance?.getProgress() - else - throw new Meteor.Error 'error-importer-not-defined', 'The importer was not defined correctly, it is missing the Import class.', { method: 'getImportProgress' } diff --git a/packages/rocketchat-importer/server/methods/getImportProgress.js b/packages/rocketchat-importer/server/methods/getImportProgress.js new file mode 100644 index 000000000000..f3ca0c279351 --- /dev/null +++ b/packages/rocketchat-importer/server/methods/getImportProgress.js @@ -0,0 +1,17 @@ +/* globals Importer */ +Meteor.methods({ + getImportProgress(name) { + if (!Meteor.userId()) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'getImportProgress' }); + } + + if (!RocketChat.authz.hasPermission(Meteor.userId(), 'run-import')) { + throw new Meteor.Error('error-action-not-allowed', 'Importing is not allowed', { method: 'setupImporter'}); + } + + if (Importer.Importers[name] != null) { + return (Importer.Importers[name].importerInstance != null ? Importer.Importers[name].importerInstance.getProgress() : undefined); + } else { + throw new Meteor.Error('error-importer-not-defined', 'The importer was not defined correctly, it is missing the Import class.', { method: 'getImportProgress' }); + } + }}); diff --git a/packages/rocketchat-importer/server/methods/getSelectionData.coffee b/packages/rocketchat-importer/server/methods/getSelectionData.coffee deleted file mode 100644 index 8b4780c894a9..000000000000 --- a/packages/rocketchat-importer/server/methods/getSelectionData.coffee +++ /dev/null @@ -1,17 +0,0 @@ -Meteor.methods - getSelectionData: (name) -> - if not Meteor.userId() - throw new Meteor.Error 'error-invalid-user', 'Invalid user', { method: 'getSelectionData' } - - if not RocketChat.authz.hasPermission(Meteor.userId(), 'run-import') - throw new Meteor.Error('error-action-not-allowed', 'Importing is not allowed', { method: 'setupImporter'}); - - if Importer.Importers[name]?.importerInstance? - progress = Importer.Importers[name].importerInstance.getProgress() - switch progress.step - when Importer.ProgressStep.USER_SELECTION - return Importer.Importers[name].importerInstance.getSelection() - else - return false - else - throw new Meteor.Error 'error-importer-not-defined', 'The importer was not defined correctly, it is missing the Import class.', { method: 'getSelectionData' } diff --git a/packages/rocketchat-importer/server/methods/getSelectionData.js b/packages/rocketchat-importer/server/methods/getSelectionData.js new file mode 100644 index 000000000000..a8e2a2f57c11 --- /dev/null +++ b/packages/rocketchat-importer/server/methods/getSelectionData.js @@ -0,0 +1,23 @@ +/* globals Importer */ +Meteor.methods({ + getSelectionData(name) { + if (!Meteor.userId()) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'getSelectionData' }); + } + + if (!RocketChat.authz.hasPermission(Meteor.userId(), 'run-import')) { + throw new Meteor.Error('error-action-not-allowed', 'Importing is not allowed', { method: 'setupImporter'}); + } + + if ((Importer.Importers[name] != null ? Importer.Importers[name].importerInstance : undefined) != null) { + const progress = Importer.Importers[name].importerInstance.getProgress(); + switch (progress.step) { + case Importer.ProgressStep.USER_SELECTION: + return Importer.Importers[name].importerInstance.getSelection(); + default: + return false; + } + } else { + throw new Meteor.Error('error-importer-not-defined', 'The importer was not defined correctly, it is missing the Import class.', { method: 'getSelectionData' }); + } + }}); diff --git a/packages/rocketchat-importer/server/methods/restartImport.coffee b/packages/rocketchat-importer/server/methods/restartImport.coffee deleted file mode 100644 index aff7a019b43c..000000000000 --- a/packages/rocketchat-importer/server/methods/restartImport.coffee +++ /dev/null @@ -1,17 +0,0 @@ -Meteor.methods - restartImport: (name) -> - if not Meteor.userId() - throw new Meteor.Error 'error-invalid-user', 'Invalid user', { method: 'restartImport' } - - if not RocketChat.authz.hasPermission(Meteor.userId(), 'run-import') - throw new Meteor.Error('error-action-not-allowed', 'Importing is not allowed', { method: 'setupImporter'}); - - if Importer.Importers[name]? - importer = Importer.Importers[name] - importer.importerInstance.updateProgress Importer.ProgressStep.CANCELLED - importer.importerInstance.updateRecord { valid: false } - importer.importerInstance = undefined - importer.importerInstance = new importer.importer importer.name, importer.description, importer.mimeType - return importer.importerInstance.getProgress() - else - throw new Meteor.Error 'error-importer-not-defined', 'The importer was not defined correctly, it is missing the Import class.', { method: 'restartImport' } diff --git a/packages/rocketchat-importer/server/methods/restartImport.js b/packages/rocketchat-importer/server/methods/restartImport.js new file mode 100644 index 000000000000..c4a69df03ddb --- /dev/null +++ b/packages/rocketchat-importer/server/methods/restartImport.js @@ -0,0 +1,22 @@ +/* globals Importer*/ +Meteor.methods({ + restartImport(name) { + if (!Meteor.userId()) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'restartImport' }); + } + + if (!RocketChat.authz.hasPermission(Meteor.userId(), 'run-import')) { + throw new Meteor.Error('error-action-not-allowed', 'Importing is not allowed', { method: 'setupImporter'}); + } + + if (Importer.Importers[name] != null) { + const importer = Importer.Importers[name]; + importer.importerInstance.updateProgress(Importer.ProgressStep.CANCELLED); + importer.importerInstance.updateRecord({ valid: false }); + importer.importerInstance = undefined; + importer.importerInstance = new importer.importer(importer.name, importer.description, importer.mimeType); // eslint-disable-line new-cap + return importer.importerInstance.getProgress(); + } else { + throw new Meteor.Error('error-importer-not-defined', 'The importer was not defined correctly, it is missing the Import class.', { method: 'restartImport' }); + } + }}); diff --git a/packages/rocketchat-importer/server/methods/setupImporter.coffee b/packages/rocketchat-importer/server/methods/setupImporter.coffee deleted file mode 100644 index 5ae12ab836c8..000000000000 --- a/packages/rocketchat-importer/server/methods/setupImporter.coffee +++ /dev/null @@ -1,19 +0,0 @@ -Meteor.methods - setupImporter: (name) -> - if not Meteor.userId() - throw new Meteor.Error 'error-invalid-user', 'Invalid user', { method: 'setupImporter' } - - if not RocketChat.authz.hasPermission(Meteor.userId(), 'run-import') - throw new Meteor.Error('error-action-not-allowed', 'Importing is not allowed', { method: 'setupImporter'}); - - if Importer.Importers[name]?.importer? - importer = Importer.Importers[name] - # If they currently have progress, get it and return the progress. - if importer.importerInstance - return importer.importerInstance.getProgress() - else - importer.importerInstance = new importer.importer importer.name, importer.description, importer.mimeType - return importer.importerInstance.getProgress() - else - console.warn "Tried to setup #{name} as an importer." - throw new Meteor.Error 'error-importer-not-defined', 'The importer was not defined correctly, it is missing the Import class.', { method: 'setupImporter' } diff --git a/packages/rocketchat-importer/server/methods/setupImporter.js b/packages/rocketchat-importer/server/methods/setupImporter.js new file mode 100644 index 000000000000..38cd360efa1f --- /dev/null +++ b/packages/rocketchat-importer/server/methods/setupImporter.js @@ -0,0 +1,25 @@ +/* globals Importer */ +Meteor.methods({ + setupImporter(name) { + if (!Meteor.userId()) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'setupImporter' }); + } + + if (!RocketChat.authz.hasPermission(Meteor.userId(), 'run-import')) { + throw new Meteor.Error('error-action-not-allowed', 'Importing is not allowed', { method: 'setupImporter'}); + } + + if ((Importer.Importers[name] != null ? Importer.Importers[name].importer : undefined) != null) { + const importer = Importer.Importers[name]; + // If they currently have progress, get it and return the progress. + if (importer.importerInstance) { + return importer.importerInstance.getProgress(); + } else { + importer.importerInstance = new importer.importer(importer.name, importer.description, importer.mimeType); //eslint-disable-line new-cap + return importer.importerInstance.getProgress(); + } + } else { + console.warn(`Tried to setup ${ name } as an importer.`); + throw new Meteor.Error('error-importer-not-defined', 'The importer was not defined correctly, it is missing the Import class.', { method: 'setupImporter' }); + } + }}); diff --git a/packages/rocketchat-importer/server/methods/startImport.coffee b/packages/rocketchat-importer/server/methods/startImport.coffee deleted file mode 100644 index b119f6eb8a86..000000000000 --- a/packages/rocketchat-importer/server/methods/startImport.coffee +++ /dev/null @@ -1,19 +0,0 @@ -Meteor.methods - startImport: (name, input) -> - # Takes name and object with users / channels selected to import - if not Meteor.userId() - throw new Meteor.Error 'error-invalid-user', 'Invalid user', { method: 'startImport' } - - if not RocketChat.authz.hasPermission(Meteor.userId(), 'run-import') - throw new Meteor.Error('error-action-not-allowed', 'Importing is not allowed', { method: 'setupImporter'}); - - if Importer.Importers[name]?.importerInstance? - usersSelection = input.users.map (user) -> - return new Importer.SelectionUser user.user_id, user.username, user.email, user.is_deleted, user.is_bot, user.do_import - channelsSelection = input.channels.map (channel) -> - return new Importer.SelectionChannel channel.channel_id, channel.name, channel.is_archived, channel.do_import - - selection = new Importer.Selection name, usersSelection, channelsSelection - Importer.Importers[name].importerInstance.startImport selection - else - throw new Meteor.Error 'error-importer-not-defined', 'The importer was not defined correctly, it is missing the Import class.', { method: 'startImport' } diff --git a/packages/rocketchat-importer/server/methods/startImport.js b/packages/rocketchat-importer/server/methods/startImport.js new file mode 100644 index 000000000000..352193e28a2f --- /dev/null +++ b/packages/rocketchat-importer/server/methods/startImport.js @@ -0,0 +1,22 @@ +/* globals Importer */ +Meteor.methods({ + startImport(name, input) { + // Takes name and object with users / channels selected to import + if (!Meteor.userId()) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'startImport' }); + } + + if (!RocketChat.authz.hasPermission(Meteor.userId(), 'run-import')) { + throw new Meteor.Error('error-action-not-allowed', 'Importing is not allowed', { method: 'setupImporter'}); + } + + if (Importer.Importers[name] && Importer.Importers[name].importerInstance) { + const usersSelection = input.users.map(user => new Importer.SelectionUser(user.user_id, user.username, user.email, user.is_deleted, user.is_bot, user.do_import)); + const channelsSelection = input.channels.map(channel => new Importer.SelectionChannel(channel.channel_id, channel.name, channel.is_archived, channel.do_import)); + + const selection = new Importer.Selection(name, usersSelection, channelsSelection); + return Importer.Importers[name].importerInstance.startImport(selection); + } else { + throw new Meteor.Error('error-importer-not-defined', 'The importer was not defined correctly, it is missing the Import class.', { method: 'startImport' }); + } + }}); diff --git a/packages/rocketchat-importer/server/models/Imports.coffee b/packages/rocketchat-importer/server/models/Imports.coffee deleted file mode 100644 index 2e966dc83809..000000000000 --- a/packages/rocketchat-importer/server/models/Imports.coffee +++ /dev/null @@ -1,3 +0,0 @@ -Importer.Imports = new class Importer.Imports extends RocketChat.models._Base - constructor: -> - super('import') diff --git a/packages/rocketchat-importer/server/models/Imports.js b/packages/rocketchat-importer/server/models/Imports.js new file mode 100644 index 000000000000..f68dc3e1d246 --- /dev/null +++ b/packages/rocketchat-importer/server/models/Imports.js @@ -0,0 +1,6 @@ +/* globals Importer */ +Importer.Imports = new (Importer.Imports = class Imports extends RocketChat.models._Base { + constructor() { + super('import'); + } +}); diff --git a/packages/rocketchat-importer/server/models/RawImports.coffee b/packages/rocketchat-importer/server/models/RawImports.coffee deleted file mode 100644 index 1495423ea4ac..000000000000 --- a/packages/rocketchat-importer/server/models/RawImports.coffee +++ /dev/null @@ -1,3 +0,0 @@ -Importer.RawImports = new class Importer.RawImports extends RocketChat.models._Base - constructor: -> - super('raw_imports') diff --git a/packages/rocketchat-importer/server/models/RawImports.js b/packages/rocketchat-importer/server/models/RawImports.js new file mode 100644 index 000000000000..70eae7f76c06 --- /dev/null +++ b/packages/rocketchat-importer/server/models/RawImports.js @@ -0,0 +1,6 @@ +/* globals Importer */ +Importer.RawImports = new (Importer.RawImports = class RawImports extends RocketChat.models._Base { + constructor() { + super('raw_imports'); + } +}); diff --git a/packages/rocketchat-importer/server/startup/setImportsToInvalid.coffee b/packages/rocketchat-importer/server/startup/setImportsToInvalid.coffee deleted file mode 100644 index fe4e5056f1eb..000000000000 --- a/packages/rocketchat-importer/server/startup/setImportsToInvalid.coffee +++ /dev/null @@ -1,8 +0,0 @@ -Meteor.startup -> - # Make sure all imports are marked as invalid, data clean up since you can't - # restart an import at the moment. - Importer.Imports.update { valid: { $ne: false } }, { $set: { valid: false } }, { multi: true } - - # Clean up all the raw import data, since you can't restart an import at the moment - Importer.Imports.find({ valid: { $ne: true }}).forEach (item) -> - Importer.RawImports.remove { 'import': item._id, 'importer': item.type } diff --git a/packages/rocketchat-importer/server/startup/setImportsToInvalid.js b/packages/rocketchat-importer/server/startup/setImportsToInvalid.js new file mode 100644 index 000000000000..2424ef40a007 --- /dev/null +++ b/packages/rocketchat-importer/server/startup/setImportsToInvalid.js @@ -0,0 +1,9 @@ +/* globals Importer */ +Meteor.startup(function() { + // Make sure all imports are marked as invalid, data clean up since you can't + // restart an import at the moment. + Importer.Imports.update({ valid: { $ne: false } }, { $set: { valid: false } }, { multi: true }); + + // Clean up all the raw import data, since you can't restart an import at the moment + return Importer.Imports.find({ valid: { $ne: true }}).forEach(item => Importer.RawImports.remove({ 'import': item._id, 'importer': item.type })); +});