Merge pull request #8966 from RocketChat/slack-importer-related-fixes

[FIX] Importers failing when usernames exists but cases don't match and improve the importer framework's performance
pull/9003/merge
Rodrigo Nascimento 8 years ago committed by GitHub
commit 2f05cf196f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      .meteor/packages
  2. 1
      .meteor/versions
  3. 2
      packages/rocketchat-i18n/i18n/en.i18n.json
  4. 4
      packages/rocketchat-importer-csv/client/adder.js
  5. 10
      packages/rocketchat-importer-csv/info.js
  6. 10
      packages/rocketchat-importer-csv/main.js
  7. 12
      packages/rocketchat-importer-csv/package.js
  8. 5
      packages/rocketchat-importer-csv/server/adder.js
  9. 55
      packages/rocketchat-importer-csv/server/importer.js
  10. 4
      packages/rocketchat-importer-hipchat-enterprise/client/adder.js
  11. 15
      packages/rocketchat-importer-hipchat-enterprise/info.js
  12. 15
      packages/rocketchat-importer-hipchat-enterprise/main.js
  13. 11
      packages/rocketchat-importer-hipchat-enterprise/package.js
  14. 5
      packages/rocketchat-importer-hipchat-enterprise/server/adder.js
  15. 59
      packages/rocketchat-importer-hipchat-enterprise/server/importer.js
  16. 4
      packages/rocketchat-importer-hipchat/client/adder.js
  17. 7
      packages/rocketchat-importer-hipchat/info.js
  18. 6
      packages/rocketchat-importer-hipchat/main.js
  19. 12
      packages/rocketchat-importer-hipchat/package.js
  20. 349
      packages/rocketchat-importer-hipchat/server.js
  21. 5
      packages/rocketchat-importer-hipchat/server/adder.js
  22. 347
      packages/rocketchat-importer-hipchat/server/importer.js
  23. 1
      packages/rocketchat-importer-slack-users/.npm/package/.gitignore
  24. 7
      packages/rocketchat-importer-slack-users/.npm/package/README
  25. 10
      packages/rocketchat-importer-slack-users/.npm/package/npm-shrinkwrap.json
  26. 4
      packages/rocketchat-importer-slack-users/client/adder.js
  27. 10
      packages/rocketchat-importer-slack-users/info.js
  28. 29
      packages/rocketchat-importer-slack-users/package.js
  29. 5
      packages/rocketchat-importer-slack-users/server/adder.js
  30. 135
      packages/rocketchat-importer-slack-users/server/importer.js
  31. 4
      packages/rocketchat-importer-slack/client/adder.js
  32. 7
      packages/rocketchat-importer-slack/info.js
  33. 5
      packages/rocketchat-importer-slack/main.js
  34. 12
      packages/rocketchat-importer-slack/package.js
  35. 5
      packages/rocketchat-importer-slack/server/adder.js
  36. 75
      packages/rocketchat-importer-slack/server/importer.js
  37. 29
      packages/rocketchat-importer/client/ImporterWebsocketReceiver.js
  38. 10
      packages/rocketchat-importer/client/admin/adminImport.js
  39. 19
      packages/rocketchat-importer/client/admin/adminImportPrepare.js
  40. 84
      packages/rocketchat-importer/client/admin/adminImportProgress.js
  41. 11
      packages/rocketchat-importer/client/index.js
  42. 19
      packages/rocketchat-importer/lib/ImporterInfo.js
  43. 5
      packages/rocketchat-importer/lib/ImporterProgressStep.js
  44. 47
      packages/rocketchat-importer/lib/Importers.js
  45. 3
      packages/rocketchat-importer/lib/_importer.js
  46. 13
      packages/rocketchat-importer/lib/importTool.js
  47. 11
      packages/rocketchat-importer/package.js
  48. 266
      packages/rocketchat-importer/server/classes/ImporterBase.js
  49. 23
      packages/rocketchat-importer/server/classes/ImporterProgress.js
  50. 21
      packages/rocketchat-importer/server/classes/ImporterSelection.js
  51. 24
      packages/rocketchat-importer/server/classes/ImporterSelectionChannel.js
  52. 26
      packages/rocketchat-importer/server/classes/ImporterSelectionUser.js
  53. 19
      packages/rocketchat-importer/server/classes/ImporterWebsocket.js
  54. 25
      packages/rocketchat-importer/server/index.js
  55. 22
      packages/rocketchat-importer/server/methods/getImportProgress.js
  56. 34
      packages/rocketchat-importer/server/methods/getSelectionData.js
  57. 28
      packages/rocketchat-importer/server/methods/prepareImport.js
  58. 33
      packages/rocketchat-importer/server/methods/restartImport.js
  59. 28
      packages/rocketchat-importer/server/methods/setupImporter.js
  60. 33
      packages/rocketchat-importer/server/methods/startImport.js
  61. 7
      packages/rocketchat-importer/server/models/Imports.js
  62. 7
      packages/rocketchat-importer/server/models/RawImports.js
  63. 13
      packages/rocketchat-importer/server/startup/setImportsToInvalid.js
  64. 6
      packages/rocketchat-lib/server/models/Users.js
  65. 2
      packages/rocketchat-reactions/setReaction.js

@ -74,6 +74,7 @@ rocketchat:importer-csv
rocketchat:importer-hipchat
rocketchat:importer-hipchat-enterprise
rocketchat:importer-slack
rocketchat:importer-slack-users
rocketchat:integrations
rocketchat:internal-hubot
rocketchat:irc

@ -160,6 +160,7 @@ rocketchat:importer-csv@1.0.0
rocketchat:importer-hipchat@0.0.1
rocketchat:importer-hipchat-enterprise@1.0.0
rocketchat:importer-slack@0.0.1
rocketchat:importer-slack-users@1.0.0
rocketchat:integrations@0.0.1
rocketchat:internal-hubot@0.0.1
rocketchat:irc@0.0.2

@ -826,6 +826,7 @@
"Importer_From_Description": "Imports __from__ data into Rocket.Chat.",
"Importer_HipChatEnterprise_BetaWarning": "Please be aware that this import is still a work in progress, please report any errors which occur in GitHub:",
"Importer_HipChatEnterprise_Information": "The file uploaded must be a decrypted tar.gz, please read the documentation for further information:",
"Importer_Slack_Users_CSV_Information": "The file uploaded must be Slack's Users export file, which is a CSV file. See here for more information:",
"Importer_import_cancelled": "Import cancelled.",
"Importer_import_failed": "An error occurred while running the import.",
"Importer_importing_channels": "Importing the channels.",
@ -1661,6 +1662,7 @@
"SlackBridge_Out_Enabled": "SlackBridge Out Enabled",
"SlackBridge_Out_Enabled_Description": "Choose whether SlackBridge should also send your messages back to Slack",
"SlackBridge_start": "@%s has started a SlackBridge import at `#%s`. We'll let you know when it's finished.",
"Slack_Users": "Slack's Users CSV",
"Slash_Gimme_Description": "Displays ༼ つ ◕_◕ ༽つ before your message",
"Slash_LennyFace_Description": "Displays ( ͡° ͜ʖ ͡°) after your message",
"Slash_Shrug_Description": "Displays ¯\\_(ツ)_/¯ after your message",

@ -0,0 +1,4 @@
import { Importers } from 'meteor/rocketchat:importer';
import { CsvImporterInfo } from '../info';
Importers.add(new CsvImporterInfo());

@ -0,0 +1,10 @@
import { ImporterInfo } from 'meteor/rocketchat:importer';
export class CsvImporterInfo extends ImporterInfo {
constructor() {
super('csv', 'CSV', 'application/zip', [{
text: 'Importer_CSV_Information',
href: 'https://rocket.chat/docs/administrator-guides/import/csv/'
}]);
}
}

@ -1,10 +0,0 @@
/* globals Importer */
Importer.addImporter('csv', Importer.CSV, {
name: 'CSV',
warnings: [{
text: 'Importer_CSV_Information',
href: 'https://rocket.chat/docs/administrator-guides/import/csv/'
}],
mimeType: 'application/zip'
});

