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

Reply via email to