Source: routes/v1.1.js

'use strict';

/**
 * Router for TTN HTTP integrations API v1 and this API v1
 * @module routes/v1_1
 * @license MIT
 */

const router = require('express').Router(),
  color = require('console-control-strings').color,
  { Box } = require('openSenseMapAPI').models,
  decodeJSON = require('openSenseMapAPI').decoding.json.decodeMessage,
  decoder = require('../decoding'),
  cfg = require('../../config');

/**
 * send responses & do logging
 * @private
 * @param {Request} req
 * @param {Response} res
 * @param {Object} data - Format: either String or { code: Number, msg: Any }
 */
const handleResponse = function handleResponse (req, res, data) {
  // handle undhandled errors
  if (!data.code) {
    data = { code: 501, msg: data };
  }

  if (data.code >= cfg.loglevelHTTPcodes[cfg.loglevel]) {
    let codeString = `${color('red')}${data.code}${color('reset')}`;
    if (data.code < cfg.loglevelHTTPcodes['warn']) {
      codeString = `${color('green')}${data.code}${color('reset')}`;
    } else if (data.code < cfg.loglevelHTTPcodes['error']) {
      codeString = `${color('yellow')}${data.code}${color('reset')}`;
    }

    const responseTime = new Date().getTime() - req.time.getTime(),
      boxInfo = {
        dev_id: req.body.dev_id,
        port: req.body.port,
        box: req.box ? req.box._id.toString() : 'unkown',
        profile: req.body.payload_fields ? 'JSON' : req.box ? req.box.integrations.ttn.profile : 'unkown'
      };

    console.log(`  ${codeString} (${responseTime}ms)\t${JSON.stringify(boxInfo)}\n  ${data.msg}\n`);
  }

  return res.status(data.code).json(data);
};

/**
 * Accepts a POST request from the TTN HTTP integrations uplink API, version 1
 * as specified {@link https://www.thethingsnetwork.org/docs/applications/http/|here},
 * and decodes it's payload to store a set of measurements for a box.
 * The box is identified by it's registered values app_id and dev_id.
 * If a box specifies a port, it will only recieve measurements sent on that port.
 * @name 'POST /v1.1'
 * @example
 * curl -X POST -H "content-type: application/json" -d \
 *   '{ "app_id": "asdf", "dev_id": "qwerty", "payload_raw": "kzIrIYzlOycAMgEA" }' \
 *   localhost:3000/v1.1
 */
router.post('/', (req, res) => {
  const { app_id, dev_id, payload_raw, payload_fields, port } = req.body;
  let time;

  // extract time from payload: use time from gateway if available, else use servertime
  if (req.body.metadata) {
    time = req.body.metadata.time;
    if (req.body.metadata.gateways && req.body.metadata.gateways[0].time) {
      time = req.body.metadata.gateways[0].time;
    }
  }

  if (!dev_id || !app_id || !(payload_raw || payload_fields)) {
    return handleResponse(req, res, {
      code: 422,
      msg: 'malformed request: any of [dev_id, app_id, payload_fields, payload_raw] is missing'
    });
  }

  // look up box for dev_id & app_id in DB
  Box.find({
    'integrations.ttn.app_id': app_id,
    'integrations.ttn.dev_id': dev_id
  }).catch(msg => Promise.reject({ code: 501, msg }))
  .then(boxes => {
    if (!boxes.length) {
      return Promise.reject({ code: 404, msg: `no box found for dev_id '${dev_id}' and app_id '${app_id}'` });
    }

    // filter the boxes by their configured port. also include boxes with undefined port.
    req.box = boxes.filter(box => {
      const p = box.integrations.ttn.port;

      return (p === port || p === undefined);
    })[0];

    if (!req.box) {
      return Promise.reject({ code: 404, msg: `no box found for port ${port}` });
    }

    let promise;
    if (payload_fields && Object.keys(payload_fields).length) {
      promise = decodeJSON(payload_fields);
    } else {
      promise = decoder.decodeBase64(payload_raw, req.box, time);
    }

    return promise.catch(msg => Promise.reject({ code: 422, msg }));
  })

  // store measurements in DB
  .then(measurements => {
    return req.box.saveMeasurementsArray(measurements)
      .catch(msg => Promise.reject({ code: 501, msg }));
  })

  .then(() => handleResponse(req, res, { code: 201, msg: 'measurements created' }))

  // handle any error passed in
  .catch(err => handleResponse(req, res, err));
});

module.exports = router;