The communications platform that puts data protection first.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Rocket.Chat/server/startup/migrations/v099.js

218 lines
5.6 KiB

import { Meteor } from 'meteor/meteor';
import { Match } from 'meteor/check';
import { Mongo } from 'meteor/mongo';
import { RocketChatFile } from 'meteor/rocketchat:file';
import { SystemLogger } from 'meteor/rocketchat:logger';
Convert rocketchat-file-upload to main module structure (#13094) * Move rocketchat settings to specific package * WIP: Move models from rocketchat-lib to a specific package (server) * Move function from rocketchat:lib to rocketchat:utils to use it in rocketchat:models * Move client models from rocketchat:lib to rocketchat:models * Fix lint * Move rocketchat.info from lib to utils * Remove directly dependency between lib and migrations * Move statistics Model to rocketchat:models * Create rocketchat:metrics to be able to depacking rocketchat callbacks * Move callbacks to specific package * Remove unused dependency * Move rocketchat-notifications to a specific package * Move rocketchat-promises to a specific package * remove directly dependency from metrics and models * Move CachedCollection from lib to models * Move ui models/collections from ui to models * Move authorization client/ui models to rocketchat:models to be able to remove lib dependency * Creation of rocketchat:ui-utils to help decouple rocketchat:lib and rocketchat:authz * Move some common functions to rocketchat:utils * Change imports to dynamic imports to avoid directly dependency between some packages * Move authz models to rocketchat:models * Remove directly dependency between rocketchat:authz and rocketchat:lib * Move some functions from rocketchat:lib to rocketchat:utils * Add functions to settings package * Convert rocketchat:file-upload to main module structure * Import FileUpload where it is being used * Remove FileUpload and fileUploadHandler from globals eslintrc * Merge branch 'develop' into globals/move-rocketchat-callbacks * Fix missed export * Fix canBeDeleted and canBeCreated function, remove async
7 years ago
import { FileUpload } from 'meteor/rocketchat:file-upload';
import fs from 'fs';
import path from 'path';
function log(...args) {
console.log('[AVATAR]', ...args);
}
function logError(...args) {
console.error('[AVATAR]', ...args);
}
function insertAvatar({ details, avatarsFileStore, stream, callback = () => {} }) {
return new Promise((resolve) => {
Meteor.defer(() => {
Meteor.runAsUser('rocket.cat', () => {
avatarsFileStore.insert(details, stream, (err) => {
if (err) {
logError({ err });
resolve();
} else {
Meteor.setTimeout(() => {
callback();
}, 200);
}
resolve();
});
});
});
});
}
function batch(arr, limit, fn) {
if (!arr.length) {
return Promise.resolve();
}
return Promise.all(arr.splice(0, limit).map((item) => fn(item))).then(() => batch(arr, limit, fn));
}
RocketChat.Migrations.add({
version: 99,
up() {
log('Migrating avatars. This might take a while.');
const query = {
$or: [{
's3.path': {
$exists: true,
},
}, {
'googleCloudStorage.path': {
$exists: true,
},
}],
};
RocketChat.models.Uploads.find(query).forEach((record) => {
if (record.s3) {
RocketChat.models.Uploads.model.direct.update({ _id: record._id }, {
$set: {
store: 'AmazonS3:Uploads',
AmazonS3: {
path: record.s3.path + record._id,
},
},
$unset: {
s3: 1,
},
}, { multi: true });
} else {
RocketChat.models.Uploads.model.direct.update({ _id: record._id }, {
$set: {
store: 'GoogleCloudStorage:Uploads',
GoogleStorage: {
path: record.googleCloudStorage.path + record._id,
},
},
$unset: {
googleCloudStorage: 1,
},
}, { multi: true });
}
});
RocketChat.models.Uploads.model.direct.update({
store: 'fileSystem',
}, {
$set: {
store: 'FileSystem:Uploads',
},
}, {
multi: true,
});
RocketChat.models.Uploads.model.direct.update({
store: 'rocketchat_uploads',
}, {
$set: {
store: 'GridFS:Uploads',
},
}, {
multi: true,
});
const avatarOrigins = [
'upload',
'gravatar',
'facebook',
'twitter',
'github',
'google',
'url',
'gitlab',
'linkedin',
];
const avatarsPathRecord = RocketChat.models.Settings.findOne({ _id: 'Accounts_AvatarStorePath' });
const avatarStoreTypeRecord = RocketChat.models.Settings.findOne({ _id: 'Accounts_AvatarStoreType' });
const avatarsPath = avatarsPathRecord ? avatarsPathRecord.value : process.env.AVATARS_PATH;
let avatarStoreType = avatarStoreTypeRecord && avatarStoreTypeRecord.value;
const oldAvatarGridFS = new RocketChatFile.GridFS({
name: 'avatars',
});
if (avatarStoreType == null) {
const count = oldAvatarGridFS.countSync();
if (Match.test(count, Number) && count > 0) {
avatarStoreType = 'GridFS';
} else if (Match.test(avatarsPath, String) && avatarsPath.length > 0) {
avatarStoreType = 'FileSystem';
} else {
SystemLogger.error_box('Can\'t define the avatar\'s storage type.\nIf you have avatars missing and they was stored in your file system\nrun the process including the following environment variables: \n AVATARS_PATH=\'YOUR AVATAR\'S DIRECTORY\'\n MIGRATION_VERSION=99,rerun', 'WARNING');
return;
}
}
Meteor.startup(function() {
Meteor.setTimeout(function() {
const avatarsFileStore = FileUpload.getStore('Avatars');
const users = RocketChat.models.Users.find({ avatarOrigin: { $in: avatarOrigins } }, { avatarOrigin: 1, username: 1 }).fetch();
const usersTotal = users.length;
log('Total users to migrate avatars ->', usersTotal);
let current = 0;
batch(users, 300, (user) => {
const id = `${ user.username }.jpg`;
const gridFSAvatar = oldAvatarGridFS.getFileWithReadStream(id);
log('Migrating', ++current, 'of', usersTotal);
if (gridFSAvatar) {
const details = {
userId: user._id,
type: gridFSAvatar.contentType,
size: gridFSAvatar.length,
};
return insertAvatar({
details,
avatarsFileStore,
stream: gridFSAvatar.readStream,
callback() {
oldAvatarGridFS.deleteFile(id);
},
});
}
if (avatarStoreType === 'FileSystem' && avatarsPath && avatarsPath.trim()) {
try {
const filePath = path.join(avatarsPath, id);
const stat = fs.statSync(filePath);
if (stat && stat.isFile()) {
const details = {
userId: user._id,
type: 'image/jpeg',
size: stat.size,
};
return insertAvatar({
details,
avatarsFileStore,
stream: fs.createReadStream(filePath),
callback() {
fs.unlinkSync(filePath);
},
});
}
} catch (e) {
logError('Error migrating old avatar', e);
return Promise.resolve();
}
}
}).then(() => {
const avatarsFiles = new Mongo.Collection('avatars.files');
const avatarsChunks = new Mongo.Collection('avatars.chunks');
try {
avatarsFiles.rawCollection().drop();
avatarsChunks.rawCollection().drop();
} catch (error) {
console.warn('Migration Error: avatars.files and avatars.chunks collections may not exist!');
}
RocketChat.models.Settings.remove({ _id: 'Accounts_AvatarStoreType' });
RocketChat.models.Settings.remove({ _id: 'Accounts_AvatarStorePath' });
}).catch((error) => console.error(error));
}, 1000);
});
},
});