-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[DEV-2045] Refactor index and add queue event parser helper (#1259)
* Add function to subscribe and upsubscribe contact from active campaign list * Update packages/active-campaign-client/src/__tests__/handlers/manageListSubscription.test.ts Co-authored-by: marcobottaro <[email protected]> * Update packages/active-campaign-client/src/__tests__/handlers/manageListSubscription.test.ts Co-authored-by: marcobottaro <[email protected]> * Refactor index and add queue event parser helper * Update packages/active-campaign-client/.env.example Co-authored-by: Marco Ponchia <[email protected]> * pr comments * Connect webinar events to active campaign endpoint * Fix console log --------- Co-authored-by: t <[email protected]> Co-authored-by: tommaso1 <[email protected]> Co-authored-by: marcobottaro <[email protected]>
- Loading branch information
1 parent
d1dae75
commit b9003b6
Showing
6 changed files
with
305 additions
and
37 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
216 changes: 216 additions & 0 deletions
216
packages/active-campaign-client/src/__tests__/helpers/queueEventParser.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,216 @@ | ||
import { queueEventParser } from '../../helpers/queueEventParser'; | ||
import { QueueEvent, QueueEventType } from '../../types/queueEvent'; | ||
|
||
const MOCK_WEBINAR_ID = 'webinar-id'; | ||
const MOCK_COGNITO_ID = 'c67ec280-799a-40d6-b398-2a2b31aefbbd'; | ||
|
||
const generateMockBody = (eventName?: QueueEventType, webinarId?: string) => { | ||
const webinarData = webinarId ? { webinarId } : {}; | ||
return { | ||
...webinarData, | ||
version: '0', | ||
id: 'c67ec280-799a-40d6-b398-2a2b31aefbbd', | ||
'detail-type': 'AWS API Call via CloudTrail', | ||
source: 'aws.cognito-idp', | ||
account: '99999999999', | ||
time: '2024-11-25T13:34:12Z', | ||
region: 'region', | ||
resources: [], | ||
detail: { | ||
eventVersion: '1.08', | ||
userIdentity: { | ||
type: 'Unknown', | ||
principalId: 'Anonymous', | ||
}, | ||
eventTime: '2024-11-25T13:34:12Z', | ||
eventSource: 'cognito-idp.amazonaws.com', | ||
eventName: `${eventName || 'Unknown'}`, | ||
awsRegion: 'region', | ||
sourceIPAddress: '1.1.1.1', | ||
userAgent: | ||
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', | ||
requestParameters: { | ||
userAttributes: 'HIDDEN_DUE_TO_SECURITY_REASONS', | ||
accessToken: 'HIDDEN_DUE_TO_SECURITY_REASONS', | ||
}, | ||
responseElements: null, | ||
additionalEventData: { | ||
sub: MOCK_COGNITO_ID, | ||
}, | ||
requestID: 'c67ec280-799a-40d6-b398-2a2b31aefbbd', | ||
eventID: '1b231015-853a-4042-a157-4127a9ec5530', | ||
readOnly: false, | ||
eventType: 'AwsApiCall', | ||
managementEvent: true, | ||
recipientAccountId: '999999999', | ||
eventCategory: 'Management', | ||
tlsDetails: { | ||
tlsVersion: 'TLSv1.3', | ||
cipherSuite: 'TLS_AES_128_GCM_SHA256', | ||
clientProvidedHostHeader: 'clientProvidedHostHeader', | ||
}, | ||
}, | ||
}; | ||
}; | ||
|
||
const generateSQSMockEvent = (params?: { | ||
readonly eventType?: QueueEventType; | ||
readonly webinarId?: string; | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
readonly customBody?: Record<string, unknown>; | ||
}) => ({ | ||
Records: [ | ||
{ | ||
messageId: '983ad1cf-e06a-4393-8382-e51af60c4f58', | ||
receiptHandle: 'receiptHandle', | ||
body: JSON.stringify( | ||
params?.customBody || | ||
generateMockBody(params?.eventType, params?.webinarId) | ||
), | ||
attributes: { | ||
ApproximateReceiveCount: '1', | ||
SentTimestamp: '99999999', | ||
SequenceNumber: '1245', | ||
MessageGroupId: 'userEvents', | ||
SenderId: 'SenderId', | ||
MessageDeduplicationId: 'MessageDeduplicationId', | ||
ApproximateFirstReceiveTimestamp: '99999999', | ||
}, | ||
messageAttributes: {}, | ||
md5OfBody: 'sdf1df457fg71d5sf1dfsd7', | ||
eventSource: 'aws:sqs', | ||
eventSourceARN: 'eventSourceARN', | ||
awsRegion: 'awsRegion', | ||
}, | ||
], | ||
}); | ||
|
||
describe('Helpers: queueEventParser', () => { | ||
it('should rise an error if event is different from QueueEventType', async () => { | ||
const sqsEvent = generateSQSMockEvent(); | ||
expect(() => { | ||
queueEventParser(sqsEvent); | ||
}).toThrow('Event missing required fields'); | ||
}); | ||
|
||
it('should rise an error if body is not a valid JSON', async () => { | ||
const sqsEvent = generateSQSMockEvent(); | ||
expect(() => { | ||
queueEventParser(sqsEvent); | ||
}).toThrow('Event missing required fields'); | ||
}); | ||
|
||
it('should properly convert UpdateUserAttributes event', async () => { | ||
const sqsEvent = generateSQSMockEvent({ | ||
eventType: 'UpdateUserAttributes', | ||
}); | ||
const parsedQueueEvent = queueEventParser(sqsEvent); | ||
const queueEvent: QueueEvent = { | ||
detail: { | ||
eventName: 'UpdateUserAttributes', | ||
additionalEventData: { | ||
sub: MOCK_COGNITO_ID, | ||
}, | ||
}, | ||
}; | ||
expect(parsedQueueEvent.detail.eventName).toEqual( | ||
queueEvent.detail.eventName | ||
); | ||
expect(parsedQueueEvent.detail.additionalEventData.sub).toEqual( | ||
queueEvent.detail.additionalEventData.sub | ||
); | ||
}); | ||
|
||
it('should properly convert DeleteUser event', async () => { | ||
const sqsEvent = generateSQSMockEvent({ eventType: 'DeleteUser' }); | ||
const parsedQueueEvent = queueEventParser(sqsEvent); | ||
const queueEvent: QueueEvent = { | ||
detail: { | ||
eventName: 'DeleteUser', | ||
additionalEventData: { | ||
sub: MOCK_COGNITO_ID, | ||
}, | ||
}, | ||
}; | ||
expect(parsedQueueEvent.detail.eventName).toEqual( | ||
queueEvent.detail.eventName | ||
); | ||
expect(parsedQueueEvent.detail.additionalEventData.sub).toEqual( | ||
queueEvent.detail.additionalEventData.sub | ||
); | ||
}); | ||
|
||
it('should properly convert ConfirmSignUp event', async () => { | ||
const sqsEvent = generateSQSMockEvent({ eventType: 'ConfirmSignUp' }); | ||
const parsedQueueEvent = queueEventParser(sqsEvent); | ||
const queueEvent: QueueEvent = { | ||
detail: { | ||
eventName: 'ConfirmSignUp', | ||
additionalEventData: { | ||
sub: MOCK_COGNITO_ID, | ||
}, | ||
}, | ||
}; | ||
expect(parsedQueueEvent.detail.eventName).toEqual( | ||
queueEvent.detail.eventName | ||
); | ||
expect(parsedQueueEvent.detail.additionalEventData.sub).toEqual( | ||
queueEvent.detail.additionalEventData.sub | ||
); | ||
}); | ||
|
||
it('should properly convert DynamoINSERT event', async () => { | ||
const sqsEvent = generateSQSMockEvent({ | ||
eventType: 'DynamoINSERT', | ||
webinarId: MOCK_WEBINAR_ID, | ||
}); | ||
const parsedQueueEvent = queueEventParser(sqsEvent); | ||
const queueEvent: QueueEvent = { | ||
detail: { | ||
eventName: 'DynamoINSERT', | ||
additionalEventData: { | ||
sub: MOCK_COGNITO_ID, | ||
}, | ||
}, | ||
webinarId: MOCK_WEBINAR_ID, | ||
}; | ||
expect(parsedQueueEvent.detail.eventName).toEqual( | ||
queueEvent.detail.eventName | ||
); | ||
expect(parsedQueueEvent.detail.additionalEventData.sub).toEqual( | ||
queueEvent.detail.additionalEventData.sub | ||
); | ||
expect(parsedQueueEvent.webinarId).toEqual(queueEvent.webinarId); | ||
}); | ||
|
||
it('should properly convert DynamoREMOVE event', async () => { | ||
const sqsEvent = generateSQSMockEvent({ | ||
eventType: 'DynamoREMOVE', | ||
webinarId: MOCK_WEBINAR_ID, | ||
}); | ||
const parsedQueueEvent = queueEventParser(sqsEvent); | ||
const queueEvent: QueueEvent = { | ||
detail: { | ||
eventName: 'DynamoREMOVE', | ||
additionalEventData: { | ||
sub: MOCK_COGNITO_ID, | ||
}, | ||
}, | ||
webinarId: MOCK_WEBINAR_ID, | ||
}; | ||
expect(parsedQueueEvent.detail.eventName).toEqual( | ||
queueEvent.detail.eventName | ||
); | ||
expect(parsedQueueEvent.detail.additionalEventData.sub).toEqual( | ||
queueEvent.detail.additionalEventData.sub | ||
); | ||
expect(parsedQueueEvent.webinarId).toEqual(queueEvent.webinarId); | ||
}); | ||
|
||
it('should rise an error if webinar id is missing for Dynamo event', async () => { | ||
const sqsEvent = generateSQSMockEvent({ eventType: 'DynamoREMOVE' }); | ||
expect(() => { | ||
queueEventParser(sqsEvent); | ||
}).toThrow('Event missing required fields'); | ||
}); | ||
}); |
46 changes: 46 additions & 0 deletions
46
packages/active-campaign-client/src/handlers/sqsQueueHandler.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import { APIGatewayProxyResult, SQSEvent } from 'aws-lambda'; | ||
import { getUserFromCognito } from '../helpers/getUserFromCognito'; | ||
import { addContact } from '../helpers/addContact'; | ||
import { updateContact } from '../helpers/updateContact'; | ||
import { deleteContact } from '../helpers/deleteContact'; | ||
import { queueEventParser } from '../helpers/queueEventParser'; | ||
import { | ||
addContactToList, | ||
removeContactToList, | ||
} from '../helpers/manageListSubscription'; | ||
|
||
export async function sqsQueueHandler(event: { | ||
readonly Records: SQSEvent['Records']; | ||
}): Promise<APIGatewayProxyResult> { | ||
try { | ||
console.log('Event:', event); // TODO: Remove after testing | ||
const queueEvent = queueEventParser(event); | ||
switch (queueEvent.detail.eventName) { | ||
case 'ConfirmSignUp': | ||
return await addContact(await getUserFromCognito(queueEvent)); | ||
case 'UpdateUserAttributes': | ||
return await updateContact(await getUserFromCognito(queueEvent)); | ||
case 'DeleteUser': | ||
return await deleteContact(queueEvent.detail.additionalEventData.sub); | ||
case 'DynamoINSERT': | ||
return await addContactToList( | ||
queueEvent.detail.additionalEventData.sub, | ||
queueEvent.webinarId || '' | ||
); | ||
case 'DynamoREMOVE': | ||
return await removeContactToList( | ||
queueEvent.detail.additionalEventData.sub, | ||
queueEvent.webinarId || '' | ||
); | ||
default: | ||
// eslint-disable-next-line functional/no-throw-statements | ||
throw new Error('Unknown event'); | ||
} | ||
} catch (error) { | ||
console.error('Error:', error); // TODO: Remove after testing | ||
return { | ||
statusCode: 500, | ||
body: JSON.stringify({ message: 'Internal server error' }), | ||
}; | ||
} | ||
} |
33 changes: 33 additions & 0 deletions
33
packages/active-campaign-client/src/helpers/queueEventParser.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { SQSEvent } from 'aws-lambda'; | ||
import { QueueEvent, QueueEventType } from '../types/queueEvent'; | ||
|
||
const queueEvents: readonly QueueEventType[] = [ | ||
'ConfirmSignUp', | ||
'UpdateUserAttributes', | ||
'DeleteUser', | ||
'DynamoINSERT', | ||
'DynamoREMOVE', | ||
]; | ||
|
||
const dynamoEvents: readonly QueueEventType[] = [ | ||
'DynamoINSERT', | ||
'DynamoREMOVE', | ||
]; | ||
|
||
export function queueEventParser(event: { | ||
readonly Records: SQSEvent['Records']; | ||
}): QueueEvent { | ||
const queueEvent = JSON.parse(event.Records[0].body) as unknown as QueueEvent; | ||
|
||
if ( | ||
!queueEvents.includes(queueEvent.detail.eventName) || | ||
!queueEvent.detail.additionalEventData.sub || | ||
(dynamoEvents.includes(queueEvent.detail.eventName) && | ||
!queueEvent.webinarId) | ||
) { | ||
// eslint-disable-next-line functional/no-throw-statements | ||
throw new Error('Event missing required fields'); | ||
} | ||
|
||
return queueEvent; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,38 +1,8 @@ | ||
import { APIGatewayProxyResult, SQSEvent } from 'aws-lambda'; | ||
import { getUserFromCognito } from './helpers/getUserFromCognito'; | ||
import { QueueEvent } from './types/queueEvent'; | ||
import { addContact } from './helpers/addContact'; | ||
import { updateContact } from './helpers/updateContact'; | ||
import { deleteContact } from './helpers/deleteContact'; | ||
import { SQSEvent } from 'aws-lambda'; | ||
import { sqsQueueHandler } from './handlers/sqsQueueHandler'; | ||
|
||
export async function handler(event: { | ||
export async function sqsQueue(event: { | ||
readonly Records: SQSEvent['Records']; | ||
}): Promise<APIGatewayProxyResult> { | ||
try { | ||
console.log('Event:', event); // TODO: Remove after testing | ||
const queueEvent = JSON.parse( | ||
event.Records[0].body | ||
) as unknown as QueueEvent; | ||
switch (queueEvent.detail.eventName) { | ||
case 'ConfirmSignUp': | ||
return await addContact(await getUserFromCognito(queueEvent)); | ||
case 'UpdateUserAttributes': | ||
return await updateContact(await getUserFromCognito(queueEvent)); | ||
case 'DeleteUser': | ||
return await deleteContact(queueEvent.detail.additionalEventData.sub); | ||
default: | ||
console.log('Unknown event:', queueEvent.detail.eventName); | ||
break; | ||
} | ||
return { | ||
statusCode: 200, | ||
body: JSON.stringify(event), | ||
}; | ||
} catch (error) { | ||
console.error('Error:', error); | ||
return { | ||
statusCode: 500, | ||
body: JSON.stringify({ message: 'Internal server error' }), | ||
}; | ||
} | ||
}) { | ||
return await sqsQueueHandler(event); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters