diff --git a/.travis.yml b/.travis.yml index 0b8233f..141aee8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -60,7 +60,7 @@ deploy: #build the docs for dev branch only zip_file: dist.zip on: branch: dev - - provider: elasticbeanstalk # deploy the dev us server + - provider: elasticbeanstalk # deploy the prod us server access_key_id: $AWS_ACCESS_KEY_ID secret_access_key: $AWS_SECRET_ACCESS_KEY region: $AWS_EB_REGION_US @@ -69,5 +69,27 @@ deploy: #build the docs for dev branch only bucket_name: $AWS_EB_APP_BUCKET_US_PROD skip_cleanup: true zip_file: dist.zip + on: + branch: master + - provider: elasticbeanstalk # deploy the dev in server + access_key_id: $AWS_ACCESS_KEY_ID + secret_access_key: $AWS_SECRET_ACCESS_KEY + region: $AWS_EB_REGION_IN + app: $AWS_EB_APP_IN + env: $AWS_EB_APP_ENV_IN_DEV + bucket_name: $AWS_EB_APP_BUCKET_IN_DEV + skip_cleanup: true + zip_file: dist.zip + on: + branch: dev + - provider: elasticbeanstalk # deploy the prod in server + access_key_id: $AWS_ACCESS_KEY_ID + secret_access_key: $AWS_SECRET_ACCESS_KEY + region: $AWS_EB_REGION_IN + app: $AWS_EB_APP_IN + env: $AWS_EB_APP_ENV_IN_PROD + bucket_name: $AWS_EB_APP_BUCKET_IN_PROD + skip_cleanup: true + zip_file: dist.zip on: branch: master \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 825f46c..20aad1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,4 +52,10 @@ API Server for CogniCity * Remove ?format option from /reports endpoint * Change floods endpoint to return new attributes column * Requires cognicity-schema v3.0.7 or later -* Updated npm packages \ No newline at end of file +* Updated npm packages + +### v3.1.0 +* Add city query parameter to /floods/timeseries and /floods/archive endpoints +* Add time zone validation to all endpoints accepting timestamps +* Notifications of received reports are now run from the server (added for testing in v3.0.6) +* Add deployments for India diff --git a/README.md b/README.md index 48e86d6..d26de67 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ A few points to note on config: Run `npm run -s build` to build. ### Testing -Testing is run by [Travis](https://travis-ci.org/urbanriskmap/cognicity-server). ESLint runs to check syntax. Integration tests, formed by chaining unit tests, are used to check the API. Coverage is provided by Istanbul and [Coveralls](https://coveralls.io/github/urbanriskmap/cognicity-server). See src/test/ for scripts. Beware that integration tests may pollute tables (e.g. user tables in feeds), it is not recommended to run tests against prod databases with live data. The default database (used for testing) is cognicity. Travis-ci creates a new schema instance for testing using https://github.com/urbanriskmap/cognicity-schema, see .travis.yml for more details. +Testing is run by [Travis](https://travis-ci.org/urbanriskmap/cognicity-server). ESLint runs to check syntax. Integration tests, formed by chaining unit tests, are used to check the API. Coverage is provided by Istanbul and [Coveralls](https://coveralls.io/github/urbanriskmap/cognicity-server). See src/test/ for scripts. Beware that integration tests may pollute tables (e.g. user tables in feeds), it is not recommended to run tests against prod databases with live data. The default database (used for testing) is cognicity. Travis-ci creates a new schema instance for testing using https://github.com/urbanriskmap/cognicity-schema, see the .travis.yml file for more details. To run tests locally a new database "cognicity_server_testing" is required on localhost. diff --git a/apigw/pb/swagger.json b/apigw/id/swagger.json similarity index 100% rename from apigw/pb/swagger.json rename to apigw/id/swagger.json diff --git a/apigw/rm/swagger.json b/apigw/rm/swagger.json index 78c1fbf..5dc5e82 100644 --- a/apigw/rm/swagger.json +++ b/apigw/rm/swagger.json @@ -4,7 +4,7 @@ "version": "2017-05-11T05:43:00Z", "title": "cognicity" }, - "host": "data.riskmap.in", + "host": "data-dev.riskmap.in", "schemes": [ "https" ], @@ -23,12 +23,12 @@ } }, "x-amazon-apigateway-integration": { + "uri": "https://${stageVariables.env}.riskmap.in", "responses": { "default": { "statusCode": "200" } }, - "uri": "https://${stageVariables.env}.riskmap.in", "passthroughBehavior": "when_no_match", "httpMethod": "GET", "type": "http_proxy" @@ -54,12 +54,12 @@ } ], "x-amazon-apigateway-integration": { + "uri": "https://${stageVariables.env}.riskmap.in/cards", "responses": { "default": { "statusCode": "200" } }, - "uri": "https://${stageVariables.env}.riskmap.in/cards", "passthroughBehavior": "when_no_match", "httpMethod": "POST", "type": "http_proxy" @@ -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" } }, @@ -213,6 +213,7 @@ } }, "x-amazon-apigateway-integration": { + "uri": "https://${stageVariables.env}.riskmap.in/cards/{cardId}", "responses": { "400": { "statusCode": "400", @@ -248,7 +249,6 @@ "requestParameters": { "integration.request.path.cardId": "method.request.path.cardId" }, - "uri": "https://${stageVariables.env}.riskmap.in/cards/{cardId}", "passthroughBehavior": "when_no_match", "httpMethod": "ANY", "type": "http" @@ -288,6 +288,7 @@ } }, "x-amazon-apigateway-integration": { + "uri": "https://${stageVariables.env}.riskmap.in/cards/{cardId}/images", "responses": { "default": { "statusCode": "200", @@ -300,7 +301,6 @@ "integration.request.path.cardId": "method.request.path.cardId", "integration.request.header.content-type": "method.request.header.content-type" }, - "uri": "https://${stageVariables.env}.riskmap.in/cards/{cardId}/images", "passthroughBehavior": "when_no_match", "httpMethod": "GET", "type": "http" @@ -343,10 +343,10 @@ } } }, - "passthroughBehavior": "when_no_match", "requestTemplates": { "application/json": "{\"statusCode\": 200}" }, + "passthroughBehavior": "when_no_match", "type": "mock" } } @@ -379,12 +379,12 @@ } }, "x-amazon-apigateway-integration": { + "uri": "https://${stageVariables.env}.riskmap.in/cities", "responses": { "default": { "statusCode": "200" } }, - "uri": "https://${stageVariables.env}.riskmap.in/cities", "passthroughBehavior": "when_no_match", "httpMethod": "GET", "cacheNamespace": "h1la88", @@ -432,10 +432,10 @@ } } }, - "passthroughBehavior": "when_no_match", "requestTemplates": { "application/json": "{\"statusCode\": 200}" }, + "passthroughBehavior": "when_no_match", "type": "mock" } } @@ -478,10 +478,10 @@ } } }, - "passthroughBehavior": "when_no_match", "requestTemplates": { "application/json": "{\"statusCode\": 200}" }, + "passthroughBehavior": "when_no_match", "type": "mock" } } @@ -510,6 +510,7 @@ } ], "x-amazon-apigateway-integration": { + "uri": "https://${stageVariables.env}.riskmap.in/feeds/qlue", "responses": { "default": { "statusCode": "200", @@ -518,7 +519,6 @@ } } }, - "uri": "https://${stageVariables.env}.riskmap.in/feeds/qlue", "passthroughBehavior": "when_no_match", "httpMethod": "POST", "type": "http" @@ -561,10 +561,10 @@ } } }, - "passthroughBehavior": "when_no_match", "requestTemplates": { "application/json": "{\"statusCode\": 200}" }, + "passthroughBehavior": "when_no_match", "type": "mock" } } @@ -608,6 +608,7 @@ } }, "x-amazon-apigateway-integration": { + "uri": "https://${stageVariables.env}.riskmap.in/floodgauges", "responses": { "default": { "statusCode": "200", @@ -621,7 +622,6 @@ "integration.request.querystring.geoformat": "method.request.querystring.geoformat", "integration.request.querystring.format": "method.request.querystring.format" }, - "uri": "https://${stageVariables.env}.riskmap.in/floodgauges", "passthroughBehavior": "when_no_match", "httpMethod": "GET", "cacheNamespace": "so358j", @@ -670,10 +670,10 @@ } } }, - "passthroughBehavior": "when_no_match", "requestTemplates": { "application/json": "{\"statusCode\": 200}" }, + "passthroughBehavior": "when_no_match", "type": "mock" } } @@ -716,10 +716,10 @@ } } }, - "passthroughBehavior": "when_no_match", "requestTemplates": { "application/json": "{\"statusCode\": 200}" }, + "passthroughBehavior": "when_no_match", "type": "mock" } }, @@ -740,6 +740,7 @@ ], "responses": {}, "x-amazon-apigateway-integration": { + "uri": "https://${stageVariables.env}.riskmap.in/floodgauges/{id}", "responses": { "default": { "statusCode": "200" @@ -748,7 +749,6 @@ "requestParameters": { "integration.request.path.id": "method.request.path.id" }, - "uri": "https://${stageVariables.env}.riskmap.in/floodgauges/{id}", "passthroughBehavior": "when_no_match", "httpMethod": "ANY", "cacheNamespace": "p829iq", @@ -808,6 +808,7 @@ } }, "x-amazon-apigateway-integration": { + "uri": "https://${stageVariables.env}.riskmap.in/floods/", "responses": { "default": { "statusCode": "200", @@ -823,7 +824,6 @@ "integration.request.querystring.minimum_state": "method.request.querystring.minimum_state", "integration.request.querystring.format": "method.request.querystring.format" }, - "uri": "https://${stageVariables.env}.riskmap.in/floods/", "passthroughBehavior": "when_no_match", "httpMethod": "GET", "cacheNamespace": "nvy9s5", @@ -857,10 +857,10 @@ "statusCode": "200" } }, - "passthroughBehavior": "when_no_match", "requestTemplates": { "application/json": "{\"statusCode\": 200}" }, + "passthroughBehavior": "when_no_match", "type": "mock" } }, @@ -901,10 +901,10 @@ } } }, - "passthroughBehavior": "when_no_match", "requestTemplates": { "application/json": "{\"statusCode\": 200}" }, + "passthroughBehavior": "when_no_match", "type": "mock" } } @@ -948,6 +948,7 @@ } }, "x-amazon-apigateway-integration": { + "uri": "https://${stageVariables.env}.riskmap.in/floods/states", "responses": { "default": { "statusCode": "200", @@ -961,7 +962,6 @@ "integration.request.querystring.minimum_state": "method.request.querystring.minimum_state", "integration.request.querystring.format": "method.request.querystring.format" }, - "uri": "https://${stageVariables.env}.riskmap.in/floods/states", "passthroughBehavior": "when_no_match", "httpMethod": "GET", "cacheNamespace": "cxp4qo", @@ -1010,10 +1010,10 @@ } } }, - "passthroughBehavior": "when_no_match", "requestTemplates": { "application/json": "{\"statusCode\": 200}" }, + "passthroughBehavior": "when_no_match", "type": "mock" } } @@ -1056,10 +1056,10 @@ } } }, - "passthroughBehavior": "when_no_match", "requestTemplates": { "application/json": "{\"statusCode\": 200}" }, + "passthroughBehavior": "when_no_match", "type": "mock" } }, @@ -1080,6 +1080,7 @@ ], "responses": {}, "x-amazon-apigateway-integration": { + "uri": "https://${stageVariables.env}.riskmap.in/floods/{id}", "responses": { "default": { "statusCode": "200" @@ -1088,7 +1089,6 @@ "requestParameters": { "integration.request.path.id": "method.request.path.id" }, - "uri": "https://${stageVariables.env}.riskmap.in/floods/{id}", "passthroughBehavior": "when_no_match", "httpMethod": "ANY", "cacheNamespace": "8q9uet", @@ -1138,10 +1138,10 @@ } } }, - "passthroughBehavior": "when_no_match", "requestTemplates": { "application/json": "{\"statusCode\": 200}" }, + "passthroughBehavior": "when_no_match", "type": "mock" } } @@ -1184,10 +1184,10 @@ } } }, - "passthroughBehavior": "when_no_match", "requestTemplates": { "application/json": "{\"statusCode\": 200}" }, + "passthroughBehavior": "when_no_match", "type": "mock" } }, @@ -1220,6 +1220,7 @@ ], "responses": {}, "x-amazon-apigateway-integration": { + "uri": "https://${stageVariables.env}.riskmap.in/infrastructure/{type}", "responses": { "default": { "statusCode": "200" @@ -1228,7 +1229,6 @@ "requestParameters": { "integration.request.path.type": "method.request.path.type" }, - "uri": "https://${stageVariables.env}.riskmap.in/infrastructure/{type}", "passthroughBehavior": "when_no_match", "httpMethod": "ANY", "cacheNamespace": "qtdnlp", @@ -1287,6 +1287,7 @@ } }, "x-amazon-apigateway-integration": { + "uri": "https://${stageVariables.env}.riskmap.in/reports", "responses": { "default": { "statusCode": "200", @@ -1301,7 +1302,6 @@ "integration.request.querystring.timeperiod": "method.request.querystring.timeperiod", "integration.request.querystring.format": "method.request.querystring.format" }, - "uri": "https://${stageVariables.env}.riskmap.in/reports", "passthroughBehavior": "when_no_match", "httpMethod": "GET", "cacheNamespace": "8vxlp8", @@ -1351,19 +1351,16 @@ } } }, - "passthroughBehavior": "when_no_match", "requestTemplates": { "application/json": "{\"statusCode\": 200}" }, + "passthroughBehavior": "when_no_match", "type": "mock" } } }, - "/reports/{id+}": { - "options": { - "consumes": [ - "application/json" - ], + "/reports/archive": { + "get": { "produces": [ "application/json" ], @@ -1372,76 +1369,21 @@ "description": "200 response", "schema": { "$ref": "#/definitions/Empty" - }, - "headers": { - "Access-Control-Allow-Origin": { - "type": "string" - }, - "Access-Control-Allow-Methods": { - "type": "string" - }, - "Access-Control-Allow-Headers": { - "type": "string" - } } } }, "x-amazon-apigateway-integration": { - "responses": { - "default": { - "statusCode": "200", - "responseParameters": { - "method.response.header.Access-Control-Allow-Methods": "'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'", - "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'", - "method.response.header.Access-Control-Allow-Origin": "'*'" - } - } - }, - "passthroughBehavior": "when_no_match", - "requestTemplates": { - "application/json": "{\"statusCode\": 200}" - }, - "type": "mock" - } - }, - "x-amazon-apigateway-any-method": { - "parameters": [ - { - "name": "format", - "in": "query", - "required": false, - "type": "string" - }, - { - "name": "id", - "in": "path", - "required": true, - "type": "string" - } - ], - "responses": {}, - "x-amazon-apigateway-integration": { + "uri": "https://${stageVariables.env}.riskmap.in/reports/archive", "responses": { "default": { "statusCode": "200" } }, - "requestParameters": { - "integration.request.path.id": "method.request.path.id" - }, - "uri": "https://${stageVariables.env}.riskmap.in/reports/{id}", "passthroughBehavior": "when_no_match", - "httpMethod": "ANY", - "cacheNamespace": "5tep32", - "cacheKeyParameters": [ - "method.request.path.id", - "method.request.querystring.format" - ], + "httpMethod": "GET", "type": "http_proxy" } - } - }, - "/stats": { + }, "options": { "consumes": [ "application/json" @@ -1479,15 +1421,15 @@ } } }, - "passthroughBehavior": "when_no_match", "requestTemplates": { "application/json": "{\"statusCode\": 200}" }, + "passthroughBehavior": "when_no_match", "type": "mock" } } }, - "/stats/floodedRWsSummary": { + "/reports/timeseries": { "get": { "produces": [ "application/json" @@ -1497,28 +1439,19 @@ "description": "200 response", "schema": { "$ref": "#/definitions/Empty" - }, - "headers": { - "Access-Control-Allow-Origin": { - "type": "string" - } } } }, "x-amazon-apigateway-integration": { + "uri": "https://${stageVariables.env}.riskmap.us/reports/timeseries", "responses": { "default": { - "statusCode": "200", - "responseParameters": { - "method.response.header.Access-Control-Allow-Origin": "'*'" - } + "statusCode": "200" } }, - "uri": "arn:aws:apigateway:ap-south-1:lambda:path/2015-03-31/functions/arn:aws:lambda:ap-south-1:917524458155:function:cognicity-statistics-${stageVariables.stage}-floodedRWsSummary/invocations", "passthroughBehavior": "when_no_match", - "httpMethod": "POST", - "contentHandling": "CONVERT_TO_TEXT", - "type": "aws_proxy" + "httpMethod": "GET", + "type": "http_proxy" } }, "options": { @@ -1552,103 +1485,32 @@ "default": { "statusCode": "200", "responseParameters": { - "method.response.header.Access-Control-Allow-Methods": "'GET,OPTIONS'", - "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'", + "method.response.header.Access-Control-Allow-Methods": "'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'", + "method.response.header.Access-Control-Allow-Headers": "'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'", "method.response.header.Access-Control-Allow-Origin": "'*'" } } }, - "passthroughBehavior": "when_no_match", "requestTemplates": { "application/json": "{\"statusCode\": 200}" }, + "passthroughBehavior": "when_no_match", "type": "mock" } } }, - "/stats/floodedRegionsSummary": { + "/{id}": { "get": { "produces": [ "application/json" ], - "responses": { - "200": { - "description": "200 response", - "schema": { - "$ref": "#/definitions/Empty" - }, - "headers": { - "Access-Control-Allow-Origin": { - "type": "string" - } - } - } - }, - "x-amazon-apigateway-integration": { - "responses": { - "default": { - "statusCode": "200", - "responseParameters": { - "method.response.header.Access-Control-Allow-Origin": "'*'" - } - } - }, - "uri": "arn:aws:apigateway:ap-south-1:lambda:path/2015-03-31/functions/arn:aws:lambda:ap-south-1:917524458155:function:cognicity-statistics-${stageVariables.stage}-floodedRegionsSummary/invocations", - "passthroughBehavior": "when_no_match", - "httpMethod": "POST", - "contentHandling": "CONVERT_TO_TEXT", - "type": "aws_proxy" - } - }, - "options": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "responses": { - "200": { - "description": "200 response", - "schema": { - "$ref": "#/definitions/Empty" - }, - "headers": { - "Access-Control-Allow-Origin": { - "type": "string" - }, - "Access-Control-Allow-Methods": { - "type": "string" - }, - "Access-Control-Allow-Headers": { - "type": "string" - } - } + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string" } - }, - "x-amazon-apigateway-integration": { - "responses": { - "default": { - "statusCode": "200", - "responseParameters": { - "method.response.header.Access-Control-Allow-Methods": "'GET,OPTIONS'", - "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'", - "method.response.header.Access-Control-Allow-Origin": "'*'" - } - } - }, - "passthroughBehavior": "when_no_match", - "requestTemplates": { - "application/json": "{\"statusCode\": 200}" - }, - "type": "mock" - } - } - }, - "/stats/reportsSummary": { - "get": { - "produces": [ - "application/json" ], "responses": { "200": { @@ -1659,16 +1521,18 @@ } }, "x-amazon-apigateway-integration": { + "uri": "https://${stageVariables.env}.riskmap.in/reports/{id}", "responses": { "default": { "statusCode": "200" } }, - "uri": "arn:aws:apigateway:ap-south-1:lambda:path/2015-03-31/functions/arn:aws:lambda:ap-south-1:917524458155:function:cognicity-statistics-${stageVariables.stage}-reportsSummary/invocations", + "requestParameters": { + "integration.request.path.id": "method.request.path.id" + }, "passthroughBehavior": "when_no_match", - "httpMethod": "POST", - "contentHandling": "CONVERT_TO_TEXT", - "type": "aws_proxy" + "httpMethod": "GET", + "type": "http_proxy" } }, "options": { @@ -1708,99 +1572,46 @@ } } }, - "passthroughBehavior": "when_no_match", "requestTemplates": { "application/json": "{\"statusCode\": 200}" }, + "passthroughBehavior": "when_no_match", "type": "mock" } - } - }, - "/twilio": { - "post": { - "consumes": [ - "application/x-www-form-urlencoded" - ], - "produces": [ - "text/xml" - ], - "responses": { - "200": { - "description": "200 response", - "schema": { - "$ref": "#/definitions/Empty" - }, - "headers": { - "Access-Control-Allow-Origin": { - "type": "string" - } - } - } - }, - "x-amazon-apigateway-integration": { - "responses": { - ".*": { - "statusCode": "200", - "responseParameters": { - "method.response.header.Access-Control-Allow-Origin": "'*'" - }, - "responseTemplates": { - "text/xml": "#set ($errorMessageObj = $util.parseJson($input.path('$.errorMessage')))\n$errorMessageObj.message\n" - } - } - }, - "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}" - }, - "contentHandling": "CONVERT_TO_TEXT", - "type": "aws" - } }, - "options": { - "consumes": [ - "application/json" - ], + "patch": { "produces": [ "application/json" ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string" + } + ], "responses": { "200": { "description": "200 response", "schema": { "$ref": "#/definitions/Empty" - }, - "headers": { - "Access-Control-Allow-Origin": { - "type": "string" - }, - "Access-Control-Allow-Methods": { - "type": "string" - }, - "Access-Control-Allow-Headers": { - "type": "string" - } } } }, "x-amazon-apigateway-integration": { + "uri": "https://${stageVariables.env}.riskmap.in/reports/{id}", "responses": { "default": { - "statusCode": "200", - "responseParameters": { - "method.response.header.Access-Control-Allow-Methods": "'POST,OPTIONS'", - "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'", - "method.response.header.Access-Control-Allow-Origin": "'*'" - } + "statusCode": "200" } }, - "passthroughBehavior": "when_no_match", - "requestTemplates": { - "application/json": "{\"statusCode\": 200}" + "requestParameters": { + "integration.request.path.id": "method.request.path.id" }, - "type": "mock" + "passthroughBehavior": "when_no_match", + "httpMethod": "PATCH", + "type": "http_proxy" } } } diff --git a/apigw/br/swagger.json b/apigw/us/swagger.json similarity index 100% rename from apigw/br/swagger.json rename to apigw/us/swagger.json diff --git a/package.json b/package.json index 3670553..7aa11a3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cognicity-server", - "version": "3.0.6", + "version": "3.1.0", "description": "Data Server for CogniCity", "main": "dist", "engines": { diff --git a/src/api/routes/cards/index.js b/src/api/routes/cards/index.js index bb93b93..c053411 100644 --- a/src/api/routes/cards/index.js +++ b/src/api/routes/cards/index.js @@ -14,7 +14,9 @@ import {cacheResponse, handleResponse} from '../../../lib/util'; import Notify from '../../../lib/notify'; // Import validation dependencies -import Joi from 'joi'; +import BaseJoi from 'joi'; +import Extension from 'joi-date-extensions'; +const Joi = BaseJoi.extend(Extension); import validate from 'celebrate'; // Import image upload capabilities @@ -130,7 +132,8 @@ export default ({config, db, logger}) => { }), text: Joi.string().allow(''), image_url: Joi.string().allow(''), - created_at: Joi.date().iso().required(), + created_at: Joi.alternatives(Joi.date().format('YYYY-MM-DDTHH:mm:ssZ'), + Joi.date().format('YYYY-MM-DDTHH:mm:ss.SSSZ')).required(), location: Joi.object().required().keys({ lat: Joi.number().min(-90).max(90).required(), lng: Joi.number().min(-180).max(180).required(), diff --git a/src/api/routes/feeds/index.js b/src/api/routes/feeds/index.js index 6c96677..797c275 100644 --- a/src/api/routes/feeds/index.js +++ b/src/api/routes/feeds/index.js @@ -8,7 +8,9 @@ import feeds from './model'; // Import validation dependencies -import Joi from 'joi'; +import BaseJoi from 'joi'; +import Extension from 'joi-date-extensions'; +const Joi = BaseJoi.extend(Extension); import validate from 'celebrate'; /** @@ -28,7 +30,7 @@ export default ({config, db, logger}) => { api.post('/qlue', validate({ body: Joi.object().keys({ post_id: Joi.number().integer().required(), - created_at: Joi.date().iso().required(), + created_at: Joi.date().format('YYYY-MM-DDTHH:mm:ssZ').required(), title: Joi.string().allow(''), text: Joi.string().allow('').required(), image_url: Joi.string(), @@ -57,7 +59,7 @@ export default ({config, db, logger}) => { api.post('/detik', validate({ body: Joi.object().keys({ contribution_id: Joi.number().integer().required(), - created_at: Joi.date().iso().required(), + created_at: Joi.date().format('YYYY-MM-DDTHH:mm:ssZ').required(), title: Joi.string().allow(''), text: Joi.string().allow('').required(), url: Joi.string().allow(''), diff --git a/src/api/routes/floods/archive/index.js b/src/api/routes/floods/archive/index.js index 67fed71..7d03013 100644 --- a/src/api/routes/floods/archive/index.js +++ b/src/api/routes/floods/archive/index.js @@ -34,6 +34,8 @@ export default ({config, db, logger}) => { 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.alternatives().try(Joi.string(), + Joi.any().valid(null)).default(null, 'city'), }, }), (req, res, next) => { @@ -53,7 +55,8 @@ export default ({config, db, logger}) => { ]}}); return; } - archive(config, db, logger).maxstate(req.query.start, req.query.end) + archive(config, db, logger).maxstate(req.query.start, req.query.end, + req.query.city) .then((data) => res.status(200).json({statusCode: 200, result: data})) .catch((err) => { /* istanbul ignore next */ diff --git a/src/api/routes/floods/archive/model.js b/src/api/routes/floods/archive/model.js index bf0c369..320cbb2 100644 --- a/src/api/routes/floods/archive/model.js +++ b/src/api/routes/floods/archive/model.js @@ -15,13 +15,21 @@ export default (config, db, logger) => ({ // Get max state of all flood reports over time - maxstate: (start, end) => new Promise((resolve, reject) => { + maxstate: (start, end, city) => 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)`; + let query = `SELECT + mf.local_area AS area_id, + mf.changed AS last_updated, + mf.max_state + FROM + cognicity.rem_get_max_flood($1, $2) AS mf, + ${config.TABLE_LOCAL_AREAS} AS la + WHERE + mf.local_area = la.pkey AND + ($3 IS NULL OR la.instance_region_code = $3)`; // Setup values - let values = [start, end]; + let values = [start, end, city]; // Execute logger.debug(query, values); @@ -35,3 +43,4 @@ export default (config, db, logger) => ({ }); }), }); + diff --git a/src/api/routes/floods/index.js b/src/api/routes/floods/index.js index d36bb58..e66c071 100644 --- a/src/api/routes/floods/index.js +++ b/src/api/routes/floods/index.js @@ -87,7 +87,8 @@ export default ({config, db, logger}) => { message: 'format must be \'json\' when geoformat ' +'IN (\'geojson\',\'topojson\')'}); } else { -floods(config, db, logger).allGeo(req.query.city, req.query.minimum_state) + floods(config, db, logger).allGeo(req.query.city, + req.query.minimum_state) .then((data) => req.query.geoformat === 'cap' ? // If CAP format has been required convert to geojson then to CAP @@ -110,7 +111,7 @@ floods(config, db, logger).allGeo(req.query.city, req.query.minimum_state) /* istanbul ignore next */ next(err); }); -} + } } ); diff --git a/src/api/routes/floods/model.js b/src/api/routes/floods/model.js index 3c52b46..efa414a 100644 --- a/src/api/routes/floods/model.js +++ b/src/api/routes/floods/model.js @@ -14,7 +14,7 @@ */ export default (config, db, logger) => ({ - // Get all flood reports for a given city + // Get all flooded areas for a given city all: (city, minimumState) => new Promise((resolve, reject) => { // Setup query let query = `SELECT local_area as area_id, state, last_updated @@ -38,7 +38,7 @@ export default (config, db, logger) => ({ }); }), - // Get all flood reports for a given city + // Get all flooded areas for a given city allGeo: (city, minimumState) => new Promise((resolve, reject) => { // Setup query let query = `SELECT la.the_geom, la.pkey as area_id, la.geom_id, diff --git a/src/api/routes/floods/timeseries/index.js b/src/api/routes/floods/timeseries/index.js index 0c1da00..0d9a187 100644 --- a/src/api/routes/floods/timeseries/index.js +++ b/src/api/routes/floods/timeseries/index.js @@ -34,6 +34,8 @@ export default ({config, db, logger}) => { 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.alternatives().try(Joi.string(), + Joi.any().valid(null)).default(null, 'city'), }, }), (req, res, next) => { @@ -54,7 +56,8 @@ export default ({config, db, logger}) => { return; } - timeseries(config, db, logger).count(req.query.start, req.query.end) + timeseries(config, db, logger) + .count(req.query.start, req.query.end, req.query.city) .then((data) => res.status(200).json({statusCode: 200, result: data})) .catch((err) => { /* istanbul ignore next */ diff --git a/src/api/routes/floods/timeseries/model.js b/src/api/routes/floods/timeseries/model.js index a6e9e25..8a191b6 100644 --- a/src/api/routes/floods/timeseries/model.js +++ b/src/api/routes/floods/timeseries/model.js @@ -15,17 +15,24 @@ export default (config, db, logger) => ({ // Get all flood reports for a given city - count: (start, end) => new Promise((resolve, reject) => { + count: (start, end, city) => 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(date_trunc('hour', $1::timestamp with time zone), + let query = `SELECT series.ts, count(series.local_area) + FROM + (SELECT (cognicity.rem_get_flood(ts)).local_area, ts + FROM + generate_series(date_trunc('hour', $1::timestamp with time zone), date_trunc('hour', $2::timestamp with time zone),'1 hour') - as series(ts)) output - GROUP BY ts ORDER BY ts`; + AS series(ts)) AS series, + ${config.TABLE_LOCAL_AREAS} AS la + WHERE + series.local_area = la.pkey AND + ($3 IS NULL OR la.instance_region_code = $3) + GROUP BY series.ts + ORDER BY series.ts`; // Setup values - let values = [start, end]; + let values = [start, end, city]; // Execute logger.debug(query, values); diff --git a/src/api/routes/infrastructure/model.js b/src/api/routes/infrastructure/model.js index 3fcfdca..1484bd9 100644 --- a/src/api/routes/infrastructure/model.js +++ b/src/api/routes/infrastructure/model.js @@ -16,7 +16,7 @@ export default (config, db, logger) => ({ // A list of all infrastructure matching a given type all: (city, type) => new Promise((resolve, reject) => { // Setup query - let query = `SELECT name, the_geom + let query = `SELECT name, tags, the_geom FROM infrastructure.${type} WHERE ($1 IS NULL OR tags->>'instance_region_code'=$1)`; diff --git a/src/api/routes/reports/archive/index.js b/src/api/routes/reports/archive/index.js index 8613323..cb12f66 100644 --- a/src/api/routes/reports/archive/index.js +++ b/src/api/routes/reports/archive/index.js @@ -14,7 +14,6 @@ import {cacheResponse, handleGeoResponse} from '../../../../lib/util'; import BaseJoi from 'joi'; import Extension from 'joi-date-extensions'; const Joi = BaseJoi.extend(Extension); - import validate from 'celebrate'; /** * Methods to get historic flood reports from database diff --git a/src/config.js b/src/config.js index f84a2fc..03f4971 100644 --- a/src/config.js +++ b/src/config.js @@ -44,7 +44,7 @@ export default { IMAGES_BUCKET: process.env.IMAGES_BUCKET || 'petabencana-image-uploads', IMAGES_HOST: process.env.IMAGES_HOST || 'images.petabencana.id', IMAGE_MIME_TYPES: (process.env.IMAGE_MIME_TYPES || 'image/png,image/jpeg,image/gif').split(','), - INFRASTRUCTURE_TYPES: (process.env.INFRASTRUCTURE_TYPES || 'floodgates,pumps,waterways').split(','), + INFRASTRUCTURE_TYPES: (process.env.INFRASTRUCTURE_TYPES || 'basins,floodgates,pumps,sites,waterways').split(','), LANGUAGES: (process.env.LANGUAGES || 'en,id').split(','), LOG_CONSOLE: process.env.LOG_CONSOLE === 'true' || false, LOG_DIR: process.env.LOG_DIR || '', diff --git a/src/test/testCards.js b/src/test/testCards.js index fdcae19..0b35584 100644 --- a/src/test/testCards.js +++ b/src/test/testCards.js @@ -15,7 +15,7 @@ import * as test from 'unit.js'; export default function(app, createdAt) { // Cards endpoint describe('Cards endpoint', function() { - // Cards + // Cards 404 error handling it('Return 404 if card requested without ID (GET /cards)', function(done) { test.httpAgent(app) .get('/cards') @@ -30,7 +30,7 @@ export default function(app, createdAt) { }); }); - // Can get reports + // Cards 400 card ID error handling it('Return 400 if card ID is invalid (GET /cards/:id)', function(done) { test.httpAgent(app) .get('/cards/1') @@ -104,6 +104,34 @@ export default function(app, createdAt) { }); }); + // Request a card, catch time zone error + it('Put card data', function(done) { + test.httpAgent(app) + .put('/cards/'+cardId) + .send({ + 'disaster_type': 'flood', + 'card_data': { + 'flood_depth': 20, + 'report_type': 'flood', + }, + 'text': 'integration testing', + 'created_at': '2017-11-01T00:00', + 'location': { + 'lat': -6.4, + 'lng': 106.6, + }, + }) + .expect(400) + .expect('Content-Type', /json/) + .end(function(err, res) { + if (err) { + test.fail(err.message + ' ' + JSON.stringify(res)); + } else { + done(); + } + }); + }); + // Request a card, submit and get resulting report it('Put card data', function(done) { test.httpAgent(app) diff --git a/src/test/testFeeds.js b/src/test/testFeeds.js index d93c9ed..a2fd4aa 100644 --- a/src/test/testFeeds.js +++ b/src/test/testFeeds.js @@ -20,7 +20,7 @@ export default function(app) { .post('/feeds/qlue') .send({ 'post_id': '9999', - 'created_at': '2017-06-07T14:32+0700', + 'created_at': '2017-06-07T14:32:00+0700', 'qlue_city': 'surabaya', 'disaster_type': 'flood', 'text': 'test report', @@ -46,7 +46,7 @@ export default function(app) { .post('/feeds/qlue') .send({ 'post_id': '9999', - 'created_at': '2017-06-07T14:32+0700', + 'created_at': '2017-06-07T14:32:00+0700', 'qlue_city': 'surabaya', 'disaster_type': 'flood', 'text': 'test report', @@ -73,7 +73,7 @@ export default function(app) { .post('/feeds/detik') .send({ 'contribution_id': '9999', - 'created_at': '2017-06-07T14:32+0700', + 'created_at': '2017-06-07T14:32:00+0700', 'disaster_type': 'flood', 'location': { 'latitude': 45, @@ -98,7 +98,7 @@ export default function(app) { .post('/feeds/detik') .send({ 'contribution_id': '9999', - 'created_at': '2017-06-07T14:32+0700', + 'created_at': '2017-06-07T14:32:00+0700', 'disaster_type': 'flood', 'location': { 'latitude': 45,