Skip to content

Commit a2fde4d

Browse files
committed
Add tool to get all posts and save as a JSON file
1 parent 820e5b5 commit a2fde4d

File tree

6 files changed

+187
-1
lines changed

6 files changed

+187
-1
lines changed

bin/cli.js

+2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import revueStripe from '../commands/revue-stripe.js';
3333
import letterdropStripe from '../commands/letterdrop-stripe.js';
3434
import postTiers from '../commands/post-tiers.js';
3535
import pageToPost from '../commands/page-to-post.js';
36+
import getPosts from '../commands/get-posts.js';
3637

3738
prettyCLI.command(addMemberCompSubscriptionCommands);
3839
prettyCLI.command(removeMemberCompSubscriptionCommands);
@@ -64,6 +65,7 @@ prettyCLI.command(revueStripe);
6465
prettyCLI.command(letterdropStripe);
6566
prettyCLI.command(postTiers);
6667
prettyCLI.command(pageToPost);
68+
prettyCLI.command(getPosts);
6769

6870
prettyCLI.style({
6971
usageCommandPlaceholder: () => '<source or utility>'

commands/get-posts.js

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import {ui} from '@tryghost/pretty-cli';
2+
import getPosts from '../tasks/get-posts.js';
3+
4+
// Internal ID in case we need one.
5+
const id = 'get-posts';
6+
7+
const group = 'Content:';
8+
9+
// The command to run and any params
10+
const flags = 'get-posts <apiURL> <adminAPIKey>';
11+
12+
// Description for the top level command
13+
const desc = 'Get all posts from Ghost';
14+
15+
// Descriptions for the individual params
16+
const paramsDesc = [
17+
'URL to your Ghost API',
18+
'Admin API key'
19+
];
20+
21+
// Configure all the options
22+
const setup = (sywac) => {
23+
sywac.boolean('-V --verbose', {
24+
defaultValue: false,
25+
desc: 'Show verbose output'
26+
});
27+
sywac.number('--delayBetweenCalls', {
28+
defaultValue: 50,
29+
desc: 'The delay between API calls, in ms'
30+
});
31+
};
32+
33+
// What to do when this command is executed
34+
const run = async (argv) => {
35+
let timer = Date.now();
36+
let context = {errors: []};
37+
38+
try {
39+
// Fetch the tasks, configured correctly according to the options passed in
40+
let runner = getPosts.getTaskRunner(argv);
41+
42+
// Run the migration
43+
await runner.run(context);
44+
} catch (error) {
45+
ui.log.error('Done with errors', context.errors);
46+
}
47+
48+
// Report success
49+
ui.log.ok(`Successfully got ${context.found.length} posts in ${Date.now() - timer}ms.`);
50+
};
51+
52+
export default {
53+
id,
54+
group,
55+
flags,
56+
desc,
57+
paramsDesc,
58+
setup,
59+
run
60+
};

commands/interactive.js

+4
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@ const run = async () => {
9292
name: tasks.postTiers.choice.name,
9393
value: tasks.postTiers.choice.value
9494
},
95+
{
96+
name: tasks.getPosts.choice.name,
97+
value: tasks.getPosts.choice.value
98+
},
9599
new inquirer.Separator('--- Members API Utilities ---------'),
96100
{
97101
name: tasks.addMemberCompSubscription.choice.name,

prompts/get-posts.js

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import inquirer from 'inquirer';
2+
import {ui} from '@tryghost/pretty-cli';
3+
import getPosts from '../tasks/get-posts.js';
4+
import ghostAPICreds from '../lib/ghost-api-creds.js';
5+
6+
const choice = {
7+
name: 'Get all posts',
8+
value: 'getPosts'
9+
};
10+
11+
const options = [
12+
...ghostAPICreds
13+
];
14+
15+
async function run() {
16+
await inquirer.prompt(options).then(async (answers) => {
17+
let timer = Date.now();
18+
let context = {errors: []};
19+
20+
try {
21+
let runner = getPosts.getTaskRunner(answers);
22+
await runner.run(context);
23+
ui.log.ok(`Successfully got ${context.found.length} posts in ${Date.now() - timer}ms.`);
24+
} catch (error) {
25+
ui.log.error('Done with errors', context.errors);
26+
}
27+
});
28+
}
29+
30+
export default {
31+
choice,
32+
options,
33+
run
34+
};

prompts/index.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
/* eslint-disable max-lines */
2+
13
import zipSplit from './zip-split.js';
24
import zipCreate from './zip-create.js';
35
import jsonSplit from './json-split.js';
@@ -22,6 +24,7 @@ import addMemberNewsletterSubscription from './add-member-newsletter-subscriptio
2224
import addMemberCompSubscription from './add-member-comp-subscription.js';
2325
import removeMemberCompSubscription from './remove-member-comp-subscription.js';
2426
import postTiers from './post-tiers.js';
27+
import getPosts from './get-posts.js';
2528

2629
export default {
2730
zipSplit,
@@ -47,5 +50,6 @@ export default {
4750
removeMemberCompSubscription,
4851
contentStats,
4952
dedupeMembersCsv,
50-
postTiers
53+
postTiers,
54+
getPosts
5155
};

tasks/get-posts.js

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import fsUtils from '@tryghost/mg-fs-utils';
2+
import GhostAdminAPI from '@tryghost/admin-api';
3+
import {makeTaskRunner} from '@tryghost/listr-smart-renderer';
4+
import _ from 'lodash';
5+
import {discover} from '../lib/batch-ghost-discover.js';
6+
7+
const initialise = (options) => {
8+
return {
9+
title: 'Initialising API connection',
10+
task: (ctx, task) => {
11+
let defaults = {
12+
verbose: false,
13+
tag: false,
14+
author: false,
15+
delayBetweenCalls: 50
16+
};
17+
18+
const url = options.apiURL.replace(/\/$/, '');
19+
const key = options.adminAPIKey;
20+
const api = new GhostAdminAPI({
21+
url: url.replace('localhost', '127.0.0.1'),
22+
key,
23+
version: 'v5.0'
24+
});
25+
26+
ctx.fileCache = new fsUtils.FileCache(options.apiURL);
27+
ctx.args = _.mergeWith(defaults, options);
28+
ctx.api = api;
29+
ctx.posts = [];
30+
ctx.found = 0;
31+
32+
task.output = `Workspace initialised at ${ctx.fileCache.cacheDir}`;
33+
}
34+
};
35+
};
36+
37+
const getFullTaskList = (options) => {
38+
return [
39+
initialise(options),
40+
{
41+
title: 'Fetch Post Content from Ghost API',
42+
task: async (ctx, task) => {
43+
let postDiscoveryOptions = {
44+
api: ctx.api,
45+
type: 'posts',
46+
limit: 100,
47+
include: 'tags,authors',
48+
progress: (options.verbose) ? true : false
49+
};
50+
51+
try {
52+
ctx.posts = await discover(postDiscoveryOptions);
53+
task.output = `Found ${ctx.posts.length} posts`;
54+
} catch (error) {
55+
ctx.errors.push(error);
56+
throw error;
57+
}
58+
}
59+
},
60+
{
61+
title: 'Saving as JSON file',
62+
task: async (ctx) => {
63+
await ctx.fileCache.saveFile(`${ctx.fileCache.tmpDir}/posts.json`, JSON.stringify({posts: ctx.posts}, null, 2));
64+
ctx.found = ctx.posts.length;
65+
}
66+
}
67+
];
68+
};
69+
70+
const getTaskRunner = (options) => {
71+
let tasks = [];
72+
73+
tasks = getFullTaskList(options);
74+
75+
return makeTaskRunner(tasks, Object.assign({topLevel: true}, options));
76+
};
77+
78+
export default {
79+
initialise,
80+
getFullTaskList,
81+
getTaskRunner
82+
};

0 commit comments

Comments
 (0)