Skip to content

Commit

Permalink
Merge pull request #67 from urbanriskmap/dev
Browse files Browse the repository at this point in the history
Merge dev to form v3.0.3
  • Loading branch information
tomasholderness authored Dec 5, 2017
2 parents 486613c + 8cdaa50 commit 282045f
Show file tree
Hide file tree
Showing 24 changed files with 1,266 additions and 53 deletions.
15 changes: 14 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]`)
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cognicity-server",
"version": "3.0.2",
"version": "3.0.3",
"description": "Data Server for CogniCity",
"main": "dist",
"engines": {
Expand Down
16 changes: 14 additions & 2 deletions src/api/routes/cards/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
);
}),

Expand Down
67 changes: 67 additions & 0 deletions src/api/routes/floods/archive/index.js
Original file line number Diff line number Diff line change
@@ -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;
};
37 changes: 37 additions & 0 deletions src/api/routes/floods/archive/model.js
Original file line number Diff line number Diff line change
@@ -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);
});
}),
});
10 changes: 9 additions & 1 deletion src/api/routes/floods/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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;
};
68 changes: 68 additions & 0 deletions src/api/routes/floods/timeseries/index.js
Original file line number Diff line number Diff line change
@@ -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;
};
41 changes: 41 additions & 0 deletions src/api/routes/floods/timeseries/model.js
Original file line number Diff line number Diff line change
@@ -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);
});
}),
});
22 changes: 20 additions & 2 deletions src/api/routes/reports/archive/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,33 @@ 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) => {
/* istanbul ignore next */
logger.error(err);
/* istanbul ignore next */
next(err);
})
});
}
);

return api;
Expand Down
6 changes: 4 additions & 2 deletions src/api/routes/reports/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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'),
Expand All @@ -75,6 +78,5 @@ export default ({config, db, logger}) => {
})
);


return api;
};
Loading

0 comments on commit 282045f

Please sign in to comment.