Source: api/routes/floods/index.js

/**
 * CogniCity Server /floods endpoint
 * @module src/api/floods/index
 **/
import {Router} from 'express';

// Import our data model
import floods from './model';

// Import any required utility functions
import {cacheResponse, formatGeo, jwtCheck} from '../../../lib/util';

// Caching
import apicache from 'apicache';
const CACHE_GROUP_FLOODS = '/floods';
const CACHE_GROUP_FLOODS_STATES = '/floods/states';

// Cap formatter helper
import Cap from '../../../lib/cap';

// Import validation dependencies
import Joi from 'joi';
import validate from 'celebrate';

// Rem status codes
const REM_STATES = {
	1: {
		severity: 'Unknown',
		levelDescription: 'AN UNKNOWN LEVEL OF FLOODING - USE CAUTION -',
	},
	2: {
		severity: 'Minor',
		levelDescription: 'FLOODING OF BETWEEN 10 and 70 CENTIMETERS',
	},
	3: {
		severity: 'Moderate',
		levelDescription: 'FLOODING OF BETWEEN 71 and 150 CENTIMETERS',
	},
	4: {
		severity: 'Severe',
		levelDescription: 'FLOODING OF OVER 150 CENTIMETERS',
	},
};

// Function to clear out the cache
const clearCache = () => {
	apicache.clear(CACHE_GROUP_FLOODS);
	apicache.clear(CACHE_GROUP_FLOODS_STATES);
};

/**
 * Endpoint specification for floods data
 * @alias module:src/api/floods/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 reports route
 */
export default ({config, db, logger}) => {
	let api = Router(); // eslint-disable-line new-cap
	const cap = new Cap(logger); // Setup our cap formatter

	// Get a list of all floods
	api.get('/', cacheResponse(config.CACHE_DURATION_FLOODS),
		validate({
			query: {
				city: Joi.any().valid(config.REGION_CODES),
				format: Joi.any().valid(['xml'].concat(config.FORMATS))
								.default(config.FORMAT_DEFAULT),
				geoformat: Joi.any().valid(['cap'].concat(config.GEO_FORMATS))
								.default(config.GEO_FORMAT_DEFAULT),
				minimum_state: Joi.number().integer().valid(Object.keys(REM_STATES)),
			},
		}),
		(req, res, next) => {
			req.apicacheGroup = CACHE_GROUP_FLOODS;
			if (req.query.geoformat === 'cap' && req.query.format !== 'xml') {
				res.status(400).json({statusCode: 400,
										message: 'format must be \'xml\' when geoformat=\'cap\''});
			}	else if (config.GEO_FORMATS.indexOf(req.query.geoformat) > -1
				&& req.query.format !== 'json') {
					res.status(400).json({statusCode: 400,
						message: 'format must be \'json\' when geoformat '
											+'IN (\'geojson\',\'topojson\')'});
			} else {
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
						formatGeo(data, 'geojson')
							.then((formatted) => res.status(200)
								.set('Content-Type', 'text/xml')
								.send(cap.geoJsonToAtomCap(formatted.features)))
							/* istanbul ignore next */
							.catch((err) => next(err)) :
						// Otherwise hand off to geo formatter
						formatGeo(data, req.query.geoformat)
							.then((formatted) => res.status(200)
								.json({statusCode: 200, result: formatted}))
							/* istanbul ignore next */
							.catch((err) => next(err))
				)
				.catch((err) => {
					/* istanbul ignore next */
					logger.error(err);
					/* istanbul ignore next */
					next(err);
				});
}
		}
  );

	// Just get the states without the geographic boundaries
	api.get('/states', cacheResponse(config.CACHE_DURATION_FLOODS_STATES),
		validate({
			query: {
				city: Joi.any().valid(config.REGION_CODES),
				format: Joi.any().valid(config.FORMATS).default(config.FORMAT_DEFAULT),
				minimum_state: Joi.number().integer().valid(Object.keys(REM_STATES)),
			},
		}),
		(req, res, next) => {
			req.apicacheGroup = CACHE_GROUP_FLOODS_STATES;
			floods(config, db, logger).all(req.query.city, req.query.minimum_state)
				.then((data) => res.status(200).json({statusCode: 200, result: data}))
				.catch((err) => {
					/* istanbul ignore next */
					logger.error(err);
					/* istanbul ignore next */
					next(err);
				});
		}
  );

	// Update the flood status of a local area
	api.put('/:localAreaId', jwtCheck,
		validate({
			params: {localAreaId: Joi.number().integer().required()},
			body: Joi.object().keys({
				state: Joi.number().integer()
					.valid(Object.keys(REM_STATES).map((state) => parseInt(state)))
					.required(),
			}),
			query: {
				username: Joi.string().required(),
			},
		}),
		(req, res, next) => floods(config, db, logger)
		.updateREMState(req.params.localAreaId, req.body.state, req.query.username)
			.then(() => {
				clearCache();
				res.status(200).json({localAreaId: req.params.localAreaId,
					state: req.body.state, updated: true});
			})
			/* istanbul ignore next */
			.catch((err) => {
				/* istanbul ignore next */
				logger.error(err);
				/* istanbul ignore next */
				next(err);
			})
  );

	// Remove the flood status of a local and add a log entry for audit
	api.delete('/:localAreaId', jwtCheck,
		validate({
			params: {localAreaId: Joi.number().integer().required()},
			query: {
				username: Joi.string().required(),
			},
		}),
		(req, res, next) => floods(config, db, logger)
			.clearREMState(req.params.localAreaId, req.query.username)
			.then(() => {
				clearCache();
				res.status(200).json({localAreaId: req.params.localAreaId,
					state: null, updated: true});
			})
			/* istanbul ignore next */
			.catch((err) => {
				/* istanbul ignore next */
				logger.error(err);
				/* istanbul ignore next */
				next(err);
			})
	);

	return api;
};