Source: api/routes/cards/index.js

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

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

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

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

// Import ID generator
import shortid from 'shortid';

// Import image upload capabilities
import AWS from 'aws-sdk';

// Caching
import apicache from 'apicache';
const CACHE_GROUP_CARDS = '/cards';

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

/**
* CogniCity Server /cards endpoint
* @alias module:src/api/cards/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 cards route
**/
export default ({config, db, logger}) => {
  // Router
  let api = Router(); // eslint-disable-line new-cap

  // Create an S3 object
  let s3 = new AWS.S3(
    {
      accessKeyId: config.AWS_S3_ACCESS_KEY_ID,
      secretAccessKey: config.AWS_S3_SECRET_ACCESS_KEY,
      signatureVersion: config.AWS_S3_SIGNATURE_VERSION,
      region: config.AWS_REGION,
    });

  // Create a new card and if successful return generated cardId
  api.post('/',
    validate({
      body: Joi.object().keys({
        username: Joi.string().required(),
        network: Joi.string().required(),
        language: Joi.string().valid(config.LANGUAGES).required(),
      }),
    }),
    (req, res, next) => {
      let cardId = shortid.generate();
      cards(config, db, logger).create(cardId, req.body)
        .then((data) => data ? res.status(200)
          .json({cardId: cardId, created: true}) :
          next(new Error('Failed to create card')))
        .catch((err) => {
          /* istanbul ignore next */
          logger.error(err);
          /* istanbul ignore next */
          next(err);
        });
    }
  );

  // Check for the existence of a card
  api.head('/:cardId', cacheResponse(config.CACHE_DURATION_CARDS),
    validate({
      params: {cardId: Joi.string().required()},
    }),
    (req, res, next) => {
      req.apicacheGroup = CACHE_GROUP_CARDS;
      cards(config, db, logger).byCardId(req.params.cardId)
        .then((data) => data ? res.status(200).end() : res.status(404).end())
        .catch((err) => {
          /* istanbul ignore next */
          logger.error(err);
          /* istanbul ignore next */
          next(err);
        });
    }
  );

  // Return a card
  api.get('/:cardId', cacheResponse(config.CACHE_DURATION_CARDS),
    validate({
      params: {cardId: Joi.string().min(7).max(14).required()},
    }),
    (req, res, next) => {
      req.apicacheGroup = CACHE_GROUP_CARDS;
      cards(config, db, logger).byCardId(req.params.cardId)
        .then((data) => handleResponse(data, req, res, next))
        .catch((err) => {
          /* istanbul ignore next */
          logger.error(err);
          /* istanbul ignore next */
          next(err);
        });
    }
  );

  // Update a card record with a report
  api.put('/:cardId', validate({
    params: {cardId: Joi.string().min(7).max(14).required()},
    body: Joi.object().keys({
      disaster_type: Joi.string().valid(config.DISASTER_TYPES).required(),
      card_data: Joi.object()
        .keys({
            flood_depth: Joi.number(),
            report_type: Joi.string().valid(config.REPORT_TYPES).required(),
        })
        .required()
        .when('disaster_type', {
            is: 'flood',
            then: Joi.object({flood_depth: Joi.number().integer().min(0)
              .max(200).required()}),    // b.c is required only when a is true
        }),
      text: Joi.string().allow(''),
      image_url: Joi.string().allow(''),
      created_at: Joi.date().iso().required(),
      location: Joi.object().required().keys({
        lat: Joi.number().min(-90).max(90).required(),
        lng: Joi.number().min(-180).max(180).required(),
      }),
    }),
  }),
  (req, res, next) => {
    try {
      // First get the card we wish to update
      cards(config, db, logger).byCardId(req.params.cardId)
        .then((card) => {
          // If the card does not exist then return an error message
          if (!card) {
            res.status(404).json({statusCode: 404, cardId: req.params.cardId,
            message: `No card exists with id '${req.params.cardId}'`});
          } else if (card && card.received) {
            // If card already has received status then return an error message
            res.status(409).json({statusCode: 409,
            cardId: req.params.cardId, message: `Report already received for '+
              ' card '${req.params.cardId}'`});
          } else {
            // We have a card and it has not yet had a report received
            // Try and submit the report and update the card
            cards(config, db, logger).submitReport(card, req.body)
              .then((data) => {
                clearCache();
                res.status(200).json({statusCode: 200,
                  cardId: req.params.cardId, created: true});
              })
              .catch((err) => {
                /* istanbul ignore next */
                logger.error(err);
                /* istanbul ignore next */
                next(err);
              });
          }
        });
      } catch (err) {
        /* istanbul ignore next */
        logger.error(err);
        /* istanbul ignore next */
        next(err);
      }
    }
  );

  // Gives an s3 signed url for the frontend to upload an image to
  api.get('/:cardId/images', validate({
    params: {cardId: Joi.string().min(7).max(14).required()},
  }),
  (req, res, next) => {
    // first, check card exists
    cards(config, db, logger).byCardId(req.params.cardId)
      .then((card) => {
        if (!card) {
          // Card was not found, return error
          res.status(404).json({statusCode: 404, cardId: req.params.cardId,
          message: `No card exists with id '${req.params.cardId}'`});
        } else {
          // Provide client with signed url for this card
          let s3params = {
            Bucket: config.IMAGES_BUCKET,
            Key: 'originals/' + req.params.cardId + '.jpg',
            ContentType: req.query.file_type,
          };
          // Call AWS S3 library
          s3.getSignedUrl('putObject', s3params, (err, data) => {
            let returnData;
            if (err) {
              /* istanbul ignore next */
              logger.error('could not get signed url from S3');
              /* istanbul ignore next */
              logger.error(err);
              returnData = {statusCode: 500, error: err};
            } else {
              returnData = {
                signedRequest: data,
                url: 'https://s3.'+config.AWS_REGION+'.amazonaws.com/'
                      + config.IMAGES_BUCKET+'/'+ s3params.Key,
              };
              // Return signed URL
              clearCache();
              logger.debug( 's3 signed request: ' + returnData.signedRequest);
              res.write(JSON.stringify(returnData));
              res.end();
            }
          });
        }
      });
  });

  // Update a card report with new details including the image URL
  api.patch('/:cardId', validate({
    params: {cardId: Joi.string().min(7).max(14).required()},
    body: Joi.object().keys({
      image_url: Joi.string().required(),
    }),
  }),
  (req, res, next) => {
    try {
      // First get the card we wish to update
      cards(config, db, logger).byCardId(req.params.cardId)
        .then((card) => {
          // If the card does not exist then return an error message
          if (!card) {
            res.status(404).json({statusCode: 404, cardId: req.params.cardId,
            message: `No card exists with id '${req.params.cardId}'`});
          } else { // We have a card
            // Verify that we can add an image to card report
            if ( card.received === false || card.report.image_url !== null ) {
              res.status(403).json({statusCode: 403,
                error: 'Card report not received or image exists already'});
            } else {
              // Try and submit the report and update the card
              cards(config, db, logger).updateReport(card, req.body)
                .then((data) => {
                  clearCache();
                  res.status(200).json({statusCode: 200,
                    cardId: req.params.cardId, updated: true});
                })
                .catch((err) => {
                  /* istanbul ignore next */
                  logger.error(err);
                  /* istanbul ignore next */
                  next(err);
                });
            }
          }
        });
      } catch (err) {
        /* istanbul ignore next */
        logger.error(err);
        /* istanbul ignore next */
        next(err);
      }
    }
  );
  return api;
};