2020-04-29 16:44:27 +01:00
|
|
|
const _ = require('lodash');
|
|
|
|
const Promise = require('bluebird');
|
|
|
|
const db = require('../../data/db');
|
|
|
|
const commands = require('../schema').commands;
|
|
|
|
const ghostVersion = require('../../lib/ghost-version');
|
2020-05-28 13:30:23 -05:00
|
|
|
const {i18n} = require('../../lib/common');
|
|
|
|
const logging = require('../../../shared/logging');
|
2020-05-22 13:22:20 -05:00
|
|
|
const errors = require('@tryghost/errors');
|
2020-08-11 14:01:16 +01:00
|
|
|
const security = require('@tryghost/security');
|
2020-04-29 16:44:27 +01:00
|
|
|
const models = require('../../models');
|
2021-03-25 16:46:56 +13:00
|
|
|
|
|
|
|
// NOTE: these tables can be optionally included to have full db-like export
|
|
|
|
const BACKUP_TABLES = [
|
|
|
|
'actions',
|
|
|
|
'api_keys',
|
|
|
|
'brute',
|
|
|
|
'emails',
|
|
|
|
'integrations',
|
|
|
|
'invites',
|
|
|
|
'labels',
|
|
|
|
'members',
|
|
|
|
'members_labels',
|
2021-04-08 14:15:30 +01:00
|
|
|
'members_products',
|
2021-03-25 16:46:56 +13:00
|
|
|
'members_stripe_customers',
|
|
|
|
'members_stripe_customers_subscriptions',
|
|
|
|
'migrations',
|
|
|
|
'migrations_lock',
|
|
|
|
'permissions',
|
|
|
|
'permissions_roles',
|
|
|
|
'permissions_users',
|
2021-04-08 14:15:30 +01:00
|
|
|
'products',
|
2021-03-25 19:27:49 +13:00
|
|
|
'webhooks',
|
|
|
|
'snippets',
|
|
|
|
'tokens',
|
|
|
|
'sessions',
|
2021-04-08 20:41:00 +05:30
|
|
|
'stripe_products',
|
|
|
|
'stripe_prices',
|
2021-03-25 19:27:49 +13:00
|
|
|
'mobiledoc_revisions',
|
|
|
|
'email_batches',
|
|
|
|
'email_recipients',
|
|
|
|
'members_payment_events',
|
|
|
|
'members_login_events',
|
|
|
|
'members_email_change_events',
|
|
|
|
'members_status_events',
|
|
|
|
'members_paid_subscription_events',
|
|
|
|
'members_subscribe_events'
|
|
|
|
];
|
|
|
|
|
|
|
|
// NOTE: exposing only tables which are going to be included in a "default" export file
|
|
|
|
// they should match with the data that is supported by the importer.
|
|
|
|
// In the future it's best to move to resource-based exports instead of database-based ones
|
|
|
|
const TABLES_ALLOWLIST = [
|
2021-03-25 16:46:56 +13:00
|
|
|
'posts',
|
|
|
|
'posts_authors',
|
|
|
|
'posts_meta',
|
|
|
|
'posts_tags',
|
|
|
|
'roles',
|
|
|
|
'roles_users',
|
|
|
|
'settings',
|
|
|
|
'tags',
|
2021-03-25 19:27:49 +13:00
|
|
|
'users'
|
2021-03-25 16:46:56 +13:00
|
|
|
];
|
|
|
|
|
|
|
|
// NOTE: these are settings keys which should never end up in the export file
|
|
|
|
const SETTING_KEYS_BLOCKLIST = [
|
2021-01-27 12:09:25 +05:30
|
|
|
'stripe_connect_publishable_key',
|
|
|
|
'stripe_connect_secret_key',
|
|
|
|
'stripe_connect_account_id',
|
|
|
|
'stripe_secret_key',
|
|
|
|
'stripe_publishable_key',
|
|
|
|
'members_stripe_webhook_id',
|
2021-04-16 20:02:21 +12:00
|
|
|
'members_stripe_webhook_secret',
|
|
|
|
'oauth_client_id',
|
|
|
|
'oauth_client_secret'
|
2021-01-27 12:09:25 +05:30
|
|
|
];
|
2020-04-29 16:44:27 +01:00
|
|
|
|
|
|
|
const modelOptions = {context: {internal: true}};
|
|
|
|
|
2021-03-25 14:55:11 +13:00
|
|
|
const exportFileName = async function exportFileName(options) {
|
2020-04-29 16:44:27 +01:00
|
|
|
const datetime = require('moment')().format('YYYY-MM-DD-HH-mm-ss');
|
|
|
|
let title = '';
|
2014-07-19 13:20:16 +01:00
|
|
|
|
2018-01-11 15:03:21 +00:00
|
|
|
options = options || {};
|
|
|
|
|
|
|
|
// custom filename
|
|
|
|
if (options.filename) {
|
2021-03-25 14:55:11 +13:00
|
|
|
return options.filename + '.json';
|
2018-01-11 15:03:21 +00:00
|
|
|
}
|
|
|
|
|
2021-03-25 14:55:11 +13:00
|
|
|
try {
|
|
|
|
const settingsTitle = await models.Settings.findOne({key: 'title'}, _.merge({}, modelOptions, _.pick(options, 'transacting')));
|
|
|
|
|
|
|
|
if (settingsTitle) {
|
|
|
|
title = security.string.safe(settingsTitle.get('value')) + '.';
|
2014-07-19 13:20:16 +01:00
|
|
|
}
|
2017-03-08 11:26:57 +01:00
|
|
|
|
2014-07-19 13:20:16 +01:00
|
|
|
return title + 'ghost.' + datetime + '.json';
|
2021-03-25 14:55:11 +13:00
|
|
|
} catch (err) {
|
2020-05-22 13:22:20 -05:00
|
|
|
logging.error(new errors.GhostError({err: err}));
|
2014-07-19 13:20:16 +01:00
|
|
|
return 'ghost.' + datetime + '.json';
|
2021-03-25 14:55:11 +13:00
|
|
|
}
|
2014-07-19 13:20:16 +01:00
|
|
|
};
|
2013-06-15 20:52:03 +00:00
|
|
|
|
2020-10-20 11:56:46 +13:00
|
|
|
const exportTable = function exportTable(tableName, options) {
|
2021-03-25 16:46:56 +13:00
|
|
|
if (TABLES_ALLOWLIST.includes(tableName) ||
|
2018-07-30 17:21:52 +02:00
|
|
|
(options.include && _.isArray(options.include) && options.include.indexOf(tableName) !== -1)) {
|
2018-08-06 17:18:59 +02:00
|
|
|
const query = (options.transacting || db.knex)(tableName);
|
|
|
|
|
|
|
|
return query.select();
|
2016-03-12 18:54:06 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-01-27 12:09:25 +05:30
|
|
|
const getSettingsTableData = function getSettingsTableData(settingsData) {
|
|
|
|
return settingsData && settingsData.filter((setting) => {
|
2021-03-25 16:46:56 +13:00
|
|
|
return !SETTING_KEYS_BLOCKLIST.includes(setting.key);
|
2021-01-27 12:09:25 +05:30
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2021-03-25 14:55:11 +13:00
|
|
|
const doExport = async function doExport(options) {
|
2018-07-30 17:21:52 +02:00
|
|
|
options = options || {include: []};
|
2017-08-01 17:27:13 +04:00
|
|
|
|
2021-03-25 14:55:11 +13:00
|
|
|
try {
|
2021-03-25 15:01:54 +13:00
|
|
|
const tables = await commands.getTables(options.transacting);
|
2016-03-12 18:54:06 +00:00
|
|
|
|
2021-03-25 14:55:11 +13:00
|
|
|
const tableData = await Promise.mapSeries(tables, function (tableName) {
|
2017-08-01 17:27:13 +04:00
|
|
|
return exportTable(tableName, options);
|
|
|
|
});
|
2021-03-25 14:55:11 +13:00
|
|
|
|
2020-04-29 16:44:27 +01:00
|
|
|
const exportData = {
|
2016-03-12 18:54:06 +00:00
|
|
|
meta: {
|
|
|
|
exported_on: new Date().getTime(),
|
2021-03-25 15:01:54 +13:00
|
|
|
version: ghostVersion.full
|
2016-03-12 18:54:06 +00:00
|
|
|
},
|
|
|
|
data: {
|
|
|
|
// Filled below
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-03-25 15:01:54 +13:00
|
|
|
tables.forEach((name, i) => {
|
2021-01-27 12:09:25 +05:30
|
|
|
if (name === 'settings') {
|
|
|
|
exportData.data[name] = getSettingsTableData(tableData[i]);
|
|
|
|
} else {
|
|
|
|
exportData.data[name] = tableData[i];
|
|
|
|
}
|
2013-09-15 17:04:42 +01:00
|
|
|
});
|
2016-03-12 18:54:06 +00:00
|
|
|
|
|
|
|
return exportData;
|
2021-03-25 14:55:11 +13:00
|
|
|
} catch (err) {
|
|
|
|
throw new errors.DataExportError({
|
2016-10-06 14:27:35 +02:00
|
|
|
err: err,
|
2020-05-22 13:22:20 -05:00
|
|
|
context: i18n.t('errors.data.export.errorExportingData')
|
2021-03-25 14:55:11 +13:00
|
|
|
});
|
|
|
|
}
|
2013-06-25 12:43:15 +01:00
|
|
|
};
|
2013-09-15 17:04:42 +01:00
|
|
|
|
2016-03-12 18:54:06 +00:00
|
|
|
module.exports = {
|
|
|
|
doExport: doExport,
|
2018-07-30 17:21:52 +02:00
|
|
|
fileName: exportFileName,
|
2021-03-25 16:46:56 +13:00
|
|
|
BACKUP_TABLES: BACKUP_TABLES
|
2016-03-12 18:54:06 +00:00
|
|
|
};
|