Skip to content

Commit

Permalink
Merge pull request #41 from martin-krcmar/1.0.4
Browse files Browse the repository at this point in the history
New stuff
  • Loading branch information
waldez authored Dec 18, 2018
2 parents e3bdcb6 + 303f056 commit cdd93f0
Show file tree
Hide file tree
Showing 5 changed files with 326 additions and 17 deletions.
6 changes: 4 additions & 2 deletions appmixer-lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ module.exports = {
db: require('./db/db'),
redis: require('./db/redis'),
lock: {
mutex: require('./lock/mutex')
mutex: require('./lock/mutex'),
method: require('./lock/method')
},
util: {
array: require('./util/array'),
Expand All @@ -15,6 +16,7 @@ module.exports = {
component: require('./util/component'),
flow: require('./util/flow'),
PagingAggregator: require('./util/paging-aggregator'),
promise: require('./util/promise')
promise: require('./util/promise'),
commons: require('./util/commons')
}
};
17 changes: 3 additions & 14 deletions db/db.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,6 @@ module.exports.ObjectID = ObjectID;
/**
* Connect to Mongo DB.
* @param {Object} connection
* @param {string} connection.host
* @param {number} connection.port
* @param {string} connection.dbName
* @param {string} connection.uri
* @param {string} connection.sslCAPath
* @param {boolean} connection.sslValidate
Expand All @@ -42,15 +39,7 @@ module.exports.connect = async function(connection) {
}

check.assert.object(connection, 'Invalid connection object.');
if (connection.uri) {
check.assert.string(connection.uri, 'Invalid connection.uri');
} else {
check.assert.string(connection.host, 'Invalid connection.host.');
check.assert.number(connection.port, 'Invalid connection.port.');
check.assert.string(connection.dbName, 'Invalid connection.dbName.');
}

let uri = connection.uri || 'mongodb://' + connection.host + ':' + connection.port + '/' + connection.dbName;
check.assert.string(connection.uri, 'Invalid connection.uri');

let options = {
promiseLibrary: Promise,
Expand All @@ -68,9 +57,9 @@ module.exports.connect = async function(connection) {
}
}

console.log('Connecting to Mongo with URI: ' + uri);
console.log('Connecting to Mongo with URI: ' + connection.uri);

const client = await MongoClient.connect(uri, options);
const client = await MongoClient.connect(connection.uri, options);
db = client.db();
return db;
};
48 changes: 48 additions & 0 deletions lock/method.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
'use strict';
const Promise = require('bluebird');
const check = require('check-types');

/**
* This class provides func to execute function only once (when called multiple times from
* various resources) and return the same result to all callers.
*/
class Method {

constructor() {

this.inProgress = false;
this.callbacks = [];
}

/**
* @param {function} func
* @return {Promise<void>}
* @public
*/
async call(func) {

check.assert.function(func, 'Invalid function.');

if (this.inProgress) {
return new Promise((resolve, reject) => {
this.callbacks.push({ resolve, reject });
});
}

try {
this.inProgress = true;
const result = await func();
this.inProgress = false;
while (this.callbacks.length > 0) {
this.callbacks.pop().resolve(result);
}
} catch (err) {
this.inProgress = false;
while (this.callbacks.length > 0) {
this.callbacks.pop().reject(err);
}
}
}
}

module.exports = Method;
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@
"bluebird": "^3.5.1",
"boom": "^7.2.0",
"check-types": "^7.1.5",
"content-type": "^1.0.4",
"handlebars": "^4.0.5",
"metrohash": "^2.3.0",
"moment": "^2.22.2",
"mongodb": "^3.1.1",
"redis": "^2.8.0",
"request": "^2.67.0"
"request": "^2.88.0"
},
"devDependencies": {
"chai": "^4.0.2",
Expand Down
268 changes: 268 additions & 0 deletions util/commons.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
'use strict';
const request = require('request');
const contentTypeUtil = require('content-type');
const urlUtil = require('url');
const moment = require('moment');

function createPathItem(key, path, delimiter = '.') {

const value = (path ? path + delimiter : '') + key;
return { label: value, value };
}

function getPathsFromJson(json, parentPath, outArray = [], delimiter = '.') {

for (let key in json) {
let value = json[key];
let item = createPathItem(key, parentPath);

outArray.push(item);

if (typeof value === 'object') {
getPathsFromJson(value, parentPath ? parentPath + delimiter + key : key, outArray);
} else {
item.data = value;
}
}

return outArray;
}

function getByPath(obj, path, delim) {

if (typeof path === 'undefined') {
return obj;
}
delim = delim || '.';
path = path.replace(/\.\[/g, delim);
path = path.replace(/\[/g, delim);
path = path.replace(/\]/g, '');
var keys = path.split(delim);
var key;

while (keys.length) {
key = keys.shift();
if (Object(obj) === obj && key in obj) {
obj = obj[key];
} else {
return undefined;
}
}
return obj;
}

// http request helpers

/**
* Converts header property 'content-type' value to more readable json.
* @param {string} value
* @return {{ type: string, parameters: Object }}
*/
function parseContentType(value) {

try {
return contentTypeUtil.parse(value);
} catch (error) {
return { type: undefined };
}
}

/**
* Callback which processes http response in such way, the result should be sent through our messanging system.
* @param {function} resolve
* @param {function} reject
* @param {?Error} error
* @param {Object} response
* @param {string|Object|Buffer} body
* @return {Object}
*/
function processResponse(resolve, reject, error, response, body) {

if (error) {
reject(error);
return;
}

const json = response.toJSON();
const contentType = parseContentType(json.headers['content-type']);
json.headers['content-type'] = contentType;

if (Buffer.isBuffer(body)) {
json.body = body.toString('base64');
}

if (contentType.type && contentType.type.toLowerCase() === 'application/json') {
try {
json.body = JSON.parse(json.body);
} catch (parseErr) {
// noop;
}
}

resolve(json);
}

/**
* Builds options for request
* @param {Object} options
* @return {{ options: Object, errors: Array.<Error> }}
*/
function buildRequestOptions(options) {

let errors = [];
try {
var url = urlUtil.parse(options.url);
} catch (error) {
errors.push(new Error('Message property \'url\' parse error. ' + error.message));
}

try {
var headers = typeof options.headers == 'string' ? JSON.parse(options.headers) : options.headers;
} catch (error) {
errors.push(new Error('Message property \'headers\' parse error. ' + error.message));
}

let body = options.body;
if (options.bodyBase64Encode && body) {
try {
body = Buffer.from(body, 'base64');
} catch (error) {
errors.push(new Error('Message property \'body\' parse base64 error. ' + error.message));
}
}

let encoding = options.responseEncoding;

let json = {
url,
headers,
body,
encoding
};

if (typeof body == 'object') {
json.json = true;
}

return { options: json, errors };
}

/**
* Return true if the string passed as the only argument can be converted into a number.
* @param {string} text Any text.
* @return {boolean}
*/
function isNumber(text) {

return !isNaN(Number(text));
}

/**
* Convert text to number.
* @param {string} text Any text.
* @return {number}
*/
function toNumber(text) {

return Number(text);
}

/**
* Return true if the string passed as the only argument can be converted into a date.
* @param {string} text Any text.
* @return {boolean}
*/
function isDate(text) {

return moment(text).isValid();
}

/**
* Return true if the first date is before the second date.
* @param {string} a Date represented as string.
* @param {string} b Date represented as string.
* @return {boolean}
*/
function isDateBefore(a, b) {

return moment(a).isBefore(moment(b));
}

/**
* Return true if the first date is after the second date.
* @param {string} a Date represented as string.
* @param {string} b Date represented as string.
* @return {boolean}
*/
function isDateAfter(a, b) {

return moment(a).isAfter(moment(b));
}

/**
* Return true if the first date is the same as the second date.
* @param {string} a Date represented as string.
* @param {string} b Date represented as string.
* @return {boolean}
*/
function isDateSame(a, b) {

return moment(a).isSame(moment(b));
}

/**
* Promisified http request.
* @param {string} method - POST, DELETE, PUT, GET
* @param {{
* url: String,
* body: String,
* bodyBase64Encode: Boolean,
* headers: String,
* responseEncoding: String
* }} json options
* @return {Promise}
*/
function requestPromisified(method, json) {

let { options, errors } = buildRequestOptions(json);
return new Promise((resolve, reject) => {

if (errors.length > 0) {
// log all errors
return reject(JSON.stringify(errors, null, '\t'));
}
options.method = method;
request(options, processResponse.bind(null, resolve, reject));
});
}

/**
* This is used in NewXXX component types to get array of new items by comparing all
* downloaded (through API) items against collection of already known (processed) items.
* @param {Set} knownItems
* @param {Array} currentItems
* @param {Array} newItems
* @param item
* @param getId
*/
function processItem(knownItems, currentItems, newItems, getId, item) {

if (knownItems && !knownItems.has(getId(item))) {
newItems.push(item);
}

currentItems.push(getId(item));
}

module.exports = {
getPathsFromJson,
getByPath,
request: requestPromisified,
isNumber,
toNumber,
isDate,
isDateBefore,
isDateAfter,
isDateSame,
processItem
};

0 comments on commit cdd93f0

Please sign in to comment.