BearND has uploaded a new change for review. https://gerrit.wikimedia.org/r/296266
Change subject: Picture of the day ...................................................................... Picture of the day http://localhost:6927/en.wikipedia.org/v1/media/image/featured/2016/04/15 Bug: T132765 Change-Id: I45148c7915abab9cc1a1d84c8aa5e178c668c169 --- M lib/dateUtil.js A lib/feed/featured-image.js M lib/mobile-util.js A routes/featured-image.js M spec.yaml A test/features/featured-image/pagecontent.js 6 files changed, 287 insertions(+), 0 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/services/mobileapps refs/changes/66/296266/1 diff --git a/lib/dateUtil.js b/lib/dateUtil.js index fc8d7d0..2ac33ac 100644 --- a/lib/dateUtil.js +++ b/lib/dateUtil.js @@ -51,6 +51,21 @@ return number; } +/** + * Returns a String formatted in ISO date format -- just the date. Timezone is UTC. + * This is similar to Date.toISOString() but without the time and time zone portions. + * + * Example: "2016-05-16" + * + * @param {Date} date date to be used + * @return {String} formatted date string + */ +function formatISODate(date) { + return date.getUTCFullYear() + + '-' + pad(date.getUTCMonth() + 1) + + '-' + pad(date.getUTCDate()); +} + function yesterday(req) { var date = getRequestedDate(req), yesterday = new Date(date - ONE_DAY); @@ -65,6 +80,7 @@ getRequestedDate: getRequestedDate, dateStringFrom: dateStringFrom, iso8601DateFrom: iso8601DateFrom, + formatISODate: formatISODate, pad: pad, yesterday: yesterday, ONE_DAY: ONE_DAY diff --git a/lib/feed/featured-image.js b/lib/feed/featured-image.js new file mode 100644 index 0000000..6b6367f --- /dev/null +++ b/lib/feed/featured-image.js @@ -0,0 +1,103 @@ +/** + * To retrieve the picture of the day for a given date. + */ + +'use strict'; + +var preq = require('preq'); +var api = require('../api-util'); +var dateUtil = require('../dateUtil'); +var mwapi = require('../mwapi'); +var mUtil = require('../mobile-util'); +var sUtil = require('../util'); +var HTTPError = sUtil.HTTPError; + + +/** + * Builds the request to get the Picture of the day of a given date. + * + * @param {Object} app the application object + * @param {String} domain the requested domain, e.g. 'de.wikipedia.org' + * @param {Date} date for which day the featured image of theday is requested + * @return {Promise} a promise resolving as an JSON object containing the response + */ +function requestPictureOfTheDay(app, domain, date) { + var isoDate = dateUtil.formatISODate(date); + var lang = mUtil.getLanguageFromDomain(domain); + return api.mwApiGet(app, 'commons.wikimedia.org', { + action: 'query', + format: 'json', + formatversion: 2, + generator: 'images', + prop: 'imageinfo|revisions', + iiextmetadatafilter: 'ImageDescription', + iiextmetadatalanguage: lang, + iiprop: 'url|extmetadata|dimensions', + iiurlwidth: 640, + rawcontinue: '', + titles: `Template:Potd/${isoDate}_(${lang})` + }); +} + +// -- functions dealing with responses: + +function getPageObject(response, dontThrow) { + if (response.body.query && response.body.query.pages[0]) { + var page = response.body.query.pages[0]; + if (!page.pageid || page.missing === true) { + throw new HTTPError({ + status: 404, + type: 'not_found', + title: 'No picture of the day for this date', + detail: 'There is no picture of the day for this date.' + }); + } + return page; + } else { + if (!dontThrow) { + throw new HTTPError({ + status: 500, + type: 'unknown_backend_response', + title: 'Unexpected backend response', + detail: 'The backend responded with gibberish.' + }); + } + } +} + +function buildPotdResponse(page) { + let imageinfo = page.imageinfo && page.imageinfo[0]; + return { + title: page.title, + thumbnail: { + source: imageinfo && imageinfo.thumburl, + width: imageinfo && imageinfo.thumbwidth, + height: imageinfo && imageinfo.thumbheight + }, + // the full res image: + image: { + source: imageinfo.url, + width: imageinfo.width, + height: imageinfo.height + }, + description: imageinfo.extmetadata && imageinfo.extmetadata.ImageDescription.value + }; +} + +function promise(app, req) { + return requestPictureOfTheDay(app, req.params.domain, dateUtil.getRequestedDate(req)) + .then(function (response) { + mwapi.checkForQueryPagesInResponse(req, response); + var page = getPageObject(response); + return { + payload: buildPotdResponse(page), + meta: { + etag: page.pageid + '/' + mwapi.getRevisionFromExtract(page) + } + }; + }); +} + +module.exports = { + promise: promise +}; diff --git a/lib/mobile-util.js b/lib/mobile-util.js index 5f079ed..105081c 100644 --- a/lib/mobile-util.js +++ b/lib/mobile-util.js @@ -87,6 +87,14 @@ return domain.split('.').slice(0,2).join('.'); } +/** + * Get the language of the wiki base don the domain name. + * Example: 'en.wikipedia.org' -> 'en'. + */ +function getLanguageFromDomain(domain) { + return domain.split('.')[0]; +} + // Merge two arrays of objects by the specified property. // Stolen from https://jsfiddle.net/guya/eAWKR/. function mergeByProp(arr1, arr2, prop, pushIfKeyNotFound) { @@ -177,6 +185,7 @@ getDateStringEtag: getDateStringEtag, mobileToCanonical: mobileToCanonical, removeTLD: removeTLD, + getLanguageFromDomain: getLanguageFromDomain, mergeByProp: mergeByProp, adjustMemberKeys: adjustMemberKeys, fillInMemberKeys: fillInMemberKeys, diff --git a/routes/featured-image.js b/routes/featured-image.js new file mode 100644 index 0000000..60b1263 --- /dev/null +++ b/routes/featured-image.js @@ -0,0 +1,42 @@ +/** + * Picture of the day + */ + +'use strict'; + +var mUtil = require('../lib/mobile-util'); +var sUtil = require('../lib/util'); +var featured = require('../lib/feed/featured-image'); + +/** + * The main router object + */ +var router = sUtil.router(); + +/** + * The main application object reported when this module is require()d + */ +var app; + +/** + * GET {domain}/v1/media/image/featured/{year}/{month}/{day} + * Gets the title and other metadata for the picture of the day of a given date. + * ETag is set to the pageid and the revision. + */ +router.get('/image/featured/:yyyy/:mm/:dd', function (req, res) { + return featured.promise(app, req) + .then(function (response) { + res.status(200); + mUtil.setETagToValue(res, response.meta.etag); + res.json(response.payload).end(); + }); +}); + +module.exports = function (appObj) { + app = appObj; + return { + path: '/media', + api_version: 1, + router: router + }; +}; diff --git a/spec.yaml b/spec.yaml index 308112a..19ce2a4 100644 --- a/spec.yaml +++ b/spec.yaml @@ -173,6 +173,59 @@ source: /.+/ width: /.+/ height: /.+/ + # from routes/featured-image.js + /{domain}/v1/media/image/featured/{yyyy}/{mm}/{dd}: + get: + tags: + - Featured image for a given date (aka Picture of the day) + description: Provides thumbnail and full res image URLs and a localized description based on the domain used. + produces: + - application/json + parameters: + - name: yyyy + in: path + description: "Year the featured image is requested for" + type: integer + required: true + minimum: 2016 + maximum: 2999 + - name: mm + in: path + description: "Month the featured image is requested for" + type: integer + required: true + minimum: 1 + maximum: 12 + - name: dd + in: path + description: "Day of the month the featured image is requested for" + type: integer + required: true + minimum: 1 + maximum: 31 + x-amples: + - title: retrieve info of the featured image for April 29, 2016 + request: + params: + yyyy: 2016 + mm: 4 + dd: 29 + response: + status: 200 + headers: + content-type: application/json + body: + title: /.+/ + description: /.+/ + image: + source: /.+/ + width: /.+/ + height: /.+/ + thumbnail: + source: /.+/ + width: /.+/ + height: /.+/ + # from routes/most-read.js /{domain}/v1/page/most-read/{yyyy}/{mm}/{dd}: get: diff --git a/test/features/featured-image/pagecontent.js b/test/features/featured-image/pagecontent.js new file mode 100644 index 0000000..c24d156 --- /dev/null +++ b/test/features/featured-image/pagecontent.js @@ -0,0 +1,64 @@ +'use strict'; + +var assert = require('../../utils/assert.js'); +var preq = require('preq'); +var server = require('../../utils/server.js'); +var headers = require('../../utils/headers.js'); + +describe('featured-image', function() { + this.timeout(20000); + + before(function () { return server.start(); }); + + it('featured image 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/media/image/featured/2016/04/15', + 'application/json'); + }); + + it('featured image of 4/15/2016 should have expected properties', function() { + return preq.get({ uri: server.config.uri + 'en.wikipedia.org/v1/media/image/featured/2016/04/15' }) + .then(function(res) { + assert.status(res, 200); + // the page id should be stable but not the revision: + assert.ok(res.headers.etag.indexOf('42184395/') == 0); + assert.equal(res.body.title, 'File:Iglesia de La Compañía, Quito, Ecuador, 2015-07-22, DD 116-118 HDR.JPG'); + assert.equal(res.body.thumbnail.source, 'https://upload.wikimedia.org/wikipedia/commons/thumb/e/e3/Iglesia_de_La_Compa%C3%B1%C3%ADa%2C_Quito%2C_Ecuador%2C_2015-07-22%2C_DD_116-118_HDR.JPG/640px-Iglesia_de_La_Compa%C3%B1%C3%ADa%2C_Quito%2C_Ecuador%2C_2015-07-22%2C_DD_116-118_HDR.JPG'); + assert.equal(res.body.image.source, 'https://upload.wikimedia.org/wikipedia/commons/e/e3/Iglesia_de_La_Compa%C3%B1%C3%ADa%2C_Quito%2C_Ecuador%2C_2015-07-22%2C_DD_116-118_HDR.JPG'); + assert.ok(res.body.description.indexOf('Main altar') >= 0); + }); + }); + + it('incomplete date should return 404', function() { + return preq.get({ uri: server.config.uri + 'en.wikipedia.org/v1/media/image/featured/2016/04' }) + .then(function(res) { + }, function(err) { + assert.status(err, 404); + }); + }); + + it('extra uri path parameter after date should return 404', function() { + return preq.get({ uri: server.config.uri + 'en.wikipedia.org/v1/media/image/featured/2016/04/15/11' }) + .then(function(res) { + }, function(err) { + assert.status(err, 404); + }); + }); + + it('unsupported language', function() { + return preq.get({ uri: server.config.uri + 'fr.wikipedia.org/v1/media/image/featured/2016/04/15' }) + .then(function(res) { + }, function(err) { + assert.status(err, 501); + assert.equal(err.body.type, 'unsupported_language'); + }); + }); + + it('featured image of an old date should return 404', function() { + return preq.get({ uri: server.config.uri + 'en.wikipedia.org/v1/media/image/featured/1970/12/31' }) + .then(function(res) { + }, function(err) { + assert.status(err, 404); + assert.equal(err.body.type, 'not_found'); + }); + }); +}); -- To view, visit https://gerrit.wikimedia.org/r/296266 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I45148c7915abab9cc1a1d84c8aa5e178c668c169 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
