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 <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits