mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-23 10:53:34 +03:00
💥 Reduced default exports content for DB APIs (#12818)
refs https://github.com/TryGhost/Team/issues/555 - Export files included a lot of data which was not used in the importer, for example: members, labels, migrations and many more. This lead to a lot of clutter in the import files and made it hard to reason about their purpose. - The main purpose of exports - is to export importable resources. These are posts, tags, and users. The rest of data like members or migrations either have their own importer (like CSV importer for members) or does not and should not have any ways to be imported. - These changes are in now way complete. It's a first step towards resource-based exports which could be properly versioned in the future on API level and not be a mirror of the DB structure. - This is sort of a breaking change. But we are doing it because: (1) its an internal API that should not be used by external clients, (2) there was no public contract to have this API stable at any point, (3) we really need to get back the control over export files structure and size - In case an external client was dependent on some structure of the exported json file they can still pass in ALL of previously exported data by passing table names in `include` query parameter.
This commit is contained in:
parent
2d4a06023d
commit
ffd866cedc
@ -11,20 +11,6 @@ const models = require('../../models');
|
||||
|
||||
// NOTE: these tables can be optionally included to have full db-like export
|
||||
const BACKUP_TABLES = [
|
||||
'sessions',
|
||||
'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
|
||||
const TABLES_ALLOWLIST = [
|
||||
'actions',
|
||||
'api_keys',
|
||||
'brute',
|
||||
@ -41,6 +27,25 @@ const TABLES_ALLOWLIST = [
|
||||
'permissions',
|
||||
'permissions_roles',
|
||||
'permissions_users',
|
||||
'webhooks',
|
||||
'snippets',
|
||||
'tokens',
|
||||
'sessions',
|
||||
'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 = [
|
||||
'posts',
|
||||
'posts_authors',
|
||||
'posts_meta',
|
||||
@ -48,11 +53,8 @@ const TABLES_ALLOWLIST = [
|
||||
'roles',
|
||||
'roles_users',
|
||||
'settings',
|
||||
'snippets',
|
||||
'tags',
|
||||
'tokens',
|
||||
'users',
|
||||
'webhooks'
|
||||
'users'
|
||||
];
|
||||
|
||||
// NOTE: these are settings keys which should never end up in the export file
|
||||
|
@ -58,7 +58,9 @@ describe('DB API', function () {
|
||||
const jsonResponse = res.body;
|
||||
should.exist(jsonResponse.db);
|
||||
jsonResponse.db.should.have.length(1);
|
||||
Object.keys(jsonResponse.db[0].data).length.should.eql(29);
|
||||
|
||||
// NOTE: 9 default tables + 1 from include parameters
|
||||
Object.keys(jsonResponse.db[0].data).length.should.eql(10);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -58,7 +58,9 @@ describe('DB API', function () {
|
||||
const jsonResponse = res.body;
|
||||
should.exist(jsonResponse.db);
|
||||
jsonResponse.db.should.have.length(1);
|
||||
Object.keys(jsonResponse.db[0].data).length.should.eql(29);
|
||||
|
||||
// NOTE: 9 default tables + 1 from include parameters
|
||||
Object.keys(jsonResponse.db[0].data).length.should.eql(10);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -58,7 +58,9 @@ describe('DB API', function () {
|
||||
const jsonResponse = res.body;
|
||||
should.exist(jsonResponse.db);
|
||||
jsonResponse.db.should.have.length(1);
|
||||
Object.keys(jsonResponse.db[0].data).length.should.eql(29);
|
||||
|
||||
// NOTE: 9 default tables + 1 from include parameters
|
||||
Object.keys(jsonResponse.db[0].data).length.should.eql(10);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -39,10 +39,9 @@ describe('Exporter', function () {
|
||||
});
|
||||
|
||||
it('should try to export all the correct tables (without excluded)', function (done) {
|
||||
// Execute
|
||||
exporter.doExport().then(function (exportData) {
|
||||
// No tables, less the number of excluded tables
|
||||
const expectedCallCount = schemaTables.length - exporter.BACKUP_TABLES.length;
|
||||
// NOTE: 9 default tables
|
||||
const expectedCallCount = 9;
|
||||
|
||||
should.exist(exportData);
|
||||
|
||||
@ -60,26 +59,9 @@ describe('Exporter', function () {
|
||||
knexMock.getCall(3).args[0].should.eql('posts_authors');
|
||||
knexMock.getCall(4).args[0].should.eql('roles');
|
||||
knexMock.getCall(5).args[0].should.eql('roles_users');
|
||||
knexMock.getCall(6).args[0].should.eql('permissions');
|
||||
knexMock.getCall(7).args[0].should.eql('permissions_users');
|
||||
knexMock.getCall(8).args[0].should.eql('permissions_roles');
|
||||
knexMock.getCall(9).args[0].should.eql('settings');
|
||||
knexMock.getCall(10).args[0].should.eql('tags');
|
||||
knexMock.getCall(11).args[0].should.eql('posts_tags');
|
||||
knexMock.getCall(12).args[0].should.eql('invites');
|
||||
knexMock.getCall(13).args[0].should.eql('brute');
|
||||
knexMock.getCall(14).args[0].should.eql('integrations');
|
||||
knexMock.getCall(15).args[0].should.eql('webhooks');
|
||||
knexMock.getCall(16).args[0].should.eql('api_keys');
|
||||
knexMock.getCall(17).args[0].should.eql('members');
|
||||
knexMock.getCall(18).args[0].should.eql('labels');
|
||||
knexMock.getCall(19).args[0].should.eql('members_labels');
|
||||
knexMock.getCall(20).args[0].should.eql('members_stripe_customers');
|
||||
knexMock.getCall(21).args[0].should.eql('members_stripe_customers_subscriptions');
|
||||
knexMock.getCall(22).args[0].should.eql('actions');
|
||||
knexMock.getCall(23).args[0].should.eql('emails');
|
||||
knexMock.getCall(24).args[0].should.eql('tokens');
|
||||
knexMock.getCall(25).args[0].should.eql('snippets');
|
||||
knexMock.getCall(6).args[0].should.eql('settings');
|
||||
knexMock.getCall(7).args[0].should.eql('tags');
|
||||
knexMock.getCall(8).args[0].should.eql('posts_tags');
|
||||
|
||||
done();
|
||||
}).catch(done);
|
||||
@ -87,8 +69,10 @@ describe('Exporter', function () {
|
||||
|
||||
it('should try to export all the correct tables with extra tables', function (done) {
|
||||
const include = ['mobiledoc_revisions', 'email_recipients'];
|
||||
|
||||
exporter.doExport({include}).then(function (exportData) {
|
||||
const expectedCallCount = schemaTables.length + include.length - exporter.BACKUP_TABLES.length;
|
||||
// NOTE: 9 default tables + 2 includes
|
||||
const expectedCallCount = 11;
|
||||
|
||||
should.exist(exportData);
|
||||
|
||||
@ -107,28 +91,11 @@ describe('Exporter', function () {
|
||||
knexMock.getCall(3).args[0].should.eql('posts_authors');
|
||||
knexMock.getCall(4).args[0].should.eql('roles');
|
||||
knexMock.getCall(5).args[0].should.eql('roles_users');
|
||||
knexMock.getCall(6).args[0].should.eql('permissions');
|
||||
knexMock.getCall(7).args[0].should.eql('permissions_users');
|
||||
knexMock.getCall(8).args[0].should.eql('permissions_roles');
|
||||
knexMock.getCall(9).args[0].should.eql('settings');
|
||||
knexMock.getCall(10).args[0].should.eql('tags');
|
||||
knexMock.getCall(11).args[0].should.eql('posts_tags');
|
||||
knexMock.getCall(12).args[0].should.eql('invites');
|
||||
knexMock.getCall(13).args[0].should.eql('brute');
|
||||
knexMock.getCall(14).args[0].should.eql('integrations');
|
||||
knexMock.getCall(15).args[0].should.eql('webhooks');
|
||||
knexMock.getCall(16).args[0].should.eql('api_keys');
|
||||
knexMock.getCall(17).args[0].should.eql('mobiledoc_revisions');
|
||||
knexMock.getCall(18).args[0].should.eql('members');
|
||||
knexMock.getCall(19).args[0].should.eql('labels');
|
||||
knexMock.getCall(20).args[0].should.eql('members_labels');
|
||||
knexMock.getCall(21).args[0].should.eql('members_stripe_customers');
|
||||
knexMock.getCall(22).args[0].should.eql('members_stripe_customers_subscriptions');
|
||||
knexMock.getCall(23).args[0].should.eql('actions');
|
||||
knexMock.getCall(24).args[0].should.eql('emails');
|
||||
knexMock.getCall(25).args[0].should.eql('email_recipients');
|
||||
knexMock.getCall(26).args[0].should.eql('tokens');
|
||||
knexMock.getCall(27).args[0].should.eql('snippets');
|
||||
knexMock.getCall(6).args[0].should.eql('settings');
|
||||
knexMock.getCall(7).args[0].should.eql('tags');
|
||||
knexMock.getCall(8).args[0].should.eql('posts_tags');
|
||||
knexMock.getCall(9).args[0].should.eql('mobiledoc_revisions');
|
||||
knexMock.getCall(10).args[0].should.eql('email_recipients');
|
||||
|
||||
done();
|
||||
}).catch(done);
|
||||
|
@ -91,8 +91,7 @@ const exportedBodyLegacy = () => {
|
||||
});
|
||||
};
|
||||
|
||||
// NOTE: clone the fixture before changing in and alias to v5, v6 or whatever the newest version is
|
||||
const exportedBodyLatest = () => {
|
||||
const exportedBodyV4 = () => {
|
||||
return _.clone({
|
||||
db: [{
|
||||
meta: {
|
||||
@ -133,8 +132,31 @@ const exportedBodyLatest = () => {
|
||||
});
|
||||
};
|
||||
|
||||
// NOTE: clone the fixture before changing in and alias to v5, v6 or whatever the newest version is
|
||||
const exportedBodyLatest = () => {
|
||||
return _.clone({
|
||||
db: [{
|
||||
meta: {
|
||||
exported_on: 1615520875631,
|
||||
version: '4.1.2'
|
||||
},
|
||||
data: {
|
||||
posts: [],
|
||||
posts_authors: [],
|
||||
posts_meta: [],
|
||||
posts_tags: [],
|
||||
roles: [],
|
||||
roles_users: [],
|
||||
settings: [],
|
||||
tags: [],
|
||||
users: []
|
||||
}
|
||||
}]
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.exportedBodyLatest = exportedBodyLatest;
|
||||
module.exports.exportedBodyV4 = exportedBodyLatest;
|
||||
module.exports.exportedBodyV4 = exportedBodyV4;
|
||||
module.exports.exportedBodyV2 = exportedBodyV2;
|
||||
module.exports.exportedBodyV1 = exportedBodyV1;
|
||||
module.exports.exportedBodyLegacy = exportedBodyLegacy;
|
||||
|
Loading…
Reference in New Issue
Block a user