piper-utils 1.0.4 → 1.0.5-7.1

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.
@@ -1,9 +1,9 @@
1
1
  import { createFilters } from './createFilters.js';
2
- import { defaultFiltersWithSubSchema, event, eventWithSingleElementAsArray, eventWithSingleElementInArray } from './mocks/mocks.js';
3
- import DB from 'sequelize';
2
+ import { defaultFiltersWithSubSchema, event, eventWithSearchString, eventWithSearchStringNumber, eventWithSingleElementAsArray, eventWithSingleElementInArray, manyStringFields } from './mocks/mocks.js';
3
+ import DB, { or } from 'sequelize';
4
4
 
5
5
  describe('createFilters', function () {
6
- it('should handle case insensitive searches', function (){
6
+ it('should handle case insensitive searches', function () {
7
7
  const where = createFilters(event, defaultFiltersWithSubSchema);
8
8
  expect(where.char).toEqual({ [DB.Op.iLike]: '%StringToSearch%' });
9
9
  });
@@ -34,4 +34,22 @@ describe('createFilters', function () {
34
34
  expect(where.notOnObject).not.toBeDefined();
35
35
  expect(where.date).not.toBeDefined();
36
36
  });
37
+
38
+ it('should handle searchString', function () {
39
+ const where = createFilters(eventWithSearchString, manyStringFields);
40
+ const orSymbols = Object.getOwnPropertySymbols(where);
41
+ expect(orSymbols.length).toBeGreaterThan(0);
42
+ });
43
+
44
+ it('should handle searchString if value is a number', function () {
45
+ const where = createFilters(eventWithSearchStringNumber, manyStringFields);
46
+ const orSymbols = Object.getOwnPropertySymbols(where);
47
+ expect(orSymbols.length).toBeGreaterThan(0);
48
+ });
49
+
50
+ it('should handle searchString sub fields', function () {
51
+ const where = createFilters(eventWithSearchStringNumber, defaultFiltersWithSubSchema);
52
+ const orSymbols = Object.getOwnPropertySymbols(where);
53
+ expect(orSymbols.length).toBeGreaterThan(0);
54
+ });
37
55
  });
@@ -31,7 +31,8 @@ export function getPaginationFromFindAll(result, limit, offset) {
31
31
  export async function findAll(model, options) {
32
32
 
33
33
  const limit = options.limit || 0;
34
- const findArgs = Object.assign({include: { all: true, nested: true }}, options, { limit: limit + 1 });
34
+ const includes = options.includes || { all: true, nested: true };
35
+ const findArgs = Object.assign({include: includes}, options, { limit: limit + 1 });
35
36
  const results = await model.findAll(findArgs);
36
37
  return getPaginationFromFindAll(results, limit, options.offset);
37
38
  }
@@ -30,6 +30,16 @@ export const defaultFiltersWithSubSchema = {
30
30
  array: { filterType: DB.Op.in }
31
31
  };
32
32
 
33
+ export const manyStringFields = {
34
+ id: { filterType: DB.Op.eq },
35
+ name: { filterType: DB.Op.iLike },
36
+ title: { filterType: DB.Op.iLike },
37
+ size: { filterType: DB.Op.iLike },
38
+ decimal: { filterType: DB.Op.or },
39
+ boolean: { type: DB.BOOLEAN, filterType: DB.Op.eq },
40
+ array: { filterType: DB.Op.in }
41
+ };
42
+
33
43
  export const event = {
34
44
  'pathParameters': {
35
45
  'id': '1'
@@ -61,6 +71,39 @@ export const event = {
61
71
  'sort': 'decimal,-id,boolean,notOnObject,-date,FLURB.id'
62
72
  }
63
73
  };
74
+ export const eventWithSearchString = {
75
+ 'pathParameters': {
76
+ 'id': '1'
77
+ },
78
+ 'body': '{}',
79
+ 'requestContext': {
80
+ 'authorizer': {
81
+ 'claims': {
82
+ 'custom:businessIds': '[\'1\',\'2\',\'3\',\'4\']'
83
+ }
84
+ }
85
+ },
86
+ 'queryStringParameters': {
87
+ 'searchString': 'alpha-search'
88
+ }
89
+ };
90
+
91
+ export const eventWithSearchStringNumber = {
92
+ 'pathParameters': {
93
+ 'id': '1'
94
+ },
95
+ 'body': '{}',
96
+ 'requestContext': {
97
+ 'authorizer': {
98
+ 'claims': {
99
+ 'custom:businessIds': '[\'1\',\'2\',\'3\',\'4\']'
100
+ }
101
+ }
102
+ },
103
+ 'queryStringParameters': {
104
+ 'searchString': '900'
105
+ }
106
+ };
64
107
 
65
108
  export const eventWithSingleElementInArray = {
66
109
  'pathParameters': {
@@ -18,7 +18,9 @@ export function handleEvents(event, transformer, errorHandlerPerFile, shouldSkip
18
18
  let path = decodeURIComponent(file);
19
19
  if (path) {
20
20
  return handleFile(path, s3Bucket, transformer, { shouldSkipFailedFolders, userImportTypes }).catch((err) => {
21
+ console.error('###############--@--->',err)
21
22
  if (errorHandlerPerFile && !errorHandlerPerFile(err, path)) {
23
+ console.error('HANDLE-FILE-CATCH: ', err);
22
24
  hasErrors = true;
23
25
  }
24
26
  });
@@ -26,7 +28,35 @@ export function handleEvents(event, transformer, errorHandlerPerFile, shouldSkip
26
28
  });
27
29
  }).then(() => {
28
30
  if (hasErrors) {
29
- throw 'Errors processing one or more files. See error logs for detail on each error.';
31
+ throw 'ERROR: HANDLE-FILE';
32
+ }
33
+ });
34
+ }
35
+
36
+ export function handleDirectS3WriteEvent(event, transformer, errorHandlerPerFile, shouldSkipFailedFolders = false, userImportTypes) {
37
+ if (!event || !event.Records) {
38
+ return Promise.resolve();
39
+ }
40
+
41
+ let hasErrors = false;
42
+
43
+ return Promise.map(event.Records, (record) => {
44
+ const s3Bucket = record.s3.bucket.name;
45
+ const file = record.s3.object.key || [];
46
+
47
+ let path = decodeURIComponent(file);
48
+ if (path) {
49
+ return handleFile(path, s3Bucket, transformer, { shouldSkipFailedFolders, userImportTypes }).catch((err) => {
50
+ if (errorHandlerPerFile && !errorHandlerPerFile(err, path)) {
51
+ console.error('HANDLE-FILE-CATCH:', err);
52
+ hasErrors = true;
53
+ }
54
+ });
55
+ }
56
+
57
+ }).then(() => {
58
+ if (hasErrors) {
59
+ throw 'ERROR: HANDLE-FILE (DIRECT S3 EVENT)';
30
60
  }
31
61
  });
32
62
  }
@@ -8,16 +8,19 @@ export async function handleFile(path, s3Bucket, transformer, options) {
8
8
  Bucket: s3Bucket,
9
9
  Key: path
10
10
  };
11
- let body = '{}'
11
+ let body = '{}';
12
12
  try {
13
- body = await S3Util.getObject(params);
13
+ const s3r = await S3Util.getObject(params);
14
+ console.log('------->s3r', s3r);
15
+ body = s3r
14
16
  const parsedBody = JSON.parse(body);
17
+ console.log('------->parsedBody-if this is empty body we need to skip transform?', parsedBody);
15
18
  const returnedValue = await transformer(parsedBody);
16
19
  await S3Util.deleteObject(params);
17
20
 
18
21
  return returnedValue;
19
22
  } catch (error) {
20
- console.error('handle-file-error', error);
23
+ console.error('HANDLE-FILE-ERROR', error);
21
24
 
22
25
  if (error.code === 'NoSuchKey' || !body) {
23
26
  return;
@@ -26,6 +29,7 @@ export async function handleFile(path, s3Bucket, transformer, options) {
26
29
  let newPath = '';
27
30
 
28
31
  if (userImportTypes) {
32
+ console.log('USER IMPORT')
29
33
  // look for items in the userImportTypes
30
34
  const usersImports = Object.values(userImportTypes);
31
35
  const items = path.split('/');
@@ -68,9 +72,22 @@ export async function handleFile(path, s3Bucket, transformer, options) {
68
72
  Body: body
69
73
  };
70
74
 
75
+ if (containsErrorTwice(newPath, 'failed-once')
76
+ || containsErrorTwice(newPath, 'failed-twice')
77
+ || containsErrorTwice(newPath, 'error')) {
78
+ throw 'NESTING ERROR';
79
+ }
80
+
71
81
  await S3Util.putObject(newParams);
72
82
  await S3Util.deleteObject(params);
73
83
 
74
84
  throw error;
75
85
  }
76
86
  }
87
+
88
+ export function containsErrorTwice(str, val) {
89
+ const firstIndex = str.toLowerCase().indexOf(val);
90
+ if (firstIndex === -1) return false;
91
+ const secondIndex = str.toLowerCase().indexOf(val, firstIndex + 1);
92
+ return secondIndex !== -1;
93
+ }
@@ -1,5 +1,5 @@
1
1
  import Promise from 'bluebird';
2
- import { handleFile } from './handleFile.js';
2
+ import { containsErrorTwice, handleFile } from './handleFile.js';
3
3
  import S3Util from '../s3/S3Utils.js/S3Utils.js';
4
4
 
5
5
  describe('handleFile', () => {
@@ -330,9 +330,9 @@ describe('handleFile', () => {
330
330
  }
331
331
  };
332
332
 
333
- spyOn(S3Util, 'getObject').and.returnValue(Promise.resolve('{ "test": "val" }' ));
333
+ spyOn(S3Util, 'getObject').and.returnValue(Promise.resolve('{ "test": "val" }'));
334
334
  spyOn(S3Util, 'deleteObject').and.returnValue(Promise.resolve('{}'));
335
- const putObjectSpy = spyOn(S3Util, 'putObject').and.returnValue(Promise.resolve('{}'))
335
+ const putObjectSpy = spyOn(S3Util, 'putObject').and.returnValue(Promise.resolve('{}'));
336
336
 
337
337
  spyOn(creator, 'createFunc').and.rejectWith({ message: 'THIS FUNCTION FAILED' });
338
338
  const userImportTypes = {
@@ -349,4 +349,33 @@ describe('handleFile', () => {
349
349
  });
350
350
  });
351
351
  });
352
+ it('should throw if nesting error folders', async () => {
353
+ const creator = {
354
+ createFunc: () => {
355
+ return Promise.resolve();
356
+ }
357
+ };
358
+
359
+ spyOn(S3Util, 'getObject').and.returnValue(Promise.resolve('{ "test": "val" }'));
360
+ spyOn(S3Util, 'deleteObject').and.returnValue(Promise.resolve('{}'));
361
+ const putObjectSpy = spyOn(S3Util, 'putObject').and.returnValue(Promise.resolve('{}'));
362
+ spyOn(creator, 'createFunc').and.rejectWith({ message: 'THIS FUNCTION FAILED' });
363
+
364
+ try {
365
+ await handleFile('error/error/file.json', 'someBucket', creator.createFunc);
366
+ } catch (e) {
367
+ expect(e).toBe('NESTING ERROR');
368
+ expect(putObjectSpy).not.toHaveBeenCalled();
369
+ }
370
+
371
+ });
372
+ it('containsErrorTwice: should throw if nesting error folders', async () => {
373
+ const r = containsErrorTwice('error/error', 'error');
374
+ expect(r).toBe(true);
375
+
376
+ });
377
+ it('containsErrorTwice: should throw if nesting error folders', async () => {
378
+ const r = containsErrorTwice('error/false', 'error');
379
+ expect(r).toBe(false);
380
+ });
352
381
  });
@@ -1,9 +1,10 @@
1
1
  import { publishEvents } from './publishEvents.js';
2
- import { handleEvents } from './handleEvents.js';
2
+ import { handleEvents, handleDirectS3WriteEvent } from './handleEvents.js';
3
3
  import _ from 'lodash';
4
4
 
5
5
  /**
6
6
  * bucket watcher watches a s3 bucket and publishes events to a sns topic, this allows you to process files in s3 with a some transformer function
7
+ * prefix objects with DIRECT to listen to direct s3 events
7
8
  *
8
9
  * @param {object} params - The parameters for watchBucket, see below
9
10
  * @param {object} params.event - The event of the lambda
@@ -14,7 +15,7 @@ import _ from 'lodash';
14
15
  * @param {function} params.transformer - the function to run on each file
15
16
  * @param {function} [params.errorHandlerPerFile] - the function to run on as a catch on each file
16
17
  * @param {boolean} [params.shouldSkipFailedFolders] - whether to use the failed-once, failed-twice, error or just error folders
17
- * @param {object} [params.userImportTypes] - user import sub directories object map
18
+ * @param {object} [params.userImportTypes] - user import subdirectories object map
18
19
  */
19
20
  export function watchBucket({
20
21
  event,
@@ -31,9 +32,27 @@ export function watchBucket({
31
32
  throw new Error('Missing required parameters. Need event, dynamoConfigTable, dynamoConfigKey, s3Bucket, snsTopic, transformer');
32
33
  }
33
34
 
34
- const EventSource = _.get(event, 'Records[0].EventSource');
35
+ const EventSource = _.get(event, 'Records[0].eventSource') || _.get(event, 'Records[0].EventSource');
36
+
35
37
  if (_.isEqual(EventSource, 'aws:sns')) {
36
38
  return handleEvents(event, transformer, errorHandlerPerFile, shouldSkipFailedFolders, userImportTypes);
39
+ } else if (_.isEqual(EventSource, 'aws:s3')) {
40
+ const failedOnce = 'failed-once';
41
+ const failedTwice = 'failed-twice';
42
+ const error = 'error';
43
+ const direct = 'DIRECT';
44
+ const key = _.get(event, 'Records[0].s3.object.key') || '';
45
+ const startsWithDirect = _.startsWith(key, direct);
46
+ if (key.includes(failedOnce) || key.includes(failedTwice) || key.includes(error)) {
47
+ // this used to throw, but the throw raised useless alarms, I believed turing throw in to a return was ok
48
+ return;
49
+ }
50
+
51
+ if (startsWithDirect) {
52
+ console.log('------->DIRECT WRITE FIRE');
53
+ return handleDirectS3WriteEvent(event, transformer, errorHandlerPerFile, shouldSkipFailedFolders, userImportTypes);
54
+ }
55
+
37
56
  } else {
38
57
  // the function is being run from the cron job so publish SNS events
39
58
  return publishEvents(dynamoConfigTable, dynamoConfigKey, s3Bucket, snsTopic, userImportTypes);
@@ -3,6 +3,84 @@ import * as handleEvents from './handleEvents.js';
3
3
  import * as publishEventsFunc from './publishEvents.js';
4
4
  import _ from 'lodash';
5
5
 
6
+ const directEventMock = {
7
+ 'Records': [
8
+ {
9
+ 'eventVersion': '2.1',
10
+ 'eventSource': 'aws:s3',
11
+ 'awsRegion': 'us-east-1',
12
+ 'eventTime': '2024-06-30T02:14:21.023Z',
13
+ 'eventName': 'ObjectCreated:Put',
14
+ 'userIdentity': {
15
+ 'principalId': 'AWS:AROA5F2DYBZDQJZJL6QJ3:piper-development-updateOrder'
16
+ },
17
+ 'requestParameters': {
18
+ 'sourceIPAddress': '3.89.10.118'
19
+ },
20
+ 'responseElements': {
21
+ 'x-amz-request-id': 'TXTBHCFPQCG7G9XZ',
22
+ 'x-amz-id-2': 'w/j8mQQpM6QcH0GOW2SQGh2yrj38NEeiOucQxnYODv77FuNYegVHwAm24B+EqcjuvhmRo1Kve28m8PzDHSENzokrGZC0r/xq'
23
+ },
24
+ 's3': {
25
+ 's3SchemaVersion': '1.0',
26
+ 'configurationId': 'piper-hooks-development-handleWebhook-e8435433a831248ee12806555a8d414d',
27
+ 'bucket': {
28
+ 'name': 'notify-webhook-dev',
29
+ 'ownerIdentity': {
30
+ 'principalId': 'A27KTZMXKEPEYT'
31
+ },
32
+ 'arn': 'arn:aws:s3:::notify-webhook-dev'
33
+ },
34
+ 'object': {
35
+ 'key': 'DIRECT-1z2HUO9nT2-106-orderUpdated-2024-06-30.json',
36
+ 'size': 186,
37
+ 'eTag': 'dca24fb9226703fcb0d64bd3e0833f16',
38
+ 'sequencer': '006680BF7CF4E2AA36'
39
+ }
40
+ }
41
+ }
42
+ ]
43
+ };
44
+
45
+ const errorBucketMock = {
46
+ 'Records': [
47
+ {
48
+ 'eventVersion': '2.1',
49
+ 'eventSource': 'aws:s3',
50
+ 'awsRegion': 'us-east-1',
51
+ 'eventTime': '2024-06-30T03:33:47.071Z',
52
+ 'eventName': 'ObjectCreated:Put',
53
+ 'userIdentity': {
54
+ 'principalId': 'AWS:AROA5F2DYBZD5OBZV274Z:piper-hooks-development-handleWebhook'
55
+ },
56
+ 'requestParameters': {
57
+ 'sourceIPAddress': '44.222.115.132'
58
+ },
59
+ 'responseElements': {
60
+ 'x-amz-request-id': 'P82WFW970DEXHPER',
61
+ 'x-amz-id-2': 'ZfM/HalpIrLKJf+hx+q1ldaKoPH1IxUZtyXe1B80dx04lbiJk+9uge7pnEFRsBchGLdNEdeFwAO1+xUfOP9oCz19rZeHHSZU'
62
+ },
63
+ 's3': {
64
+ 's3SchemaVersion': '1.0',
65
+ 'configurationId': 'piper-hooks-development-handleWebhook-e8435433a831248ee12806555a8d414d',
66
+ 'bucket': {
67
+ 'name': 'notify-webhook-dev',
68
+ 'ownerIdentity': {
69
+ 'principalId': 'A27KTZMXKEPEYT'
70
+ },
71
+ 'arn': 'arn:aws:s3:::notify-webhook-dev'
72
+ },
73
+ 'object': {
74
+ 'key': 'error/1z2HUO9nT2-106-orderUpdated-2024-06-30.json',
75
+ 'size': 2,
76
+ 'eTag': '99914b932bd37a50b983c5e7c90ae93b',
77
+ 'sequencer': '006680D21B0BAEE0A8'
78
+ }
79
+ }
80
+ }
81
+ ]
82
+ };
83
+
6
84
  describe('watchBucket.js', () => {
7
85
  describe('watchBucket', () => {
8
86
  it('should fail missing event', function () {
@@ -133,5 +211,46 @@ describe('watchBucket.js', () => {
133
211
  expect(publishEventsSpy).not.toHaveBeenCalled();
134
212
  });
135
213
  });
214
+
215
+ it('NOT run direct s3 calls if key does not start with DIRECT to prevent infinite loops', function () {
216
+ const params = {
217
+ event: directEventMock,
218
+ dynamoConfigTable: 'dynamoConfigTable',
219
+ dynamoConfigKey: 'dynamoConfigKey',
220
+ s3Bucket: 's3Bucket',
221
+ snsTopic: 'snsTopic',
222
+ transformer: _.noop
223
+ };
224
+
225
+ const fromSnsSpy = spyOn(handleEvents, 'handleDirectS3WriteEvent').and.returnValue(Promise.resolve());
226
+ const publishEventsSpy = spyOn(publishEventsFunc, 'publishEvents').and.returnValue(Promise.resolve());
227
+
228
+ return watchBucket.watchBucket(params).then(() => {
229
+ expect(fromSnsSpy).toHaveBeenCalled();
230
+ expect(publishEventsSpy).not.toHaveBeenCalled();
231
+ });
232
+ });
233
+
234
+ it('throw if a watch for s3 is fired when a file is rewritten to error folder', function () {
235
+ const params = {
236
+ event: errorBucketMock,
237
+ dynamoConfigTable: 'dynamoConfigTable',
238
+ dynamoConfigKey: 'dynamoConfigKey',
239
+ s3Bucket: 's3Bucket',
240
+ snsTopic: 'snsTopic',
241
+ transformer: _.noop
242
+ };
243
+
244
+ const fromSnsSpy = spyOn(handleEvents, 'handleDirectS3WriteEvent').and.returnValue(Promise.resolve());
245
+ const publishEventsSpy = spyOn(publishEventsFunc, 'publishEvents').and.returnValue(Promise.resolve());
246
+
247
+ try {
248
+ watchBucket.watchBucket(params);
249
+ expect(fromSnsSpy).not.toHaveBeenCalled();
250
+ expect(publishEventsSpy).not.toHaveBeenCalled();
251
+ } catch (e) {
252
+ expect(e).toBe('INVALID WATCH')
253
+ }
254
+ });
136
255
  });
137
256
  });
package/src/index.js CHANGED
@@ -1,10 +1,14 @@
1
1
  import { handleFile as handleFileImport } from './eventManager/handleFile.js';
2
2
  import { watchBucket as watchBucketImport } from './eventManager/watchBucket.js';
3
3
  import { publishEvents as publishEventsImport } from './eventManager/publishEvents.js';
4
+ import { createIncludes as createIncludesImport } from './database/dbUtils/queryStringUtils/createIncludes.js';
4
5
  import { createFilters as createFiltersImport } from './database/dbUtils/queryStringUtils/createFilters.js';
5
6
  import { createSort as createSortImport } from './database/dbUtils/queryStringUtils/createSort.js';
6
7
  import { findAll as findAllImport } from './database/dbUtils/queryStringUtils/findAll.js';
7
8
  import { checkModule as checkModuleImport } from './database/dbUtils/queryStringUtils/accessRightsUtils.js';
9
+ import { getModuleInfo as getModuleInfoImport } from './database/dbUtils/queryStringUtils/accessRightsUtils.js';
10
+ import { getAccessRightsInfo as getAccessRightsInfoImport } from './database/dbUtils/queryStringUtils/accessRightsUtils.js';
11
+ import { getDefaultBusinessIDInfo as getDefaultBusinessIDInfoImport } from './database/dbUtils/queryStringUtils/accessRightsUtils.js';
8
12
  import { failure as failureImport, success as successImport } from './requestResonse/requestResponse.js';
9
13
  import { runMigrations as runMigrationsImport } from './database/dbSetup/migrations.js';
10
14
  import { getCurrentUser as getCurrentUserImport } from './requestResonse/requestResponse.js';
@@ -17,14 +21,19 @@ import { parseEvent as parseEventImport } from './requestResonse/requestResponse
17
21
  import { getBusinessesInfo as getBusinessesInfoImport } from './database/dbUtils/queryStringUtils/accessRightsUtils.js';
18
22
  import { userDefaultBid as userDefaultBidImport } from './database/dbUtils/queryStringUtils/accessRightsUtils.js';
19
23
  import { checkWriteAccess as checkWriteAccessImport } from './database/dbUtils/queryStringUtils/accessRightsUtils.js';
24
+ import { isSystemUser as isSystemUserImport } from './database/dbUtils/queryStringUtils/accessRightsUtils.js';
20
25
 
21
26
  export const handleFile = handleFileImport;
22
27
  export const watchBucket = watchBucketImport;
23
28
  export const publishEvents = publishEventsImport;
24
29
  export const createFilters = createFiltersImport;
30
+ export const createIncludes = createIncludesImport;
25
31
  export const createSort = createSortImport;
26
32
  export const findAll = findAllImport;
27
33
  export const checkModule = checkModuleImport;
34
+ export const getAccessRightsInfo = getAccessRightsInfoImport;
35
+ export const getDefaultBusinessIDInfo = getDefaultBusinessIDInfoImport;
36
+ export const getModuleInfo = getModuleInfoImport;
28
37
  export const failure = failureImport;
29
38
  export const success = successImport;
30
39
  export const runMigrations = runMigrationsImport;
@@ -38,3 +47,4 @@ export const parseEvent = parseEventImport;
38
47
  export const getBusinessesInfo = getBusinessesInfoImport;
39
48
  export const userDefaultBid = userDefaultBidImport;
40
49
  export const checkWriteAccess = checkWriteAccessImport;
50
+ export const isSystemUser = isSystemUserImport;
@@ -3,7 +3,7 @@ import { errorList } from './errorCodes.js';
3
3
 
4
4
  /**
5
5
  * @param {Object} body
6
- * @param {{}} options
6
+ * @param {{dbClose?:function}} [options]
7
7
  */
8
8
  export function success(body, options) {
9
9
  const dbClose = _.get(options, 'dbClose', _.noop);
@@ -38,7 +38,7 @@ export function successHtml(html, options) {
38
38
  export function getCurrentUserNameFromCognitoEvent(event) {
39
39
  const attributes = _.get(event, 'request.userAttributes');
40
40
 
41
- return attributes['cognito:email_alias'] || 'UNKNOWN USER';
41
+ return attributes['cognito:email_alias'] || attributes['email'] || 'UNKNOWN USER';
42
42
  }
43
43
 
44
44
  /**
@@ -54,7 +54,7 @@ export function getCurrentUser(event) {
54
54
 
55
55
  const id = JSON.parse(jsonToParse);
56
56
 
57
- const username = _.get(event, 'requestContext.authorizer.claims.email') || _.get(event, 'requestContext.authorizer.email') || 'LOCAL TEST USER';
57
+ const username = _.get(event, 'requestContext.authorizer.claims.email') || _.get(event, 'requestContext.authorizer.email') || 'localtestuser@gexample.com';
58
58
 
59
59
  return {
60
60
  username,
@@ -74,7 +74,7 @@ export function failure(body = {}, options) {
74
74
  dbClose();
75
75
  let cleanedErrorBody;
76
76
 
77
- if (process.env.UTIL_LOG === 'LOG_ALL') {
77
+ if (process.env.UTIL_LOG === 'LOG_ALL' || process.env.BUILD_ENV === 'test') {
78
78
  console.error('------->UTIL ERROR:', body);
79
79
  }
80
80
 
@@ -85,8 +85,8 @@ export function failure(body = {}, options) {
85
85
  } else if (_.isUndefined(body.errorCode) || _.isUndefined(body.statusCode)) {
86
86
  cleanedErrorBody = detectSequelizeError(body);
87
87
  }
88
- if(_.get(body, 'response.data.message')) {
89
- console.error('------->UTIL ERROR: '+_.get(body, 'response.data.message'))
88
+ if (_.get(body, 'response.data.message')) {
89
+ console.error('------->UTIL ERROR: ' + _.get(body, 'response.data.message'));
90
90
  }
91
91
 
92
92
  const newBody = _.merge(NORMAL_ERROR, cleanedErrorBody);
@@ -194,5 +194,42 @@ export function detectJoyError(body) {
194
194
  errorBody.message = joyError.message;
195
195
  errorBody.statusCode = 400;
196
196
  errorBody.errorCode = '4000';
197
+ return errorBody;
198
+ }
199
+
200
+ /**
201
+ * Parse the body of a Dynamoose error response.
202
+ * Attempts to extract a human-readable error message from Dynamoose errors.
203
+ * This is a fallback for unknown database errors.
204
+ *
205
+ * @param {object} body - Part of a body response containing error details.
206
+ * @returns {object} - Error message object { message: string }
207
+ */
208
+ export function detectDynamooseError(body) {
209
+ const errorBody = {};
210
+
211
+ // Start with an empty message string.
212
+ let dynamooseError = '';
213
+
214
+ // If the error body contains an array of errors, concatenate their messages.
215
+ if (Array.isArray(body.errors)) {
216
+ dynamooseError += body.errors.reduce((acc, err) => {
217
+ return acc + ' ' + (err.message || '');
218
+ }, '');
219
+ }
220
+
221
+ // If there's a top-level message, add it.
222
+ if (body.message) {
223
+ dynamooseError += ' ' + body.message;
224
+ }
225
+
226
+ // If there is any additional detail provided, append it.
227
+ if (body.detail) {
228
+ dynamooseError += ' ' + body.detail;
229
+ }
230
+
231
+ // Trim the message to remove extra spaces.
232
+ errorBody.message = dynamooseError.trim();
233
+
197
234
  return errorBody;
198
235
  }
@@ -95,7 +95,7 @@ describe('requestResponse', () => {
95
95
  };
96
96
  const res = getCurrentUser(event);
97
97
  expect(res).toEqual({
98
- username: 'LOCAL TEST USER',
98
+ username: 'localtestuser@gexample.com',
99
99
  id: 0
100
100
  });
101
101
  });
@@ -139,6 +139,15 @@ describe('requestResponse', () => {
139
139
  });
140
140
  });
141
141
  describe('parseBody', () => {
142
+ // fit('parse form data', () => {
143
+ // parse body does not parse form data
144
+ // const event = {
145
+ // body: 'threeDSMethodData=eyJ0aHJlZURTTWV0aG9kTm90aWZpY2F0aW9uVVJMIjoiaHR0cHM6Ly93ZWJob29rLnNpdGUvNGYyNjEyZGMtOTA0Ny00NDRmLWIwOTUtYzcwYjg0NDliYjZlIiwidGhyZWVEU1NlcnZlclRyYW5zSUQiOiIxOTI2NThkMS1jNjA0LTQwYjQtOWFiMC0zMzJiNjcyMWI0ZmMifQ%3D%3D'
146
+ // };
147
+ //
148
+ // const res = parseBody(event);
149
+ // expect(res).toEqual({ foo: 'bar' });
150
+ // });
142
151
  it('parse a json string', () => {
143
152
  const event = {
144
153
  body: '{"foo":"bar"}'
package/webpack.config.js CHANGED
@@ -46,5 +46,5 @@ module.exports = {
46
46
  path: path.join(__dirname, './bin'),
47
47
  filename: '[name].js'
48
48
  },
49
- devtool: isProd ? 'nosources-source-map' : 'inline-source-map'
49
+ devtool: isProd ? 'source-map' : 'source-map'
50
50
  };