[NEW] Standard Importer Structure (#18357)
parent
cb61ac2b21
commit
14f7c3d509
@ -1,5 +0,0 @@ |
||||
import { CsvImporter } from './importer'; |
||||
import { Importers } from '../../importer/server'; |
||||
import { CsvImporterInfo } from '../lib/info'; |
||||
|
||||
Importers.add(new CsvImporterInfo(), CsvImporter); |
||||
@ -1 +1,5 @@ |
||||
import './adder'; |
||||
import { CsvImporter } from './importer'; |
||||
import { Importers } from '../../importer/server'; |
||||
import { CsvImporterInfo } from '../lib/info'; |
||||
|
||||
Importers.add(new CsvImporterInfo(), CsvImporter); |
||||
|
||||
@ -1,5 +0,0 @@ |
||||
import { HipChatEnterpriseImporter } from './importer'; |
||||
import { Importers } from '../../importer/server'; |
||||
import { HipChatEnterpriseImporterInfo } from '../lib/info'; |
||||
|
||||
Importers.add(new HipChatEnterpriseImporterInfo(), HipChatEnterpriseImporter); |
||||
File diff suppressed because it is too large
Load Diff
@ -1 +1,5 @@ |
||||
import './adder'; |
||||
import { HipChatEnterpriseImporter } from './importer'; |
||||
import { Importers } from '../../importer/server'; |
||||
import { HipChatEnterpriseImporterInfo } from '../lib/info'; |
||||
|
||||
Importers.add(new HipChatEnterpriseImporterInfo(), HipChatEnterpriseImporter); |
||||
|
||||
@ -1,4 +0,0 @@ |
||||
import { Importers } from '../../importer/client'; |
||||
import { HipChatImporterInfo } from '../lib/info'; |
||||
|
||||
Importers.add(new HipChatImporterInfo()); |
||||
@ -1 +0,0 @@ |
||||
import './adder'; |
||||
@ -1,7 +0,0 @@ |
||||
import { ImporterInfo } from '../../importer/lib/ImporterInfo'; |
||||
|
||||
export class HipChatImporterInfo extends ImporterInfo { |
||||
constructor() { |
||||
super('hipchat', 'HipChat (zip)', 'application/zip'); |
||||
} |
||||
} |
||||
@ -1,5 +0,0 @@ |
||||
import { HipChatImporter } from './importer'; |
||||
import { Importers } from '../../importer/server'; |
||||
import { HipChatImporterInfo } from '../lib/info'; |
||||
|
||||
Importers.add(new HipChatImporterInfo(), HipChatImporter); |
||||
@ -1,375 +0,0 @@ |
||||
import limax from 'limax'; |
||||
import { Meteor } from 'meteor/meteor'; |
||||
import { Accounts } from 'meteor/accounts-base'; |
||||
import _ from 'underscore'; |
||||
import moment from 'moment'; |
||||
|
||||
import { |
||||
RawImports, |
||||
Base, |
||||
ProgressStep, |
||||
Selection, |
||||
SelectionChannel, |
||||
SelectionUser, |
||||
} from '../../importer/server'; |
||||
import { RocketChatFile } from '../../file'; |
||||
import { Users, Rooms } from '../../models'; |
||||
import { sendMessage } from '../../lib'; |
||||
|
||||
import 'moment-timezone'; |
||||
|
||||
export class HipChatImporter extends Base { |
||||
constructor(info, importRecord) { |
||||
super(info, importRecord); |
||||
|
||||
this.userTags = []; |
||||
this.roomPrefix = 'hipchat_export/rooms/'; |
||||
this.usersPrefix = 'hipchat_export/users/'; |
||||
} |
||||
|
||||
prepare(dataURI, sentContentType, fileName, skipTypeCheck) { |
||||
super.prepare(dataURI, sentContentType, fileName, skipTypeCheck); |
||||
const { image } = RocketChatFile.dataURIParse(dataURI); |
||||
// const contentType = ref.contentType;
|
||||
const zip = new this.AdmZip(Buffer.from(image, 'base64')); |
||||
const zipEntries = zip.getEntries(); |
||||
let 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') { |
||||
super.updateProgress(ProgressStep.PREPARING_CHANNELS); |
||||
tempRooms = JSON.parse(entry.getData().toString()).rooms; |
||||
tempRooms.forEach((room) => { |
||||
room.name = limax(room.name); |
||||
}); |
||||
} else if (roomName.indexOf('/') > -1) { |
||||
const item = roomName.split('/'); |
||||
roomName = limax(item[0]); |
||||
const msgGroupData = item[1].split('.')[0]; |
||||
if (!tempMessages[roomName]) { |
||||
tempMessages[roomName] = {}; |
||||
} |
||||
try { |
||||
tempMessages[roomName][msgGroupData] = JSON.parse(entry.getData().toString()); |
||||
return tempMessages[roomName][msgGroupData]; |
||||
} 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') { |
||||
super.updateProgress(ProgressStep.PREPARING_USERS); |
||||
tempUsers = JSON.parse(entry.getData().toString()).users; |
||||
return tempUsers; |
||||
} |
||||
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); |
||||
super.updateProgress(ProgressStep.PREPARING_MESSAGES); |
||||
let messagesCount = 0; |
||||
|
||||
Object.keys(tempMessages).forEach((channel) => { |
||||
const messagesObj = tempMessages[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) => { |
||||
this.collection.insert({ |
||||
import: this.importRecord._id, |
||||
importer: this.name, |
||||
type: 'messages', |
||||
name: `${ channel }/${ date }.${ i }`, |
||||
messages: splitMsg, |
||||
channel, |
||||
date, |
||||
i, |
||||
}); |
||||
}); |
||||
} else { |
||||
this.collection.insert({ |
||||
import: this.importRecord._id, |
||||
importer: this.name, |
||||
type: 'messages', |
||||
name: `${ channel }/${ date }`, |
||||
messages: msgs, |
||||
channel, |
||||
date, |
||||
}); |
||||
} |
||||
}); |
||||
}); |
||||
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 }`); |
||||
super.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; |
||||
super.updateProgress(ProgressStep.USER_SELECTION); |
||||
return new Selection(this.name, selectionUsers, selectionChannels, selectionMessages); |
||||
} |
||||
|
||||
startImport(importSelection) { |
||||
this.users = RawImports.findOne({ import: this.importRecord._id, type: 'users' }); |
||||
this.channels = RawImports.findOne({ import: this.importRecord._id, type: 'channels' }); |
||||
this.reloadCount(); |
||||
|
||||
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) => { |
||||
if (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(() => { |
||||
super.updateProgress(ProgressStep.IMPORTING_USERS); |
||||
|
||||
try { |
||||
this.users.users.forEach((user) => { |
||||
if (!user.do_import) { |
||||
return; |
||||
} |
||||
|
||||
Meteor.runAsUser(startedByUserId, () => { |
||||
const existantUser = 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) { |
||||
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 } }); |
||||
|
||||
const channelNames = []; |
||||
|
||||
super.updateProgress(ProgressStep.IMPORTING_CHANNELS); |
||||
this.channels.channels.forEach((channel) => { |
||||
if (!channel.do_import) { |
||||
return; |
||||
} |
||||
|
||||
channelNames.push(channel.name); |
||||
Meteor.runAsUser(startedByUserId, () => { |
||||
channel.name = channel.name.replace(/ /g, ''); |
||||
const existantRoom = 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, []); |
||||
channel.rocketId = returned.rid; |
||||
}); |
||||
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 } }); |
||||
|
||||
super.updateProgress(ProgressStep.IMPORTING_MESSAGES); |
||||
const nousers = {}; |
||||
|
||||
for (const channel of channelNames) { |
||||
const hipchatChannel = this.getHipChatChannelFromName(channel); |
||||
|
||||
if (!hipchatChannel || !hipchatChannel.do_import) { |
||||
continue; |
||||
} |
||||
|
||||
const room = Rooms.findOneById(hipchatChannel.rocketId, { |
||||
fields: { |
||||
usernames: 1, |
||||
t: 1, |
||||
name: 1, |
||||
}, |
||||
}); |
||||
|
||||
const messagePacks = this.collection.find({ import: this.importRecord._id, type: 'messages', channel }); |
||||
|
||||
Meteor.runAsUser(startedByUserId, () => { |
||||
messagePacks.forEach((pack) => { |
||||
const packId = pack.i ? `${ pack.date }.${ pack.i }` : pack.date; |
||||
|
||||
this.updateRecord({ messagesstatus: `${ channel }/${ packId } (${ pack.messages.length })` }); |
||||
pack.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, |
||||
}, |
||||
}; |
||||
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); |
||||
super.updateProgress(ProgressStep.FINISHING); |
||||
|
||||
this.channels.channels.forEach((channel) => { |
||||
if (channel.do_import && channel.is_archived) { |
||||
Meteor.runAsUser(startedByUserId, () => Meteor.call('archiveRoom', channel.rocketId)); |
||||
} |
||||
}); |
||||
|
||||
super.updateProgress(ProgressStep.DONE); |
||||
} catch (e) { |
||||
this.logger.error(e); |
||||
super.updateProgress(ProgressStep.ERROR); |
||||
} |
||||
|
||||
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 ? 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; |
||||
} |
||||
} |
||||
@ -1 +0,0 @@ |
||||
import './adder'; |
||||
@ -1,5 +0,0 @@ |
||||
import { SlackUsersImporter } from './importer'; |
||||
import { Importers } from '../../importer/server'; |
||||
import { SlackUsersImporterInfo } from '../lib/info'; |
||||
|
||||
Importers.add(new SlackUsersImporterInfo(), SlackUsersImporter); |
||||
@ -1 +1,5 @@ |
||||
import './adder'; |
||||
import { SlackUsersImporter } from './importer'; |
||||
import { Importers } from '../../importer/server'; |
||||
import { SlackUsersImporterInfo } from '../lib/info'; |
||||
|
||||
Importers.add(new SlackUsersImporterInfo(), SlackUsersImporter); |
||||
|
||||
@ -1,5 +0,0 @@ |
||||
import { SlackImporter } from './importer'; |
||||
import { Importers } from '../../importer/server'; |
||||
import { SlackImporterInfo } from '../lib/info'; |
||||
|
||||
Importers.add(new SlackImporterInfo(), SlackImporter); |
||||
File diff suppressed because it is too large
Load Diff
@ -1 +1,5 @@ |
||||
import './adder'; |
||||
import { SlackImporter } from './importer'; |
||||
import { Importers } from '../../importer/server'; |
||||
import { SlackImporterInfo } from '../lib/info'; |
||||
|
||||
Importers.add(new SlackImporterInfo(), SlackImporter); |
||||
|
||||
@ -0,0 +1,832 @@ |
||||
import { Meteor } from 'meteor/meteor'; |
||||
import { Accounts } from 'meteor/accounts-base'; |
||||
import _ from 'underscore'; |
||||
|
||||
import { ImportData } from '../models/ImportData'; |
||||
import { IImportUser } from '../definitions/IImportUser'; |
||||
import { IImportMessage, IImportMessageReaction } from '../definitions/IImportMessage'; |
||||
import { IImportChannel } from '../definitions/IImportChannel'; |
||||
import { IImportUserRecord, IImportChannelRecord, IImportMessageRecord } from '../definitions/IImportRecord'; |
||||
import { Users, Rooms, Subscriptions } from '../../../models/server'; |
||||
import { generateUsernameSuggestion, insertMessage } from '../../../lib/server'; |
||||
import { setUserActiveStatus } from '../../../lib/server/functions/setUserActiveStatus'; |
||||
import { IUser } from '../../../../definition/IUser'; |
||||
|
||||
type IRoom = Record<string, any>; |
||||
type IMessage = Record<string, any>; |
||||
type IUserIdentification = { |
||||
_id: string; |
||||
username: string | undefined; |
||||
}; |
||||
type IMentionedUser = { |
||||
_id: string; |
||||
username: string; |
||||
name?: string; |
||||
}; |
||||
type IMentionedChannel = { |
||||
_id: string; |
||||
name: string; |
||||
}; |
||||
|
||||
type IMessageReaction = { |
||||
name: string; |
||||
usernames: Array<string>; |
||||
}; |
||||
|
||||
type IMessageReactions = Record<string, IMessageReaction>; |
||||
|
||||
interface IConversionCallbacks { |
||||
beforeImportFn?: { |
||||
(data: IImportUser | IImportChannel | IImportMessage, type: string): boolean; |
||||
}; |
||||
afterImportFn?: { |
||||
(data: IImportUser | IImportChannel | IImportMessage, type: string): void; |
||||
}; |
||||
} |
||||
|
||||
const guessNameFromUsername = (username: string): string => |
||||
username |
||||
.replace(/\W/g, ' ') |
||||
.replace(/\s(.)/g, (u) => u.toUpperCase()) |
||||
.replace(/^(.)/, (u) => u.toLowerCase()) |
||||
.replace(/^\w/, (u) => u.toUpperCase()); |
||||
|
||||
export class ImportDataConverter { |
||||
private _userCache: Map<string, IUserIdentification>; |
||||
|
||||
// display name uses a different cache because it's only used on mentions so we don't need to load it every time we load an user
|
||||
private _userDisplayNameCache: Map<string, string>; |
||||
|
||||
private _roomCache: Map<string, string>; |
||||
|
||||
private _roomNameCache: Map<string, string>; |
||||
|
||||
constructor() { |
||||
this._userCache = new Map(); |
||||
this._userDisplayNameCache = new Map(); |
||||
this._roomCache = new Map(); |
||||
this._roomNameCache = new Map(); |
||||
} |
||||
|
||||
addUserToCache(importId: string, _id: string, username: string | undefined): IUserIdentification { |
||||
const cache = { |
||||
_id, |
||||
username, |
||||
}; |
||||
|
||||
this._userCache.set(importId, cache); |
||||
return cache; |
||||
} |
||||
|
||||
addUserDisplayNameToCache(importId: string, name: string): string { |
||||
this._userDisplayNameCache.set(importId, name); |
||||
return name; |
||||
} |
||||
|
||||
addRoomToCache(importId: string, rid: string): string { |
||||
this._roomCache.set(importId, rid); |
||||
return rid; |
||||
} |
||||
|
||||
addRoomNameToCache(importId: string, name: string): string { |
||||
this._roomNameCache.set(importId, name); |
||||
return name; |
||||
} |
||||
|
||||
addUserDataToCache(userData: IImportUser): void { |
||||
if (!userData._id) { |
||||
return; |
||||
} |
||||
if (!userData.importIds.length) { |
||||
return; |
||||
} |
||||
|
||||
this.addUserToCache(userData.importIds[0], userData._id, userData.username); |
||||
} |
||||
|
||||
addObject(type: string, data: Record<string, any>, options: Record<string, any> = {}): void { |
||||
ImportData.model.rawCollection().insert({ |
||||
data, |
||||
dataType: type, |
||||
...options, |
||||
}); |
||||
} |
||||
|
||||
addUser(data: IImportUser): void { |
||||
this.addObject('user', data); |
||||
} |
||||
|
||||
addChannel(data: IImportChannel): void { |
||||
this.addObject('channel', data); |
||||
} |
||||
|
||||
addMessage(data: IImportMessage, useQuickInsert = false): void { |
||||
this.addObject('message', data, { |
||||
useQuickInsert: useQuickInsert || undefined, |
||||
}); |
||||
} |
||||
|
||||
updateUserId(_id: string, userData: IImportUser): void { |
||||
const updateData: Record<string, any> = { |
||||
$set: { |
||||
statusText: userData.statusText || undefined, |
||||
roles: userData.roles || ['user'], |
||||
type: userData.type || 'user', |
||||
bio: userData.bio || undefined, |
||||
name: userData.name || undefined, |
||||
}, |
||||
}; |
||||
|
||||
if (userData.importIds?.length) { |
||||
updateData.$addToSet = { |
||||
importIds: { |
||||
$each: userData.importIds, |
||||
}, |
||||
}; |
||||
} |
||||
|
||||
Users.update({ _id }, updateData); |
||||
} |
||||
|
||||
updateUser(existingUser: IUser, userData: IImportUser): void { |
||||
userData._id = existingUser._id; |
||||
|
||||
this.updateUserId(userData._id, userData); |
||||
|
||||
if (userData.importIds.length) { |
||||
this.addUserToCache(userData.importIds[0], existingUser._id, existingUser.username); |
||||
} |
||||
|
||||
if (userData.avatarUrl) { |
||||
try { |
||||
Users.update({ _id: existingUser._id }, { $set: { _pendingAvatarUrl: userData.avatarUrl } }); |
||||
} catch (error) { |
||||
console.warn(`Failed to set ${ existingUser._id }'s avatar from url ${ userData.avatarUrl }`); |
||||
console.error(error); |
||||
} |
||||
} |
||||
} |
||||
|
||||
insertUser(userData: IImportUser): IUser { |
||||
const password = `${ Date.now() }${ userData.name || '' }${ userData.emails.length ? userData.emails[0].toUpperCase() : '' }`; |
||||
const userId = userData.emails.length ? Accounts.createUser({ |
||||
email: userData.emails[0], |
||||
password, |
||||
}) : Accounts.createUser({ |
||||
username: userData.username, |
||||
password, |
||||
// @ts-ignore
|
||||
joinDefaultChannelsSilenced: true, |
||||
}); |
||||
|
||||
userData._id = userId; |
||||
const user = Users.findOneById(userId, {}); |
||||
|
||||
if (user && userData.importIds.length) { |
||||
this.addUserToCache(userData.importIds[0], user._id, userData.username); |
||||
} |
||||
|
||||
Meteor.runAsUser(userId, () => { |
||||
Meteor.call('setUsername', userData.username, { joinDefaultChannelsSilenced: true }); |
||||
if (userData.name) { |
||||
Users.setName(userId, userData.name); |
||||
} |
||||
|
||||
this.updateUserId(userId, userData); |
||||
|
||||
if (userData.utcOffset) { |
||||
Users.setUtcOffset(userId, userData.utcOffset); |
||||
} |
||||
|
||||
if (userData.avatarUrl) { |
||||
try { |
||||
Users.update({ _id: userId }, { $set: { _pendingAvatarUrl: userData.avatarUrl } }); |
||||
} catch (error) { |
||||
console.warn(`Failed to set ${ userId }'s avatar from url ${ userData.avatarUrl }`); |
||||
console.error(error); |
||||
} |
||||
} |
||||
}); |
||||
|
||||
return user; |
||||
} |
||||
|
||||
convertUsers({ beforeImportFn, afterImportFn }: IConversionCallbacks = {}): void { |
||||
const users = ImportData.find({ dataType: 'user' }); |
||||
users.forEach(({ data, _id }: IImportUserRecord) => { |
||||
try { |
||||
if (beforeImportFn && !beforeImportFn(data, 'user')) { |
||||
this.skipRecord(_id); |
||||
return; |
||||
} |
||||
|
||||
data.emails = data.emails.filter((item) => item); |
||||
data.importIds = data.importIds.filter((item) => item); |
||||
|
||||
if (!data.emails.length && !data.username) { |
||||
throw new Error('importer-user-missing-email-and-username'); |
||||
} |
||||
|
||||
let existingUser; |
||||
if (data.emails.length) { |
||||
existingUser = Users.findOneByEmailAddress(data.emails[0], {}); |
||||
} |
||||
|
||||
if (data.username) { |
||||
// If we couldn't find one by their email address, try to find an existing user by their username
|
||||
if (!existingUser) { |
||||
existingUser = Users.findOneByUsernameIgnoringCase(data.username, {}); |
||||
} |
||||
} else { |
||||
data.username = generateUsernameSuggestion({ |
||||
name: data.name, |
||||
emails: data.emails, |
||||
}); |
||||
} |
||||
|
||||
if (existingUser) { |
||||
this.updateUser(existingUser, data); |
||||
} else { |
||||
if (!data.name && data.username) { |
||||
data.name = guessNameFromUsername(data.username); |
||||
} |
||||
|
||||
existingUser = this.insertUser(data); |
||||
} |
||||
|
||||
// Deleted users are 'inactive' users in Rocket.Chat
|
||||
if (data.deleted && existingUser?.active) { |
||||
setUserActiveStatus(data._id, false, true); |
||||
} |
||||
|
||||
if (afterImportFn) { |
||||
afterImportFn(data, 'user'); |
||||
} |
||||
} catch (e) { |
||||
this.saveError(_id, e); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
saveNewId(importId: string, newId: string): void { |
||||
ImportData.update({ |
||||
_id: importId, |
||||
}, { |
||||
$set: { |
||||
id: newId, |
||||
}, |
||||
}); |
||||
} |
||||
|
||||
saveError(importId: string, error: Error): void { |
||||
console.error(error); |
||||
ImportData.update({ |
||||
_id: importId, |
||||
}, { |
||||
$push: { |
||||
errors: { |
||||
message: error.message, |
||||
stack: error.stack, |
||||
}, |
||||
}, |
||||
}); |
||||
} |
||||
|
||||
skipRecord(_id: string): void { |
||||
ImportData.update({ |
||||
_id, |
||||
}, { |
||||
$set: { |
||||
skipped: true, |
||||
}, |
||||
}); |
||||
} |
||||
|
||||
convertMessageReactions(importedReactions: Record<string, IImportMessageReaction>): undefined | IMessageReactions { |
||||
const reactions: IMessageReactions = {}; |
||||
|
||||
for (const name in importedReactions) { |
||||
if (!importedReactions.hasOwnProperty(name)) { |
||||
continue; |
||||
} |
||||
const { users } = importedReactions[name]; |
||||
|
||||
if (!users.length) { |
||||
continue; |
||||
} |
||||
|
||||
const reaction: IMessageReaction = { |
||||
name, |
||||
usernames: [], |
||||
}; |
||||
|
||||
for (const importId of users) { |
||||
const username = this.findImportedUsername(importId); |
||||
if (username && !reaction.usernames.includes(username)) { |
||||
reaction.usernames.push(username); |
||||
} |
||||
} |
||||
|
||||
if (reaction.usernames.length) { |
||||
reactions[name] = reaction; |
||||
} |
||||
} |
||||
|
||||
if (Object.keys(reactions).length > 0) { |
||||
return reactions; |
||||
} |
||||
} |
||||
|
||||
convertMessageReplies(replies: Array<string>): Array<string> { |
||||
const result: Array<string> = []; |
||||
for (const importId of replies) { |
||||
const userId = this.findImportedUserId(importId); |
||||
if (userId && !result.includes(userId)) { |
||||
result.push(userId); |
||||
} |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
convertMessageMentions(message: IImportMessage): Array<IMentionedUser> | undefined { |
||||
const { mentions } = message; |
||||
if (!mentions) { |
||||
return undefined; |
||||
} |
||||
|
||||
const result: Array<IMentionedUser> = []; |
||||
for (const importId of mentions) { |
||||
// eslint-disable-next-line no-extra-parens
|
||||
if (importId === ('all' as 'string') || importId === 'here') { |
||||
result.push({ |
||||
_id: importId, |
||||
username: importId, |
||||
}); |
||||
continue; |
||||
} |
||||
|
||||
// Loading the name will also store the remaining data on the cache if it's missing, so this won't run two queries
|
||||
const name = this.findImportedUserDisplayName(importId); |
||||
const data = this.findImportedUser(importId); |
||||
|
||||
if (!data) { |
||||
throw new Error('importer-message-mentioned-user-not-found'); |
||||
} |
||||
if (!data.username) { |
||||
throw new Error('importer-message-mentioned-username-not-found'); |
||||
} |
||||
|
||||
message.msg = message.msg.replace(new RegExp(`\@${ importId }`, 'gi'), `@${ data.username }`); |
||||
|
||||
result.push({ |
||||
_id: data._id, |
||||
username: data.username as 'string', |
||||
name, |
||||
}); |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
convertMessageChannels(message: IImportMessage): Array<IMentionedChannel> | undefined { |
||||
const { channels } = message; |
||||
if (!channels) { |
||||
return; |
||||
} |
||||
|
||||
const result: Array<IMentionedChannel> = []; |
||||
for (const importId of channels) { |
||||
// loading the name will also store the id on the cache if it's missing, so this won't run two queries
|
||||
const name = this.findImportedRoomName(importId); |
||||
const _id = this.findImportedRoomId(importId); |
||||
|
||||
if (!_id || !name) { |
||||
console.warn(`Mentioned room not found: ${ importId }`); |
||||
continue; |
||||
} |
||||
|
||||
message.msg = message.msg.replace(new RegExp(`\#${ importId }`, 'gi'), `#${ name }`); |
||||
|
||||
result.push({ |
||||
_id, |
||||
name, |
||||
}); |
||||
} |
||||
|
||||
return result; |
||||
} |
||||
|
||||
convertMessages({ beforeImportFn, afterImportFn }: IConversionCallbacks = {}): void { |
||||
const rids: Array<string> = []; |
||||
const messages = ImportData.find({ dataType: 'message' }); |
||||
messages.forEach(({ data: m, _id }: IImportMessageRecord) => { |
||||
try { |
||||
if (beforeImportFn && !beforeImportFn(m, 'message')) { |
||||
this.skipRecord(_id); |
||||
return; |
||||
} |
||||
|
||||
if (!m.ts || isNaN(m.ts as unknown as number)) { |
||||
throw new Error('importer-message-invalid-timestamp'); |
||||
} |
||||
|
||||
const creator = this.findImportedUser(m.u._id); |
||||
if (!creator) { |
||||
console.warn(`Imported user not found: ${ m.u._id }`); |
||||
throw new Error('importer-message-unknown-user'); |
||||
} |
||||
|
||||
const rid = this.findImportedRoomId(m.rid); |
||||
if (!rid) { |
||||
throw new Error('importer-message-unknown-room'); |
||||
} |
||||
if (!rids.includes(rid)) { |
||||
rids.push(rid); |
||||
} |
||||
|
||||
// Convert the mentions and channels first because these conversions can also modify the msg in the message object
|
||||
const mentions = m.mentions && this.convertMessageMentions(m); |
||||
const channels = m.channels && this.convertMessageChannels(m); |
||||
|
||||
const msgObj: IMessage = { |
||||
rid, |
||||
u: { |
||||
_id: creator._id, |
||||
username: creator.username, |
||||
}, |
||||
msg: m.msg, |
||||
ts: m.ts, |
||||
t: m.t || undefined, |
||||
groupable: m.groupable, |
||||
tmid: m.tmid, |
||||
tlm: m.tlm, |
||||
tcount: m.tcount, |
||||
replies: m.replies && this.convertMessageReplies(m.replies), |
||||
editedAt: m.editedAt, |
||||
editedBy: m.editedBy && (this.findImportedUser(m.editedBy) || undefined), |
||||
mentions, |
||||
channels, |
||||
_importFile: m._importFile, |
||||
url: m.url, |
||||
attachments: m.attachments, |
||||
bot: m.bot, |
||||
emoji: m.emoji, |
||||
alias: m.alias, |
||||
}; |
||||
|
||||
if (m._id) { |
||||
msgObj._id = m._id; |
||||
} |
||||
|
||||
if (m.reactions) { |
||||
msgObj.reactions = this.convertMessageReactions(m.reactions); |
||||
} |
||||
|
||||
try { |
||||
insertMessage(creator, msgObj, rid, true); |
||||
} catch (e) { |
||||
console.warn(`Failed to import message with timestamp ${ String(msgObj.ts) } to room ${ rid }`); |
||||
console.error(e); |
||||
} |
||||
|
||||
if (afterImportFn) { |
||||
afterImportFn(m, 'message'); |
||||
} |
||||
} catch (e) { |
||||
this.saveError(_id, e); |
||||
} |
||||
}); |
||||
|
||||
for (const rid of rids) { |
||||
try { |
||||
Rooms.resetLastMessageById(rid); |
||||
} catch (e) { |
||||
console.warn(`Failed to update last message of room ${ rid }`); |
||||
console.error(e); |
||||
} |
||||
} |
||||
} |
||||
|
||||
updateRoom(room: IRoom, roomData: IImportChannel, startedByUserId: string): void { |
||||
roomData._id = room._id; |
||||
|
||||
// eslint-disable-next-line no-extra-parens
|
||||
if ((roomData._id as string).toUpperCase() === 'GENERAL' && roomData.name !== room.name) { |
||||
Meteor.runAsUser(startedByUserId, () => { |
||||
Meteor.call('saveRoomSettings', 'GENERAL', 'roomName', roomData.name); |
||||
}); |
||||
} |
||||
|
||||
this.updateRoomId(room._id, roomData); |
||||
} |
||||
|
||||
findDMForImportedUsers(...users: Array<string>): IImportChannel | undefined { |
||||
const record = ImportData.findDMForImportedUsers(...users); |
||||
if (record) { |
||||
return record.data; |
||||
} |
||||
} |
||||
|
||||
findImportedRoomId(importId: string): string | null { |
||||
if (this._roomCache.has(importId)) { |
||||
return this._roomCache.get(importId) as string; |
||||
} |
||||
|
||||
const options = { |
||||
fields: { |
||||
_id: 1, |
||||
}, |
||||
}; |
||||
|
||||
const room = Rooms.findOneByImportId(importId, options); |
||||
if (room) { |
||||
return this.addRoomToCache(importId, room._id); |
||||
} |
||||
|
||||
return null; |
||||
} |
||||
|
||||
findImportedRoomName(importId: string): string | undefined { |
||||
if (this._roomNameCache.has(importId)) { |
||||
return this._roomNameCache.get(importId) as string; |
||||
} |
||||
|
||||
const options = { |
||||
fields: { |
||||
_id: 1, |
||||
name: 1, |
||||
}, |
||||
}; |
||||
|
||||
const room = Rooms.findOneByImportId(importId, options); |
||||
if (room) { |
||||
if (!this._roomCache.has(importId)) { |
||||
this.addRoomToCache(importId, room._id); |
||||
} |
||||
return this.addRoomNameToCache(importId, room.name); |
||||
} |
||||
} |
||||
|
||||
findImportedUser(importId: string): IUserIdentification | null { |
||||
const options = { |
||||
fields: { |
||||
_id: 1, |
||||
username: 1, |
||||
}, |
||||
}; |
||||
|
||||
if (importId === 'rocket.cat') { |
||||
return { |
||||
_id: 'rocket.cat', |
||||
username: 'rocket.cat', |
||||
}; |
||||
} |
||||
|
||||
if (this._userCache.has(importId)) { |
||||
return this._userCache.get(importId) as IUserIdentification; |
||||
} |
||||
|
||||
const user = Users.findOneByImportId(importId, options); |
||||
if (user) { |
||||
return this.addUserToCache(importId, user._id, user.username); |
||||
} |
||||
|
||||
return null; |
||||
} |
||||
|
||||
findImportedUserId(_id: string): string | undefined { |
||||
const data = this.findImportedUser(_id); |
||||
return data?._id; |
||||
} |
||||
|
||||
findImportedUsername(_id: string): string | undefined { |
||||
const data = this.findImportedUser(_id); |
||||
return data?.username; |
||||
} |
||||
|
||||
findImportedUserDisplayName(importId: string): string | undefined { |
||||
const options = { |
||||
fields: { |
||||
_id: 1, |
||||
name: 1, |
||||
username: 1, |
||||
}, |
||||
}; |
||||
|
||||
if (this._userDisplayNameCache.has(importId)) { |
||||
return this._userDisplayNameCache.get(importId); |
||||
} |
||||
|
||||
const user = importId === 'rocket.cat' ? Users.findOneById('rocket.cat', options) : Users.findOneByImportId(importId, options); |
||||
if (user) { |
||||
if (!this._userCache.has(importId)) { |
||||
this.addUserToCache(importId, user._id, user.username); |
||||
} |
||||
|
||||
return this.addUserDisplayNameToCache(importId, user.name); |
||||
} |
||||
} |
||||
|
||||
updateRoomId(_id: string, roomData: IImportChannel): void { |
||||
const set = { |
||||
ts: roomData.ts, |
||||
topic: roomData.topic, |
||||
description: roomData.description, |
||||
}; |
||||
|
||||
const roomUpdate: {$set?: Record<string, any>; $addToSet?: Record<string, any>} = {}; |
||||
|
||||
if (Object.keys(set).length > 0) { |
||||
roomUpdate.$set = set; |
||||
} |
||||
|
||||
if (roomData.importIds.length) { |
||||
roomUpdate.$addToSet = { |
||||
importIds: { |
||||
$each: roomData.importIds, |
||||
}, |
||||
}; |
||||
} |
||||
|
||||
if (roomUpdate.$set || roomUpdate.$addToSet) { |
||||
Rooms.update({ _id: roomData._id }, roomUpdate); |
||||
} |
||||
} |
||||
|
||||
getRoomCreatorId(roomData: IImportChannel, startedByUserId: string): string { |
||||
if (roomData.u) { |
||||
const creatorId = this.findImportedUserId(roomData.u._id); |
||||
if (creatorId) { |
||||
return creatorId; |
||||
} |
||||
|
||||
if (roomData.t !== 'd') { |
||||
return startedByUserId; |
||||
} |
||||
|
||||
throw new Error('importer-channel-invalid-creator'); |
||||
} |
||||
|
||||
if (roomData.t === 'd') { |
||||
for (const member of roomData.users) { |
||||
const userId = this.findImportedUserId(member); |
||||
if (userId) { |
||||
return userId; |
||||
} |
||||
} |
||||
} |
||||
|
||||
throw new Error('importer-channel-invalid-creator'); |
||||
} |
||||
|
||||
insertRoom(roomData: IImportChannel, startedByUserId: string): void { |
||||
// Find the rocketchatId of the user who created this channel
|
||||
const creatorId = this.getRoomCreatorId(roomData, startedByUserId); |
||||
const members = this.convertImportedIdsToUsernames(roomData.users, roomData.t !== 'd' ? creatorId : undefined); |
||||
|
||||
if (roomData.t === 'd') { |
||||
if (members.length < roomData.users.length) { |
||||
console.warn('One or more imported users not found: ${ roomData.users }'); |
||||
throw new Error('importer-channel-missing-users'); |
||||
} |
||||
} |
||||
|
||||
// Create the channel
|
||||
try { |
||||
Meteor.runAsUser(creatorId, () => { |
||||
const roomInfo = roomData.t === 'd' |
||||
? Meteor.call('createDirectMessage', ...members) |
||||
: Meteor.call(roomData.t === 'p' ? 'createPrivateGroup' : 'createChannel', roomData.name, members); |
||||
|
||||
roomData._id = roomInfo.rid; |
||||
}); |
||||
} catch (e) { |
||||
console.warn(roomData.name, members); |
||||
console.error(e); |
||||
throw e; |
||||
} |
||||
|
||||
this.updateRoomId(roomData._id as 'string', roomData); |
||||
} |
||||
|
||||
convertImportedIdsToUsernames(importedIds: Array<string>, idToRemove: string | undefined = undefined): Array<string> { |
||||
return importedIds.map((user) => { |
||||
if (user === 'rocket.cat') { |
||||
return user; |
||||
} |
||||
|
||||
if (this._userCache.has(user)) { |
||||
const cache = this._userCache.get(user); |
||||
if (cache) { |
||||
return cache.username; |
||||
} |
||||
} |
||||
|
||||
const obj = Users.findOneByImportId(user, { fields: { _id: 1, username: 1 } }); |
||||
if (obj) { |
||||
this.addUserToCache(user, obj._id, obj.username); |
||||
|
||||
if (idToRemove && obj._id === idToRemove) { |
||||
return false; |
||||
} |
||||
|
||||
return obj.username; |
||||
} |
||||
|
||||
return false; |
||||
}).filter((user) => user); |
||||
} |
||||
|
||||
findExistingRoom(data: IImportChannel): IRoom { |
||||
if (data._id && data._id.toUpperCase() === 'GENERAL') { |
||||
const room = Rooms.findOneById('GENERAL', {}); |
||||
// Prevent the importer from trying to create a new general
|
||||
if (!room) { |
||||
throw new Error('importer-channel-general-not-found'); |
||||
} |
||||
|
||||
return room; |
||||
} |
||||
|
||||
if (data.t === 'd') { |
||||
const users = this.convertImportedIdsToUsernames(data.users); |
||||
if (users.length !== data.users.length) { |
||||
throw new Error('importer-channel-missing-users'); |
||||
} |
||||
|
||||
return Rooms.findDirectRoomContainingAllUsernames(users, {}); |
||||
} |
||||
|
||||
return Rooms.findOneByNonValidatedName(data.name, {}); |
||||
} |
||||
|
||||
convertChannels(startedByUserId: string, { beforeImportFn, afterImportFn }: IConversionCallbacks = {}): void { |
||||
const channels = ImportData.find({ dataType: 'channel' }); |
||||
channels.forEach(({ data: c, _id }: IImportChannelRecord) => { |
||||
try { |
||||
if (beforeImportFn && !beforeImportFn(c, 'channel')) { |
||||
this.skipRecord(_id); |
||||
return; |
||||
} |
||||
|
||||
if (!c.name && c.t !== 'd') { |
||||
throw new Error('importer-channel-missing-name'); |
||||
} |
||||
|
||||
c.importIds = c.importIds.filter((item) => item); |
||||
c.users = _.uniq(c.users); |
||||
|
||||
if (!c.importIds.length) { |
||||
throw new Error('importer-channel-missing-import-id'); |
||||
} |
||||
|
||||
const existingRoom = this.findExistingRoom(c); |
||||
|
||||
if (existingRoom) { |
||||
this.updateRoom(existingRoom, c, startedByUserId); |
||||
} else { |
||||
this.insertRoom(c, startedByUserId); |
||||
} |
||||
|
||||
if (c.archived && c._id) { |
||||
this.archiveRoomById(c._id); |
||||
} |
||||
|
||||
if (afterImportFn) { |
||||
afterImportFn(c, 'channel'); |
||||
} |
||||
} catch (e) { |
||||
this.saveError(_id, e); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
archiveRoomById(rid: string): void { |
||||
Rooms.archiveById(rid); |
||||
Subscriptions.archiveByRoomId(rid); |
||||
} |
||||
|
||||
convertData(startedByUserId: string, callbacks: IConversionCallbacks = {}): void { |
||||
this.convertUsers(callbacks); |
||||
this.convertChannels(startedByUserId, callbacks); |
||||
this.convertMessages(callbacks); |
||||
|
||||
Meteor.defer(() => { |
||||
this.clearSuccessfullyImportedData(); |
||||
}); |
||||
} |
||||
|
||||
clearImportData(): void { |
||||
const rawCollection = ImportData.model.rawCollection(); |
||||
const remove = Meteor.wrapAsync(rawCollection.remove, rawCollection); |
||||
|
||||
remove({}); |
||||
} |
||||
|
||||
clearSuccessfullyImportedData(): void { |
||||
ImportData.model.rawCollection().remove({ |
||||
errors: { |
||||
$exists: false, |
||||
}, |
||||
}); |
||||
} |
||||
} |
||||
@ -0,0 +1,14 @@ |
||||
export interface IImportChannel { |
||||
_id?: string; |
||||
u?: { |
||||
_id: string; |
||||
}; |
||||
name?: string; |
||||
users: Array<string>; |
||||
importIds: Array<string>; |
||||
t: string; |
||||
topic?: string; |
||||
description?: string; |
||||
ts?: Date; |
||||
archived?: boolean; |
||||
} |
||||
@ -0,0 +1,53 @@ |
||||
export type IImportedId = 'string'; |
||||
|
||||
export interface IImportMessageReaction { |
||||
name: string; |
||||
users: Array<IImportedId>; |
||||
} |
||||
|
||||
export interface IImportPendingFile { |
||||
downloadUrl: string; |
||||
id: string; |
||||
size: number; |
||||
name: string; |
||||
external: boolean; |
||||
source: string; |
||||
original: Record<string, any>; |
||||
} |
||||
|
||||
export interface IImportAttachment extends Record<string, any> { |
||||
text: string; |
||||
title: string; |
||||
fallback: string; |
||||
} |
||||
|
||||
export interface IImportMessage { |
||||
_id?: IImportedId; |
||||
|
||||
rid: IImportedId; |
||||
u: { |
||||
_id: IImportedId; |
||||
}; |
||||
|
||||
msg: string; |
||||
alias?: string; |
||||
ts: Date; |
||||
t?: string; |
||||
reactions?: Record<string, IImportMessageReaction>; |
||||
groupable?: boolean; |
||||
|
||||
tmid?: IImportedId; |
||||
tlm?: Date; |
||||
tcount?: number; |
||||
replies?: Array<IImportedId>; |
||||
editedAt?: Date; |
||||
editedBy?: IImportedId; |
||||
mentions?: Array<IImportedId>; |
||||
channels?: Array<string>; |
||||
attachments?: IImportAttachment; |
||||
bot?: boolean; |
||||
emoji?: string; |
||||
|
||||
url?: string; |
||||
_importFile?: IImportPendingFile; |
||||
} |
||||
@ -0,0 +1,28 @@ |
||||
import { IImportUser } from './IImportUser'; |
||||
import { IImportChannel } from './IImportChannel'; |
||||
import { IImportMessage } from './IImportMessage'; |
||||
|
||||
export interface IImportRecord { |
||||
data: IImportUser | IImportChannel | IImportMessage; |
||||
dataType: 'user' | 'channel' | 'message'; |
||||
_id: string; |
||||
options?: {}; |
||||
} |
||||
|
||||
export interface IImportUserRecord extends IImportRecord { |
||||
data: IImportUser; |
||||
dataType: 'user'; |
||||
} |
||||
|
||||
export interface IImportChannelRecord extends IImportRecord { |
||||
data: IImportChannel; |
||||
dataType: 'channel'; |
||||
} |
||||
|
||||
export interface IImportMessageRecord extends IImportRecord { |
||||
data: IImportMessage; |
||||
dataType: 'message'; |
||||
options: { |
||||
useQuickInsert?: boolean; |
||||
}; |
||||
} |
||||
@ -0,0 +1,17 @@ |
||||
export interface IImportUser { |
||||
// #ToDo: Remove this _id, as it isn't part of the imported data
|
||||
_id?: string; |
||||
|
||||
username?: string; |
||||
emails: Array<string>; |
||||
importIds: Array<string>; |
||||
name?: string; |
||||
utcOffset?: number; |
||||
active?: boolean; |
||||
avatarUrl?: string; |
||||
deleted?: boolean; |
||||
statusText?: string; |
||||
roles?: Array<string>; |
||||
type: 'user' | 'bot'; |
||||
bio?: string; |
||||
} |
||||
@ -0,0 +1,88 @@ |
||||
import { Base } from '../../../models/server'; |
||||
import { IImportUserRecord, IImportChannelRecord } from '../definitions/IImportRecord'; |
||||
|
||||
class ImportDataModel extends Base { |
||||
constructor() { |
||||
super('import_data'); |
||||
} |
||||
|
||||
getAllUsersForSelection(): Array<IImportUserRecord> { |
||||
return this.find({ |
||||
dataType: 'user', |
||||
}, { |
||||
fields: { |
||||
'data.importIds': 1, |
||||
'data.username': 1, |
||||
'data.emails': 1, |
||||
'data.deleted': 1, |
||||
'data.type': 1, |
||||
}, |
||||
}).fetch(); |
||||
} |
||||
|
||||
getAllChannelsForSelection(): Array<IImportChannelRecord> { |
||||
return this.find({ |
||||
dataType: 'channel', |
||||
'data.t': { |
||||
$ne: 'd', |
||||
}, |
||||
}, { |
||||
fields: { |
||||
'data.importIds': 1, |
||||
'data.name': 1, |
||||
'data.archived': 1, |
||||
'data.t': 1, |
||||
}, |
||||
}).fetch(); |
||||
} |
||||
|
||||
checkIfDirectMessagesExists(): boolean { |
||||
return this.find({ |
||||
dataType: 'channel', |
||||
'data.t': 'd', |
||||
}, { |
||||
fields: { |
||||
_id: 1, |
||||
}, |
||||
}).count() > 0; |
||||
} |
||||
|
||||
countMessages(): number { |
||||
return this.find({ |
||||
dataType: 'message', |
||||
}).count(); |
||||
} |
||||
|
||||
findChannelImportIdByNameOrImportId(channelIdentifier: string): string | undefined { |
||||
const channel = this.findOne({ |
||||
dataType: 'channel', |
||||
$or: [ |
||||
{ |
||||
'data.name': channelIdentifier, |
||||
}, |
||||
{ |
||||
'data.importIds': channelIdentifier, |
||||
}, |
||||
], |
||||
}, { |
||||
fields: { |
||||
'data.importIds': 1, |
||||
}, |
||||
}); |
||||
|
||||
return channel?.data?.importIds?.shift(); |
||||
} |
||||
|
||||
findDMForImportedUsers(...users: Array<string>): IImportChannelRecord | undefined { |
||||
const query = { |
||||
dataType: 'channel', |
||||
'data.users': { |
||||
$all: users, |
||||
}, |
||||
}; |
||||
|
||||
return this.findOne(query); |
||||
} |
||||
} |
||||
|
||||
export const ImportData = new ImportDataModel(); |
||||
Loading…
Reference in new issue