BearND has uploaded a new change for review. https://gerrit.wikimedia.org/r/290859
Change subject: Add route for featured article of the day ...................................................................... Add route for featured article of the day Currently only for enwiki only. Code adopted from the iOS app. Added a dateUtil library since this is probably useful for other feed endpoints as well. You can specify 'today' to get the current day or skip it. Bug: T132764 Change-Id: I8aa1db44d1ffdf8d618241c3c177af4036ffba11 --- A lib/dateUtil.js M lib/mobile-util.js A routes/featured.js M spec.yaml A test/features/featured/pagecontent.js 5 files changed, 303 insertions(+), 1 deletion(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/services/mobileapps refs/changes/59/290859/1 diff --git a/lib/dateUtil.js b/lib/dateUtil.js new file mode 100644 index 0000000..9640e24 --- /dev/null +++ b/lib/dateUtil.js @@ -0,0 +1,58 @@ +'use strict'; + +var sUtil = require('../lib/util'); + +const monthNames = ["January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December" +]; + +/** + * Returns a String formatted in English date format. + * + * Example: "May 16, 2016" + * + * @param {Date} date date to be used + * @return {String} formatted date string + */ +function formatDateEnglish(date) { + const year = date.getFullYear().toString(); + const month = monthNames[date.getMonth()]; + const day = date.getDate().toString(); + return `${month} ${day}, ${year}`; +} + +/** + * Returns a Date object with the desired date as specified in the request. + * The expected format is "yyyy/mm/dd". If no date or "today" is specified then return 'undefined'. + * + * Example: "May 16, 2016" + * + * @param {Date} date date to be used + * @return {String} formatted date string + */ +function getRequestedDate(req) { + const yyyy = req.params.yyyy; + const mm = req.params.mm; + const dd = req.params.dd; + let date; + + if (yyyy && yyyy !== 'today') { + if (!mm || !dd) { + throw new sUtil.HTTPError({ + status: 404, + type: 'invalid_parameters', + title: 'If specifying a date you need to specify a full date, at least for now', + detail: 'Specify a date in the URI like yyyy/mm/dd' + }); + } + date = new Date(Date.UTC(yyyy, mm - 1, dd)); // month is 0-based + } else { + date = new Date(Date.now()); + } + return date; +} + +module.exports = { + formatDateEnglish: formatDateEnglish, + getRequestedDate: getRequestedDate +}; diff --git a/lib/mobile-util.js b/lib/mobile-util.js index c1676d3..a88c00f 100644 --- a/lib/mobile-util.js +++ b/lib/mobile-util.js @@ -37,6 +37,16 @@ } /** + * Sets the ETag header on the response object to a specified value. + * + * @param {Object} response the HTTPResponse object on which to set the header + * @param {Object} value to set the ETag to + */ +function setETagToValue(response, value) { + response.set('etag', '' + value); +} + +/** * Sets the ETag header on the response object. First, the request object is * checked for the X-Restbase-ETag header. If present, that is used as the ETag * header. Otherwise, a new ETag is created, comprised of the revision ID and @@ -56,11 +66,12 @@ if (!tid) { tid = uuid.now().toString(); } - response.set('etag', '' + revision + '/' + tid); + setETagToValue(response, revision + '/' + tid); } module.exports = { filterEmpty: filterEmpty, defaultVal: defaultVal, + setETagToValue: setETagToValue, setETag: setETag }; diff --git a/routes/featured.js b/routes/featured.js new file mode 100644 index 0000000..4d4985f --- /dev/null +++ b/routes/featured.js @@ -0,0 +1,128 @@ +/** + * Featured article of the day + */ + +'use strict'; + +var preq = require('preq'); +var mwapi = require('../lib/mwapi'); +var dateUtil = require('../lib/dateUtil'); +var mUtil = require('../lib/mobile-util'); +var sUtil = require('../lib/util'); +var util = require('util'); + +/** + * The main router object + */ +var router = sUtil.router(); + +/** + * The main application object reported when this module is require()d + */ +var app; + +// -- functions dealing with the backend request: + +/** + * Builds the request to get the Featured article of a given date. + * + * @param {Object} app the application object + * @param {Object} req the request object + * @param {Date} date for which day the featured article is requested + * @return {Promise} a promise resolving as an JSON object containing the response + */ +function requestFeaturedArticle(app, req, date) { + let formattedDateString = dateUtil.formatDateEnglish(date); + return mwapi.apiGet(app, req, { + "action": "query", + "format": "json", + "formatversion": 2, + "exchars": 100, + "explaintext": false, + "titles": `Template:TFA_title/${formattedDateString}`, + "prop": "extracts" + }); +} + +// -- functions dealing with the backend response: + +function getPageObject(response) { + if (response.body.query && response.body.query.pages[0]) { + let page = response.body.query.pages[0]; + if (!page.extract || !page.pageid || page.missing === true) { + throw new sUtil.HTTPError({ + status: 404, + type: 'not_found', + title: 'No featured article for this date', + detail: 'There is no featured article for this date.' + }); + } + return page; + } else { + throw new sUtil.HTTPError({ + status: 500, + type: 'unknown_backend_response', + title: 'Unexpected backend response', + detail: 'The backend responded with gibberish.' + }); + } +} + +/** + * HAX: TextExtracts extension will (sometimes) add "..." to the extract. In this particular case, we don't + * want it, so we remove it if present. + */ +function removeEllipsis(extract) { + if (extract.endsWith('...')) { + return extract.slice(0, -3); + } + return extract; +} + +function buildResponse(page) { + return { + page: { + title: removeEllipsis(page.extract) + } + }; +} + +function getPageId(page) { + return page.pageid; +} + +// -- the routes + +/** + * GET {domain}/v1/page/featured/{year}/{month}/{day} + * Gets the title and other metadata for a featured article of a given date. + * If date is not specified use the current date. + * ETag is set to the pageid. This should be specific enough. + */ +router.get('/featured/:yyyy?/:mm?/:dd?', function (req, res) { + if (req.params.domain.indexOf('en') !== 0) { + throw new sUtil.HTTPError({ + status: 501, + type: 'unsupported_language', + title: 'Language not supported', + detail: 'The language you have requested is not yet supported.' + }); + } + + return requestFeaturedArticle(app, req, dateUtil.getRequestedDate(req)) + .then(function (response) { + let page = getPageObject(response); + res.status(200); + mUtil.setETagToValue(res, getPageId(page)); + res.json(buildResponse(page)).end(); + }); +}); + +module.exports = function (appObj) { + app = appObj; + return { + path: '/page', + api_version: 1, + router: router + }; +}; diff --git a/spec.yaml b/spec.yaml index 69f890b..7d37ea0 100644 --- a/spec.yaml +++ b/spec.yaml @@ -55,6 +55,53 @@ description: /.+/ version: /.+/ home: /.+/ + # from routes/featured.js + /{domain}/v1/page/featured/{yyyy}/{mm}/{dd}: + get: + tags: + - Featured article for a given date + description: title of the featured article (only works on enwiki for now) + produces: + - application/json + x-amples: + - title: retrieve featured article for today + request: + params: + yyyy: today + response: + status: 200 + headers: + content-type: application/json + body: + page: + title: /.+/ + - title: retrieve title of the featured article for April 16, 2016 + request: + params: + - yyyy: 2016 + mm: 4 + dd: 16 + response: + status: 200 + headers: + content-type: application/json + body: + page: + title: /.+/ + # TODO: The next one is wrong! It should return a 404 and error info in body. Is the swagger spec test only using the request params from the first x-ample? + - title: retrieve title of the featured article for a date in the past which doesn't have a featured article + request: + params: + - yyyy: 1970 + mm: 12 + dd: 31 + response: + status: 200 + headers: + content-type: application/json + body: + page: + title: /.+/ # from routes/media.js /{domain}/v1/page/media/{title}: get: diff --git a/test/features/featured/pagecontent.js b/test/features/featured/pagecontent.js new file mode 100644 index 0000000..b8d16bf --- /dev/null +++ b/test/features/featured/pagecontent.js @@ -0,0 +1,58 @@ +'use strict'; + +const assert = require('../../utils/assert.js'); +const preq = require('preq'); +const server = require('../../utils/server.js'); +const headers = require('../../utils/headers.js'); + +describe('featured', function() { + this.timeout(20000); + + before(function () { return server.start(); }); + + it('today\'s featured article should respond to GET request with expected headers, incl. CORS and CSP headers', function() { + return headers.checkHeaders(server.config.uri + 'en.wikipedia.org/v1/page/featured/today', + 'application/json'); + }); + + it('featured article of a specific date should respond to GET request with expected headers, incl. CORS and CSP headers', function() { + return headers.checkHeaders(server.config.uri + 'en.wikipedia.org/v1/page/featured/2016/04/16', + 'application/json'); + }); + + it('featured article of 4/16/2016 should have title "Cosmic Stories and Stirring Science Stories"', function() { + return preq.get({ uri: server.config.uri + 'en.wikipedia.org/v1/page/featured/2016/04/16' }) + .then(function(res) { + assert.deepEqual(res.status, 200); + assert.deepEqual(res.body.page.title, 'Cosmic Stories and Stirring Science Stories'); + assert.deepEqual(res.headers.etag, '50089449'); + }); + }); + + it('incomplete date should return 404', function() { + return preq.get({ uri: server.config.uri + 'en.wikipedia.org/v1/page/featured/2016/04' }) + .then(function(res) { + }, function(err) { + assert.status(err, 404); + assert.deepEqual(err.body.type, 'invalid_parameters'); + }); + }); + + it('unsupported language', function() { + return preq.get({ uri: server.config.uri + 'fr.wikipedia.org/v1/page/featured' }) + .then(function(res) { + }, function(err) { + assert.status(err, 501); + assert.deepEqual(err.body.type, 'unsupported_language'); + }); + }); + + it('featured article of an old date should return 404', function() { + return preq.get({ uri: server.config.uri + 'en.wikipedia.org/v1/page/featured/1970/12/31' }) + .then(function(res) { + }, function(err) { + assert.status(err, 404); + assert.deepEqual(err.body.type, 'not_found'); + }); + }); +}); -- To view, visit https://gerrit.wikimedia.org/r/290859 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I8aa1db44d1ffdf8d618241c3c177af4036ffba11 Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/services/mobileapps Gerrit-Branch: master Gerrit-Owner: BearND <bsitzm...@wikimedia.org> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits