lib/applications/send.js
'use strict';
const moment = require('moment');
const request = require('request');
const database = require('../database');
/*
* @typedef requestEndpoint
* @type object
*
* @property {array} options - options for the Request module
* @property {string} options.url - application endpoint url
* @property {string} options.method - type of request
* @property {object} options.json - request body
* @property {string} options.json.id - client id
* @property {string} options.json.secret - client secret
*/
/*
* @typedef endpointResponse
* @type object
* Contains the response and timestamp for an endpoint request
*
* @property {date} timestamp - timestamp of request
* @property {number} response - statusCode response from request
*/
/*
* @typedef options
* @type object
* Application options is the object passed between each of these functions
*
* @property {string} trigger - one of live/sunset/updated
* @property {array} apps - applications pulled from database
* @property {array} endpoints - endpoints derived from apps (first added by `endpoints` function)
* @property {string} endpoints.id - id of a given app (added by `endpoints` function)
* @property {requestEndpoint} endpoints.options - see requestEndpoint typedef (added by `endpoints` function)
* @property {endpointResponse} endpoints.response - see endpointResponse typedef (added during `send` Promise.resolve)
*/
/**
* Creates an array from all application endpoints that match the trigger
*
* @param {options} options - see options typedef
*
* @returns {options} - see options typedef
*/
const endpoints = options => {
const opts = options;
return new Promise((resolve, reject) => {
if (!Array.isArray(options.apps)) {
reject('Apps must be an array');
}
opts.endpoints = options.apps.map(app => {
if (app[`${options.trigger}-endpoint`] && app[`${options.trigger}-endpoint`] !== '') {
return {
id: app.id,
options: {
url: app[`${options.trigger}-endpoint`],
method: 'POST',
json: {
id: app['client-id'],
secret: app['client-secret'],
},
},
};
}
return false;
}).filter(Boolean);
resolve(opts);
});
};
/*
* Promise wrapper for Request
*/
const req = options => {
return new Promise((res) => {
request(options, (err, response) => {
let status;
if (err) {
res({
response: 500,
timestamp: moment().unix(),
});
}
if (typeof response === 'object' && response.hasOwnProperty('statusCode')) {
status = response.statusCode;
}
res({
response: status || 500,
timestamp: moment().unix(),
});
return true;
});
});
};
/*
* Send request to endpoints
*
* @param {requestEndpoint} options - see requestEndpoint typedef
*
* @returns {endpointResponse} - see endpointResponse typedef
*/
const requests = options => {
const opts = options;
// grabs every promise for every endpoint
const ends = opts.endpoints.map(en => {
const end = en;
return req(end.options).then(resp => {
end.response = resp;
return end;
});
});
return Promise.all(ends).then(result => {
opts.endpoints = result;
return opts;
});
};
/**
* Updates database with responses from each endpoint
*
* @param {options} options - see options typedef
*
* @returns {promise} - knex transactions
*/
const save = options => {
const ids = options.endpoints.map(end => {
return end.id;
}).filter(id => {
if (typeof id === 'undefined') {
return false;
}
return true;
});
return database.transaction(trx => {
return trx('applications')
.select('id', 'responses')
.whereIn('id', ids)
.then(aps => {
const apps = aps.map(app => {
let responses = app.responses;
// make sure responses is an object
if (responses === null || typeof responses !== 'object') {
responses = {};
}
// check that this trigger exists/is array, make an array if not
if (!responses.hasOwnProperty(options.trigger) || !Array.isArray(responses[options.trigger])) {
responses[options.trigger] = [];
}
const endpoint = options.endpoints.find((end) => {
return end.id === app.id;
});
// add response from endpoint
responses[options.trigger].push(endpoint.response);
return trx('applications').where('id', '=', app.id).update('responses', responses).returning(['*']);
});
return Promise.all(apps);
});
});
};
/**
* Send to application endpoints
* @param {options} options - see options typedef
*
* @returns {Promise} - promise results in an array of changed applications
*/
const send = options => {
return Promise.resolve(options)
.then(endpoints)
.then(requests)
.then(save);
};
module.exports = send;
module.exports.endpoints = endpoints;
module.exports.request = req;
module.exports.save = save;