diff --git a/.travis.yml b/.travis.yml index bef97cc..0600126 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,4 +28,17 @@ before_install: install: - npm install -after_success: npm run coverage +after_success: npm run coverage + +before_deploy: + - npm run jsdoc && cd jsdoc + +deploy: #build the docs for dev branch only + provider: s3 + bucket: "dev-cognicity-server-docs" + region: "us-east-1" + access_key_id: $AWS_S3_ACCESS_KEY_ID + secret_access_key: $AWS_S3_SECRET_ACCESS_KEY + skip_cleanup: true + on: + branch: dev diff --git a/CHANGELOG.md b/CHANGELOG.md index b5e8f76..b2f71fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,3 +23,12 @@ API Server for CogniCity * Card IDs generated as UUID by Postgres * Added schema version to / endpoint * Image upload changed to use AWS S3 signed links + +### v3.0.3 +* Added a default expire time to CAP output +* Add /floods/archive endpoint +* Added database log entry for card creation +* Added /floods/timeseries endpoint +* Added /reports/timeseries endpoint +* Added time window check on archive and time series endpoints +* Updated API definitions in swagger file diff --git a/README.md b/README.md index ef8788e..e35ebea 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,8 @@ Server configuration parameters are stored in a configuration file which is pars * `CACHE_DURATION_FLOODS`: How long should floods be cached for? (default: '1 hour') * `CACHE_DURATION_FLOODS_STATES`: How long should flood states be cached for? (default: '1 hour') * `CACHE_DURATION_INFRASTRUCTURE`: How long should infrastructure be cached for? (default: '1 hour') +* `CAP_DEFAULT_EXPIRE_SECONDS`: Default expire value for CAP output in seconds +* `CAP_TIMEZONE`: Timezone for CAP output * `COMPRESS`: Should the server gzip compress results? Only works if CACHE is disabled. (default: `false`) * `CORS`: Should Cross Object Resource Sharing (CORS) be enabled (default: `false`) * `CORS_HEADERS`: CORS headers to use (default: `[Link]`) diff --git a/package-lock.json b/package-lock.json index 5ba6b48..377ca34 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "cognicity-server", - "version": "3.0.2", + "version": "3.0.3", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 03be1b8..fb51be8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cognicity-server", - "version": "3.0.2", + "version": "3.0.3", "description": "Data Server for CogniCity", "main": "dist", "engines": { diff --git a/src/api/routes/cards/model.js b/src/api/routes/cards/model.js index a8a1ff3..3af2ed9 100644 --- a/src/api/routes/cards/model.js +++ b/src/api/routes/cards/model.js @@ -26,12 +26,24 @@ export default (config, db, logger) => ({ // Execute logger.debug(query, values); db.oneOrNone(query, values).timeout(config.PGTIMEOUT) - .then((data) => resolve(data)) + .then((data) => { + // Card created, update database log + let query = `INSERT INTO ${config.TABLE_GRASP_LOG} + (card_id, event_type) VALUES ($1, $2)`; + let values = [data.card_id, 'CARD CREATED']; + db.oneOrNone(query, values).timeout(config.PGTIMEOUT) + .then(() => { + resolve(data); + }) + .catch((err) => { + reject(err); + }); + }) /* istanbul ignore next */ .catch((err) => { /* istanbul ignore next */ reject(err); -} + } ); }), diff --git a/src/api/routes/floods/archive/index.js b/src/api/routes/floods/archive/index.js new file mode 100644 index 0000000..67fed71 --- /dev/null +++ b/src/api/routes/floods/archive/index.js @@ -0,0 +1,67 @@ +/** + * CogniCity Server /floods/archive archive endpoint + * @module src/api/floods/archive/index + **/ +import {Router} from 'express'; + +// Import our data model +import archive from './model'; + +// Import any required utility functions +import {cacheResponse} from '../../../../lib/util'; + +// Import validation dependencies +import BaseJoi from 'joi'; +import Extension from 'joi-date-extensions'; +const Joi = BaseJoi.extend(Extension); +import validate from 'celebrate'; + +/** + * Endpoint specification for floods archive + * @alias module:src/api/floods/archive/index + * @param {Object} config Server configuration + * @param {Object} db PG Promise database instance + * @param {Object} logger Configured Winston logger instance + * @return {Object} api Express router object for route + */ +export default ({config, db, logger}) => { + let api = Router(); // eslint-disable-line new-cap + // TODO add support for multiple cities + // Just get the states without the geographic boundaries + api.get('/', cacheResponse('1 minute'), + validate({ + query: { + start: Joi.date().format('YYYY-MM-DDTHH:mm:ssZ').required(), + end: Joi.date().format('YYYY-MM-DDTHH:mm:ssZ') + .min(Joi.ref('start')).required(), + }, + }), + (req, res, next) => { + // validate the time window, if fails send 400 error + let maxWindow = new Date(req.query.start).getTime() + + (config.API_REPORTS_TIME_WINDOW_MAX * 1000); + let end = new Date(req.query.end); + if (end > maxWindow) { + res.status(400).json({'statusCode': 400, 'error': 'Bad Request', + 'message': 'child \'end\' fails because [end is more than ' + + config.API_REPORTS_TIME_WINDOW_MAX + + ' seconds greater than \'start\']', + 'validation': { + 'source': 'query', + 'keys': [ + 'end', + ]}}); + return; + } + archive(config, db, logger).maxstate(req.query.start, req.query.end) + .then((data) => res.status(200).json({statusCode: 200, result: data})) + .catch((err) => { + /* istanbul ignore next */ + logger.error(err); + /* istanbul ignore next */ + next(err); + }); + } + ); + return api; +}; diff --git a/src/api/routes/floods/archive/model.js b/src/api/routes/floods/archive/model.js new file mode 100644 index 0000000..bf0c369 --- /dev/null +++ b/src/api/routes/floods/archive/model.js @@ -0,0 +1,37 @@ +/** + * CogniCity Server /floods/archive data model + * @module src/api/floods/archive model + **/ + import Promise from 'bluebird'; + + /** + * Methods to interact with flood layers in database + * @alias module:src/api/floods/model + * @param {Object} config Server configuration + * @param {Object} db PG Promise database instance + * @param {Object} logger Configured Winston logger instance + * @return {Object} Query methods + */ +export default (config, db, logger) => ({ + + // Get max state of all flood reports over time + maxstate: (start, end) => new Promise((resolve, reject) => { + // Setup query + let query = `SELECT local_area as area_id, changed as last_updated, + max_state FROM cognicity.rem_get_max_flood($1, $2)`; + + // Setup values + let values = [start, end]; + + // Execute + logger.debug(query, values); + db.any(query, values).timeout(config.PGTIMEOUT) + .then((data) => { + resolve(data); + }) + .catch((err) => { + /* istanbul ignore next */ + reject(err); + }); + }), +}); diff --git a/src/api/routes/floods/index.js b/src/api/routes/floods/index.js index b50f6cc..d36bb58 100644 --- a/src/api/routes/floods/index.js +++ b/src/api/routes/floods/index.js @@ -7,6 +7,10 @@ import {Router} from 'express'; // Import our data model import floods from './model'; +// Import child routes +import archive from './archive'; +import timeseries from './timeseries'; + // Import any required utility functions import {cacheResponse, formatGeo, jwtCheck} from '../../../lib/util'; @@ -58,7 +62,7 @@ const clearCache = () => { */ export default ({config, db, logger}) => { let api = Router(); // eslint-disable-line new-cap - const cap = new Cap(logger); // Setup our cap formatter + const cap = new Cap(config, logger); // Setup our cap formatter // Get a list of all floods api.get('/', cacheResponse(config.CACHE_DURATION_FLOODS), @@ -185,5 +189,9 @@ floods(config, db, logger).allGeo(req.query.city, req.query.minimum_state) }) ); + // to get max flood states between two dates + api.use('/archive', archive({config, db, logger})); + api.use('/timeseries', timeseries({config, db, logger})); + return api; }; diff --git a/src/api/routes/floods/timeseries/index.js b/src/api/routes/floods/timeseries/index.js new file mode 100644 index 0000000..0c1da00 --- /dev/null +++ b/src/api/routes/floods/timeseries/index.js @@ -0,0 +1,68 @@ +/** + * CogniCity Server /floods timeseries endpoint + * @module src/api/floods/timeseries/index + **/ +import {Router} from 'express'; + +// Import our data model +import timeseries from './model'; + +// Import any required utility functions +import {cacheResponse} from '../../../../lib/util'; + +// Import validation dependencies +import BaseJoi from 'joi'; +import Extension from 'joi-date-extensions'; +const Joi = BaseJoi.extend(Extension); +import validate from 'celebrate'; + +/** + * Endpoint specification for floods timeseries data + * @alias module:src/api/floods/timeseries/index + * @param {Object} config Server configuration + * @param {Object} db PG Promise database instance + * @param {Object} logger Configured Winston logger instance + * @return {Object} api Express router object for route + */ +export default ({config, db, logger}) => { + let api = Router(); // eslint-disable-line new-cap + // TODO add support for multiple cities + // Just get the states without the geographic boundaries + api.get('/', cacheResponse('1 minute'), + validate({ + query: { + start: Joi.date().format('YYYY-MM-DDTHH:mm:ssZ').required(), + end: Joi.date().format('YYYY-MM-DDTHH:mm:ssZ') + .min(Joi.ref('start')).required(), + }, + }), + (req, res, next) => { + // validate the time window, if fails send 400 error + let maxWindow = new Date(req.query.start).getTime() + + (config.API_REPORTS_TIME_WINDOW_MAX * 1000); + let end = new Date(req.query.end); + if (end > maxWindow) { + res.status(400).json({'statusCode': 400, 'error': 'Bad Request', + 'message': 'child \'end\' fails because [end is more than ' + + config.API_REPORTS_TIME_WINDOW_MAX + + ' seconds greater than \'start\']', + 'validation': { + 'source': 'query', + 'keys': [ + 'end', + ]}}); + return; + } + + timeseries(config, db, logger).count(req.query.start, req.query.end) + .then((data) => res.status(200).json({statusCode: 200, result: data})) + .catch((err) => { + /* istanbul ignore next */ + logger.error(err); + /* istanbul ignore next */ + next(err); + }); + } + ); + return api; +}; diff --git a/src/api/routes/floods/timeseries/model.js b/src/api/routes/floods/timeseries/model.js new file mode 100644 index 0000000..7a981c6 --- /dev/null +++ b/src/api/routes/floods/timeseries/model.js @@ -0,0 +1,41 @@ +/** + * CogniCity Server /floods/timeseries data model + * @module src/api/floods/timeseries/model + **/ + import Promise from 'bluebird'; + + /** + * Methods to interact with flood layers in database + * @alias module:src/api/floods/timeseries/model + * @param {Object} config Server configuration + * @param {Object} db PG Promise database instance + * @param {Object} logger Configured Winston logger instance + * @return {Object} Query methods + */ +export default (config, db, logger) => ({ + + // Get all flood reports for a given city + count: (start, end) => new Promise((resolve, reject) => { + // Setup query + let query = `SELECT ts, count(local_area) FROM + (SELECT (cognicity.rem_get_flood(ts)).local_area, ts + FROM generate_series($1::timestamp with time zone, + $2::timestamp with time zone,'1 hour') + as series(ts)) output + GROUP BY ts ORDER BY ts`; + + // Setup values + let values = [start, end]; + + // Execute + logger.debug(query, values); + db.any(query, values).timeout(config.PGTIMEOUT) + .then((data) => { + resolve(data); + }) + .catch((err) => { + /* istanbul ignore next */ + reject(err); + }); + }), +}); diff --git a/src/api/routes/reports/archive/index.js b/src/api/routes/reports/archive/index.js index fc522ab..5faf7a9 100644 --- a/src/api/routes/reports/archive/index.js +++ b/src/api/routes/reports/archive/index.js @@ -41,7 +41,24 @@ export default ({config, db, logger}) => { .default(config.GEO_FORMAT_DEFAULT), }, }), - (req, res, next) => archive(config, db, logger) + (req, res, next) => { + // validate the time window, if fails send 400 error + let maxWindow = new Date(req.query.start).getTime() + + (config.API_REPORTS_TIME_WINDOW_MAX * 1000); + let end = new Date(req.query.end); + if (end > maxWindow) { + res.status(400).json({'statusCode': 400, 'error': 'Bad Request', + 'message': 'child \'end\' fails because [end is more than ' + + config.API_REPORTS_TIME_WINDOW_MAX + + ' seconds greater than \'start\']', + 'validation': { + 'source': 'query', + 'keys': [ + 'end', + ]}}); + return; + } + archive(config, db, logger) .all(req.query.start, req.query.end, req.query.city) .then((data) => handleGeoResponse(data, req, res, next)) .catch((err) => { @@ -49,7 +66,8 @@ export default ({config, db, logger}) => { logger.error(err); /* istanbul ignore next */ next(err); - }) + }); + } ); return api; diff --git a/src/api/routes/reports/index.js b/src/api/routes/reports/index.js index b2f82cf..c7c0013 100644 --- a/src/api/routes/reports/index.js +++ b/src/api/routes/reports/index.js @@ -7,7 +7,9 @@ // Import our data model import reports from './model'; +// Import child routes import archive from './archive'; +import timeseries from './timeseries'; // Import any required utility functions import {cacheResponse, handleGeoResponse} from '../../../lib/util'; @@ -51,8 +53,9 @@ export default ({config, db, logger}) => { }) ); - // to get all reports between two dates + // child routes before /:id api.use('/archive', archive({config, db, logger})); + api.use('/timeseries', timeseries({config, db, logger})); // Get a single report api.get('/:id', cacheResponse('1 minute'), @@ -75,6 +78,5 @@ export default ({config, db, logger}) => { }) ); - return api; }; diff --git a/src/api/routes/reports/timeseries/index.js b/src/api/routes/reports/timeseries/index.js new file mode 100644 index 0000000..92c8954 --- /dev/null +++ b/src/api/routes/reports/timeseries/index.js @@ -0,0 +1,68 @@ +/** + * CogniCity Server /reports timeseries endpoint + * @module src/api/reports/timeseries/index + **/ +import {Router} from 'express'; + +// Import our data model +import timeseries from './model'; + +// Import any required utility functions +import {cacheResponse} from '../../../../lib/util'; + +// Import validation dependencies +import BaseJoi from 'joi'; +import Extension from 'joi-date-extensions'; +const Joi = BaseJoi.extend(Extension); +import validate from 'celebrate'; + +/** + * Endpoint specification for reports timeseries data + * @alias module:src/api/reports/timeseries/index + * @param {Object} config Server configuration + * @param {Object} db PG Promise database instance + * @param {Object} logger Configured Winston logger instance + * @return {Object} api Express router object for route + */ +export default ({config, db, logger}) => { + let api = Router(); // eslint-disable-line new-cap + // TODO add support for multiple cities + // Just get the states without the geographic boundaries + api.get('/', cacheResponse('1 minute'), + validate({ + query: { + start: Joi.date().format('YYYY-MM-DDTHH:mm:ssZ').required(), + end: Joi.date().format('YYYY-MM-DDTHH:mm:ssZ') + .min(Joi.ref('start')).required(), + city: Joi.any().valid(config.REGION_CODES), + }, + }), + (req, res, next) => { + // validate the time window, if fails send 400 error + let maxWindow = new Date(req.query.start).getTime() + + (config.API_REPORTS_TIME_WINDOW_MAX * 1000); + let end = new Date(req.query.end); + if (end > maxWindow) { + res.status(400).json({'statusCode': 400, 'error': 'Bad Request', + 'message': 'child \'end\' fails because [end is more than ' + + config.API_REPORTS_TIME_WINDOW_MAX + + ' seconds greater than \'start\']', + 'validation': { + 'source': 'query', + 'keys': [ + 'end', + ]}}); + return; + } + timeseries(config, db, logger).count(req.query.start, req.query.end) + .then((data) => res.status(200).json({statusCode: 200, result: data})) + .catch((err) => { + /* istanbul ignore next */ + logger.error(err); + /* istanbul ignore next */ + next(err); + }); + } + ); + return api; +}; diff --git a/src/api/routes/reports/timeseries/model.js b/src/api/routes/reports/timeseries/model.js new file mode 100644 index 0000000..72fffdd --- /dev/null +++ b/src/api/routes/reports/timeseries/model.js @@ -0,0 +1,42 @@ +/** + * CogniCity Server /reports/timeseries data model + * @module src/api/reports/timeseries/model + **/ + import Promise from 'bluebird'; + + /** + * Methods to interact with report layer in database + * @alias module:src/api/reports/timeseries/model + * @param {Object} config Server configuration + * @param {Object} db PG Promise database instance + * @param {Object} logger Configured Winston logger instance + * @return {Object} Query methods + */ +export default (config, db, logger) => ({ + + // Get all flood reports for a given city + count: (start, end, city) => new Promise((resolve, reject) => { + // Setup query + let query = `SELECT ts, count(r.pkey) + FROM generate_series($1::timestamp with time zone, + $2::timestamp with time zone, '1 hour') ts + LEFT JOIN cognicity.all_reports r + ON date_trunc('hour', r.created_at) = ts + AND ($3 IS NULL OR tags->>'instance_region_code'=$3) + GROUP BY ts ORDER BY ts`; + + // Setup values + let values = [start, end, city]; + + // Execute + logger.debug(query, values); + db.any(query, values).timeout(config.PGTIMEOUT) + .then((data) => { + resolve(data); + }) + .catch((err) => { + /* istanbul ignore next */ + reject(err); + }); + }), +}); diff --git a/src/config.js b/src/config.js index df5ded0..e07778f 100644 --- a/src/config.js +++ b/src/config.js @@ -30,6 +30,8 @@ export default { CACHE_DURATION_FLOODS: process.env.CACHE_DURATION_FLOODS || '1 hour', CACHE_DURATION_FLOODS_STATES: process.env.CACHE_DURATION_FLOODS_STATES || '1 hour', CACHE_DURATION_INFRASTRUCTURE: process.env.CACHE_DURATION_INFRASTRUCTURE || '1 hour', + CAP_DEFAULT_EXPIRE_SECONDS: process.env.CAP_DEFAULT_EXPIRE_SECONDS || 21600, + CAP_TIMEZONE: process.env.CAP_TIMEZONE || 'Asia/Jakarta', COMPRESS: process.env.COMPRESS === 'true' || false, CORS: process.env.CORS === 'true' || false, CORS_HEADERS: process.env.CORS_HEADERS || ['Link'], diff --git a/src/lib/cap.js b/src/lib/cap.js index f8a1786..fe8c8d6 100644 --- a/src/lib/cap.js +++ b/src/lib/cap.js @@ -14,9 +14,11 @@ module.exports = class Cap { /** * Setup the CAP object to user specified logger * @alias module:lib/cap + * @param {Object} config Server configuration * @param {Object} logger Configured Winston logger instance */ - constructor(logger) { + constructor(config, logger) { + this.config = config; this.logger = logger; } /** @@ -94,8 +96,11 @@ module.exports = class Cap { alert.identifier = encodeURI(identifier); alert.sender = 'BPBD.JAKARTA.GOV.ID'; - alert.sent = moment.tz(feature.properties.last_updated, 'Asia/Jakarta' - ).format('YYYY-MM-DDTHH:mm:ssZ'); + alert.sent = moment.tz(feature.properties.last_updated, + self.config.CAP_TIMEZONE).format('YYYY-MM-DDTHH:mm:ssZ'); + alert.expires = moment.tz(new Date().getTime() + + self.config.CAP_DEFAULT_EXPIRE_SECONDS * 1000, + self.config.CAP_TIMEZONE).format('YYYY-MM-DDTHH:mm:ssZ'); alert.status = 'Actual'; alert.msgType = 'Alert'; alert.scope = 'Public'; diff --git a/src/test/index.js b/src/test/index.js index 3cb382f..68139f1 100644 --- a/src/test/index.js +++ b/src/test/index.js @@ -35,8 +35,11 @@ import testFeeds from './testFeeds.js'; import testFloodgauges from './testFloodgauges.js'; import testInfrastructure from './testInfrastructure.js'; import testFloods from './testFloods.js'; +import testFloodsArchive from './testFloodsArchive'; +import testFloodsTimeseries from './testFloodsTimeseries'; import testReports from './testReports.js'; import testReportsArchive from './testReportsArchive'; +import testReportsTimeseries from './testReportsTimeseries'; import testCAP from './testCAP.js'; import testDB from './testDB.js'; @@ -76,9 +79,12 @@ let token = jwt.sign({}, testFloodgauges(app); testInfrastructure(app); testFloods(app, token); + testFloodsArchive(app); + testFloodsTimeseries(app); testReports(app, reportid, createdAt); - testReportsArchive(app, createdAt); - testCAP(logger); + testReportsArchive(app); + testReportsTimeseries(app); + testCAP(config, logger); testDB(); // Removes dummy data diff --git a/src/test/testCAP.js b/src/test/testCAP.js index 3c9b1a4..e043215 100644 --- a/src/test/testCAP.js +++ b/src/test/testCAP.js @@ -11,11 +11,12 @@ import Cap from '../lib/cap'; // Cap formatter helper /** * Test CAP data format utility * @function testCAP + * @param {Object} config - Server configuration * @param {Object} logger - CogniCity Server logger object */ -export default function(logger) { +export default function(config, logger) { describe('CAP Utility', function() { - const cap = new Cap(logger); // Setup our cap formatter + const cap = new Cap(config, logger); // Setup our cap formatter // dummy data (polygon) let feature = {'type': 'Feature', diff --git a/src/test/testFloodsArchive.js b/src/test/testFloodsArchive.js new file mode 100644 index 0000000..806c2ca --- /dev/null +++ b/src/test/testFloodsArchive.js @@ -0,0 +1,127 @@ +/* eslint-disable max-len */ +/** + * testReportsArchive module + * @module test/testFloodsArchive + * A module to test the /floods/archive endpoint + */ + +import * as test from 'unit.js'; + +// TODO +// - test against an actual time in the database +// - Entered through cards.js (or add a new call to cards here) + +/** + * Test floods archive endpoint + * @function testFloodsArchive + * @param {Object} app - CogniCity server app object + */ +export default function(app) { + // Reports endpoint + describe('Reports Archive Endpoint', function() { + // Can get reports between given timestamps + it('Can get floods between given timestamps', function(done) { + test.httpAgent(app) + .get('/floods/archive?start=2017-06-07T00:00:00%2B0700&end=2017-06-08T23:00:00%2B0700') + .expect(200) + .expect('Content-Type', /json/) + .end(function(err, res) { + if (err) { + test.fail(err.message + ' ' + JSON.stringify(res)); + } else { + done(); + } + }); + }); + + // Can catch no start parameter + it('Required start parameter by default', function(done) { + test.httpAgent(app) + .get('/floods/archive?end=2017-02-22T07:00:00%2B0700') + .expect(400) + .expect('Content-Type', /json/) + .end(function(err, res) { + if (err) { + test.fail(err.message + ' ' + JSON.stringify(res)); + } else { + done(); + } + }); + }); + + // Can catch no end parameter + it('Required end parameter by default', function(done) { + test.httpAgent(app) + .get('/floods/archive?start=2017-02-22T07:00:00%2B0700') + .expect(400) + .expect('Content-Type', /json/) + .end(function(err, res) { + if (err) { + test.fail(err.message + ' ' + JSON.stringify(res)); + } else { + done(); + } + }); + }); + + // Catch end time before start time + it('Required end time to be after start time', function(done) { + test.httpAgent(app) + .get('/reports/archive?start=2017-02-21T07:00:00%2B0700&end=2017-02-20T07:00:00') + .expect(400) + .expect('Content-Type', /json/) + .end(function(err, res) { + if (err) { + test.fail(err.message + ' ' + JSON.stringify(res)); + } else { + done(); + } + }); + }); + + // Can catch no UTC offset in start parameter + it('Required start parameter to have a UTC offset', function(done) { + test.httpAgent(app) + .get('/reports/archive?start=2017-02-21T07:00:00') + .expect(400) + .expect('Content-Type', /json/) + .end(function(err, res) { + if (err) { + test.fail(err.message + ' ' + JSON.stringify(res)); + } else { + done(); + } + }); + }); + + // Can catch no UTC offset in start parameter + it('Catches badly formed time stamp', function(done) { + test.httpAgent(app) + .get('/reports/archive?start=2017-02-21') + .expect(400) + .expect('Content-Type', /json/) + .end(function(err, res) { + if (err) { + test.fail(err.message + ' ' + JSON.stringify(res)); + } else { + done(); + } + }); + }); + + // Catches start - end time window greater than one week + it('Catch large time windows', function(done) { + test.httpAgent(app) + .get('/floods/archive?start=2017-06-07T00:00:00%2B0700&end=2017-06-15T23:00:00%2B0700') + .expect(400) + .expect('Content-Type', /json/) + .end(function(err, res) { + if (err) { + test.fail(err.message + ' ' + JSON.stringify(res)); + } else { + done(); + } + }); + }); + }); +} diff --git a/src/test/testFloodsTimeseries.js b/src/test/testFloodsTimeseries.js new file mode 100644 index 0000000..d282672 --- /dev/null +++ b/src/test/testFloodsTimeseries.js @@ -0,0 +1,127 @@ +/* eslint-disable max-len */ +/** + * testFloodsArchive module + * @module test/testFloodsTimeseries + * A module to test the /floods/timeseries endpoint + */ + +import * as test from 'unit.js'; + +// TODO +// - test against an actual time in the database +// - Entered through cards.js (or add a new call to cards here) + +/** + * Test floods timeseries endpoint + * @function testFloodsTimeseries + * @param {Object} app - CogniCity server app object + */ +export default function(app) { + // Floods endpoint + describe('Floods Archive Endpoint', function() { + // Can get floods between given timestamps + it('Can get floods timeseries given timestamps', function(done) { + test.httpAgent(app) + .get('/floods/timeseries?start=2017-06-07T00:00:00%2B0700&end=2017-06-08T23:00:00%2B0700') + .expect(200) + .expect('Content-Type', /json/) + .end(function(err, res) { + if (err) { + test.fail(err.message + ' ' + JSON.stringify(res)); + } else { + done(); + } + }); + }); + + // Can catch no start parameter + it('Required start parameter by default', function(done) { + test.httpAgent(app) + .get('/floods/timeseries?end=2017-02-22T07:00:00%2B0700') + .expect(400) + .expect('Content-Type', /json/) + .end(function(err, res) { + if (err) { + test.fail(err.message + ' ' + JSON.stringify(res)); + } else { + done(); + } + }); + }); + + // Can catch no end parameter + it('Required end parameter by default', function(done) { + test.httpAgent(app) + .get('/floods/timeseries?start=2017-02-22T07:00:00%2B0700') + .expect(400) + .expect('Content-Type', /json/) + .end(function(err, res) { + if (err) { + test.fail(err.message + ' ' + JSON.stringify(res)); + } else { + done(); + } + }); + }); + + // Catch end time before start time + it('Required end time to be after start time', function(done) { + test.httpAgent(app) + .get('/floods/timeseries?start=2017-02-21T07:00:00%2B0700&end=2017-02-20T07:00:00') + .expect(400) + .expect('Content-Type', /json/) + .end(function(err, res) { + if (err) { + test.fail(err.message + ' ' + JSON.stringify(res)); + } else { + done(); + } + }); + }); + + // Can catch no UTC offset in start parameter + it('Required start parameter to have a UTC offset', function(done) { + test.httpAgent(app) + .get('/floods/timeseries?start=2017-02-21T07:00:00') + .expect(400) + .expect('Content-Type', /json/) + .end(function(err, res) { + if (err) { + test.fail(err.message + ' ' + JSON.stringify(res)); + } else { + done(); + } + }); + }); + + // Can catch no UTC offset in start parameter + it('Catches badly formed time stamp', function(done) { + test.httpAgent(app) + .get('/floods/timeseries?start=2017-02-21') + .expect(400) + .expect('Content-Type', /json/) + .end(function(err, res) { + if (err) { + test.fail(err.message + ' ' + JSON.stringify(res)); + } else { + done(); + } + }); + }); + + // Catches start - end time window greater than one week + it('Catch large time windows', function(done) { + test.httpAgent(app) + .get('/floods/timeseries?start=2017-06-07T00:00:00%2B0700&end=2017-06-15T23:00:00%2B0700') + .expect(400) + .expect('Content-Type', /json/) + .end(function(err, res) { + if (err) { + test.fail(err.message + ' ' + JSON.stringify(res)); + } else { + done(); + } + }); + }); + }); +} diff --git a/src/test/testReportsArchive.js b/src/test/testReportsArchive.js index 4de52c3..f4dd5f6 100644 --- a/src/test/testReportsArchive.js +++ b/src/test/testReportsArchive.js @@ -15,14 +15,14 @@ import * as test from 'unit.js'; * Test reports archive endpoint * @function testReportsArchive * @param {Object} app - CogniCity server app object - * @param {String} createdAt - Sample ISO 8601 timestamp of report to test */ -export default function(app, createdAt) { +export default function(app) { // Reports endpoint describe('Reports Archive Endpoint', function() { // Can get reports between given timestamps - - let end = new Date().toISOString().slice(0, -5)+'Z'; + let end = new Date('2017-06-07T00:00:00+0700'); + end.setHours(end.getHours() + 72); + end = end.toISOString().slice(0, -5)+'Z'; it('Can get reports between given timestamps', function(done) { test.httpAgent(app) @@ -38,6 +38,34 @@ export default function(app, createdAt) { }); }); + it('Can get reports in given city', function(done) { + test.httpAgent(app) + .get('/reports/archive?start=2017-06-07T00:00:00%2B0700&end=2017-06-08T23:00:00%2B0700&city=jbd') + .expect(200) + .expect('Content-Type', /json/) + .end(function(err, res) { + if (err) { + test.fail(err.message + ' ' + JSON.stringify(res)); + } else { + done(); + } + }); + }); + + it('Catches bad city name', function(done) { + test.httpAgent(app) + .get('/reports/archive?start=2017-06-07T00:00:00%2B0700&end=2017-06-08T23:00:00%2B0700&city=123') + .expect(400) + .expect('Content-Type', /json/) + .end(function(err, res) { + if (err) { + test.fail(err.message + ' ' + JSON.stringify(res)); + } else { + done(); + } + }); + }); + it('Can get reports between given timestamps as geojson', function(done) { test.httpAgent(app) .get('/reports/archive?start=2017-06-07T00:00:00%2B0700&end='+end+'&format=json&geoformat=geojson') @@ -146,5 +174,19 @@ export default function(app, createdAt) { } }); }); + // Catches start - end time window greater than one week + it('Catch large time windows', function(done) { + test.httpAgent(app) + .get('/floods/archive?start=2017-06-07T00:00:00%2B0700&end=2017-06-15T23:00:00%2B0700') + .expect(400) + .expect('Content-Type', /json/) + .end(function(err, res) { + if (err) { + test.fail(err.message + ' ' + JSON.stringify(res)); + } else { + done(); + } + }); + }); }); } diff --git a/src/test/testReportsTimeseries.js b/src/test/testReportsTimeseries.js new file mode 100644 index 0000000..a7b1c97 --- /dev/null +++ b/src/test/testReportsTimeseries.js @@ -0,0 +1,156 @@ +/* eslint-disable max-len */ +/** + * testReportsArchive module + * @module test/testReportsTimeseries + * A module to test the /reports/timeseries endpoint + */ + +import * as test from 'unit.js'; + +// TODO +// - test against an actual time in the database +// - Entered through cards.js (or add a new call to cards here) + +/** + * Test floods timeseries endpoint + * @function testReportsTimeseries + * @param {Object} app - CogniCity server app object + */ +export default function(app) { + // Reports endpoint + describe('Reports Archive Endpoint', function() { + // Can get reports between given timestamps + it('Can get floods timeseries given timestamps', function(done) { + test.httpAgent(app) + .get('/reports/timeseries?start=2017-06-07T00:00:00%2B0700&end=2017-06-08T23:00:00%2B0700') + .expect(200) + .expect('Content-Type', /json/) + .end(function(err, res) { + if (err) { + test.fail(err.message + ' ' + JSON.stringify(res)); + } else { + done(); + } + }); + }); + + // Can get reports between given timestamps with city + it('Can get floods timeseries given timestamps', function(done) { + test.httpAgent(app) + .get('/reports/timeseries?start=2017-06-07T00:00:00%2B0700&end=2017-06-08T23:00:00%2B0700&city=jbd') + .expect(200) + .expect('Content-Type', /json/) + .end(function(err, res) { + if (err) { + test.fail(err.message + ' ' + JSON.stringify(res)); + } else { + done(); + } + }); + }); + + // Catches bad city name + it('Catches bad city name', function(done) { + test.httpAgent(app) + .get('/reports/timeseries?start=2017-06-07T00:00:00%2B0700&end=2017-06-08T23:00:00%2B0700&city=123') + .expect(400) + .expect('Content-Type', /json/) + .end(function(err, res) { + if (err) { + test.fail(err.message + ' ' + JSON.stringify(res)); + } else { + done(); + } + }); + }); + + // Can catch no start parameter + it('Required start parameter by default', function(done) { + test.httpAgent(app) + .get('/reports/timeseries?end=2017-02-22T07:00:00%2B0700') + .expect(400) + .expect('Content-Type', /json/) + .end(function(err, res) { + if (err) { + test.fail(err.message + ' ' + JSON.stringify(res)); + } else { + done(); + } + }); + }); + + // Can catch no end parameter + it('Required end parameter by default', function(done) { + test.httpAgent(app) + .get('/reports/timeseries?start=2017-02-22T07:00:00%2B0700') + .expect(400) + .expect('Content-Type', /json/) + .end(function(err, res) { + if (err) { + test.fail(err.message + ' ' + JSON.stringify(res)); + } else { + done(); + } + }); + }); + + // Catch end time before start time + it('Required end time to be after start time', function(done) { + test.httpAgent(app) + .get('/reports/timeseries?start=2017-02-21T07:00:00%2B0700&end=2017-02-20T07:00:00') + .expect(400) + .expect('Content-Type', /json/) + .end(function(err, res) { + if (err) { + test.fail(err.message + ' ' + JSON.stringify(res)); + } else { + done(); + } + }); + }); + + // Can catch no UTC offset in start parameter + it('Required start parameter to have a UTC offset', function(done) { + test.httpAgent(app) + .get('/reports/timeseries?start=2017-02-21T07:00:00') + .expect(400) + .expect('Content-Type', /json/) + .end(function(err, res) { + if (err) { + test.fail(err.message + ' ' + JSON.stringify(res)); + } else { + done(); + } + }); + }); + + // Can catch no UTC offset in start parameter + it('Catches badly formed time stamp', function(done) { + test.httpAgent(app) + .get('/reports/timeseries?start=2017-02-21') + .expect(400) + .expect('Content-Type', /json/) + .end(function(err, res) { + if (err) { + test.fail(err.message + ' ' + JSON.stringify(res)); + } else { + done(); + } + }); + }); + // Catches start - end time window greater than one week + it('Catch large time windows', function(done) { + test.httpAgent(app) + .get('/floods/archive?start=2017-06-07T00:00:00%2B0700&end=2017-06-15T23:00:00%2B0700') + .expect(400) + .expect('Content-Type', /json/) + .end(function(err, res) { + if (err) { + test.fail(err.message + ' ' + JSON.stringify(res)); + } else { + done(); + } + }); + }); + }); +} diff --git a/swagger-api.json b/swagger-api.json index 8fd2049..3bef6f4 100644 --- a/swagger-api.json +++ b/swagger-api.json @@ -1,7 +1,7 @@ { "swagger": "2.0", "info": { - "version": "2017-06-29T01:04:57Z", + "version": "2017-12-04T20:41:17Z", "title": "cognicity" }, "host": "data-dev.petabencana.id", @@ -102,10 +102,10 @@ } } }, + "passthroughBehavior": "when_no_match", "requestTemplates": { "application/json": "{\"statusCode\": 200}" }, - "passthroughBehavior": "when_no_match", "type": "mock" } } @@ -148,10 +148,10 @@ } } }, + "passthroughBehavior": "when_no_match", "requestTemplates": { "application/json": "{\"statusCode\": 200}" }, - "passthroughBehavior": "when_no_match", "type": "mock" } }, @@ -245,10 +245,10 @@ } } }, - "uri": "https://${stageVariables.env}.petabencana.id/cards/{cardId}", "requestParameters": { "integration.request.path.cardId": "method.request.path.cardId" }, + "uri": "https://${stageVariables.env}.petabencana.id/cards/{cardId}", "passthroughBehavior": "when_no_match", "httpMethod": "ANY", "type": "http" @@ -261,6 +261,12 @@ "application/json" ], "parameters": [ + { + "name": "content-type", + "in": "header", + "required": true, + "type": "string" + }, { "name": "cardId", "in": "path", @@ -290,10 +296,11 @@ } } }, - "uri": "https://${stageVariables.env}.petabencana.id/cards/{cardId}/images", "requestParameters": { - "integration.request.path.cardId": "method.request.path.cardId" + "integration.request.path.cardId": "method.request.path.cardId", + "integration.request.header.content-type": "method.request.header.content-type" }, + "uri": "https://${stageVariables.env}.petabencana.id/cards/{cardId}/images", "passthroughBehavior": "when_no_match", "httpMethod": "GET", "type": "http" @@ -306,6 +313,14 @@ "produces": [ "application/json" ], + "parameters": [ + { + "name": "content-type", + "in": "header", + "required": true, + "type": "string" + } + ], "responses": { "200": { "description": "200 response", @@ -336,10 +351,10 @@ } } }, + "passthroughBehavior": "when_no_match", "requestTemplates": { "application/json": "{\"statusCode\": 200}" }, - "passthroughBehavior": "when_no_match", "type": "mock" } } @@ -425,10 +440,10 @@ } } }, + "passthroughBehavior": "when_no_match", "requestTemplates": { "application/json": "{\"statusCode\": 200}" }, - "passthroughBehavior": "when_no_match", "type": "mock" } } @@ -471,10 +486,10 @@ } } }, + "passthroughBehavior": "when_no_match", "requestTemplates": { "application/json": "{\"statusCode\": 200}" }, - "passthroughBehavior": "when_no_match", "type": "mock" } } @@ -554,10 +569,10 @@ } } }, + "passthroughBehavior": "when_no_match", "requestTemplates": { "application/json": "{\"statusCode\": 200}" }, - "passthroughBehavior": "when_no_match", "type": "mock" } } @@ -609,12 +624,12 @@ } } }, - "uri": "https://${stageVariables.env}.petabencana.id/floodgauges", "requestParameters": { "integration.request.querystring.city": "method.request.querystring.city", "integration.request.querystring.geoformat": "method.request.querystring.geoformat", "integration.request.querystring.format": "method.request.querystring.format" }, + "uri": "https://${stageVariables.env}.petabencana.id/floodgauges", "passthroughBehavior": "when_no_match", "httpMethod": "GET", "cacheNamespace": "so358j", @@ -663,10 +678,10 @@ } } }, + "passthroughBehavior": "when_no_match", "requestTemplates": { "application/json": "{\"statusCode\": 200}" }, - "passthroughBehavior": "when_no_match", "type": "mock" } } @@ -709,10 +724,10 @@ } } }, + "passthroughBehavior": "when_no_match", "requestTemplates": { "application/json": "{\"statusCode\": 200}" }, - "passthroughBehavior": "when_no_match", "type": "mock" } }, @@ -741,10 +756,10 @@ "statusCode": "200" } }, - "uri": "https://${stageVariables.env}.petabencana.id/floodgauges/{id}", "requestParameters": { "integration.request.path.id": "method.request.path.id" }, + "uri": "https://${stageVariables.env}.petabencana.id/floodgauges/{id}", "passthroughBehavior": "when_no_match", "httpMethod": "ANY", "cacheNamespace": "p829iq", @@ -814,13 +829,13 @@ } } }, - "uri": "https://${stageVariables.env}.petabencana.id/floods/", "requestParameters": { "integration.request.querystring.city": "method.request.querystring.city", "integration.request.querystring.geoformat": "method.request.querystring.geoformat", "integration.request.querystring.minimum_state": "method.request.querystring.minimum_state", "integration.request.querystring.format": "method.request.querystring.format" }, + "uri": "https://${stageVariables.env}.petabencana.id/floods/", "passthroughBehavior": "when_no_match", "httpMethod": "GET", "cacheNamespace": "nvy9s5", @@ -854,10 +869,10 @@ "statusCode": "200" } }, + "passthroughBehavior": "when_no_match", "requestTemplates": { "application/json": "{\"statusCode\": 200}" }, - "passthroughBehavior": "when_no_match", "type": "mock" } }, @@ -898,10 +913,87 @@ } } }, + "passthroughBehavior": "when_no_match", "requestTemplates": { "application/json": "{\"statusCode\": 200}" }, + "type": "mock" + } + } + }, + "/floods/archive": { + "get": { + "produces": [ + "application/json" + ], + "parameters": [ + { + "name": "start", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "end", + "in": "query", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "200 response", + "schema": { + "$ref": "#/definitions/Empty" + } + } + }, + "x-amazon-apigateway-integration": { + "responses": { + "default": { + "statusCode": "200" + } + }, + "requestParameters": { + "integration.request.querystring.start": "method.request.querystring.start", + "integration.request.querystring.end": "method.request.querystring.end" + }, + "uri": "https://${stageVariables.env}.petabencana.id/reports/archive", "passthroughBehavior": "when_no_match", + "httpMethod": "GET", + "cacheNamespace": "bvx1ea", + "cacheKeyParameters": [ + "method.request.querystring.start", + "method.request.querystring.end" + ], + "type": "http" + } + }, + "options": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "200 response", + "schema": { + "$ref": "#/definitions/Empty" + } + } + }, + "x-amazon-apigateway-integration": { + "responses": { + "default": { + "statusCode": "200" + } + }, + "passthroughBehavior": "when_no_match", + "requestTemplates": { + "application/json": "{\"statusCode\": 200}" + }, "type": "mock" } } @@ -953,12 +1045,12 @@ } } }, - "uri": "https://${stageVariables.env}.petabencana.id/floods/states", "requestParameters": { "integration.request.querystring.city": "method.request.querystring.city", "integration.request.querystring.minimum_state": "method.request.querystring.minimum_state", "integration.request.querystring.format": "method.request.querystring.format" }, + "uri": "https://${stageVariables.env}.petabencana.id/floods/states", "passthroughBehavior": "when_no_match", "httpMethod": "GET", "cacheNamespace": "cxp4qo", @@ -1007,10 +1099,87 @@ } } }, + "passthroughBehavior": "when_no_match", "requestTemplates": { "application/json": "{\"statusCode\": 200}" }, + "type": "mock" + } + } + }, + "/floods/timeseries": { + "get": { + "produces": [ + "application/json" + ], + "parameters": [ + { + "name": "start", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "end", + "in": "query", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "200 response", + "schema": { + "$ref": "#/definitions/Empty" + } + } + }, + "x-amazon-apigateway-integration": { + "responses": { + "default": { + "statusCode": "200" + } + }, + "requestParameters": { + "integration.request.querystring.start": "method.request.querystring.start", + "integration.request.querystring.end": "method.request.querystring.end" + }, + "uri": "https://${stageVariables.env}.petabencana.id/reports/timeseries", "passthroughBehavior": "when_no_match", + "httpMethod": "GET", + "cacheNamespace": "5r93vu", + "cacheKeyParameters": [ + "method.request.querystring.end", + "method.request.querystring.start" + ], + "type": "http" + } + }, + "options": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "200 response", + "schema": { + "$ref": "#/definitions/Empty" + } + } + }, + "x-amazon-apigateway-integration": { + "responses": { + "default": { + "statusCode": "200" + } + }, + "passthroughBehavior": "when_no_match", + "requestTemplates": { + "application/json": "{\"statusCode\": 200}" + }, "type": "mock" } } @@ -1053,10 +1222,10 @@ } } }, + "passthroughBehavior": "when_no_match", "requestTemplates": { "application/json": "{\"statusCode\": 200}" }, - "passthroughBehavior": "when_no_match", "type": "mock" } }, @@ -1085,10 +1254,10 @@ "statusCode": "200" } }, - "uri": "https://${stageVariables.env}.petabencana.id/floods/{id}", "requestParameters": { "integration.request.path.id": "method.request.path.id" }, + "uri": "https://${stageVariables.env}.petabencana.id/floods/{id}", "passthroughBehavior": "when_no_match", "httpMethod": "ANY", "cacheNamespace": "8q9uet", @@ -1138,10 +1307,10 @@ } } }, + "passthroughBehavior": "when_no_match", "requestTemplates": { "application/json": "{\"statusCode\": 200}" }, - "passthroughBehavior": "when_no_match", "type": "mock" } } @@ -1184,10 +1353,10 @@ } } }, + "passthroughBehavior": "when_no_match", "requestTemplates": { "application/json": "{\"statusCode\": 200}" }, - "passthroughBehavior": "when_no_match", "type": "mock" } }, @@ -1228,10 +1397,10 @@ "statusCode": "200" } }, - "uri": "https://${stageVariables.env}.petabencana.id/infrastructure/{type}", "requestParameters": { "integration.request.path.type": "method.request.path.type" }, + "uri": "https://${stageVariables.env}.petabencana.id/infrastructure/{type}", "passthroughBehavior": "when_no_match", "httpMethod": "ANY", "cacheNamespace": "qtdnlp", @@ -1298,13 +1467,13 @@ } } }, - "uri": "https://${stageVariables.env}.petabencana.id/reports", "requestParameters": { "integration.request.querystring.city": "method.request.querystring.city", "integration.request.querystring.geoformat": "method.request.querystring.geoformat", "integration.request.querystring.timeperiod": "method.request.querystring.timeperiod", "integration.request.querystring.format": "method.request.querystring.format" }, + "uri": "https://${stageVariables.env}.petabencana.id/reports", "passthroughBehavior": "when_no_match", "httpMethod": "GET", "cacheNamespace": "8vxlp8", @@ -1354,10 +1523,201 @@ } } }, + "passthroughBehavior": "when_no_match", "requestTemplates": { "application/json": "{\"statusCode\": 200}" }, + "type": "mock" + } + } + }, + "/reports/archive": { + "get": { + "produces": [ + "application/json" + ], + "parameters": [ + { + "name": "start", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "city", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "format", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "geoformat", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "end", + "in": "query", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "200 response", + "schema": { + "$ref": "#/definitions/Empty" + } + } + }, + "x-amazon-apigateway-integration": { + "responses": { + "default": { + "statusCode": "200" + } + }, + "requestParameters": { + "integration.request.querystring.city": "method.request.querystring.city", + "integration.request.querystring.start": "method.request.querystring.start", + "integration.request.querystring.geoformat": "method.request.querystring.geoformat", + "integration.request.querystring.format": "method.request.querystring.format", + "integration.request.querystring.end": "method.request.querystring.end" + }, + "uri": "https://${stageVariables.env}.petabencana.id/reports/archive", "passthroughBehavior": "when_no_match", + "httpMethod": "GET", + "cacheNamespace": "cvt4dg", + "cacheKeyParameters": [ + "method.request.querystring.end", + "method.request.querystring.city", + "method.request.querystring.format", + "method.request.querystring.geoformat", + "method.request.querystring.start" + ], + "type": "http" + } + }, + "options": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "200 response", + "schema": { + "$ref": "#/definitions/Empty" + } + } + }, + "x-amazon-apigateway-integration": { + "responses": { + "default": { + "statusCode": "200" + } + }, + "passthroughBehavior": "when_no_match", + "requestTemplates": { + "application/json": "{\"statusCode\": 200}" + }, + "type": "mock" + } + } + }, + "/reports/timeseries": { + "get": { + "produces": [ + "application/json" + ], + "parameters": [ + { + "name": "start", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "city", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "end", + "in": "query", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "200 response", + "schema": { + "$ref": "#/definitions/Empty" + }, + "headers": { + "Access-Control-Allow-Origin": { + "type": "string" + } + } + } + }, + "x-amazon-apigateway-integration": { + "responses": { + "default": { + "statusCode": "200" + } + }, + "requestParameters": { + "integration.request.querystring.city": "method.request.querystring.city", + "integration.request.querystring.start": "method.request.querystring.start", + "integration.request.querystring.end": "method.request.querystring.end" + }, + "uri": "https://${stageVariables.env}.petabencana.id/reports/timeseries", + "passthroughBehavior": "when_no_match", + "httpMethod": "GET", + "cacheNamespace": "jnnaak", + "cacheKeyParameters": [ + "method.request.querystring.end", + "method.request.querystring.start", + "method.request.querystring.city" + ], + "type": "http" + } + }, + "options": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "200 response", + "schema": { + "$ref": "#/definitions/Empty" + } + } + }, + "x-amazon-apigateway-integration": { + "responses": { + "default": { + "statusCode": "200" + } + }, + "passthroughBehavior": "when_no_match", + "requestTemplates": { + "application/json": "{\"statusCode\": 200}" + }, "type": "mock" } } @@ -1400,10 +1760,10 @@ } } }, + "passthroughBehavior": "when_no_match", "requestTemplates": { "application/json": "{\"statusCode\": 200}" }, - "passthroughBehavior": "when_no_match", "type": "mock" } }, @@ -1432,10 +1792,10 @@ "statusCode": "200" } }, - "uri": "https://${stageVariables.env}.petabencana.id/reports/{id}", "requestParameters": { "integration.request.path.id": "method.request.path.id" }, + "uri": "https://${stageVariables.env}.petabencana.id/reports/{id}", "passthroughBehavior": "when_no_match", "httpMethod": "ANY", "cacheNamespace": "5tep32", @@ -1485,10 +1845,10 @@ } } }, + "passthroughBehavior": "when_no_match", "requestTemplates": { "application/json": "{\"statusCode\": 200}" }, - "passthroughBehavior": "when_no_match", "type": "mock" } } @@ -1564,10 +1924,10 @@ } } }, + "passthroughBehavior": "when_no_match", "requestTemplates": { "application/json": "{\"statusCode\": 200}" }, - "passthroughBehavior": "when_no_match", "type": "mock" } } @@ -1643,10 +2003,10 @@ } } }, + "passthroughBehavior": "when_no_match", "requestTemplates": { "application/json": "{\"statusCode\": 200}" }, - "passthroughBehavior": "when_no_match", "type": "mock" } } @@ -1714,10 +2074,10 @@ } } }, + "passthroughBehavior": "when_no_match", "requestTemplates": { "application/json": "{\"statusCode\": 200}" }, - "passthroughBehavior": "when_no_match", "type": "mock" } } @@ -1756,11 +2116,11 @@ } }, "uri": "arn:aws:apigateway:ap-southeast-1:lambda:path/2015-03-31/functions/arn:aws:lambda:ap-southeast-1:917524458155:function:twilio/invocations", + "passthroughBehavior": "when_no_templates", + "httpMethod": "POST", "requestTemplates": { "application/x-www-form-urlencoded": "{\n \"reqbody\":\"$input.path('$')\"\n}" }, - "passthroughBehavior": "when_no_templates", - "httpMethod": "POST", "contentHandling": "CONVERT_TO_TEXT", "type": "aws" } @@ -1802,10 +2162,10 @@ } } }, + "passthroughBehavior": "when_no_match", "requestTemplates": { "application/json": "{\"statusCode\": 200}" }, - "passthroughBehavior": "when_no_match", "type": "mock" } }