diff --git a/readme.md b/readme.md index 0613671..532cb84 100644 --- a/readme.md +++ b/readme.md @@ -18,13 +18,15 @@ space [options] **Example commands:** `space launch` - shows id, launch name, time (scheduled attempted) and available live stream options for the next rocket launch -`space launch -v --tz Asia/Shanghai -n 5` - shows id, launch name, time (scheduled attempted) converted to CST time zone, name of a rocket, and missions (with type and description) for the next 5 launches +`space launch -v --tz Asia/Shanghai -n 5` - shows id, launch name, time (scheduled attempted) converted to CST time zone, name of a rocket, and missions (with type and description) for the next 5 launches +`space news` - shows news articles (source, title and link) from current and previous day, grouped by date ## Settings -Settings allow you to store preferred values (e.g. time zone) which will be automatically used during [empty] option call. Settings can be found in `/.spacecli` dir. +Settings allow you to store preferred values (e.g. time zone), which will be automatically used with a command unless other value is provided (in-command values are prioritized over settings). +Settings file (`settings.json`) can be found in `spacecli`, placed in system's respective config directory (set with `XDG_CONFIG_HOME`, or `~/.config` for *nix and `~/AppData/Local/` for win) **Example commands:** `space settings --tz Asia/Shanghai` - save Asia/Shanghai as preferred time zone -`space launch --tz` - Command will automatically use Asia/Shanghai for time convertion, unless different time zone is specified +`space launch --tz` - Command will automatically use Asia/Shanghai for time conversion, unless different time zone is specified Space CLI is in active development, to see currently available commands and options, use `space -h`. diff --git a/src/index.js b/src/index.js index db8d5d4..9e56f07 100644 --- a/src/index.js +++ b/src/index.js @@ -4,6 +4,7 @@ const yargs = require('yargs'); // ./modules const rocketLaunch = require('./modules/rocketLaunch'); +const news = require('./modules/news'); const info = require('./modules/info'); const settings = require('./modules/settings'); @@ -28,6 +29,16 @@ const argv = yargs // eslint-disable-line }, rocketLaunch.nextLaunch ) + .command('news', 'Get recent news articles', + function (yargs) { + return yargs + .option('n', { + alias: ['number', 'limit'], + describe: 'Define amount of articles to show' + }); + }, + news.listArticles + ) .command('settings', 'Set default settings', function (yargs) { return yargs.option('tz', { diff --git a/src/modules/info.js b/src/modules/info.js index de54db9..8da1852 100644 --- a/src/modules/info.js +++ b/src/modules/info.js @@ -3,7 +3,7 @@ const helpers = require('../helpers'); exports.about = function () { const welcome = chalk`{green Welcome to Space CLI} - a CLI for space information`; - const credits = `Credits:\nhttps://launchlibrary.net/ - API documentation for upcoming launches`; + const credits = `Credits:\nhttps://launchlibrary.net/ - API for upcoming launches\nhttps://www.spaceflightnewsapi.net/ - API for space information like news, manned flights or ISS status`; helpers.printMessage(`${welcome}\n\n${credits}`); }; diff --git a/src/modules/news.js b/src/modules/news.js new file mode 100644 index 0000000..7d2c5d4 --- /dev/null +++ b/src/modules/news.js @@ -0,0 +1,47 @@ +const axios = require('axios'); +const chalk = require('chalk'); + +const helpers = require('../helpers'); + +exports.listArticles = function (argv) { + const requestParams = {}; + + if (argv.limit && argv.limit > 1) { + requestParams.limit = argv.limit; + } else { + const now = new Date(); + const todayStart = Date.UTC(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 1); + const fullDayMs = 24 * 60 * 60 * 1000; + requestParams.since_added = (todayStart - fullDayMs) / 1000; + } + + axios.get('https://api.spaceflightnewsapi.net/articles/', { + params: requestParams + }).then((response) => { + const articles = response.data; + + const articlesByDate = articles.reduce((byDate, article) => { + const title = chalk`${article.title}`; + + const publishedAt = new Date(article.date_published * 1000).toLocaleDateString(); + const articleDetails = chalk`{yellow ${article.news_site_long}}`; + + const readSource = `Read on ${article.url}`; + + const dayArticles = byDate[publishedAt] = byDate[publishedAt] || []; + dayArticles.push(`${articleDetails} | ${title}\n${readSource} \n`); + + return byDate; + }, {}); + + for (const date in articlesByDate) { + const dayArticles = articlesByDate[date]; + + helpers.printMessage(`${date}\n----------`); + dayArticles.forEach(article => helpers.printMessage(article)); + } + }).catch(error => { + const errorMessage = `${error.code}: ${error.message}`; + helpers.printError(errorMessage); + }); +}; diff --git a/test/mockData.js b/test/mockData/launch.js similarity index 100% rename from test/mockData.js rename to test/mockData/launch.js diff --git a/test/mockData/news.js b/test/mockData/news.js new file mode 100644 index 0000000..26a5625 --- /dev/null +++ b/test/mockData/news.js @@ -0,0 +1,31 @@ +exports.multiResponse = [ + { + 'tags': [ + 'space', + 'stars', + 'planet', + 'orbit' + ], + 'categories': [], + '_id': '12345abc', + 'news_site': 'space', + 'news_site_long': 'Space', + 'title': 'Space is big', + 'url': 'https://example.com/2018/12/30/space-is-big/', + 'date_published': 1546197906, + 'date_added': 1546197906, + 'featured_image': 'https://example.com/2018/12/30/image.jpg' + }, + { + 'tags': [], + 'categories': [], + '_id': 'vbnm6789', + 'news_site': 'space', + 'news_site_long': 'Space', + 'title': 'Live coverage: Earth is spinning', + 'url': 'https://example.com.com/2018/12/30/earth-is-spinning/', + 'date_published': 1546180509, + 'date_added': 1546180509, + 'featured_image': 'https://example.com/2018/12/image.jpg' + } +]; diff --git a/test/news.test.js b/test/news.test.js new file mode 100644 index 0000000..04a11e6 --- /dev/null +++ b/test/news.test.js @@ -0,0 +1,57 @@ +const axios = require('axios'); + +const news = require('../src/modules/news'); +const helpers = require('../src/helpers'); + +const newsResponse = require('./mockData/news.js'); + +jest.mock('axios'); + +describe('Space news', function () { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('list articles', function () { + it('should set limit if applied and call printMessage for each news', function (done) { + expect.hasAssertions(); + + const spy = jest.spyOn(helpers, 'printMessage'); + axios.get.mockImplementation((url, query) => { + expect(query.params).not.toHaveProperty('since_added'); + expect(query.params).toHaveProperty('limit'); + + return Promise.resolve({ + data: newsResponse.multiResponse + }); + }); + + const newsCount = 2; + news.listArticles({ + limit: newsCount + }); + + const newsListMessageCount = newsCount + 1; // Adds 1 output for data label + setTimeout(() => { + expect(spy).toHaveBeenCalledTimes(newsListMessageCount); + done(); + }); + }); + it('should set date requirement if no limit applied', function () { + expect.hasAssertions(); + + axios.get.mockImplementation((url, query) => { + expect(query.params).toHaveProperty('since_added'); + expect(query.params).not.toHaveProperty('limit'); + + return Promise.resolve({ + data: newsResponse.multiResponse + }); + }); + + news.listArticles({ + since_added: new Date() + }); + }); + }); +}); diff --git a/test/rocketLaunch.test.js b/test/rocketLaunch.test.js index a8ed0a6..a684c87 100644 --- a/test/rocketLaunch.test.js +++ b/test/rocketLaunch.test.js @@ -3,7 +3,7 @@ const axios = require('axios'); const rocketLaunch = require('../src/modules/rocketLaunch'); const helpers = require('../src/helpers'); -const nextResponse = require('./mockData.js'); +const nextResponse = require('./mockData/launch.js'); jest.mock('axios');