@felloh-org/lambda-wrapper 1.0.0 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintignore +5 -0
- package/.eslintrc.yml +1 -0
- package/.github/workflows/release.yml +2 -0
- package/babel.config.js +20 -1
- package/cli/seed/create-schema.js +22 -0
- package/dist/config/dependencies.js +36 -0
- package/dist/dependency-injection/dependency-aware.js +43 -0
- package/dist/dependency-injection/dependency-injection.js +135 -0
- package/dist/entity/agent-data/booking/index.js +127 -0
- package/dist/entity/agent-data/booking-component/index.js +39 -0
- package/dist/entity/agent-data/booking-component-type/index.js +39 -0
- package/dist/entity/agent-data/index.js +24 -0
- package/dist/entity/agent-data/supplier/index.js +39 -0
- package/dist/entity/agent-data/supplier-type/index.js +39 -0
- package/dist/entity/bank/account/index.js +87 -0
- package/dist/entity/bank/beneficiary-organisation/index.js +69 -0
- package/dist/entity/bank/beneficiary-provider/index.js +79 -0
- package/dist/entity/bank/disbursal/index.js +39 -0
- package/dist/entity/bank/index.js +20 -0
- package/dist/entity/index.js +22 -0
- package/dist/entity/nuapay/account/index.js +96 -0
- package/dist/entity/nuapay/balance/index.js +64 -0
- package/dist/entity/nuapay/beneficiary/index.js +60 -0
- package/dist/entity/nuapay/index.js +20 -0
- package/dist/entity/nuapay/transaction/index.js +120 -0
- package/dist/entity/payment/index.js +28 -0
- package/dist/entity/payment/payment-link/index.js +161 -0
- package/dist/entity/payment/transaction/index.js +150 -0
- package/dist/entity/payment/transaction-event/index.js +71 -0
- package/dist/entity/payment/transaction-event-type/index.js +39 -0
- package/dist/entity/payment/transaction-metadata/index.js +140 -0
- package/dist/entity/payment/transaction-method/index.js +39 -0
- package/dist/entity/payment/transaction-status/index.js +39 -0
- package/dist/entity/payment/transaction-type/index.js +39 -0
- package/dist/entity/user/feature/index.js +63 -0
- package/dist/entity/user/index.js +26 -0
- package/dist/entity/user/organisation/index.js +111 -0
- package/dist/entity/user/organisation-feature/index.js +69 -0
- package/dist/entity/user/role/index.js +107 -0
- package/dist/entity/user/user/index.js +154 -0
- package/dist/entity/user/user-activity/index.js +82 -0
- package/dist/entity/user/user-role/index.js +83 -0
- package/dist/index.js +359 -0
- package/dist/migration/agent-data/1642121041760-inital.js +15 -0
- package/dist/migration/agent-data/1642121610577-organisation-suppliers.js +13 -0
- package/dist/migration/agent-data/1642121681610-organisation-suppliers-type.js +13 -0
- package/dist/migration/agent-data/1642122014731-booking-component-type.js +13 -0
- package/dist/migration/agent-data/1642505882721-booking-fields.js +29 -0
- package/dist/migration/agent-data/1642508385880-booking-id.js +19 -0
- package/dist/migration/bank/1642313073694-bank-initial.js +25 -0
- package/dist/migration/nuapay/1642200051764-initial.js +13 -0
- package/dist/migration/nuapay/1642201303413-add-fields-nuapay.js +15 -0
- package/dist/migration/nuapay/1642282666475-add-nuapay-account.js +13 -0
- package/dist/migration/nuapay/1642283420696-fix-iban-length.js +15 -0
- package/dist/migration/nuapay/1642283781684-account-relation.js +15 -0
- package/dist/migration/nuapay/1642286393959-beneficiary.js +13 -0
- package/dist/migration/nuapay/1642316424390-account-ibans.js +21 -0
- package/dist/migration/nuapay/1642318117667-cleanup-beneficaries.js +23 -0
- package/dist/migration/nuapay/1642365244026-nullable-bic.js +13 -0
- package/dist/migration/nuapay/1642366232568-remove-bic.js +13 -0
- package/dist/migration/nuapay/1642370449589-transaction.js +17 -0
- package/dist/migration/payment/1642120204899-inital.js +21 -0
- package/dist/migration/payment/1642514728358-payment-link-updates.js +39 -0
- package/dist/migration/payment/1642516017835-payment-link-add-booking.js +15 -0
- package/dist/migration/payment/1642519453365-transaction.js +63 -0
- package/dist/migration/payment/1642520774562-transaction-payment-link.js +15 -0
- package/dist/migration/payment/1642533711013-transaction-metadata.js +15 -0
- package/dist/migration/payment/1642540883807-transaction-event.js +27 -0
- package/dist/migration/user/1641774481300-initial.js +27 -0
- package/dist/migration/user/1641829697638-add_parent_org.js +25 -0
- package/dist/migration/user/1641901624008-add_roles.js +21 -0
- package/dist/migration/user/1641903059924-add_roles_kyc.js +13 -0
- package/dist/migration/user/1641903425025-roles-defaults.js +15 -0
- package/dist/migration/user/1641939221103-feature.js +13 -0
- package/dist/migration/user/1642019067334-organisation-feature.js +17 -0
- package/dist/migration/user/1642078564295-user-activity-relation.js +16 -0
- package/dist/migration/user/1642207858247-fix-user-constraint.js +19 -0
- package/dist/model/index.js +45 -0
- package/dist/model/response/index.js +150 -0
- package/dist/model/status/index.js +82 -0
- package/dist/service/http.js +55 -0
- package/dist/service/logger.js +236 -0
- package/dist/service/request.js +342 -0
- package/dist/service/warehouse.js +59 -0
- package/dist/util/lambda-termination.js +39 -0
- package/dist/util/promisified-delay.js +59 -0
- package/dist/wrapper/index.js +165 -0
- package/ormconfig.js +25 -0
- package/package.json +19 -5
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = exports.REQUEST_TYPES = exports.HTTP_METHODS_WITH_PAYLOADS = exports.HTTP_METHODS_WITHOUT_PAYLOADS = exports.ERROR_TYPES = void 0;
|
|
7
|
+
|
|
8
|
+
var _querystring = _interopRequireDefault(require("querystring"));
|
|
9
|
+
|
|
10
|
+
var _useragent = _interopRequireDefault(require("useragent"));
|
|
11
|
+
|
|
12
|
+
var _validate = _interopRequireDefault(require("validate.js/validate"));
|
|
13
|
+
|
|
14
|
+
var _xml2js = _interopRequireDefault(require("xml2js"));
|
|
15
|
+
|
|
16
|
+
var _dependencies = require("../config/dependencies");
|
|
17
|
+
|
|
18
|
+
var _dependencyAware = _interopRequireDefault(require("../dependency-injection/dependency-aware"));
|
|
19
|
+
|
|
20
|
+
var _response = _interopRequireDefault(require("../model/response"));
|
|
21
|
+
|
|
22
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
23
|
+
|
|
24
|
+
/* eslint-disable class-methods-use-this */
|
|
25
|
+
|
|
26
|
+
/* eslint-disable sonarjs/no-duplicate-string */
|
|
27
|
+
const REQUEST_TYPES = {
|
|
28
|
+
DELETE: 'DELETE',
|
|
29
|
+
GET: 'GET',
|
|
30
|
+
HEAD: 'HEAD',
|
|
31
|
+
OPTIONS: 'OPTIONS',
|
|
32
|
+
PATCH: 'PATCH',
|
|
33
|
+
POST: 'POST',
|
|
34
|
+
PUT: 'PUT'
|
|
35
|
+
};
|
|
36
|
+
exports.REQUEST_TYPES = REQUEST_TYPES;
|
|
37
|
+
const HTTP_METHODS_WITHOUT_PAYLOADS = [REQUEST_TYPES.DELETE, REQUEST_TYPES.GET, REQUEST_TYPES.HEAD, REQUEST_TYPES.OPTIONS];
|
|
38
|
+
exports.HTTP_METHODS_WITHOUT_PAYLOADS = HTTP_METHODS_WITHOUT_PAYLOADS;
|
|
39
|
+
const HTTP_METHODS_WITH_PAYLOADS = [REQUEST_TYPES.PATCH, REQUEST_TYPES.POST, REQUEST_TYPES.PUT]; // Define action specific error types
|
|
40
|
+
|
|
41
|
+
exports.HTTP_METHODS_WITH_PAYLOADS = HTTP_METHODS_WITH_PAYLOADS;
|
|
42
|
+
const ERROR_TYPES = {
|
|
43
|
+
VALIDATION_ERROR: new _response.default({}, 400, 'required fields are missing')
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* RequestService class
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
exports.ERROR_TYPES = ERROR_TYPES;
|
|
50
|
+
|
|
51
|
+
class Request extends _dependencyAware.default {
|
|
52
|
+
/**
|
|
53
|
+
* Get a parameter from the request.
|
|
54
|
+
*
|
|
55
|
+
* @param parameter
|
|
56
|
+
* @param ifNull
|
|
57
|
+
* @param requestType
|
|
58
|
+
*/
|
|
59
|
+
get(parameter, ifNull = null, requestType = null) {
|
|
60
|
+
const queryParameters = this.getAll(requestType);
|
|
61
|
+
|
|
62
|
+
if (queryParameters === null) {
|
|
63
|
+
return ifNull;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return typeof queryParameters[parameter] !== 'undefined' ? queryParameters[parameter] : ifNull;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Get all HTTP headers included in the request.
|
|
70
|
+
*
|
|
71
|
+
* @returns {object} An object with a key for each header.
|
|
72
|
+
*/
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
getAllHeaders() {
|
|
76
|
+
return { ...this.getContainer().getEvent().headers
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Get an HTTP header from the request.
|
|
81
|
+
*
|
|
82
|
+
* The header name is case-insensitive.
|
|
83
|
+
*
|
|
84
|
+
* @param {string} name The name of the header.
|
|
85
|
+
* @param {string} [whenMissing] Value to return if the header is missing.
|
|
86
|
+
* (default: empty string)
|
|
87
|
+
*
|
|
88
|
+
* @returns {string}
|
|
89
|
+
*/
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
getHeader(name, whenMissing = '') {
|
|
93
|
+
const headers = this.getAllHeaders();
|
|
94
|
+
|
|
95
|
+
if (!headers) {
|
|
96
|
+
return whenMissing;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const lowerName = name.toLowerCase();
|
|
100
|
+
const key = Object.keys(headers).find(k => k.toLowerCase() === lowerName);
|
|
101
|
+
return key && headers[key] || whenMissing;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Get authorization token
|
|
105
|
+
*
|
|
106
|
+
* @returns {*}
|
|
107
|
+
*/
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
getAuthorizationToken() {
|
|
111
|
+
const authorization = this.getHeader('Authorization');
|
|
112
|
+
|
|
113
|
+
if (!authorization) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const tokenParts = authorization.split(' ');
|
|
118
|
+
const tokenValue = tokenParts[1];
|
|
119
|
+
|
|
120
|
+
if (!(tokenParts[0].toLowerCase() === 'bearer' && tokenValue)) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return tokenValue;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Get a path parameter
|
|
128
|
+
*
|
|
129
|
+
* @param parameter
|
|
130
|
+
* @param ifNull mixed
|
|
131
|
+
*/
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
getPathParameter(parameter = null, ifNull = {}) {
|
|
135
|
+
const event = this.getContainer().getEvent(); // If no parameter has been requested, return all path parameters
|
|
136
|
+
|
|
137
|
+
if (parameter === null && typeof event.pathParameters === 'object') {
|
|
138
|
+
return event.pathParameters;
|
|
139
|
+
} // If a specifc parameter has been requested, return the parameter if it exists
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
if (typeof parameter === 'string' && typeof event.pathParameters === 'object' && event.pathParameters !== null && typeof event.pathParameters[parameter] !== 'undefined') {
|
|
143
|
+
return event.pathParameters[parameter];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return ifNull;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Get all request parameters
|
|
150
|
+
*
|
|
151
|
+
* @param requestType
|
|
152
|
+
* @returns {{}}
|
|
153
|
+
*/
|
|
154
|
+
// eslint-disable-next-line sonarjs/cognitive-complexity
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
getAll(requestType = null) {
|
|
158
|
+
const event = this.getContainer().getEvent();
|
|
159
|
+
|
|
160
|
+
if (HTTP_METHODS_WITHOUT_PAYLOADS.includes(event.httpMethod) || HTTP_METHODS_WITHOUT_PAYLOADS.includes(requestType)) {
|
|
161
|
+
// get simple parameters
|
|
162
|
+
const params = { ...event.queryStringParameters
|
|
163
|
+
}; // add array parameters as arrays
|
|
164
|
+
|
|
165
|
+
Object.keys(params).filter(key => key.endsWith('[]')).forEach(key => {
|
|
166
|
+
params[key] = event.multiValueQueryStringParameters[key];
|
|
167
|
+
});
|
|
168
|
+
return params;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (HTTP_METHODS_WITH_PAYLOADS.includes(event.httpMethod) || HTTP_METHODS_WITH_PAYLOADS.includes(requestType)) {
|
|
172
|
+
const contentType = this.getHeader('Content-Type');
|
|
173
|
+
let queryParameters = {};
|
|
174
|
+
|
|
175
|
+
if (contentType.includes('application/x-www-form-urlencoded')) {
|
|
176
|
+
queryParameters = _querystring.default.parse(event.body);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (contentType.includes('application/json')) {
|
|
180
|
+
try {
|
|
181
|
+
queryParameters = JSON.parse(event.body);
|
|
182
|
+
} catch (error) {
|
|
183
|
+
queryParameters = {};
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (contentType.includes('text/xml')) {
|
|
188
|
+
_xml2js.default.parseString(event.body, (error, result) => {
|
|
189
|
+
queryParameters = error ? {} : result;
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (contentType.includes('multipart/form-data')) {
|
|
194
|
+
queryParameters = this.parseForm(true);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return typeof queryParameters !== 'undefined' ? queryParameters : {};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Fetch the request IP address
|
|
204
|
+
*
|
|
205
|
+
* @returns {*}
|
|
206
|
+
*/
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
getIp() {
|
|
210
|
+
const event = this.getContainer().getEvent();
|
|
211
|
+
|
|
212
|
+
if (typeof event.requestContext !== 'undefined' && typeof event.requestContext.identity !== 'undefined' && typeof event.requestContext.identity.sourceIp !== 'undefined') {
|
|
213
|
+
return event.requestContext.identity.sourceIp;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Get user agent
|
|
220
|
+
*
|
|
221
|
+
* @returns {*}
|
|
222
|
+
*/
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
getUserBrowserAndDevice() {
|
|
226
|
+
const userAgent = this.getHeader('user-agent', null);
|
|
227
|
+
|
|
228
|
+
if (userAgent === null) {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
const agent = _useragent.default.parse(userAgent);
|
|
234
|
+
|
|
235
|
+
const os = agent.os.toJSON();
|
|
236
|
+
return {
|
|
237
|
+
'browser-type': agent.family,
|
|
238
|
+
'browser-version': agent.toVersion(),
|
|
239
|
+
'device-type': agent.device.family,
|
|
240
|
+
'operating-system': os.family,
|
|
241
|
+
'operating-system-version': agent.os.toVersion()
|
|
242
|
+
};
|
|
243
|
+
} catch (error) {
|
|
244
|
+
this.getContainer().get(_dependencies.DEFINITIONS.LOGGER).label('user-agent-parsing-failed');
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Test a request against validation constraints
|
|
250
|
+
*
|
|
251
|
+
* @param constraints
|
|
252
|
+
* @returns {Promise<any>}
|
|
253
|
+
*/
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
validateAgainstConstraints(constraints) {
|
|
257
|
+
const Logger = this.getContainer().get(_dependencies.DEFINITIONS.LOGGER);
|
|
258
|
+
return new Promise((resolve, reject) => {
|
|
259
|
+
const validation = (0, _validate.default)(this.getAll(), constraints);
|
|
260
|
+
|
|
261
|
+
if (typeof validation === 'undefined') {
|
|
262
|
+
resolve();
|
|
263
|
+
} else {
|
|
264
|
+
Logger.label('request-validation-failed');
|
|
265
|
+
const validationErrorResponse = ERROR_TYPES.VALIDATION_ERROR;
|
|
266
|
+
validationErrorResponse.setBodyVariable('validation_errors', validation);
|
|
267
|
+
reject(validationErrorResponse);
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Fetch the request multipart form
|
|
273
|
+
*
|
|
274
|
+
* @param useBuffer
|
|
275
|
+
* @returns {*}
|
|
276
|
+
*/
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
parseForm(useBuffer) {
|
|
280
|
+
const event = this.getContainer().getEvent();
|
|
281
|
+
const boundary = this.getBoundary(event);
|
|
282
|
+
const body = event.isBase64Encoded ? Buffer.from(event.body, 'base64').toString('binary').trim() : event.body;
|
|
283
|
+
const result = {};
|
|
284
|
+
body.split(boundary).forEach(item => {
|
|
285
|
+
if (/filename=".+"/g.test(item)) {
|
|
286
|
+
result[item.match(/name=".+";/g)[0].slice(6, -2)] = {
|
|
287
|
+
type: 'file',
|
|
288
|
+
filename: item.match(/filename=".+"/g)[0].slice(10, -1),
|
|
289
|
+
contentType: item.match(/Content-Type:\s.+/g)[0].slice(14),
|
|
290
|
+
content: useBuffer ? Buffer.from(item.slice(item.search(/Content-Type:\s.+/g) + item.match(/Content-Type:\s.+/g)[0].length + 4, -4), 'binary') : item.slice(item.search(/Content-Type:\s.+/g) + item.match(/Content-Type:\s.+/g)[0].length + 4, -4)
|
|
291
|
+
};
|
|
292
|
+
} else if (/name=".+"/g.test(item)) {
|
|
293
|
+
result[item.match(/name=".+"/g)[0].slice(6, -1)] = item.slice(item.search(/name=".+"/g) + item.match(/name=".+"/g)[0].length + 4, -4);
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
return result;
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Fetch the request AWS event Records
|
|
300
|
+
*
|
|
301
|
+
* @returns {*}
|
|
302
|
+
*/
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
getAWSRecords() {
|
|
306
|
+
const event = this.getContainer().getEvent();
|
|
307
|
+
const eventRecord = event.Records && event.Records[0];
|
|
308
|
+
|
|
309
|
+
if (typeof event.Records !== 'undefined' && typeof event.Records[0] !== 'undefined' && typeof eventRecord.eventSource !== 'undefined') {
|
|
310
|
+
return eventRecord;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Gets a value independently from
|
|
317
|
+
* the case of the key
|
|
318
|
+
*
|
|
319
|
+
* @param object
|
|
320
|
+
* @param key
|
|
321
|
+
*/
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
getValueIgnoringKeyCase(object, key) {
|
|
325
|
+
const foundKey = Object.keys(object).find(currentKey => currentKey.toLocaleLowerCase() === key.toLowerCase());
|
|
326
|
+
return object[foundKey];
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Returns the content type
|
|
330
|
+
* assoiated with the request
|
|
331
|
+
*
|
|
332
|
+
* @param event
|
|
333
|
+
*/
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
getBoundary(event) {
|
|
337
|
+
return this.getValueIgnoringKeyCase(event.headers, 'Content-Type').split('=')[1];
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
exports.default = Request;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
|
|
8
|
+
var _typeorm = require("typeorm");
|
|
9
|
+
|
|
10
|
+
var _typeormAuroraDataApiDriver = _interopRequireDefault(require("typeorm-aurora-data-api-driver"));
|
|
11
|
+
|
|
12
|
+
var _dependencyAware = _interopRequireDefault(require("../dependency-injection/dependency-aware"));
|
|
13
|
+
|
|
14
|
+
var _entity = _interopRequireDefault(require("../entity"));
|
|
15
|
+
|
|
16
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
17
|
+
|
|
18
|
+
// eslint-disable-line no-unused-vars
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Warehouse Serv8ce
|
|
22
|
+
*/
|
|
23
|
+
class Warehouse extends _dependencyAware.default {
|
|
24
|
+
constructor() {
|
|
25
|
+
super();
|
|
26
|
+
this.connection = null;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
*
|
|
30
|
+
* @returns {Promise<Connection>}
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
async connect() {
|
|
35
|
+
if (this.connection) {
|
|
36
|
+
return this.connection;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
this.connection = await (0, _typeorm.getConnection)();
|
|
41
|
+
} catch (error) {
|
|
42
|
+
this.connection = await (0, _typeorm.createConnection)({
|
|
43
|
+
type: 'aurora-data-api-pg',
|
|
44
|
+
database: process.env.WAREHOUSE_DATABASE,
|
|
45
|
+
schema: process.env.WAREHOUSE_SCHEMA,
|
|
46
|
+
secretArn: process.env.WAREHOUSE_SECRET_ARN,
|
|
47
|
+
resourceArn: process.env.WAREHOUSE_ARN,
|
|
48
|
+
region: process.env.REGION,
|
|
49
|
+
entities: _entity.default,
|
|
50
|
+
logging: true
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return this.connection;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
exports.default = Warehouse;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
*
|
|
10
|
+
*/
|
|
11
|
+
class LambdaTermination extends Error {
|
|
12
|
+
/**
|
|
13
|
+
* Triggers a Lambda Termination.
|
|
14
|
+
* Offers developer details (that are logged)
|
|
15
|
+
* an code for the Lambda and a front facing
|
|
16
|
+
* consumer message.
|
|
17
|
+
*
|
|
18
|
+
* @param {object|string} internal
|
|
19
|
+
* @param {number?} code
|
|
20
|
+
* @param {object|string?} body
|
|
21
|
+
* @param details
|
|
22
|
+
*/
|
|
23
|
+
constructor(internal, code = 500, body = null, details = 'unknown error') {
|
|
24
|
+
let stringified = internal;
|
|
25
|
+
|
|
26
|
+
if (typeof internal !== 'string') {
|
|
27
|
+
stringified = JSON.stringify(internal);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
super(stringified);
|
|
31
|
+
this.internal = internal;
|
|
32
|
+
this.code = code;
|
|
33
|
+
this.body = body || 'unknown error';
|
|
34
|
+
this.details = details;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
exports.default = LambdaTermination;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
const STANDARD_LATENCY_DELAYS = {
|
|
8
|
+
2000: 70,
|
|
9
|
+
3500: 15,
|
|
10
|
+
4000: 10,
|
|
11
|
+
5000: 5
|
|
12
|
+
};
|
|
13
|
+
const HIGH_LATENCY_DELAYS = {
|
|
14
|
+
2000: 65,
|
|
15
|
+
3500: 15,
|
|
16
|
+
4000: 9,
|
|
17
|
+
5000: 5,
|
|
18
|
+
10000: 5,
|
|
19
|
+
20000: 1
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* PromisifiedDelay class
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
class PromisifiedDelay {
|
|
26
|
+
/**
|
|
27
|
+
* PromisifiedDelay constructor
|
|
28
|
+
*
|
|
29
|
+
* @param highLatency
|
|
30
|
+
*/
|
|
31
|
+
constructor(highLatency = true) {
|
|
32
|
+
this.delays = [];
|
|
33
|
+
const delayArray = highLatency === true ? HIGH_LATENCY_DELAYS : STANDARD_LATENCY_DELAYS;
|
|
34
|
+
Object.keys(delayArray).forEach(delayDuration => {
|
|
35
|
+
const delayIterations = delayArray[delayDuration];
|
|
36
|
+
|
|
37
|
+
for (let i = 0; i < delayIterations; i += 1) {
|
|
38
|
+
this.delays.push(delayDuration);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Create a promisified delay
|
|
44
|
+
*
|
|
45
|
+
* @returns {Promise<any>}
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
get() {
|
|
50
|
+
return new Promise(resolve => {
|
|
51
|
+
setTimeout(() => {
|
|
52
|
+
resolve();
|
|
53
|
+
}, this.delays[Math.floor(Math.random() * this.delays.length)]);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
exports.default = PromisifiedDelay;
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.handleSuccess = exports.handleError = exports.default = void 0;
|
|
7
|
+
|
|
8
|
+
var _epsagon = _interopRequireDefault(require("epsagon"));
|
|
9
|
+
|
|
10
|
+
var _dependencies = require("../config/dependencies");
|
|
11
|
+
|
|
12
|
+
var _dependencyInjection = _interopRequireDefault(require("../dependency-injection/dependency-injection"));
|
|
13
|
+
|
|
14
|
+
var _response = _interopRequireDefault(require("../model/response"));
|
|
15
|
+
|
|
16
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
17
|
+
|
|
18
|
+
/* eslint-disable sonarjs/cognitive-complexity */
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Processes the outcome once we have one
|
|
22
|
+
*
|
|
23
|
+
* @param di
|
|
24
|
+
* @param outcome
|
|
25
|
+
*/
|
|
26
|
+
const handleSuccess = (di, outcome) => {
|
|
27
|
+
const logger = di.get(_dependencies.DEFINITIONS.LOGGER); // Outcome may be undefined as not all lambdas have a return value.
|
|
28
|
+
|
|
29
|
+
logger.metric('lambda.statusCode', outcome && outcome.statusCode || 200);
|
|
30
|
+
return outcome;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Gracefully handles an error
|
|
34
|
+
* logging in Epsagon and generating
|
|
35
|
+
* a response reflecting the `code`
|
|
36
|
+
* of the error, if defined.
|
|
37
|
+
*
|
|
38
|
+
* Note about Epsagon:
|
|
39
|
+
* Epsagon generates alerts for logs on level ERROR.
|
|
40
|
+
* This means that logger.error will produce an alert.
|
|
41
|
+
* To avoid not meaningful notifications, most likely
|
|
42
|
+
* coming from tests, we log INFO unless either:
|
|
43
|
+
*
|
|
44
|
+
* 1. `error.raiseOnEpsagon` is defined & truthy
|
|
45
|
+
* 2. `error.code` is defined and `error.code >= 500`.
|
|
46
|
+
*
|
|
47
|
+
* @param {DependencyInjection} di
|
|
48
|
+
* @param {Error} error
|
|
49
|
+
* @param {boolean} [throwError=false]
|
|
50
|
+
*/
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
exports.handleSuccess = handleSuccess;
|
|
54
|
+
|
|
55
|
+
const handleError = (di, error, throwError = false) => {
|
|
56
|
+
const logger = di.get(_dependencies.DEFINITIONS.LOGGER);
|
|
57
|
+
logger.metric('lambda.statusCode', error.code || 500);
|
|
58
|
+
|
|
59
|
+
if (error.raiseOnEpsagon || !error.code || error.code >= 500) {
|
|
60
|
+
logger.error(error);
|
|
61
|
+
} else {
|
|
62
|
+
logger.info(error);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (throwError) {
|
|
66
|
+
if (error instanceof Error) {
|
|
67
|
+
return error;
|
|
68
|
+
} // We want to be absolutely sure
|
|
69
|
+
// that we are returning an error
|
|
70
|
+
// as Lambda sync handlers will only fail
|
|
71
|
+
// if the object is instanceof Error
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
return new Error(error);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const responseDetails = {
|
|
78
|
+
body: error.body || {},
|
|
79
|
+
code: error.code || 500,
|
|
80
|
+
details: error.details || 'unknown error'
|
|
81
|
+
};
|
|
82
|
+
return _response.default.generate(responseDetails.body, responseDetails.code, responseDetails.details);
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Lambda Wrapper.
|
|
86
|
+
*
|
|
87
|
+
* Wraps a lambda handler, generating a new function
|
|
88
|
+
* that has access to the dependency injection
|
|
89
|
+
* for the service and handles logging and exceptions.
|
|
90
|
+
*
|
|
91
|
+
* @param configuration
|
|
92
|
+
* @param handler
|
|
93
|
+
* @param throwError
|
|
94
|
+
*/
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
exports.handleError = handleError;
|
|
98
|
+
|
|
99
|
+
const wrapper = (configuration, handler, throwError = false) => {
|
|
100
|
+
let instance = (event, context, callback) => {
|
|
101
|
+
const di = new _dependencyInjection.default(configuration, event, context);
|
|
102
|
+
const request = di.get(_dependencies.DEFINITIONS.REQUEST);
|
|
103
|
+
const logger = di.get(_dependencies.DEFINITIONS.LOGGER);
|
|
104
|
+
context.callbackWaitsForEmptyEventLoop = false; // If the event is to trigger a warm up, then don't bother returning the function.
|
|
105
|
+
|
|
106
|
+
if (di.getEvent().source === 'serverless-plugin-warmup') {
|
|
107
|
+
return callback(null, 'Lambda is warm!');
|
|
108
|
+
} // Log the users ip address silently for use in error tracing
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
if (request.getIp() !== null) {
|
|
112
|
+
logger.metric('ipAddress', request.getIp(), true);
|
|
113
|
+
} // Add metrics with user browser information for rapid debugging
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
const userBrowserAndDevice = request.getUserBrowserAndDevice();
|
|
117
|
+
|
|
118
|
+
if (userBrowserAndDevice !== null && typeof userBrowserAndDevice === 'object') {
|
|
119
|
+
Object.keys(userBrowserAndDevice).forEach(metricKey => {
|
|
120
|
+
logger.metric(metricKey, userBrowserAndDevice[metricKey], true);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
let outcome;
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
outcome = handler.call(instance, di, request, callback);
|
|
128
|
+
|
|
129
|
+
if (outcome instanceof Promise) {
|
|
130
|
+
outcome = outcome.then(value => handleSuccess(di, value)).catch(error => {
|
|
131
|
+
const handled = handleError(di, error, throwError);
|
|
132
|
+
|
|
133
|
+
if (throwError) {
|
|
134
|
+
// AWS Lambda with async handler is looking for a rejection
|
|
135
|
+
// and not an error object directly
|
|
136
|
+
// and will treat resolved errors as successful
|
|
137
|
+
// as it will cast the error to JSON, i.e. `{}`
|
|
138
|
+
return Promise.reject(handled);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return handled;
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
} catch (error) {
|
|
145
|
+
outcome = handleError(di, error, throwError);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return outcome;
|
|
149
|
+
}; // If the Epsagon token is enabled, then wrap the instance in the Epsagon wrapper
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
if (typeof process.env.EPSAGON_TOKEN === 'string' && process.env.EPSAGON_TOKEN !== 'undefined' && typeof process.env.EPSAGON_SERVICE_NAME === 'string' && process.env.EPSAGON_SERVICE_NAME !== 'undefined') {
|
|
153
|
+
_epsagon.default.init({
|
|
154
|
+
token: process.env.EPSAGON_TOKEN,
|
|
155
|
+
appName: process.env.EPSAGON_SERVICE_NAME
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
instance = _epsagon.default.lambdaWrapper(instance);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return instance;
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
var _default = wrapper;
|
|
165
|
+
exports.default = _default;
|
package/ormconfig.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const DotEnv = require('dotenv');
|
|
2
|
+
|
|
3
|
+
DotEnv.config();
|
|
4
|
+
|
|
5
|
+
module.exports = {
|
|
6
|
+
"type": "aurora-data-api-pg",
|
|
7
|
+
"database": process.env.WAREHOUSE_DATABASE,
|
|
8
|
+
"secretArn": process.env.WAREHOUSE_SECRET_ARN,
|
|
9
|
+
"resourceArn": process.env.WAREHOUSE_ARN,
|
|
10
|
+
"region": process.env.REGION,
|
|
11
|
+
"synchronize": true,
|
|
12
|
+
"logging": false,
|
|
13
|
+
"schema": 'user',
|
|
14
|
+
"entities": [
|
|
15
|
+
__dirname + '/dist/entity/**/*.ts',
|
|
16
|
+
__dirname + '/dist/entity/**/*.js',
|
|
17
|
+
],
|
|
18
|
+
"migrations": [
|
|
19
|
+
__dirname + '/dist/migration/**/*.ts',
|
|
20
|
+
__dirname + '/dist/migration/**/*.js'
|
|
21
|
+
],
|
|
22
|
+
"cli": {
|
|
23
|
+
"migrationsDir": __dirname + '/src/migration'
|
|
24
|
+
},
|
|
25
|
+
};
|