@ -11,9 +11,17 @@ Package.onUse(function(api) {
'rocketchat:lib',
'rocketchat:importer'
]);
api.use('rocketchat:logger', 'server');
api.addFiles('server.js', 'server');
api.addFiles('main.js', ['client', 'server']);
// Importer information to both server and client
api.addFiles('info.js');
// Server files
api.addFiles(['server/importer.js', 'server/adder.js'], 'server');
// Client files
api.addFiles('client/adder.js', 'client');
});
Npm.depends({

@ -0,0 +1,5 @@
import { Importers } from 'meteor/rocketchat:importer';
import { CsvImporterInfo } from '../info';
import { CsvImporter } from './importer';
Importers.add(new CsvImporterInfo(), CsvImporter);

@ -1,9 +1,14 @@
/* globals Importer */
Importer.CSV = class ImporterCSV extends Importer.Base {
constructor(name, descriptionI18N, mimeType) {
super(name, descriptionI18N, mimeType);
this.logger.debug('Constructed a new CSV Importer.');
import {
Base,
ProgressStep,
Selection,
SelectionChannel,
SelectionUser
} from 'meteor/rocketchat:importer';
export class CsvImporter extends Base {
constructor(info) {
super(info);
this.csvParser = Npm.require('csv-parse/lib/sync');
this.messages = new Map();
@ -36,7 +41,7 @@ Importer.CSV = class ImporterCSV extends Importer.Base {
//Parse the channels
if (entry.entryName.toLowerCase() === 'channels.csv') {
super.updateProgress(Importer.ProgressStep.PREPARING_CHANNELS);
super.updateProgress(ProgressStep.PREPARING_CHANNELS);
const parsedChannels = this.csvParser(entry.getData().toString());
tempChannels = parsedChannels.map((c) => {
return {
@ -52,7 +57,7 @@ Importer.CSV = class ImporterCSV extends Importer.Base {
//Parse the users
if (entry.entryName.toLowerCase() === 'users.csv') {
super.updateProgress(Importer.ProgressStep.PREPARING_USERS);
super.updateProgress(ProgressStep.PREPARING_USERS);
const parsedUsers = this.csvParser(entry.getData().toString());
tempUsers = parsedUsers.map((u) => { return { id: u[0].trim().replace('.', '_'), username: u[0].trim(), email: u[1].trim(), name: u[2].trim() }; });
continue;
@ -96,7 +101,7 @@ Importer.CSV = class ImporterCSV extends Importer.Base {
super.addCountToTotal(tempChannels.length);
// Save the messages records to the import record for `startImport` usage
super.updateProgress(Importer.ProgressStep.PREPARING_MESSAGES);
super.updateProgress(ProgressStep.PREPARING_MESSAGES);
let messagesCount = 0;
for (const [channel, messagesMap] of tempMessages.entries()) {
if (!this.messages.get(channel)) {
@ -107,8 +112,8 @@ Importer.CSV = class ImporterCSV extends Importer.Base {
messagesCount += msgs.length;
super.updateRecord({ 'messagesstatus': `${ channel }/${ msgGroupData }` });
if (Importer.Base.getBSONSize(msgs) > Importer.Base.MaxBSONSize) {
Importer.Base.getBSONSafeArraysFromAnArray(msgs).forEach((splitMsg, i) => {
if (Base.getBSONSize(msgs) > Base.getMaxBSONSize()) {
Base.getBSONSafeArraysFromAnArray(msgs).forEach((splitMsg, i) => {
const messagesId = this.collection.insert({ 'import': this.importRecord._id, 'importer': this.name, 'type': 'messages', 'name': `${ channel }/${ msgGroupData }.${ i }`, 'messages': splitMsg });
this.messages.get(channel).set(`${ msgGroupData }.${ i }`, this.collection.findOne(messagesId));
});
@ -125,16 +130,16 @@ Importer.CSV = class ImporterCSV extends Importer.Base {
//Ensure we have at least a single user, channel, or message
if (tempUsers.length === 0 && tempChannels.length === 0 && messagesCount === 0) {
this.logger.error('No users, channels, or messages found in the import file.');
super.updateProgress(Importer.ProgressStep.ERROR);
super.updateProgress(ProgressStep.ERROR);
return super.getProgress();
}
const selectionUsers = tempUsers.map((u) => new Importer.SelectionUser(u.id, u.username, u.email, false, false, true));
const selectionChannels = tempChannels.map((c) => new Importer.SelectionChannel(c.id, c.name, false, true, c.isPrivate));
const selectionUsers = tempUsers.map((u) => new SelectionUser(u.id, u.username, u.email, false, false, true));
const selectionChannels = tempChannels.map((c) => new SelectionChannel(c.id, c.name, false, true, c.isPrivate));
const selectionMessages = this.importRecord.count.messages;
super.updateProgress(Importer.ProgressStep.USER_SELECTION);
return new Importer.Selection(this.name, selectionUsers, selectionChannels, selectionMessages);
super.updateProgress(ProgressStep.USER_SELECTION);
return new Selection(this.name, selectionUsers, selectionChannels, selectionMessages);
}
startImport(importSelection) {
@ -163,7 +168,7 @@ Importer.CSV = class ImporterCSV extends Importer.Base {
const startedByUserId = Meteor.userId();
Meteor.defer(() => {
super.updateProgress(Importer.ProgressStep.IMPORTING_USERS);
super.updateProgress(ProgressStep.IMPORTING_USERS);
//Import the users
for (const u of this.users.users) {
if (!u.do_import) {
@ -198,7 +203,7 @@ Importer.CSV = class ImporterCSV extends Importer.Base {
this.collection.update({ _id: this.users._id }, { $set: { 'users': this.users.users }});
//Import the channels
super.updateProgress(Importer.ProgressStep.IMPORTING_CHANNELS);
super.updateProgress(ProgressStep.IMPORTING_CHANNELS);
for (const c of this.channels.channels) {
if (!c.do_import) {
continue;
@ -277,7 +282,7 @@ Importer.CSV = class ImporterCSV extends Importer.Base {
//Import the Messages
super.updateProgress(Importer.ProgressStep.IMPORTING_MESSAGES);
super.updateProgress(ProgressStep.IMPORTING_MESSAGES);
for (const [ch, messagesMap] of this.messages.entries()) {
const csvChannel = this.getChannelFromName(ch);
if (!csvChannel || !csvChannel.do_import) {
@ -325,8 +330,8 @@ Importer.CSV = class ImporterCSV extends Importer.Base {
});
}
super.updateProgress(Importer.ProgressStep.FINISHING);
super.updateProgress(Importer.ProgressStep.DONE);
super.updateProgress(ProgressStep.FINISHING);
super.updateProgress(ProgressStep.DONE);
const timeTook = Date.now() - started;
this.logger.log(`CSV Import took ${ timeTook } milliseconds.`);
});
@ -335,11 +340,11 @@ Importer.CSV = class ImporterCSV extends Importer.Base {
}
getSelection() {
const selectionUsers = this.users.users.map((u) => new Importer.SelectionUser(u.id, u.username, u.email, false, false, true));
const selectionChannels = this.channels.channels.map((c) => new Importer.SelectionChannel(c.id, c.name, false, true, c.isPrivate));
const selectionUsers = this.users.users.map((u) => new SelectionUser(u.id, u.username, u.email, false, false, true));
const selectionChannels = this.channels.channels.map((c) => new SelectionChannel(c.id, c.name, false, true, c.isPrivate));
const selectionMessages = this.importRecord.count.messages;
return new Importer.Selection(this.name, selectionUsers, selectionChannels, selectionMessages);
return new Selection(this.name, selectionUsers, selectionChannels, selectionMessages);
}
getChannelFromName(channelName) {
@ -357,4 +362,4 @@ Importer.CSV = class ImporterCSV extends Importer.Base {
}
}
}
};
}

@ -0,0 +1,4 @@
import { Importers } from 'meteor/rocketchat:importer';
import { HipChatEnterpriseImporterInfo } from '../info';
Importers.add(new HipChatEnterpriseImporterInfo());

@ -0,0 +1,15 @@
import { ImporterInfo } from 'meteor/rocketchat:importer';
export class HipChatEnterpriseImporterInfo extends ImporterInfo {
constructor() {
super('hipchatenterprise', 'HipChat Enterprise', 'application/gzip', [
{
text: 'Importer_HipChatEnterprise_Information',
href: 'https://rocket.chat/docs/administrator-guides/import/hipchat/enterprise/'
}, {
text: 'Importer_HipChatEnterprise_BetaWarning',
href: 'https://github.com/RocketChat/Rocket.Chat/issues/new'
}
]);
}
}

@ -1,15 +0,0 @@
/* globals Importer */
Importer.addImporter('hipchatenterprise', Importer.HipChatEnterprise, {
name: 'HipChat Enterprise',
warnings: [
{
text: 'Importer_HipChatEnterprise_Information',
href: 'https://rocket.chat/docs/administrator-guides/import/hipchat/enterprise/'
}, {
text: 'Importer_HipChatEnterprise_BetaWarning',
href: 'https://github.com/RocketChat/Rocket.Chat/issues/new'
}
],
mimeType: 'application/gzip'
});

@ -13,8 +13,15 @@ Package.onUse(function(api) {
]);
api.use('rocketchat:logger', 'server');
api.addFiles('server.js', 'server');
api.addFiles('main.js', ['client', 'server']);
// Importer information to both server and client
api.addFiles('info.js');
// Server files
api.addFiles(['server/importer.js', 'server/adder.js'], 'server');
// Client files
api.addFiles('client/adder.js', 'client');
});
Npm.depends({

@ -0,0 +1,5 @@
import { Importers } from 'meteor/rocketchat:importer';
import { HipChatEnterpriseImporterInfo } from '../info';
import { HipChatEnterpriseImporter } from './importer';
Importers.add(new HipChatEnterpriseImporterInfo(), HipChatEnterpriseImporter);

@ -1,9 +1,14 @@
/* globals Importer */
Importer.HipChatEnterprise = class ImporterHipChatEnterprise extends Importer.Base {
constructor(name, descriptionI18N, mimeType) {
super(name, descriptionI18N, mimeType);
this.logger.debug('Constructed a new HipChat Enterprise Importer.');
import {
Base,
ProgressStep,
Selection,
SelectionChannel,
SelectionUser
} from 'meteor/rocketchat:importer';
export class HipChatEnterpriseImporter extends Base {
constructor(info) {
super(info);
this.Readable = require('stream').Readable;
this.zlib = require('zlib');
@ -31,7 +36,7 @@ Importer.HipChatEnterprise = class ImporterHipChatEnterprise extends Importer.Ba
const file = JSON.parse(chunk);
if (info.base === 'users.json') {
super.updateProgress(Importer.ProgressStep.PREPARING_USERS);
super.updateProgress(ProgressStep.PREPARING_USERS);
for (const u of file) {
tempUsers.push({
id: u.User.id,
@ -44,7 +49,7 @@ Importer.HipChatEnterprise = class ImporterHipChatEnterprise extends Importer.Ba
});
}
} else if (info.base === 'rooms.json') {
super.updateProgress(Importer.ProgressStep.PREPARING_CHANNELS);
super.updateProgress(ProgressStep.PREPARING_CHANNELS);
for (const r of file) {
tempRooms.push({
id: r.Room.id,
@ -136,7 +141,7 @@ Importer.HipChatEnterprise = class ImporterHipChatEnterprise extends Importer.Ba
super.addCountToTotal(tempRooms.length);
// Save the messages records to the import record for `startImport` usage
super.updateProgress(Importer.ProgressStep.PREPARING_MESSAGES);
super.updateProgress(ProgressStep.PREPARING_MESSAGES);
let messagesCount = 0;
for (const [channel, msgs] of tempMessages.entries()) {
if (!this.messages.get(channel)) {
@ -146,8 +151,8 @@ Importer.HipChatEnterprise = class ImporterHipChatEnterprise extends Importer.Ba
messagesCount += msgs.length;
super.updateRecord({ 'messagesstatus': channel });
if (Importer.Base.getBSONSize(msgs) > Importer.Base.MaxBSONSize) {
Importer.Base.getBSONSafeArraysFromAnArray(msgs).forEach((splitMsg, i) => {
if (Base.getBSONSize(msgs) > Base.getMaxBSONSize()) {
Base.getBSONSafeArraysFromAnArray(msgs).forEach((splitMsg, i) => {
const messagesId = this.collection.insert({ 'import': this.importRecord._id, 'importer': this.name, 'type': 'messages', 'name': `${ channel }/${ i }`, 'messages': splitMsg });
this.messages.get(channel).set(`${ channel }.${ i }`, this.collection.findOne(messagesId));
});
@ -166,8 +171,8 @@ Importer.HipChatEnterprise = class ImporterHipChatEnterprise extends Importer.Ba
messagesCount += msgs.length;
super.updateRecord({ 'messagesstatus': directMsgUser });
if (Importer.Base.getBSONSize(msgs) > Importer.Base.MaxBSONSize) {
Importer.Base.getBSONSafeArraysFromAnArray(msgs).forEach((splitMsg, i) => {
if (Base.getBSONSize(msgs) > Base.getMaxBSONSize()) {
Base.getBSONSafeArraysFromAnArray(msgs).forEach((splitMsg, i) => {
const messagesId = this.collection.insert({ 'import': this.importRecord._id, 'importer': this.name, 'type': 'directMessages', 'name': `${ directMsgUser }/${ i }`, 'messages': splitMsg });
this.directMessages.get(directMsgUser).set(`${ directMsgUser }.${ i }`, this.collection.findOne(messagesId));
});
@ -183,18 +188,18 @@ Importer.HipChatEnterprise = class ImporterHipChatEnterprise extends Importer.Ba
//Ensure we have some users, channels, and messages
if (tempUsers.length === 0 || tempRooms.length === 0 || messagesCount === 0) {
this.logger.warn(`The loaded users count ${ tempUsers.length }, the loaded rooms ${ tempRooms.length }, and the loaded messages ${ messagesCount }`);
super.updateProgress(Importer.ProgressStep.ERROR);
super.updateProgress(ProgressStep.ERROR);
reject();
return;
}
const selectionUsers = tempUsers.map((u) => new Importer.SelectionUser(u.id, u.username, u.email, u.isDeleted, false, true));
const selectionChannels = tempRooms.map((r) => new Importer.SelectionChannel(r.id, r.name, r.isArchived, true, r.isPrivate));
const selectionUsers = tempUsers.map((u) => new SelectionUser(u.id, u.username, u.email, u.isDeleted, false, true));
const selectionChannels = tempRooms.map((r) => new SelectionChannel(r.id, r.name, r.isArchived, true, r.isPrivate));
const selectionMessages = this.importRecord.count.messages;
super.updateProgress(Importer.ProgressStep.USER_SELECTION);
super.updateProgress(ProgressStep.USER_SELECTION);
resolve(new Importer.Selection(this.name, selectionUsers, selectionChannels, selectionMessages));
resolve(new Selection(this.name, selectionUsers, selectionChannels, selectionMessages));
}));
//Wish I could make this cleaner :(
@ -234,7 +239,7 @@ Importer.HipChatEnterprise = class ImporterHipChatEnterprise extends Importer.Ba
const startedByUserId = Meteor.userId();
Meteor.defer(() => {
super.updateProgress(Importer.ProgressStep.IMPORTING_USERS);
super.updateProgress(ProgressStep.IMPORTING_USERS);
//Import the users
for (const u of this.users.users) {
this.logger.debug(`Starting the user import: ${ u.username } and are we importing them? ${ u.do_import }`);
@ -282,7 +287,7 @@ Importer.HipChatEnterprise = class ImporterHipChatEnterprise extends Importer.Ba
this.collection.update({ _id: this.users._id }, { $set: { 'users': this.users.users }});
//Import the channels
super.updateProgress(Importer.ProgressStep.IMPORTING_CHANNELS);
super.updateProgress(ProgressStep.IMPORTING_CHANNELS);
for (const c of this.channels.channels) {
if (!c.do_import) {
continue;
@ -318,7 +323,7 @@ Importer.HipChatEnterprise = class ImporterHipChatEnterprise extends Importer.Ba
this.collection.update({ _id: this.channels._id }, { $set: { 'channels': this.channels.channels }});
//Import the Messages
super.updateProgress(Importer.ProgressStep.IMPORTING_MESSAGES);
super.updateProgress(ProgressStep.IMPORTING_MESSAGES);
for (const [ch, messagesMap] of this.messages.entries()) {
const hipChannel = this.getChannelFromRoomIdentifier(ch);
if (!hipChannel.do_import) {
@ -420,8 +425,8 @@ Importer.HipChatEnterprise = class ImporterHipChatEnterprise extends Importer.Ba
}
}
super.updateProgress(Importer.ProgressStep.FINISHING);
super.updateProgress(Importer.ProgressStep.DONE);
super.updateProgress(ProgressStep.FINISHING);
super.updateProgress(ProgressStep.DONE);
const timeTook = Date.now() - started;
this.logger.log(`HipChat Enterprise Import took ${ timeTook } milliseconds.`);
});
@ -430,11 +435,11 @@ Importer.HipChatEnterprise = class ImporterHipChatEnterprise extends Importer.Ba
}
getSelection() {
const selectionUsers = this.users.users.map((u) => new Importer.SelectionUser(u.id, u.username, u.email, false, false, true));
const selectionChannels = this.channels.channels.map((c) => new Importer.SelectionChannel(c.id, c.name, false, true, c.isPrivate));
const selectionUsers = this.users.users.map((u) => new SelectionUser(u.id, u.username, u.email, false, false, true));
const selectionChannels = this.channels.channels.map((c) => new SelectionChannel(c.id, c.name, false, true, c.isPrivate));
const selectionMessages = this.importRecord.count.messages;
return new Importer.Selection(this.name, selectionUsers, selectionChannels, selectionMessages);
return new Selection(this.name, selectionUsers, selectionChannels, selectionMessages);
}
getChannelFromRoomIdentifier(roomIdentifier) {
@ -460,4 +465,4 @@ Importer.HipChatEnterprise = class ImporterHipChatEnterprise extends Importer.Ba
}
}
}
};
}

@ -0,0 +1,4 @@
import { Importers } from 'meteor/rocketchat:importer';
import { HipChatImporterInfo } from '../info';
Importers.add(new HipChatImporterInfo());

@ -0,0 +1,7 @@
import { ImporterInfo } from 'meteor/rocketchat:importer';
export class HipChatImporterInfo extends ImporterInfo {
constructor() {
super('hipchat', 'HipChat', 'application/zip');
}
}

@ -1,6 +0,0 @@
/* globals Importer */
Importer.addImporter('hipchat', Importer.HipChat, {
name: 'HipChat',
mimeType: 'application/zip'
});

@ -11,7 +11,15 @@ Package.onUse(function(api) {
'rocketchat:lib',
'rocketchat:importer'
]);
api.use('rocketchat:logger', 'server');
api.addFiles('server.js', 'server');
api.addFiles('main.js', ['client', 'server']);
// Importer information to both server and client
api.addFiles('info.js');
// Server files
api.addFiles(['server/importer.js', 'server/adder.js'], 'server');
// Client files
api.addFiles('client/adder.js', 'client');
});

@ -1,349 +0,0 @@
/* globals Importer */
import _ from 'underscore';
import s from 'underscore.string';
import moment from 'moment';
import 'moment-timezone';
Importer.HipChat = Importer.HipChat = (function() {
class HipChat extends Importer.Base {
constructor(name, descriptionI18N, mimeType) {
super(name, descriptionI18N, mimeType);
this.logger.debug('Constructed a new Slack Importer.');
this.userTags = [];
}
prepare(dataURI, sentContentType, fileName) {
super.prepare(dataURI, sentContentType, fileName);
const image = RocketChatFile.dataURIParse(dataURI).image;
// const contentType = ref.contentType;
const zip = new this.AdmZip(new Buffer(image, 'base64'));
const zipEntries = zip.getEntries();
const tempRooms = [];
let tempUsers = [];
const tempMessages = {};
zipEntries.forEach(entry => {
if (entry.entryName.indexOf('__MACOSX') > -1) {
this.logger.debug(`Ignoring the file: ${ entry.entryName }`);
}
if (entry.isDirectory) {
return;
}
if (entry.entryName.indexOf(Importer.HipChat.RoomPrefix) > -1) {
let roomName = entry.entryName.split(Importer.HipChat.RoomPrefix)[1];
if (roomName === 'list.json') {
this.updateProgress(Importer.ProgressStep.PREPARING_CHANNELS);
const tempRooms = JSON.parse(entry.getData().toString()).rooms;
tempRooms.forEach(room => {
room.name = s.slugify(room.name);
});
} else if (roomName.indexOf('/') > -1) {
const item = roomName.split('/');
roomName = s.slugify(item[0]);
const msgGroupData = item[1].split('.')[0];
if (!tempMessages[roomName]) {
tempMessages[roomName] = {};
}
try {
return tempMessages[roomName][msgGroupData] = JSON.parse(entry.getData().toString());
} catch (error) {
return this.logger.warn(`${ entry.entryName } is not a valid JSON file! Unable to import it.`);
}
}
} else if (entry.entryName.indexOf(Importer.HipChat.UsersPrefix) > -1) {
const usersName = entry.entryName.split(Importer.HipChat.UsersPrefix)[1];
if (usersName === 'list.json') {
this.updateProgress(Importer.ProgressStep.PREPARING_USERS);
return tempUsers = JSON.parse(entry.getData().toString()).users;
} else {
return this.logger.warn(`Unexpected file in the ${ this.name } import: ${ entry.entryName }`);
}
}
});
const usersId = this.collection.insert({
'import': this.importRecord._id,
'importer': this.name,
'type': 'users',
'users': tempUsers
});
this.users = this.collection.findOne(usersId);
this.updateRecord({
'count.users': tempUsers.length
});
this.addCountToTotal(tempUsers.length);
const channelsId = this.collection.insert({
'import': this.importRecord._id,
'importer': this.name,
'type': 'channels',
'channels': tempRooms
});
this.channels = this.collection.findOne(channelsId);
this.updateRecord({
'count.channels': tempRooms.length
});
this.addCountToTotal(tempRooms.length);
this.updateProgress(Importer.ProgressStep.PREPARING_MESSAGES);
let messagesCount = 0;
Object.keys(tempMessages).forEach(channel => {
const messagesObj = tempMessages[channel];
this.messages[channel] = this.messages[channel] || {};
Object.keys(messagesObj).forEach(date => {
const msgs = messagesObj[date];
messagesCount += msgs.length;
this.updateRecord({
'messagesstatus': `${ channel }/${ date }`
});
if (Importer.Base.getBSONSize(msgs) > Importer.Base.MaxBSONSize) {
Importer.Base.getBSONSafeArraysFromAnArray(msgs).forEach((splitMsg, i) => {
const messagesId = this.collection.insert({
'import': this.importRecord._id,
'importer': this.name,
'type': 'messages',
'name': `${ channel }/${ date }.${ i }`,
'messages': splitMsg
});
this.messages[channel][`${ date }.${ i }`] = this.collection.findOne(messagesId);
});
} else {
const messagesId = this.collection.insert({
'import': this.importRecord._id,
'importer': this.name,
'type': 'messages',
'name': `${ channel }/${ date }`,
'messages': msgs
});
this.messages[channel][date] = this.collection.findOne(messagesId);
}
});
});
this.updateRecord({
'count.messages': messagesCount,
'messagesstatus': null
});
this.addCountToTotal(messagesCount);
if (tempUsers.length === 0 || tempRooms.length === 0 || messagesCount === 0) {
this.logger.warn(`The loaded users count ${ tempUsers.length }, the loaded channels ${ tempRooms.length }, and the loaded messages ${ messagesCount }`);
this.updateProgress(Importer.ProgressStep.ERROR);
return this.getProgress();
}
const selectionUsers = tempUsers.map(function(user) {
return new Importer.SelectionUser(user.user_id, user.name, user.email, user.is_deleted, false, !user.is_bot);
});
const selectionChannels = tempRooms.map(function(room) {
return new Importer.SelectionChannel(room.room_id, room.name, room.is_archived, true, false);
});
const selectionMessages = this.importRecord.count.messages;
this.updateProgress(Importer.ProgressStep.USER_SELECTION);
return new Importer.Selection(this.name, selectionUsers, selectionChannels, selectionMessages);
}
startImport(importSelection) {
super.startImport(importSelection);
const start = Date.now();
importSelection.users.forEach(user => {
this.users.users.forEach(u => {
if (u.user_id === user.user_id) {
u.do_import = user.do_import;
}
});
});
this.collection.update({_id: this.users._id}, { $set: { 'users': this.users.users } });
importSelection.channels.forEach(channel =>
this.channels.channels.forEach(c => c.room_id === channel.channel_id && (c.do_import = channel.do_import))
);
this.collection.update({ _id: this.channels._id }, { $set: { 'channels': this.channels.channels }});
const startedByUserId = Meteor.userId();
Meteor.defer(() => {
this.updateProgress(Importer.ProgressStep.IMPORTING_USERS);
this.users.users.forEach(user => {
if (!user.do_import) {
return;
}
Meteor.runAsUser(startedByUserId, () => {
const existantUser = RocketChat.models.Users.findOneByEmailAddress(user.email);
if (existantUser) {
user.rocketId = existantUser._id;
this.userTags.push({
hipchat: `@${ user.mention_name }`,
rocket: `@${ existantUser.username }`
});
} else {
const userId = Accounts.createUser({
email: user.email,
password: Date.now() + user.name + user.email.toUpperCase()
});
user.rocketId = userId;
this.userTags.push({
hipchat: `@${ user.mention_name }`,
rocket: `@${ user.mention_name }`
});
Meteor.runAsUser(userId, () => {
Meteor.call('setUsername', user.mention_name, {
joinDefaultChannelsSilenced: true
});
Meteor.call('setAvatarFromService', user.photo_url, undefined, 'url');
return Meteor.call('userSetUtcOffset', parseInt(moment().tz(user.timezone).format('Z').toString().split(':')[0]));
});
if (user.name != null) {
RocketChat.models.Users.setName(userId, user.name);
}
if (user.is_deleted) {
Meteor.call('setUserActiveStatus', userId, false);
}
}
return this.addCountCompleted(1);
});
});
this.collection.update({ _id: this.users._id }, { $set: { 'users': this.users.users }});
this.updateProgress(Importer.ProgressStep.IMPORTING_CHANNELS);
this.channels.channels.forEach(channel => {
if (!channel.do_import) {
return;
}
Meteor.runAsUser(startedByUserId, () => {
channel.name = channel.name.replace(/ /g, '');
const existantRoom = RocketChat.models.Rooms.findOneByName(channel.name);
if (existantRoom) {
channel.rocketId = existantRoom._id;
} else {
let userId = '';
this.users.users.forEach(user => {
if (user.user_id === channel.owner_user_id) {
userId = user.rocketId;
}
});
if (userId === '') {
this.logger.warn(`Failed to find the channel creator for ${ channel.name }, setting it to the current running user.`);
userId = startedByUserId;
}
Meteor.runAsUser(userId, () => {
const returned = Meteor.call('createChannel', channel.name, []);
return channel.rocketId = returned.rid;
});
RocketChat.models.Rooms.update({
_id: channel.rocketId
}, {
$set: {
'ts': new Date(channel.created * 1000)
}
});
}
return this.addCountCompleted(1);
});
});
this.collection.update({
_id: this.channels._id
}, {
$set: {
'channels': this.channels.channels
}
});
this.updateProgress(Importer.ProgressStep.IMPORTING_MESSAGES);
const nousers = {};
Object.keys(this.messages).forEach(channel => {
const messagesObj = this.messages[channel];
Meteor.runAsUser(startedByUserId, () => {
const hipchatChannel = this.getHipChatChannelFromName(channel);
if (hipchatChannel != null ? hipchatChannel.do_import : undefined) {
const room = RocketChat.models.Rooms.findOneById(hipchatChannel.rocketId, {
fields: {
usernames: 1,
t: 1,
name: 1
}
});
Object.keys(messagesObj).forEach(date => {
const msgs = messagesObj[date];
this.updateRecord({
'messagesstatus': `${ channel }/${ date }.${ msgs.messages.length }`
});
msgs.messages.forEach(message => {
if (message.from != null) {
const user = this.getRocketUser(message.from.user_id);
if (user != null) {
const msgObj = {
msg: this.convertHipChatMessageToRocketChat(message.message),
ts: new Date(message.date),
u: {
_id: user._id,
username: user.username
}
};
RocketChat.sendMessage(user, msgObj, room, true);
} else if (!nousers[message.from.user_id]) {
nousers[message.from.user_id] = message.from;
}
} else if (!_.isArray(message)) {
console.warn('Please report the following:', message);
}
this.addCountCompleted(1);
});
});
}
});
});
this.logger.warn('The following did not have users:', nousers);
this.updateProgress(Importer.ProgressStep.FINISHING);
this.channels.channels.forEach(channel => {
if (channel.do_import && channel.is_archived) {
Meteor.runAsUser(startedByUserId, () => {
return Meteor.call('archiveRoom', channel.rocketId);
});
}
});
this.updateProgress(Importer.ProgressStep.DONE);
const timeTook = Date.now() - start;
return this.logger.log(`Import took ${ timeTook } milliseconds.`);
});
return this.getProgress();
}
getHipChatChannelFromName(channelName) {
return this.channels.channels.find(channel => channel.name === channelName);
}
getRocketUser(hipchatId) {
const user = this.users.users.find(user => user.user_id === hipchatId);
return user ? RocketChat.models.Users.findOneById(user.rocketId, {
fields: {
username: 1,
name: 1
}
}) : undefined;
}
convertHipChatMessageToRocketChat(message) {
if (message != null) {
this.userTags.forEach(userReplace => {
message = message.replace(userReplace.hipchat, userReplace.rocket);
});
} else {
message = '';
}
return message;
}
getSelection() {
const selectionUsers = this.users.users.map(function(user) {
return new Importer.SelectionUser(user.user_id, user.name, user.email, user.is_deleted, false, !user.is_bot);
});
const selectionChannels = this.channels.channels.map(function(room) {
return new Importer.SelectionChannel(room.room_id, room.name, room.is_archived, true, false);
});
const selectionMessages = this.importRecord.count.messages;
return new Importer.Selection(this.name, selectionUsers, selectionChannels, selectionMessages);
}
}
HipChat.RoomPrefix = 'hipchat_export/rooms/';
HipChat.UsersPrefix = 'hipchat_export/users/';
return HipChat;
}());

@ -0,0 +1,5 @@
import { Importers } from 'meteor/rocketchat:importer';
import { HipChatImporterInfo } from '../info';
import { HipChatImporter } from './importer';
Importers.add(new HipChatImporterInfo(), HipChatImporter);

@ -0,0 +1,347 @@
import {
Base,
ProgressStep,
Selection,
SelectionChannel,
SelectionUser
} from 'meteor/rocketchat:importer';
import _ from 'underscore';
import s from 'underscore.string';
import moment from 'moment';
import 'moment-timezone';
export class HipChatImporter extends Base {
constructor(info) {
super(info);
this.userTags = [];
this.roomPrefix = 'hipchat_export/rooms/';
this.usersPrefix = 'hipchat_export/users/';
}
prepare(dataURI, sentContentType, fileName) {
super.prepare(dataURI, sentContentType, fileName);
const image = RocketChatFile.dataURIParse(dataURI).image;
// const contentType = ref.contentType;
const zip = new this.AdmZip(new Buffer(image, 'base64'));
const zipEntries = zip.getEntries();
const tempRooms = [];
let tempUsers = [];
const tempMessages = {};
zipEntries.forEach(entry => {
if (entry.entryName.indexOf('__MACOSX') > -1) {
this.logger.debug(`Ignoring the file: ${ entry.entryName }`);
}
if (entry.isDirectory) {
return;
}
if (entry.entryName.indexOf(this.roomPrefix) > -1) {
let roomName = entry.entryName.split(this.roomPrefix)[1];
if (roomName === 'list.json') {
this.updateProgress(ProgressStep.PREPARING_CHANNELS);
const tempRooms = JSON.parse(entry.getData().toString()).rooms;
tempRooms.forEach(room => {
room.name = s.slugify(room.name);
});
} else if (roomName.indexOf('/') > -1) {
const item = roomName.split('/');
roomName = s.slugify(item[0]);
const msgGroupData = item[1].split('.')[0];
if (!tempMessages[roomName]) {
tempMessages[roomName] = {};
}
try {
return tempMessages[roomName][msgGroupData] = JSON.parse(entry.getData().toString());
} catch (error) {
return this.logger.warn(`${ entry.entryName } is not a valid JSON file! Unable to import it.`);
}
}
} else if (entry.entryName.indexOf(this.usersPrefix) > -1) {
const usersName = entry.entryName.split(this.usersPrefix)[1];
if (usersName === 'list.json') {
this.updateProgress(ProgressStep.PREPARING_USERS);
return tempUsers = JSON.parse(entry.getData().toString()).users;
} else {
return this.logger.warn(`Unexpected file in the ${ this.name } import: ${ entry.entryName }`);
}
}
});
const usersId = this.collection.insert({
'import': this.importRecord._id,
'importer': this.name,
'type': 'users',
'users': tempUsers
});
this.users = this.collection.findOne(usersId);
this.updateRecord({
'count.users': tempUsers.length
});
this.addCountToTotal(tempUsers.length);
const channelsId = this.collection.insert({
'import': this.importRecord._id,
'importer': this.name,
'type': 'channels',
'channels': tempRooms
});
this.channels = this.collection.findOne(channelsId);
this.updateRecord({
'count.channels': tempRooms.length
});
this.addCountToTotal(tempRooms.length);
this.updateProgress(ProgressStep.PREPARING_MESSAGES);
let messagesCount = 0;
Object.keys(tempMessages).forEach(channel => {
const messagesObj = tempMessages[channel];
this.messages[channel] = this.messages[channel] || {};
Object.keys(messagesObj).forEach(date => {
const msgs = messagesObj[date];
messagesCount += msgs.length;
this.updateRecord({
'messagesstatus': `${ channel }/${ date }`
});
if (Base.getBSONSize(msgs) > Base.getMaxBSONSize()) {
Base.getBSONSafeArraysFromAnArray(msgs).forEach((splitMsg, i) => {
const messagesId = this.collection.insert({
'import': this.importRecord._id,
'importer': this.name,
'type': 'messages',
'name': `${ channel }/${ date }.${ i }`,
'messages': splitMsg
});
this.messages[channel][`${ date }.${ i }`] = this.collection.findOne(messagesId);
});
} else {
const messagesId = this.collection.insert({
'import': this.importRecord._id,
'importer': this.name,
'type': 'messages',
'name': `${ channel }/${ date }`,
'messages': msgs
});
this.messages[channel][date] = this.collection.findOne(messagesId);
}
});
});
this.updateRecord({
'count.messages': messagesCount,
'messagesstatus': null
});
this.addCountToTotal(messagesCount);
if (tempUsers.length === 0 || tempRooms.length === 0 || messagesCount === 0) {
this.logger.warn(`The loaded users count ${ tempUsers.length }, the loaded channels ${ tempRooms.length }, and the loaded messages ${ messagesCount }`);
this.updateProgress(ProgressStep.ERROR);
return this.getProgress();
}
const selectionUsers = tempUsers.map(function(user) {
return new SelectionUser(user.user_id, user.name, user.email, user.is_deleted, false, !user.is_bot);
});
const selectionChannels = tempRooms.map(function(room) {
return new SelectionChannel(room.room_id, room.name, room.is_archived, true, false);
});
const selectionMessages = this.importRecord.count.messages;
this.updateProgress(ProgressStep.USER_SELECTION);
return new Selection(this.name, selectionUsers, selectionChannels, selectionMessages);
}
startImport(importSelection) {
super.startImport(importSelection);
const start = Date.now();
importSelection.users.forEach(user => {
this.users.users.forEach(u => {
if (u.user_id === user.user_id) {
u.do_import = user.do_import;
}
});
});
this.collection.update({_id: this.users._id}, { $set: { 'users': this.users.users } });
importSelection.channels.forEach(channel =>
this.channels.channels.forEach(c => c.room_id === channel.channel_id && (c.do_import = channel.do_import))
);
this.collection.update({ _id: this.channels._id }, { $set: { 'channels': this.channels.channels }});
const startedByUserId = Meteor.userId();
Meteor.defer(() => {
this.updateProgress(ProgressStep.IMPORTING_USERS);
this.users.users.forEach(user => {
if (!user.do_import) {
return;
}
Meteor.runAsUser(startedByUserId, () => {
const existantUser = RocketChat.models.Users.findOneByEmailAddress(user.email);
if (existantUser) {
user.rocketId = existantUser._id;
this.userTags.push({
hipchat: `@${ user.mention_name }`,
rocket: `@${ existantUser.username }`
});
} else {
const userId = Accounts.createUser({
email: user.email,
password: Date.now() + user.name + user.email.toUpperCase()
});
user.rocketId = userId;
this.userTags.push({
hipchat: `@${ user.mention_name }`,
rocket: `@${ user.mention_name }`
});
Meteor.runAsUser(userId, () => {
Meteor.call('setUsername', user.mention_name, {
joinDefaultChannelsSilenced: true
});
Meteor.call('setAvatarFromService', user.photo_url, undefined, 'url');
return Meteor.call('userSetUtcOffset', parseInt(moment().tz(user.timezone).format('Z').toString().split(':')[0]));
});
if (user.name != null) {
RocketChat.models.Users.setName(userId, user.name);
}
if (user.is_deleted) {
Meteor.call('setUserActiveStatus', userId, false);
}
}
return this.addCountCompleted(1);
});
});
this.collection.update({ _id: this.users._id }, { $set: { 'users': this.users.users }});
this.updateProgress(ProgressStep.IMPORTING_CHANNELS);
this.channels.channels.forEach(channel => {
if (!channel.do_import) {
return;
}
Meteor.runAsUser(startedByUserId, () => {
channel.name = channel.name.replace(/ /g, '');
const existantRoom = RocketChat.models.Rooms.findOneByName(channel.name);
if (existantRoom) {
channel.rocketId = existantRoom._id;
} else {
let userId = '';
this.users.users.forEach(user => {
if (user.user_id === channel.owner_user_id) {
userId = user.rocketId;
}
});
if (userId === '') {
this.logger.warn(`Failed to find the channel creator for ${ channel.name }, setting it to the current running user.`);
userId = startedByUserId;
}
Meteor.runAsUser(userId, () => {
const returned = Meteor.call('createChannel', channel.name, []);
return channel.rocketId = returned.rid;
});
RocketChat.models.Rooms.update({
_id: channel.rocketId
}, {
$set: {
'ts': new Date(channel.created * 1000)
}
});
}
return this.addCountCompleted(1);
});
});
this.collection.update({
_id: this.channels._id
}, {
$set: {
'channels': this.channels.channels
}
});
this.updateProgress(ProgressStep.IMPORTING_MESSAGES);
const nousers = {};
Object.keys(this.messages).forEach(channel => {
const messagesObj = this.messages[channel];
Meteor.runAsUser(startedByUserId, () => {
const hipchatChannel = this.getHipChatChannelFromName(channel);
if (hipchatChannel != null ? hipchatChannel.do_import : undefined) {
const room = RocketChat.models.Rooms.findOneById(hipchatChannel.rocketId, {
fields: {
usernames: 1,
t: 1,
name: 1
}
});
Object.keys(messagesObj).forEach(date => {
const msgs = messagesObj[date];
this.updateRecord({
'messagesstatus': `${ channel }/${ date }.${ msgs.messages.length }`
});
msgs.messages.forEach(message => {
if (message.from != null) {
const user = this.getRocketUser(message.from.user_id);
if (user != null) {
const msgObj = {
msg: this.convertHipChatMessageToRocketChat(message.message),
ts: new Date(message.date),
u: {
_id: user._id,
username: user.username
}
};
RocketChat.sendMessage(user, msgObj, room, true);
} else if (!nousers[message.from.user_id]) {
nousers[message.from.user_id] = message.from;
}
} else if (!_.isArray(message)) {
console.warn('Please report the following:', message);
}
this.addCountCompleted(1);
});
});
}
});
});
this.logger.warn('The following did not have users:', nousers);
this.updateProgress(ProgressStep.FINISHING);
this.channels.channels.forEach(channel => {
if (channel.do_import && channel.is_archived) {
Meteor.runAsUser(startedByUserId, () => {
return Meteor.call('archiveRoom', channel.rocketId);
});
}
});
this.updateProgress(ProgressStep.DONE);
const timeTook = Date.now() - start;
return this.logger.log(`Import took ${ timeTook } milliseconds.`);
});
return this.getProgress();
}
getHipChatChannelFromName(channelName) {
return this.channels.channels.find(channel => channel.name === channelName);
}
getRocketUser(hipchatId) {
const user = this.users.users.find(user => user.user_id === hipchatId);
return user ? RocketChat.models.Users.findOneById(user.rocketId, {
fields: {
username: 1,
name: 1
}
}) : undefined;
}
convertHipChatMessageToRocketChat(message) {
if (message != null) {
this.userTags.forEach(userReplace => {
message = message.replace(userReplace.hipchat, userReplace.rocket);
});
} else {
message = '';
}
return message;
}
getSelection() {
const selectionUsers = this.users.users.map(function(user) {
return new SelectionUser(user.user_id, user.name, user.email, user.is_deleted, false, !user.is_bot);
});
const selectionChannels = this.channels.channels.map(function(room) {
return new SelectionChannel(room.room_id, room.name, room.is_archived, true, false);
});
return new Selection(this.name, selectionUsers, selectionChannels, this.importRecord.count.messages);
}
}

@ -0,0 +1,7 @@
This directory and the files immediately inside it are automatically generated
when you change this package's NPM dependencies. Commit the files in this
directory (npm-shrinkwrap.json, .gitignore, and this README) to source control
so that others run the same versions of sub-dependencies.
You should NOT check in the node_modules directory that Meteor automatically
creates; if you are using git, the .gitignore file tells git to ignore it.

@ -0,0 +1,10 @@
{
"lockfileVersion": 1,
"dependencies": {
"csv-parse": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-1.2.0.tgz",
"integrity": "sha1-BHtzhoq5qFdG6IX2N/ntD7ZFpCU="
}
}
}

@ -0,0 +1,4 @@
import { Importers } from 'meteor/rocketchat:importer';
import { SlackUsersImporterInfo } from '../info';
Importers.add(new SlackUsersImporterInfo());

@ -0,0 +1,10 @@
import { ImporterInfo } from 'meteor/rocketchat:importer';
export class SlackUsersImporterInfo extends ImporterInfo {
constructor() {
super('slack-users', 'Slack_Users', 'text/csv', [{
text: 'Importer_Slack_Users_CSV_Information',
href: 'https://rocket.chat/docs/administrator-guides/import/slack/users'
}]);
}
}

@ -0,0 +1,29 @@
Package.describe({
name: 'rocketchat:importer-slack-users',
version: '1.0.0',
summary: 'Importer for Slack\'s CSV User Export',
git: ''
});
Package.onUse(function(api) {
api.use([
'ecmascript',
'rocketchat:lib',
'rocketchat:importer'
]);
api.use('rocketchat:logger', 'server');
// Importer information to both server and client
api.addFiles('info.js');
// Server files
api.addFiles(['server/importer.js', 'server/adder.js'], 'server');
// Client files
api.addFiles('client/adder.js', 'client');
});
Npm.depends({
'csv-parse': '1.2.0'
});

@ -0,0 +1,5 @@
import { Importers } from 'meteor/rocketchat:importer';
import { SlackUsersImporterInfo } from '../info';
import { SlackUsersImporter } from './importer';
Importers.add(new SlackUsersImporterInfo(), SlackUsersImporter);

@ -0,0 +1,135 @@
import {
Base,
ProgressStep,
Selection,
SelectionUser
} from 'meteor/rocketchat:importer';
export class SlackUsersImporter extends Base {
constructor(info) {
super(info);
this.csvParser = Npm.require('csv-parse/lib/sync');
this.userMap = new Map();
this.admins = []; //Array of ids of the users which are admins
}
prepare(dataURI, sentContentType, fileName) {
super.prepare(dataURI, sentContentType, fileName, true);
super.updateProgress(ProgressStep.PREPARING_USERS);
const uriResult = RocketChatFile.dataURIParse(dataURI);
const buf = new Buffer(uriResult.image, 'base64');
const parsed = this.csvParser(buf.toString());
parsed.forEach((user, index) => {
// Ignore the first column
if (index === 0) {
return;
}
const id = Random.id();
const username = user[0];
const email = user[1];
let isBot = false;
let isDeleted = false;
switch (user[2]) {
case 'Admin':
this.admins.push(id);
break;
case 'Bot':
isBot = true;
break;
case 'Deactivated':
isDeleted = true;
break;
}
this.userMap.set(id, new SelectionUser(id, username, email, isDeleted, isBot, true));
});
const userArray = Array.from(this.userMap.values());
const usersId = this.collection.insert({ 'import': this.importRecord._id, 'importer': this.name, 'type': 'users', 'users': userArray });
this.users = this.collection.findOne(usersId);
super.updateRecord({ 'count.users': this.userMap.size });
super.addCountToTotal(this.userMap.size);
if (this.userMap.size === 0) {
this.logger.error('No users found in the import file.');
super.updateProgress(ProgressStep.ERROR);
return super.getProgress();
}
super.updateProgress(ProgressStep.USER_SELECTION);
return new Selection(this.name, userArray, [], 0);
}
startImport(importSelection) {
super.startImport(importSelection);
const started = Date.now();
for (const user of importSelection.users) {
const u = this.userMap.get(user.user_id);
u.do_import = user.do_import;
this.userMap.set(user.user_id, u);
}
this.collection.update({ _id: this.users._id }, { $set: { 'users': Array.from(this.userMap.values()) }});
const startedByUserId = Meteor.userId();
Meteor.defer(() => {
super.updateProgress(ProgressStep.IMPORTING_USERS);
for (const u of this.users.users) {
if (!u.do_import) {
continue;
}
Meteor.runAsUser(startedByUserId, () => {
const existantUser = RocketChat.models.Users.findOneByEmailAddress(u.email) || RocketChat.models.Users.findOneByUsername(u.username);
let userId = existantUser._id;
if (existantUser) {
//since we have an existing user, let's try a few things
u.rocketId = existantUser._id;
RocketChat.models.Users.update({ _id: u.rocketId }, { $addToSet: { importIds: u.id } });
RocketChat.models.Users.setEmail(existantUser._id, u.email);
RocketChat.models.Users.setEmailVerified(existantUser._id, u.email);
} else {
userId = Accounts.createUser({ username: u.username + Random.id(), password: Date.now() + u.name + u.email.toUpperCase() });
if (!userId) {
console.warn('An error happened while creating a user.');
return;
}
Meteor.runAsUser(userId, () => {
Meteor.call('setUsername', u.username, {joinDefaultChannelsSilenced: true});
RocketChat.models.Users.setName(userId, u.name);
RocketChat.models.Users.update({ _id: userId }, { $addToSet: { importIds: u.id } });
RocketChat.models.Users.setEmail(userId, u.email);
RocketChat.models.Users.setEmailVerified(userId, u.email);
u.rocketId = userId;
});
}
if (this.admins.includes(u.user_id)) {
Meteor.call('setAdminStatus', userId, true);
}
super.addCountCompleted(1);
});
}
super.updateProgress(ProgressStep.FINISHING);
super.updateProgress(ProgressStep.DONE);
const timeTook = Date.now() - started;
this.logger.log(`Slack Users Import took ${ timeTook } milliseconds.`);
});
return super.getProgress();
}
}

@ -0,0 +1,4 @@
import { Importers } from 'meteor/rocketchat:importer';
import { SlackImporterInfo } from '../info';
Importers.add(new SlackImporterInfo());

@ -0,0 +1,7 @@
import { ImporterInfo } from 'meteor/rocketchat:importer';
export class SlackImporterInfo extends ImporterInfo {
constructor() {
super('slack', 'Slack', 'application/zip');
}
}

@ -1,5 +0,0 @@
/* globals Importer */
Importer.addImporter('slack', Importer.Slack, {
name: 'Slack',
mimeType: 'application/zip'
});

@ -11,7 +11,15 @@ Package.onUse(function(api) {
'rocketchat:lib',
'rocketchat:importer'
]);
api.use('rocketchat:logger', 'server');
api.addFiles('server.js', 'server');
api.addFiles('main.js', ['client', 'server']);
// Importer information to both server and client
api.addFiles('info.js');
// Server files
api.addFiles(['server/importer.js', 'server/adder.js'], 'server');
// Client files
api.addFiles('client/adder.js', 'client');
});

@ -0,0 +1,5 @@
import { Importers } from 'meteor/rocketchat:importer';
import { SlackImporterInfo } from '../info';
import { SlackImporter } from './importer';
Importers.add(new SlackImporterInfo(), SlackImporter);

@ -1,12 +1,18 @@
/* globals Importer */
import {
Base,
ProgressStep,
Selection,
SelectionChannel,
SelectionUser
} from 'meteor/rocketchat:importer';
import _ from 'underscore';
Importer.Slack = class extends Importer.Base {
constructor(name, descriptionI18N, mimeType) {
super(name, descriptionI18N, mimeType);
export class SlackImporter extends Base {
constructor(info) {
super(info);
this.userTags = [];
this.bots = {};
this.logger.debug('Constructed a new Slack Importer.');
}
prepare(dataURI, sentContentType, fileName) {
@ -26,13 +32,13 @@ Importer.Slack = class extends Importer.Base {
}
if (entry.entryName === 'channels.json') {
this.updateProgress(Importer.ProgressStep.PREPARING_CHANNELS);
this.updateProgress(ProgressStep.PREPARING_CHANNELS);
tempChannels = JSON.parse(entry.getData().toString()).filter(channel => channel.creator != null);
return;
}
if (entry.entryName === 'users.json') {
this.updateProgress(Importer.ProgressStep.PREPARING_USERS);
this.updateProgress(ProgressStep.PREPARING_USERS);
tempUsers = JSON.parse(entry.getData().toString());
tempUsers.forEach(user => {
@ -72,7 +78,7 @@ Importer.Slack = class extends Importer.Base {
this.addCountToTotal(tempChannels.length);
// Insert the messages records
this.updateProgress(Importer.ProgressStep.PREPARING_MESSAGES);
this.updateProgress(ProgressStep.PREPARING_MESSAGES);
let messagesCount = 0;
Object.keys(tempMessages).forEach(channel => {
@ -83,8 +89,8 @@ Importer.Slack = class extends Importer.Base {
const msgs = messagesObj[date];
messagesCount += msgs.length;
this.updateRecord({ 'messagesstatus': `${ channel }/${ date }` });
if (Importer.Base.getBSONSize(msgs) > Importer.Base.MaxBSONSize) {
const tmp = Importer.Base.getBSONSafeArraysFromAnArray(msgs);
if (Base.getBSONSize(msgs) > Base.getMaxBSONSize()) {
const tmp = Base.getBSONSafeArraysFromAnArray(msgs);
Object.keys(tmp).forEach(i => {
const splitMsg = tmp[i];
const messagesId = this.collection.insert({ 'import': this.importRecord._id, 'importer': this.name, 'type': 'messages', 'name': `${ channel }/${ date }.${ i }`, 'messages': splitMsg });
@ -99,18 +105,22 @@ Importer.Slack = class extends Importer.Base {
this.updateRecord({ 'count.messages': messagesCount, 'messagesstatus': null });
this.addCountToTotal(messagesCount);
if ([tempUsers.length, tempChannels.length, messagesCount].some(e => e === 0)) {
this.logger.warn(`The loaded users count ${ tempUsers.length }, the loaded channels ${ tempChannels.length }, and the loaded messages ${ messagesCount }`);
console.log(`The loaded users count ${ tempUsers.length }, the loaded channels ${ tempChannels.length }, and the loaded messages ${ messagesCount }`);
this.updateProgress(Importer.ProgressStep.ERROR);
this.updateProgress(ProgressStep.ERROR);
return this.getProgress();
}
const selectionUsers = tempUsers.map(user => new Importer.SelectionUser(user.id, user.name, user.profile.email, user.deleted, user.is_bot, !user.is_bot));
const selectionChannels = tempChannels.map(channel => new Importer.SelectionChannel(channel.id, channel.name, channel.is_archived, true, false));
const selectionUsers = tempUsers.map(user => new SelectionUser(user.id, user.name, user.profile.email, user.deleted, user.is_bot, !user.is_bot));
const selectionChannels = tempChannels.map(channel => new SelectionChannel(channel.id, channel.name, channel.is_archived, true, false));
const selectionMessages = this.importRecord.count.messages;
this.updateProgress(Importer.ProgressStep.USER_SELECTION);
return new Importer.Selection(this.name, selectionUsers, selectionChannels, selectionMessages);
this.updateProgress(ProgressStep.USER_SELECTION);
return new Selection(this.name, selectionUsers, selectionChannels, selectionMessages);
}
startImport(importSelection) {
super.startImport(importSelection);
const start = Date.now();
@ -140,7 +150,7 @@ Importer.Slack = class extends Importer.Base {
const startedByUserId = Meteor.userId();
Meteor.defer(() => {
try {
this.updateProgress(Importer.ProgressStep.IMPORTING_USERS);
this.updateProgress(ProgressStep.IMPORTING_USERS);
this.users.users.forEach(user => {
if (!user.do_import) {
return;
@ -199,7 +209,7 @@ Importer.Slack = class extends Importer.Base {
});
this.collection.update({ _id: this.users._id }, { $set: { 'users': this.users.users }});
this.updateProgress(Importer.ProgressStep.IMPORTING_CHANNELS);
this.updateProgress(ProgressStep.IMPORTING_CHANNELS);
this.channels.channels.forEach(channel => {
if (!channel.do_import) {
return;
@ -255,7 +265,7 @@ Importer.Slack = class extends Importer.Base {
const missedTypes = {};
const ignoreTypes = { 'bot_add': true, 'file_comment': true, 'file_mention': true };
this.updateProgress(Importer.ProgressStep.IMPORTING_MESSAGES);
this.updateProgress(ProgressStep.IMPORTING_MESSAGES);
Object.keys(this.messages).forEach(channel => {
const messagesObj = this.messages[channel];
@ -277,6 +287,7 @@ Importer.Slack = class extends Importer.Base {
msgDataDefaults.reactions = {};
message.reactions.forEach(reaction => {
reaction.name = `:${ reaction.name }:`;
msgDataDefaults.reactions[reaction.name] = { usernames: [] };
reaction.users.forEach(u => {
@ -420,7 +431,7 @@ Importer.Slack = class extends Importer.Base {
console.log('Missed import types:', missedTypes);
}
this.updateProgress(Importer.ProgressStep.FINISHING);
this.updateProgress(ProgressStep.FINISHING);
this.channels.channels.forEach(channel => {
if (channel.do_import && channel.is_archived) {
@ -429,30 +440,32 @@ Importer.Slack = class extends Importer.Base {
});
}
});
this.updateProgress(Importer.ProgressStep.DONE);
const timeTook = Date.now() - start;
this.updateProgress(ProgressStep.DONE);
this.logger.log(`Import took ${ timeTook } milliseconds.`);
this.logger.log(`Import took ${ Date.now() - start } milliseconds.`);
} catch (e) {
this.logger.error(e);
this.updateProgress(Importer.ProgressStep.ERROR);
this.updateProgress(ProgressStep.ERROR);
}
});
return this.getProgress();
}
getSlackChannelFromName(channelName) {
return this.channels.channels.find(channel => channel.name === channelName);
}
getRocketUser(slackId) {
const user = this.users.users.find(user => user.id === slackId);
if (user) {
return RocketChat.models.Users.findOneById(user.rocketId, { fields: { username: 1, name: 1 }});
}
}
convertSlackMessageToRocketChat(message) {
if (message != null) {
if (message) {
message = message.replace(/<!everyone>/g, '@all');
message = message.replace(/<!channel>/g, '@all');
message = message.replace(/<!here>/g, '@here');
@ -464,6 +477,7 @@ Importer.Slack = class extends Importer.Base {
message = message.replace(/:piggy:/g, ':pig:');
message = message.replace(/:uk:/g, ':gb:');
message = message.replace(/<(http[s]?:[^>]*)>/g, '$1');
for (const userReplace of Array.from(this.userTags)) {
message = message.replace(userReplace.slack, userReplace.rocket);
message = message.replace(userReplace.slackLong, userReplace.rocket);
@ -471,12 +485,13 @@ Importer.Slack = class extends Importer.Base {
} else {
message = '';
}
return message;
}
getSelection() {
const selectionUsers = this.users.users.map(user => new Importer.SelectionUser(user.id, user.name, user.profile.email, user.deleted, user.is_bot, !user.is_bot));
const selectionChannels = this.channels.channels.map(channel => new Importer.SelectionChannel(channel.id, channel.name, channel.is_archived, true, false));
const selectionMessages = this.importRecord.count.messages;
return new Importer.Selection(this.name, selectionUsers, selectionChannels, selectionMessages);
const selectionUsers = this.users.users.map(user => new SelectionUser(user.id, user.name, user.profile.email, user.deleted, user.is_bot, !user.is_bot));
const selectionChannels = this.channels.channels.map(channel => new SelectionChannel(channel.id, channel.name, channel.is_archived, true, false));
return new Selection(this.name, selectionUsers, selectionChannels, this.importRecord.count.messages);
}
};
}

@ -0,0 +1,29 @@
class ImporterWebsocketReceiverDef {
constructor() {
this.streamer = new Meteor.Streamer('importers');
this.callbacks = [];
this.streamer.on('progress', this.progressUpdated.bind(this));
}
progressUpdated(progress) {
this.callbacks.forEach((c) => c(progress));
}
registerCallback(callback) {
if (typeof callback !== 'function') {
throw new Error('Callback must be a function.');
}
this.callbacks.push(callback);
}
unregisterCallback(callback) {
const i = this.callbacks.indexOf(callback);
if (i >= 0) {
this.callbacks.splice(i, 1);
}
}
}
export const ImporterWebsocketReceiver = new ImporterWebsocketReceiverDef();

@ -1,5 +1,4 @@
/* globals Importer */
import _ from 'underscore';
import { Importers } from 'meteor/rocketchat:importer';
Template.adminImport.helpers({
isAdmin() {
@ -9,12 +8,7 @@ Template.adminImport.helpers({
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;
return Importers.getAll();
}
});

@ -1,5 +1,4 @@
/* globals Importer */
import _ from 'underscore';
import { Importers } from 'meteor/rocketchat:importer';
import toastr from 'toastr';
Template.adminImportPrepare.helpers({
@ -8,15 +7,8 @@ Template.adminImportPrepare.helpers({
},
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;
return Importers.get(importerKey);
},
isLoaded() {
return Template.instance().loaded.get();
@ -142,9 +134,12 @@ Template.adminImportPrepare.onCreated(function() {
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':
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) {

@ -1,4 +1,7 @@
import { Importers, ImporterWebsocketReceiver, ProgressStep } from 'meteor/rocketchat:importer';
import toastr from 'toastr';
Template.adminImportProgress.helpers({
step() {
return Template.instance().step.get();
@ -16,35 +19,60 @@ Template.adminImportProgress.onCreated(function() {
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') }`);
}
});
// Ensure there is an importer how they're accessing it
const key = FlowRouter.getParam('importer').toLowerCase();
if (key === '' || !Importers.get(key)) {
FlowRouter.go('/admin/import');
return;
}
function _updateProgress(progress) {
switch (progress.step) {
case ProgressStep.DONE:
toastr.success(t(progress.step[0].toUpperCase() + progress.step.slice(1)));
return FlowRouter.go('/admin/import');
case ProgressStep.ERROR:
toastr.error(t(progress.step[0].toUpperCase() + progress.step.slice(1)));
return FlowRouter.go(`/admin/import/prepare/${ key }`);
default:
instance.step.set(t(progress.step[0].toUpperCase() + progress.step.slice(1)));
instance.completed.set(progress.count.completed);
instance.total.set(progress.count.total);
break;
}
}
this.progressUpdated = function _progressUpdated(progress) {
if (progress.key.toLowerCase() !== key) {
return;
}
_updateProgress(progress);
};
return instance.updateProgress();
Meteor.call('getImportProgress', key, function(error, progress) {
if (error) {
console.warn('Error on getting the import progress:', error);
handleError(error);
return FlowRouter.go('/admin/import');
}
if (!progress) {
toastr.warning(t('Importer_not_in_progress'));
return FlowRouter.go(`/admin/import/prepare/${ key }`);
}
const whereTo = _updateProgress(progress);
if (!whereTo) {
ImporterWebsocketReceiver.registerCallback(instance.progressUpdated);
}
});
});
Template.adminImportProgress.onDestroyed(function() {
const instance = this;
ImporterWebsocketReceiver.unregisterCallback(instance.progressUpdated);
});

@ -0,0 +1,11 @@
import { Importers } from '../lib/Importers';
import { ImporterInfo } from '../lib/ImporterInfo';
import { ImporterWebsocketReceiver } from './ImporterWebsocketReceiver';
import { ProgressStep } from '../lib/ImporterProgressStep';
export {
Importers,
ImporterInfo,
ImporterWebsocketReceiver,
ProgressStep
};

@ -0,0 +1,19 @@
export class ImporterInfo {
/**
* Creates a new class which contains information about the importer.
*
* @param {string} key The unique key of this importer.
* @param {string} name The i18n name.
* @param {string} mimeType The type of file it expects.
* @param {{ href: string, text: string }[]} warnings An array of warning objects. `{ href, text }`
*/
constructor(key, name = '', mimeType = '', warnings = []) {
this.key = key;
this.name = name;
this.mimeType = mimeType;
this.warnings = warnings;
this.importer = undefined;
this.instance = undefined;
}
}

@ -1,6 +1,5 @@
/* globals Importer */
// "ENUM" of the import step, the value is the translation string
Importer.ProgressStep = Object.freeze({
/** The progress step that an importer is at. */
export const ProgressStep = Object.freeze({
NEW: 'importer_new',
PREPARING_STARTED: 'importer_preparing_started',
PREPARING_USERS: 'importer_preparing_users',

@ -0,0 +1,47 @@
import { ImporterInfo } from './ImporterInfo';
/** Container class which holds all of the importer details. */
class ImportersContainer {
constructor() {
this.importers = new Map();
}
/**
* Adds an importer to the import collection. Adding it more than once will
* overwrite the previous one.
*
* @param {ImporterInfo} info The information related to the importer.
* @param {*} importer The class for the importer, will be undefined on the client.
*/
add(info, importer) {
if (!(info instanceof ImporterInfo)) {
throw new Error('The importer must be a valid ImporterInfo instance.');
}
info.importer = importer;
this.importers.set(info.key, info);
return this.importers.get(info.key);
}
/**
* Gets the importer information that is stored.
*
* @param {string} key The key of the importer.
*/
get(key) {
return this.importers.get(key);
}
/**
* Gets all of the importers in array format.
*
* @returns {ImporterInfo[]} The array of importer information.
*/
getAll() {
return Array.from(this.importers.values());
}
}
export const Importers = new ImportersContainer();

@ -1,3 +0,0 @@
/* globals Importer */
Importer = {};
export default Importer;

@ -1,13 +0,0 @@
/* 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
};
}
};

@ -17,14 +17,16 @@ Package.onUse(function(api) {
api.use('templating', 'client');
//Import Framework
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');
api.addFiles('server/classes/ImporterWebsocket.js', 'server');
api.addFiles('lib/ImporterInfo.js');
api.addFiles('lib/ImporterProgressStep.js');
api.addFiles('lib/Importers.js');
//Database models
api.addFiles('server/models/Imports.js', 'server');
@ -49,7 +51,8 @@ Package.onUse(function(api) {
//Imports database records cleanup, mark all as not valid.
api.addFiles('server/startup/setImportsToInvalid.js', 'server');
api.export('Importer');
api.mainModule('client/index.js', 'client');
api.mainModule('server/index.js', 'server');
});
Npm.depends({

@ -1,36 +1,59 @@
/* 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
import { Progress } from './ImporterProgress';
import { ProgressStep } from '../../lib/ImporterProgressStep';
import { Selection } from './ImporterSelection';
import { Imports } from '../models/Imports';
import { ImporterInfo } from '../../lib/ImporterInfo';
import { RawImports } from '../models/RawImports';
import { ImporterWebsocket } from './ImporterWebsocket';
import http from 'http';
import https from 'https';
import AdmZip from 'adm-zip';
import getFileType from 'file-type';
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.
/**
* Base class for all of the importers.
*/
export class Base {
/**
* 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.
*
* @param {any} item The item to calculate the BSON size of.
* @returns {number} The size of the item passed in.
* @static
*/
static getBSONSize(item) {
const { BSON } = require('bson').native();
const bson = new BSON();
return bson.calculateObjectSize(object);
return bson.calculateObjectSize(item);
}
/**
* 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.
*
* @returns {number} 8000000 bytes.
*/
static getMaxBSONSize() {
return 8000000;
}
/**
* Splits the passed in array to at least one array which has a size that
* is safe to store in the database.
*
* @param {any[]} theArray The array to split out
* @returns {any[][]} The safe sized arrays
* @static
*/
static getBSONSafeArraysFromAnArray(theArray) {
const BSONSize = Importer.Base.getBSONSize(theArray);
const maxSize = Math.floor(theArray.length / (Math.ceil(BSONSize / Importer.Base.MaxBSONSize)));
const BSONSize = Base.getBSONSize(theArray);
const maxSize = Math.floor(theArray.length / (Math.ceil(BSONSize / Base.getMaxBSONSize())));
const safeArrays = [];
let i = 0;
while (i < theArray.length) {
@ -39,19 +62,23 @@ Importer.Base = class Base {
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) {
/**
* Constructs a new importer, adding an empty collection, AdmZip property, and empty users & channels
*
* @param {string} name The importer's name.
* @param {string} description The i18n string which describes the importer
* @param {string} mimeType The expected file type.
*/
constructor(info) {
if (!(info instanceof ImporterInfo)) {
throw new Error('Information passed in must be a valid ImporterInfo instance.');
}
this.http = http;
this.https = https;
this.AdmZip = AdmZip;
this.getFileType = getFileType;
this.MaxBSONSize = 8000000;
this.prepare = this.prepare.bind(this);
this.startImport = this.startImport.bind(this);
this.getSelection = this.getSelection.bind(this);
@ -62,112 +89,128 @@ Importer.Base = class Base {
this.updateRecord = this.updateRecord.bind(this);
this.uploadFile = this.uploadFile.bind(this);
this.name = name;
this.description = description;
this.mimeType = mimeType;
this.info = info;
this.logger = new Logger(`${ this.name } Importer`, {});
this.progress = new Importer.Progress(this.name);
this.collection = Importer.RawImports;
this.logger = new Logger(`${ this.info.name } Importer`, {});
this.progress = new Progress(this.info.key, this.info.name);
this.collection = RawImports;
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);
const importId = Imports.insert({ 'type': this.info.name, 'ts': Date.now(), 'status': this.progress.step, 'valid': true, 'user': Meteor.user()._id });
this.importRecord = Imports.findOne(importId);
this.users = {};
this.channels = {};
this.messages = {};
this.oldSettings = {};
this.logger.debug(`Constructed a new ${ info.name } Importer.`);
}
// 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.`);
this.updateProgress(Importer.ProgressStep.ERROR);
throw new Meteor.Error('error-invalid-file-uploaded', `Invalid file uploaded to import ${ this.name } data from.`, { step: 'prepare' });
/**
* Takes the uploaded file and extracts the users, channels, and messages from it.
*
* @param {string} dataURI Base64 string of the uploaded file
* @param {string} sentContentType The sent file type.
* @param {string} fileName The name of the uploaded file.
* @param {boolean} skipTypeCheck Optional property that says to not check the type provided.
* @returns {Progress} The progress record of the import.
*/
prepare(dataURI, sentContentType, fileName, skipTypeCheck) {
if (!skipTypeCheck) {
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.info.mimeType);
if (!fileType || (fileType.mime !== this.info.mimeType)) {
this.logger.warn(`Invalid file uploaded for the ${ this.info.name } importer.`);
this.updateProgress(ProgressStep.ERROR);
throw new Meteor.Error('error-invalid-file-uploaded', `Invalid file uploaded to import ${ this.info.name } data from.`, { step: 'prepare' });
}
}
this.updateProgress(Importer.ProgressStep.PREPARING_STARTED);
this.updateProgress(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
//
/**
* 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 {Selection} importSelection The selection data.
* @returns {Progress} The progress record 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
if (!(importSelection instanceof Selection)) {
throw new Error(`Invalid Selection data provided to the ${ this.info.name } importer.`);
} 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
throw new Error(`Users in the selected data wasn't found, it must but at least an empty array for the ${ this.info.name } importer.`);
} 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
throw new Error(`Channels in the selected data wasn't found, it must but at least an empty array for the ${ this.info.name } importer.`);
}
return this.updateProgress(Importer.ProgressStep.IMPORTING_STARTED);
return this.updateProgress(ProgressStep.IMPORTING_STARTED);
}
// Gets the Importer.Selection object for the import.
//
// @return [Importer.Selection] the users and channels selection
/**
* Gets the Selection object for the import.
*
* @returns {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.`);
throw new Error(`Invalid 'getSelection' called on ${ this.info.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
//
/**
* Gets the progress of this import.
*
* @returns {Progress} The progress record of the import.
*/
getProgress() {
return this.progress;
}
// Updates the progress step of this importer.
//
// @return [Importer.Progress] the progress of the import
//
/**
* Updates the progress step of this importer.
* It also changes some internal settings at various stages of the import.
* This way the importer can adjust user/room information at will.
*
* @param {ProgressStep} step The progress step which this import is currently at.
* @returns {Progress} The progress record of the import.
*/
updateProgress(step) {
this.progress.step = step;
switch (step) {
case Importer.ProgressStep.IMPORTING_STARTED:
case ProgressStep.IMPORTING_STARTED:
this.oldSettings.Accounts_AllowedDomainsList = RocketChat.models.Settings.findOneById('Accounts_AllowedDomainsList').value;
RocketChat.models.Settings.updateValueById('Accounts_AllowedDomainsList', '');
this.oldSettings.Accounts_AllowUsernameChange = RocketChat.models.Settings.findOneById('Accounts_AllowUsernameChange').value;
RocketChat.models.Settings.updateValueById('Accounts_AllowUsernameChange', true);
break;
case Importer.ProgressStep.DONE:
case Importer.ProgressStep.ERROR:
case ProgressStep.DONE:
case ProgressStep.ERROR:
RocketChat.models.Settings.updateValueById('Accounts_AllowedDomainsList', this.oldSettings.Accounts_AllowedDomainsList);
RocketChat.models.Settings.updateValueById('Accounts_AllowUsernameChange', this.oldSettings.Accounts_AllowUsernameChange);
break;
}
this.logger.debug(`${ this.name } is now at ${ step }.`);
this.logger.debug(`${ this.info.name } is now at ${ step }.`);
this.updateRecord({ 'status': this.progress.step });
ImporterWebsocket.progressUpdated(this.progress);
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
//
/**
* Adds the passed in value to the total amount of items needed to complete.
*
* @param {number} count The amount to add to the total count of items.
* @returns {Progress} The progress record of the import.
*/
addCountToTotal(count) {
this.progress.count.total = this.progress.count.total + count;
this.updateRecord({ 'count.total': this.progress.count.total });
@ -175,10 +218,12 @@ Importer.Base = class Base {
return this.progress;
}
// Adds the passed in value to the total amount of items completed.
//
// @return [Importer.Progress] the progress of the import
//
/**
* Adds the passed in value to the total amount of items completed.
*
* @param {number} count The amount to add to the total count of finished items.
* @returns {Progress} The progress record of the import.
*/
addCountCompleted(count) {
this.progress.count.completed = this.progress.count.completed + count;
@ -188,28 +233,33 @@ Importer.Base = class Base {
this.updateRecord({ 'count.completed': this.progress.count.completed });
}
ImporterWebsocket.progressUpdated(this.progress);
return this.progress;
}
// Updates the import record with the given fields being `set`
//
// @return [Importer.Imports] the import record object
//
/**
* Updates the import record with the given fields being `set`.
*
* @param {any} fields The fields to set, it should be an object with key/values.
* @returns {Imports} The import record.
*/
updateRecord(fields) {
Importer.Imports.update({ _id: this.importRecord._id }, { $set: fields });
this.importRecord = Importer.Imports.findOne(this.importRecord._id);
Imports.update({ _id: this.importRecord._id }, { $set: fields });
this.importRecord = 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
//
/**
* Uploads the file to the storage.
*
* @param {any} details An object with details about the upload: `name`, `size`, `type`, and `rid`.
* @param {string} fileUrl Url of the file to download/import.
* @param {any} user The Rocket.Chat user.
* @param {any} 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) ? this.https : this.http;
@ -271,4 +321,4 @@ Importer.Base = class Base {
}));
}));
}
};
}

@ -1,13 +1,16 @@
/* 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) {
import { ProgressStep } from '../../lib/ImporterProgressStep';
export class Progress {
/**
* Creates a new progress container for the importer.
*
* @param {string} key The unique key of the importer.
* @param {string} name The name of the importer.
*/
constructor(key, name) {
this.key = key;
this.name = name;
this.step = Importer.ProgressStep.NEW;
this.step = ProgressStep.NEW;
this.count = { completed: 0, total: 0 };
}
});
}

@ -1,17 +1,16 @@
/* 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<Importer.User>] users the array of users
// @param [Array<Importer.Channel>] channels the array of channels
// @param [Integer] number of collected messages
//
export class Selection {
/**
* Constructs a new importer selection object.
*
* @param {string} name the name of the importer
* @param {SelectionUser[]} users the users which can be selected
* @param {SelectionChannel[]} channels the channels which can be selected
* @param {number} message_count the number of messages
*/
constructor(name, users, channels, message_count) {
this.name = name;
this.users = users;
this.channels = channels;
this.message_count = message_count;
}
});
}

@ -1,14 +1,13 @@
/* 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
//
export 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;
@ -16,5 +15,4 @@ Importer.SelectionChannel = (Importer.SelectionChannel = class SelectionChannel
this.do_import = do_import;
this.is_private = is_private;
}
});
//TODO: Add some verification?
}

@ -1,15 +1,14 @@
/* 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
//
export 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;
@ -18,5 +17,4 @@ Importer.SelectionUser = (Importer.SelectionUser = class SelectionUser {
this.is_bot = is_bot;
this.do_import = do_import;
}
});
//TODO: Add some verification?
}

@ -0,0 +1,19 @@
class ImporterWebsocketDef {
constructor() {
this.streamer = new Meteor.Streamer('importers', { retransmit: false });
this.streamer.allowRead('all');
this.streamer.allowEmit('all');
this.streamer.allowWrite('none');
}
/**
* Called when the progress is updated.
*
* @param {Progress} progress The progress of the import.
*/
progressUpdated(progress) {
this.streamer.emit('progress', progress);
}
}
export const ImporterWebsocket = new ImporterWebsocketDef();

@ -0,0 +1,25 @@
import { Base } from './classes/ImporterBase';
import { Imports } from './models/Imports';
import { Importers } from '../lib/Importers';
import { ImporterInfo } from '../lib/ImporterInfo';
import { ImporterWebsocket } from './classes/ImporterWebsocket';
import { Progress } from './classes/ImporterProgress';
import { ProgressStep } from '../lib/ImporterProgressStep';
import { RawImports } from './models/RawImports';
import { Selection } from './classes/ImporterSelection';
import { SelectionChannel } from './classes/ImporterSelectionChannel';
import { SelectionUser } from './classes/ImporterSelectionUser';
export {
Base,
Imports,
Importers,
ImporterInfo,
ImporterWebsocket,
Progress,
ProgressStep,
RawImports,
Selection,
SelectionChannel,
SelectionUser
};

@ -1,6 +1,7 @@
/* globals Importer */
import { Importers } from 'meteor/rocketchat:importer';
Meteor.methods({
getImportProgress(name) {
getImportProgress(key) {
if (!Meteor.userId()) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'getImportProgress' });
}
@ -9,9 +10,16 @@ Meteor.methods({
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' });
const importer = Importers.get(key);
if (!importer) {
throw new Meteor.Error('error-importer-not-defined', `The importer (${ key }) has no import class defined.`, { method: 'getImportProgress' });
}
}});
if (!importer.instance) {
return undefined;
}
return importer.instance.getProgress();
}
});

@ -1,6 +1,10 @@
/* globals Importer */
import {
Importers,
ProgressStep
} from 'meteor/rocketchat:importer';
Meteor.methods({
getSelectionData(name) {
getSelectionData(key) {
if (!Meteor.userId()) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'getSelectionData' });
}
@ -9,15 +13,19 @@ Meteor.methods({
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' });
const importer = Importers.get(key);
if (!importer || !importer.instance) {
throw new Meteor.Error('error-importer-not-defined', `The importer (${ key }) has no import class defined.`, { method: 'getSelectionData' });
}
const progress = importer.instance.getProgress();
switch (progress.step) {
case ProgressStep.USER_SELECTION:
return importer.instance.getSelection();
default:
return undefined;
}
}});
}
});

@ -1,7 +1,7 @@
/* globals Importer */
import { Importers } from 'meteor/rocketchat:importer';
Meteor.methods({
prepareImport(name, dataURI, contentType, fileName) {
prepareImport(key, dataURI, contentType, fileName) {
if (!Meteor.userId()) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'prepareImport' });
}
@ -10,24 +10,22 @@ Meteor.methods({
throw new Meteor.Error('error-action-not-allowed', 'Importing is not allowed', { method: 'setupImporter'});
}
check(name, String);
check(key, String);
check(dataURI, String);
check(fileName, String);
if (name && Importer.Importers[name] && Importer.Importers[name].importerInstance) {
const results = Importer.Importers[name].importerInstance.prepare(dataURI, contentType, fileName);
const importer = Importers.get(key);
if (typeof results === 'object') {
if (results instanceof Promise) {
return results.catch(e => { throw new Meteor.Error(e); });
} else {
return results;
}
}
} else if (!name) {
throw new Meteor.Error('error-importer-not-defined', `No Importer Found: "${ name }"`, { method: 'prepareImport' });
if (!importer) {
throw new Meteor.Error('error-importer-not-defined', `The importer (${ key }) has no import class defined.`, { method: 'prepareImport' });
}
const results = importer.instance.prepare(dataURI, contentType, fileName);
if (results instanceof Promise) {
return results.catch(e => { throw new Meteor.Error(e); });
} else {
throw new Meteor.Error('error-importer-not-defined', `The importer, "${ name }", was not defined correctly, it is missing the Import class.`, { method: 'prepareImport' });
return results;
}
}
});

@ -1,6 +1,10 @@
/* globals Importer*/
import {
Importers,
ProgressStep
} from 'meteor/rocketchat:importer';
Meteor.methods({
restartImport(name) {
restartImport(key) {
if (!Meteor.userId()) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'restartImport' });
}
@ -9,14 +13,19 @@ Meteor.methods({
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' });
const importer = Importers.get(key);
if (!importer) {
throw new Meteor.Error('error-importer-not-defined', `The importer (${ key }) has no import class defined.`, { method: 'restartImport' });
}
}});
if (importer.instance) {
importer.instance.updateProgress(ProgressStep.CANCELLED);
importer.instance.updateRecord({ valid: false });
importer.instance = undefined;
}
importer.instance = new importer.importer(importer); // eslint-disable-line new-cap
return importer.instance.getProgress();
}
});

@ -1,6 +1,7 @@
/* globals Importer */
import { Importers } from 'meteor/rocketchat:importer';
Meteor.methods({
setupImporter(name) {
setupImporter(key) {
if (!Meteor.userId()) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'setupImporter' });
}
@ -9,17 +10,18 @@ Meteor.methods({
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 {
const importer = Importers.get(key);
if (!importer) {
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' });
}
}});
if (importer.instance) {
return importer.instance.getProgress();
}
importer.instance = new importer.importer(importer); //eslint-disable-line new-cap
return importer.instance.getProgress();
}
});

@ -1,6 +1,12 @@
/* globals Importer */
import {
Importers,
Selection,
SelectionChannel,
SelectionUser
} from 'meteor/rocketchat:importer';
Meteor.methods({
startImport(name, input) {
startImport(key, 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' });
@ -10,17 +16,20 @@ Meteor.methods({
throw new Meteor.Error('error-action-not-allowed', 'Importing is not allowed', { method: 'startImport'});
}
if (!name) {
throw new Meteor.Error('error-invalid-importer', `No defined importer by: "${ name }"`, { method: 'startImport' });
if (!key) {
throw new Meteor.Error('error-invalid-importer', `No defined importer by: "${ key }"`, { method: 'startImport' });
}
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 importer = Importers.get(key);
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' });
if (!importer || !importer.instance) {
throw new Meteor.Error('error-importer-not-defined', `The importer (${ key }) has no import class defined.`, { method: 'startImport' });
}
}});
const usersSelection = input.users.map(user => new SelectionUser(user.user_id, user.username, user.email, user.is_deleted, user.is_bot, user.do_import));
const channelsSelection = input.channels.map(channel => new SelectionChannel(channel.channel_id, channel.name, channel.is_archived, channel.do_import));
const selection = new Selection(importer.name, usersSelection, channelsSelection);
return importer.instance.startImport(selection);
}
});

@ -1,6 +1,7 @@
/* globals Importer */
Importer.Imports = new (Importer.Imports = class Imports extends RocketChat.models._Base {
class ImportsModel extends RocketChat.models._Base {
constructor() {
super('import');
}
});
}
export const Imports = new ImportsModel();

@ -1,6 +1,7 @@
/* globals Importer */
Importer.RawImports = new (Importer.RawImports = class RawImports extends RocketChat.models._Base {
class RawImportsModel extends RocketChat.models._Base {
constructor() {
super('raw_imports');
}
});
}
export const RawImports = new RawImportsModel();

@ -1,9 +1,16 @@
/* globals Importer */
import { Imports } from '../models/Imports';
import { RawImports } from '../models/RawImports';
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 });
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 }));
try {
RawImports.model.rawCollection().drop();
} catch (e) {
console.log('errror', e); //TODO: Remove
// ignored
}
});

@ -21,7 +21,11 @@ class ModelUsers extends RocketChat.models._Base {
}
findOneByUsername(username, options) {
const query = {username};
if (typeof username === 'string') {
username = new RegExp(username, 'i');
}
const query = {username};
return this.findOne(query, options);
}

@ -33,6 +33,8 @@ Meteor.methods({
return false;
}
reaction = `:${ reaction.replace(/:/g, '') }:`;
if (message.reactions && message.reactions[reaction] && message.reactions[reaction].usernames.indexOf(user.username) !== -1) {
message.reactions[reaction].usernames.splice(message.reactions[reaction].usernames.indexOf(user.username), 1);

Loading…
Cancel
Save