Skip to content

Commit fa46ccf

Browse files
committed
feat(backup): Added members export to backup
refs: TryGhost/Toolbox#334 refs: #468 - this ensures that we export a members.csv file if the endpoint exists
1 parent fbc7fdd commit fa46ccf

File tree

5 files changed

+53
-18
lines changed

5 files changed

+53
-18
lines changed

lib/tasks/backup.js

+5-3
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ const {ProcessError} = require('../errors');
88
const {exportTask} = require('./import');
99

1010
module.exports = async function (ui, instance) {
11-
// First we need to export the content into a JSON file
11+
// First we need to export the content into a JSON file & members into a CSV file
1212
const contentExportPath = 'data/backup.json';
13-
await exportTask(ui, instance, path.join(instance.dir, 'content/', contentExportPath));
13+
const membersExportPath = 'data/members.csv';
14+
15+
await exportTask(ui, instance, path.join(instance.dir, 'content/', contentExportPath), path.join(instance.dir, 'content/', membersExportPath));
1416

1517
// Next we need to copy `redirects.*` files from `data/` to `settings/` because
1618
// we're not going to backup `data/
@@ -36,7 +38,7 @@ module.exports = async function (ui, instance) {
3638
const zipPath = path.join(process.cwd(), `backup-from-v${instance.version}-on-${datetime}.zip`);
3739

3840
try {
39-
execa.shellSync(`zip -r ${zipPath} ${contentExportPath} files/ images/ media/ settings/ themes/`, {
41+
execa.shellSync(`zip -r ${zipPath} ${contentExportPath} ${membersExportPath} files/ images/ media/ settings/ themes/`, {
4042
cwd: path.join(instance.dir, 'content/')
4143
});
4244
} catch (err) {

lib/tasks/import/api.js

+16-2
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ async function runImport(version, url, auth, exportFile) {
126126
await got.post('/db/', {...authOpts, body});
127127
}
128128

129-
async function downloadExport(version, url, auth, outputFile) {
129+
async function downloadContentExport(version, url, auth, outputFile) {
130130
const authOpts = await getAuthOpts(version, url, auth);
131131

132132
await new Promise((resolve, reject) => {
@@ -137,10 +137,24 @@ async function downloadExport(version, url, auth, outputFile) {
137137
});
138138
}
139139

140+
async function downloadMembersExport(version, url, auth, outputFile) {
141+
const authOpts = await getAuthOpts(version, url, auth);
142+
143+
await new Promise((resolve, reject) => {
144+
const ws = fs.createWriteStream(outputFile);
145+
got
146+
.stream('/members/upload/', {...authOpts})
147+
.on('finish', () => resolve())
148+
.on('error', reject)
149+
.pipe(ws);
150+
});
151+
}
152+
140153
module.exports = {
141154
getBaseUrl,
142155
isSetup,
143156
setup,
144157
runImport,
145-
downloadExport
158+
downloadContentExport,
159+
downloadMembersExport
146160
};

lib/tasks/import/tasks.js

+22-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ const validator = require('validator');
22

33
const {SystemError} = require('../../errors');
44
const parseExport = require('./parse-export');
5-
const {isSetup, setup, runImport, downloadExport} = require('./api');
5+
const {isSetup, setup, runImport, downloadContentExport, downloadMembersExport} = require('./api');
66

77
const authPrompts = [{
88
type: 'string',
@@ -40,7 +40,18 @@ async function importTask(ui, instance, exportFile) {
4040
}], false);
4141
}
4242

43-
async function exportTask(ui, instance, exportFile) {
43+
async function tryMembersDownload(version, url, authData, membersFile) {
44+
try {
45+
await downloadMembersExport(version, url, authData, membersFile);
46+
} catch (error) {
47+
// Members endpoint may not exist, we can ignore this
48+
if (!error.statusCode === 404) {
49+
throw error;
50+
}
51+
}
52+
}
53+
54+
async function exportTask(ui, instance, contentFile, membersFile) {
4455
const url = instance.config.get('url');
4556

4657
const blogIsSetup = await isSetup(instance.version, url);
@@ -49,7 +60,15 @@ async function exportTask(ui, instance, exportFile) {
4960
}
5061

5162
const authData = await ui.prompt(authPrompts);
52-
await downloadExport(instance.version, url, authData, exportFile);
63+
64+
return ui.listr([{
65+
title: 'Exporting content',
66+
task: () => downloadContentExport(instance.version, url, authData, contentFile)
67+
}, {
68+
title: 'Exporting members',
69+
task: () => tryMembersDownload(instance.version, url, authData, membersFile),
70+
enabled: () => !!membersFile
71+
}], false);
5372
}
5473

5574
module.exports = {

test/unit/tasks/import/api-spec.js

+7-7
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const tmp = require('tmp');
55
const fs = require('fs-extra');
66

77
const {SystemError} = require('../../../../lib/errors');
8-
const {getBaseUrl, isSetup, setup, runImport, downloadExport} = require('../../../../lib/tasks/import/api');
8+
const {getBaseUrl, isSetup, setup, runImport, downloadContentExport} = require('../../../../lib/tasks/import/api');
99

1010
const testUrl = 'http://localhost:2368';
1111

@@ -360,7 +360,7 @@ describe('Unit > Tasks > Import > setup', function () {
360360
});
361361
});
362362

363-
describe('downloadExport', function () {
363+
describe('downloadContentExport', function () {
364364
it('1.x', async function () {
365365
const clientId = 'client-id';
366366
const clientSecret = 'client-secret';
@@ -409,7 +409,7 @@ describe('Unit > Tasks > Import > setup', function () {
409409
const tmpDir = tmp.dirSync();
410410
const outputFile = path.join(tmpDir.name, '1.x.json');
411411

412-
await downloadExport('1.0.0', testUrl, {
412+
await downloadContentExport('1.0.0', testUrl, {
413413
username: '[email protected]',
414414
password: 'password'
415415
}, outputFile);
@@ -454,7 +454,7 @@ describe('Unit > Tasks > Import > setup', function () {
454454
const tmpDir = tmp.dirSync();
455455
const outputFile = path.join(tmpDir.name, '2.x.json');
456456

457-
await downloadExport('2.0.0', 'http://localhost:2368', {
457+
await downloadContentExport('2.0.0', 'http://localhost:2368', {
458458
username: '[email protected]',
459459
password: 'password'
460460
}, outputFile);
@@ -498,7 +498,7 @@ describe('Unit > Tasks > Import > setup', function () {
498498
const tmpDir = tmp.dirSync();
499499
const outputFile = path.join(tmpDir.name, '3.x.json');
500500

501-
await downloadExport('3.0.0', 'http://localhost:2368', {
501+
await downloadContentExport('3.0.0', 'http://localhost:2368', {
502502
username: '[email protected]',
503503
password: 'password'
504504
}, outputFile);
@@ -542,7 +542,7 @@ describe('Unit > Tasks > Import > setup', function () {
542542
const tmpDir = tmp.dirSync();
543543
const outputFile = path.join(tmpDir.name, '4.x.json');
544544

545-
await downloadExport('4.0.0', 'http://localhost:2368', {
545+
await downloadContentExport('4.0.0', 'http://localhost:2368', {
546546
username: '[email protected]',
547547
password: 'password'
548548
}, outputFile);
@@ -587,7 +587,7 @@ describe('Unit > Tasks > Import > setup', function () {
587587
const tmpDir = tmp.dirSync();
588588
const outputFile = path.join(tmpDir.name, '5.x.json');
589589

590-
await downloadExport('5.0.0', 'http://localhost:2368', {
590+
await downloadContentExport('5.0.0', 'http://localhost:2368', {
591591
username: '[email protected]',
592592
password: 'password'
593593
}, outputFile);

test/unit/tasks/import/tasks-spec.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -122,20 +122,20 @@ describe('Unit: Tasks > Import > Tasks', function () {
122122

123123
it('exports content', async function () {
124124
const isSetup = sinon.stub().resolves(true);
125-
const downloadExport = sinon.stub().resolves();
125+
const downloadContentExport = sinon.stub().resolves();
126126
const config = createConfigStub();
127127
const prompt = sinon.stub().resolves({username: 'username', password: 'password'});
128128

129129
config.get.withArgs('url').returns('http://localhost:2368');
130130

131131
const {exportTask} = proxyquire(modulePath, {
132-
'./api': {isSetup, downloadExport}
132+
'./api': {isSetup, downloadContentExport}
133133
});
134134

135135
await exportTask({prompt}, {config, version: '1.0.0'}, 'test-export.json');
136136
expect(isSetup.calledOnceWithExactly('1.0.0', 'http://localhost:2368')).to.be.true;
137137
expect(prompt.calledOnce).to.be.true;
138-
expect(downloadExport.calledOnceWithExactly('1.0.0', 'http://localhost:2368', {
138+
expect(downloadContentExport.calledOnceWithExactly('1.0.0', 'http://localhost:2368', {
139139
username: 'username', password: 'password'
140140
}, 'test-export.json'));
141141
});

0 commit comments

Comments
 (0)