Skip to content

Commit cdd93f0

Browse files
authored
Merge pull request #41 from martin-krcmar/1.0.4
New stuff
2 parents e3bdcb6 + 303f056 commit cdd93f0

File tree

5 files changed

+326
-17
lines changed

5 files changed

+326
-17
lines changed

appmixer-lib.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ module.exports = {
55
db: require('./db/db'),
66
redis: require('./db/redis'),
77
lock: {
8-
mutex: require('./lock/mutex')
8+
mutex: require('./lock/mutex'),
9+
method: require('./lock/method')
910
},
1011
util: {
1112
array: require('./util/array'),
@@ -15,6 +16,7 @@ module.exports = {
1516
component: require('./util/component'),
1617
flow: require('./util/flow'),
1718
PagingAggregator: require('./util/paging-aggregator'),
18-
promise: require('./util/promise')
19+
promise: require('./util/promise'),
20+
commons: require('./util/commons')
1921
}
2022
};

db/db.js

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,6 @@ module.exports.ObjectID = ObjectID;
2222
/**
2323
* Connect to Mongo DB.
2424
* @param {Object} connection
25-
* @param {string} connection.host
26-
* @param {number} connection.port
27-
* @param {string} connection.dbName
2825
* @param {string} connection.uri
2926
* @param {string} connection.sslCAPath
3027
* @param {boolean} connection.sslValidate
@@ -42,15 +39,7 @@ module.exports.connect = async function(connection) {
4239
}
4340

4441
check.assert.object(connection, 'Invalid connection object.');
45-
if (connection.uri) {
46-
check.assert.string(connection.uri, 'Invalid connection.uri');
47-
} else {
48-
check.assert.string(connection.host, 'Invalid connection.host.');
49-
check.assert.number(connection.port, 'Invalid connection.port.');
50-
check.assert.string(connection.dbName, 'Invalid connection.dbName.');
51-
}
52-
53-
let uri = connection.uri || 'mongodb://' + connection.host + ':' + connection.port + '/' + connection.dbName;
42+
check.assert.string(connection.uri, 'Invalid connection.uri');
5443

5544
let options = {
5645
promiseLibrary: Promise,
@@ -68,9 +57,9 @@ module.exports.connect = async function(connection) {
6857
}
6958
}
7059

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

73-
const client = await MongoClient.connect(uri, options);
62+
const client = await MongoClient.connect(connection.uri, options);
7463
db = client.db();
7564
return db;
7665
};

lock/method.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
'use strict';
2+
const Promise = require('bluebird');
3+
const check = require('check-types');
4+
5+
/**
6+
* This class provides func to execute function only once (when called multiple times from
7+
* various resources) and return the same result to all callers.
8+
*/
9+
class Method {
10+
11+
constructor() {
12+
13+
this.inProgress = false;
14+
this.callbacks = [];
15+
}
16+
17+
/**
18+
* @param {function} func
19+
* @return {Promise<void>}
20+
* @public
21+
*/
22+
async call(func) {
23+
24+
check.assert.function(func, 'Invalid function.');
25+
26+
if (this.inProgress) {
27+
return new Promise((resolve, reject) => {
28+
this.callbacks.push({ resolve, reject });
29+
});
30+
}
31+
32+
try {
33+
this.inProgress = true;
34+
const result = await func();
35+
this.inProgress = false;
36+
while (this.callbacks.length > 0) {
37+
this.callbacks.pop().resolve(result);
38+
}
39+
} catch (err) {
40+
this.inProgress = false;
41+
while (this.callbacks.length > 0) {
42+
this.callbacks.pop().reject(err);
43+
}
44+
}
45+
}
46+
}
47+
48+
module.exports = Method;

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@
1010
"bluebird": "^3.5.1",
1111
"boom": "^7.2.0",
1212
"check-types": "^7.1.5",
13+
"content-type": "^1.0.4",
1314
"handlebars": "^4.0.5",
1415
"metrohash": "^2.3.0",
16+
"moment": "^2.22.2",
1517
"mongodb": "^3.1.1",
1618
"redis": "^2.8.0",
17-
"request": "^2.67.0"
19+
"request": "^2.88.0"
1820
},
1921
"devDependencies": {
2022
"chai": "^4.0.2",

util/commons.js

Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
'use strict';
2+
const request = require('request');
3+
const contentTypeUtil = require('content-type');
4+
const urlUtil = require('url');
5+
const moment = require('moment');
6+
7+
function createPathItem(key, path, delimiter = '.') {
8+
9+
const value = (path ? path + delimiter : '') + key;
10+
return { label: value, value };
11+
}
12+
13+
function getPathsFromJson(json, parentPath, outArray = [], delimiter = '.') {
14+
15+
for (let key in json) {
16+
let value = json[key];
17+
let item = createPathItem(key, parentPath);
18+
19+
outArray.push(item);
20+
21+
if (typeof value === 'object') {
22+
getPathsFromJson(value, parentPath ? parentPath + delimiter + key : key, outArray);
23+
} else {
24+
item.data = value;
25+
}
26+
}
27+
28+
return outArray;
29+
}
30+
31+
function getByPath(obj, path, delim) {
32+
33+
if (typeof path === 'undefined') {
34+
return obj;
35+
}
36+
delim = delim || '.';
37+
path = path.replace(/\.\[/g, delim);
38+
path = path.replace(/\[/g, delim);
39+
path = path.replace(/\]/g, '');
40+
var keys = path.split(delim);
41+
var key;
42+
43+
while (keys.length) {
44+
key = keys.shift();
45+
if (Object(obj) === obj && key in obj) {
46+
obj = obj[key];
47+
} else {
48+
return undefined;
49+
}
50+
}
51+
return obj;
52+
}
53+
54+
// http request helpers
55+
56+
/**
57+
* Converts header property 'content-type' value to more readable json.
58+
* @param {string} value
59+
* @return {{ type: string, parameters: Object }}
60+
*/
61+
function parseContentType(value) {
62+
63+
try {
64+
return contentTypeUtil.parse(value);
65+
} catch (error) {
66+
return { type: undefined };
67+
}
68+
}
69+
70+
/**
71+
* Callback which processes http response in such way, the result should be sent through our messanging system.
72+
* @param {function} resolve
73+
* @param {function} reject
74+
* @param {?Error} error
75+
* @param {Object} response
76+
* @param {string|Object|Buffer} body
77+
* @return {Object}
78+
*/
79+
function processResponse(resolve, reject, error, response, body) {
80+
81+
if (error) {
82+
reject(error);
83+
return;
84+
}
85+
86+
const json = response.toJSON();
87+
const contentType = parseContentType(json.headers['content-type']);
88+
json.headers['content-type'] = contentType;
89+
90+
if (Buffer.isBuffer(body)) {
91+
json.body = body.toString('base64');
92+
}
93+
94+
if (contentType.type && contentType.type.toLowerCase() === 'application/json') {
95+
try {
96+
json.body = JSON.parse(json.body);
97+
} catch (parseErr) {
98+
// noop;
99+
}
100+
}
101+
102+
resolve(json);
103+
}
104+
105+
/**
106+
* Builds options for request
107+
* @param {Object} options
108+
* @return {{ options: Object, errors: Array.<Error> }}
109+
*/
110+
function buildRequestOptions(options) {
111+
112+
let errors = [];
113+
try {
114+
var url = urlUtil.parse(options.url);
115+
} catch (error) {
116+
errors.push(new Error('Message property \'url\' parse error. ' + error.message));
117+
}
118+
119+
try {
120+
var headers = typeof options.headers == 'string' ? JSON.parse(options.headers) : options.headers;
121+
} catch (error) {
122+
errors.push(new Error('Message property \'headers\' parse error. ' + error.message));
123+
}
124+
125+
let body = options.body;
126+
if (options.bodyBase64Encode && body) {
127+
try {
128+
body = Buffer.from(body, 'base64');
129+
} catch (error) {
130+
errors.push(new Error('Message property \'body\' parse base64 error. ' + error.message));
131+
}
132+
}
133+
134+
let encoding = options.responseEncoding;
135+
136+
let json = {
137+
url,
138+
headers,
139+
body,
140+
encoding
141+
};
142+
143+
if (typeof body == 'object') {
144+
json.json = true;
145+
}
146+
147+
return { options: json, errors };
148+
}
149+
150+
/**
151+
* Return true if the string passed as the only argument can be converted into a number.
152+
* @param {string} text Any text.
153+
* @return {boolean}
154+
*/
155+
function isNumber(text) {
156+
157+
return !isNaN(Number(text));
158+
}
159+
160+
/**
161+
* Convert text to number.
162+
* @param {string} text Any text.
163+
* @return {number}
164+
*/
165+
function toNumber(text) {
166+
167+
return Number(text);
168+
}
169+
170+
/**
171+
* Return true if the string passed as the only argument can be converted into a date.
172+
* @param {string} text Any text.
173+
* @return {boolean}
174+
*/
175+
function isDate(text) {
176+
177+
return moment(text).isValid();
178+
}
179+
180+
/**
181+
* Return true if the first date is before the second date.
182+
* @param {string} a Date represented as string.
183+
* @param {string} b Date represented as string.
184+
* @return {boolean}
185+
*/
186+
function isDateBefore(a, b) {
187+
188+
return moment(a).isBefore(moment(b));
189+
}
190+
191+
/**
192+
* Return true if the first date is after the second date.
193+
* @param {string} a Date represented as string.
194+
* @param {string} b Date represented as string.
195+
* @return {boolean}
196+
*/
197+
function isDateAfter(a, b) {
198+
199+
return moment(a).isAfter(moment(b));
200+
}
201+
202+
/**
203+
* Return true if the first date is the same as the second date.
204+
* @param {string} a Date represented as string.
205+
* @param {string} b Date represented as string.
206+
* @return {boolean}
207+
*/
208+
function isDateSame(a, b) {
209+
210+
return moment(a).isSame(moment(b));
211+
}
212+
213+
/**
214+
* Promisified http request.
215+
* @param {string} method - POST, DELETE, PUT, GET
216+
* @param {{
217+
* url: String,
218+
* body: String,
219+
* bodyBase64Encode: Boolean,
220+
* headers: String,
221+
* responseEncoding: String
222+
* }} json options
223+
* @return {Promise}
224+
*/
225+
function requestPromisified(method, json) {
226+
227+
let { options, errors } = buildRequestOptions(json);
228+
return new Promise((resolve, reject) => {
229+
230+
if (errors.length > 0) {
231+
// log all errors
232+
return reject(JSON.stringify(errors, null, '\t'));
233+
}
234+
options.method = method;
235+
request(options, processResponse.bind(null, resolve, reject));
236+
});
237+
}
238+
239+
/**
240+
* This is used in NewXXX component types to get array of new items by comparing all
241+
* downloaded (through API) items against collection of already known (processed) items.
242+
* @param {Set} knownItems
243+
* @param {Array} currentItems
244+
* @param {Array} newItems
245+
* @param item
246+
* @param getId
247+
*/
248+
function processItem(knownItems, currentItems, newItems, getId, item) {
249+
250+
if (knownItems && !knownItems.has(getId(item))) {
251+
newItems.push(item);
252+
}
253+
254+
currentItems.push(getId(item));
255+
}
256+
257+
module.exports = {
258+
getPathsFromJson,
259+
getByPath,
260+
request: requestPromisified,
261+
isNumber,
262+
toNumber,
263+
isDate,
264+
isDateBefore,
265+
isDateAfter,
266+
isDateSame,
267+
processItem
268+
};

0 commit comments

Comments
 (0)