From 4984a2a4f22763d38182c358c641af86b5eff468 Mon Sep 17 00:00:00 2001 From: t Date: Thu, 5 Dec 2024 15:48:26 +0100 Subject: [PATCH 01/19] sync user wip --- .../fetchSubscribedWebinarsFromDynamo.ts | 30 ++++++ .../src/helpers/getUserFromCognito.ts | 8 +- .../src/helpers/resyncUser.ts | 96 +++++++++++++++++++ 3 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 packages/active-campaign-client/src/helpers/fetchSubscribedWebinarsFromDynamo.ts create mode 100644 packages/active-campaign-client/src/helpers/resyncUser.ts diff --git a/packages/active-campaign-client/src/helpers/fetchSubscribedWebinarsFromDynamo.ts b/packages/active-campaign-client/src/helpers/fetchSubscribedWebinarsFromDynamo.ts new file mode 100644 index 0000000000..7f59d6d1bc --- /dev/null +++ b/packages/active-campaign-client/src/helpers/fetchSubscribedWebinarsFromDynamo.ts @@ -0,0 +1,30 @@ +import { DynamoDBClient, QueryCommand } from '@aws-sdk/client-dynamodb'; +import { APIGatewayProxyResult } from 'aws-lambda'; + +export async function fetchSubscribedWebinarsFromDynamo( + username: string +): Promise { + try { + const dynamoClient = new DynamoDBClient({ region: process.env.AWS_REGION }); + const command = new QueryCommand({ + TableName: process.env.DYNAMO_WEBINARS_TABLE_NAME, + KeyConditionExpression: 'username = :username', + ExpressionAttributeValues: { + ':username': { S: username }, + }, + }); + + const response = await dynamoClient.send(command); + console.log('getWebinarSubscriptions', response); + return { + statusCode: 200, + body: JSON.stringify(response.Items), + }; + } catch (error) { + console.error('Error querying items by username:', error); + return { + statusCode: 500, + body: JSON.stringify({ message: 'Internal server error' }), + }; + } +} diff --git a/packages/active-campaign-client/src/helpers/getUserFromCognito.ts b/packages/active-campaign-client/src/helpers/getUserFromCognito.ts index 41d02e7c7b..325b4d2efd 100644 --- a/packages/active-campaign-client/src/helpers/getUserFromCognito.ts +++ b/packages/active-campaign-client/src/helpers/getUserFromCognito.ts @@ -4,9 +4,15 @@ import { QueueEvent } from '../types/queueEvent'; import { listUsersCommandOutputToUser } from './listUsersCommandOutputToUser'; export async function getUserFromCognito(queueEvent: QueueEvent) { + return await getUserFromCognitoByUsername( + queueEvent.detail.additionalEventData.sub + ); +} + +export async function getUserFromCognitoByUsername(username: string) { const command = new ListUsersCommand({ UserPoolId: process.env.COGNITO_USER_POOL_ID, - Filter: `username = "${queueEvent.detail.additionalEventData.sub}"`, + Filter: `username = "${username}"`, }); const listUsersCommandOutput = await cognitoClient.send(command); const user = listUsersCommandOutputToUser(listUsersCommandOutput); diff --git a/packages/active-campaign-client/src/helpers/resyncUser.ts b/packages/active-campaign-client/src/helpers/resyncUser.ts new file mode 100644 index 0000000000..59bc5576d7 --- /dev/null +++ b/packages/active-campaign-client/src/helpers/resyncUser.ts @@ -0,0 +1,96 @@ +import { deleteContact } from './deleteContact'; +import { getUserFromCognitoByUsername } from './getUserFromCognito'; +import { fetchSubscribedWebinarsFromDynamo } from './fetchSubscribedWebinarsFromDynamo'; +import { addContact } from './addContact'; +import { User } from '../types/user'; +import { APIGatewayProxyResult } from 'aws-lambda'; + +export async function resyncUser( + cognitoId: string +): Promise { + /* + La lambda cancella l’utente e, se esiste ancora su Cognito, lo ricrea e lo associa ai webinar corrispondenti (liste su AC). + + Capire se esiste già uno script python (fatto da Christian) che fa la stessa cosa. + + */ + // Step 1: Delete user on active campaign + const deletionResult = await deleteContact(cognitoId); + if (deletionResult.statusCode != 200 && deletionResult.statusCode != 404) { + console.log('Error deleting contact', deletionResult); + return deletionResult; + } + + // Step 2: Check if user exists on Cognito + // eslint-disable-next-line functional/no-let + let user: User | null = null; + + try { + user = await getUserFromCognitoByUsername(cognitoId); + } catch (e) { + // User not found -> user stays null + } + + // If the user is not present the sync is done + if (!user) { + console.log(`User: ${cognitoId} not present on Cognito, sync done.`); + return { + statusCode: 200, + body: JSON.stringify({ + message: 'User not present on Cognito, sync done.', + }), + }; + } + + // Fetch all the webinars the user is subscribed to + const webinars = await fetchSubscribedWebinarsFromDynamo(cognitoId); + /* + { + "statusCode": 200, + "body": "[{\"createdAt\":{\"S\":\"2024-12-05T14:18:28.601Z\"},\"username\":{\"S\":\"56beb230-f081-70a4-f0e1-4b09723b4771\"},\"webinarId\":{\"S\":\"DevTalks-pagoPA-IBAN\"}},{\"createdAt\":{\"S\":\"2024-12-05T14:18:22.429Z\"},\"username\":{\"S\":\"56beb230-f081-70a4-f0e1-4b09723b4771\"},\"webinarId\":{\"S\":\"PagoPALAB-sanita\"}}]" +} + */ + const webinarIds = JSON.parse(webinars.body) + .map( + (webinar: { readonly webinarId: { readonly S: string } }) => + webinar?.webinarId?.S + ) + .filter(Boolean); + + console.log('Webinar IDs:', webinarIds); + + // Step 3: Create user on active campaign + addContact(user); + + // Step 4: Add user to the webinars lists + // TBD + + return { + statusCode: 200, + body: JSON.stringify({ message: 'User resynced' }), + }; +} + +/* + +{ + "Records": [ + { + "messageId": "19dd0b57-b21e-4ac1-bd88-01bbb068cb78", + "receiptHandle": "MessageReceiptHandle", + "body": "{\"detail\":{\"eventName\":\"ResyncUser\", \"additionalEventData\" : {\"sub\": \"56beb230-f081-70a4-f0e1-4b09723b4771\"}}}", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1523232000000", + "SenderId": "123456789012", + "ApproximateFirstReceiveTimestamp": "1523232000001" + }, + "messageAttributes": {}, + "md5OfBody": "{{{md5_of_body}}}", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:us-east-1:123456789012:MyQueue", + "awsRegion": "us-east-1" + } + ] +} + */ From 33802ef0ac469e013e91eb2768331f156030bf34 Mon Sep 17 00:00:00 2001 From: t Date: Thu, 5 Dec 2024 16:08:13 +0100 Subject: [PATCH 02/19] sync user --- .../src/helpers/resyncUser.ts | 48 +++++++------------ packages/active-campaign-client/src/index.ts | 10 ++++ 2 files changed, 26 insertions(+), 32 deletions(-) diff --git a/packages/active-campaign-client/src/helpers/resyncUser.ts b/packages/active-campaign-client/src/helpers/resyncUser.ts index 59bc5576d7..13cf815b7b 100644 --- a/packages/active-campaign-client/src/helpers/resyncUser.ts +++ b/packages/active-campaign-client/src/helpers/resyncUser.ts @@ -4,6 +4,7 @@ import { fetchSubscribedWebinarsFromDynamo } from './fetchSubscribedWebinarsFrom import { addContact } from './addContact'; import { User } from '../types/user'; import { APIGatewayProxyResult } from 'aws-lambda'; +import { addContactToList } from './manageListSubscription'; export async function resyncUser( cognitoId: string @@ -44,12 +45,7 @@ export async function resyncUser( // Fetch all the webinars the user is subscribed to const webinars = await fetchSubscribedWebinarsFromDynamo(cognitoId); - /* - { - "statusCode": 200, - "body": "[{\"createdAt\":{\"S\":\"2024-12-05T14:18:28.601Z\"},\"username\":{\"S\":\"56beb230-f081-70a4-f0e1-4b09723b4771\"},\"webinarId\":{\"S\":\"DevTalks-pagoPA-IBAN\"}},{\"createdAt\":{\"S\":\"2024-12-05T14:18:22.429Z\"},\"username\":{\"S\":\"56beb230-f081-70a4-f0e1-4b09723b4771\"},\"webinarId\":{\"S\":\"PagoPALAB-sanita\"}}]" -} - */ + const webinarIds = JSON.parse(webinars.body) .map( (webinar: { readonly webinarId: { readonly S: string } }) => @@ -60,37 +56,25 @@ export async function resyncUser( console.log('Webinar IDs:', webinarIds); // Step 3: Create user on active campaign - addContact(user); + const res = await addContact(user); + console.log('Add contact result:', res); // Step 4: Add user to the webinars lists - // TBD + // eslint-disable-next-line functional/no-loop-statements + for (const webinarId of webinarIds) { + console.log('Adding contact to list:', webinarId); + try { + const result = await addContactToList(cognitoId, webinarId); + console.log('Add contact to list result:', result); + // wait 1 sec to avoid rate limiting + await new Promise((resolve) => setTimeout(resolve, 1000)); + } catch (e) { + console.error('Error adding contact to list', e); + } + } return { statusCode: 200, body: JSON.stringify({ message: 'User resynced' }), }; } - -/* - -{ - "Records": [ - { - "messageId": "19dd0b57-b21e-4ac1-bd88-01bbb068cb78", - "receiptHandle": "MessageReceiptHandle", - "body": "{\"detail\":{\"eventName\":\"ResyncUser\", \"additionalEventData\" : {\"sub\": \"56beb230-f081-70a4-f0e1-4b09723b4771\"}}}", - "attributes": { - "ApproximateReceiveCount": "1", - "SentTimestamp": "1523232000000", - "SenderId": "123456789012", - "ApproximateFirstReceiveTimestamp": "1523232000001" - }, - "messageAttributes": {}, - "md5OfBody": "{{{md5_of_body}}}", - "eventSource": "aws:sqs", - "eventSourceARN": "arn:aws:sqs:us-east-1:123456789012:MyQueue", - "awsRegion": "us-east-1" - } - ] -} - */ diff --git a/packages/active-campaign-client/src/index.ts b/packages/active-campaign-client/src/index.ts index 0d1edb3b65..f939993611 100644 --- a/packages/active-campaign-client/src/index.ts +++ b/packages/active-campaign-client/src/index.ts @@ -1,8 +1,18 @@ import { SQSEvent } from 'aws-lambda'; import { sqsQueueHandler } from './handlers/sqsQueueHandler'; +import { resyncUser } from './helpers/resyncUser'; export async function sqsQueue(event: { readonly Records: SQSEvent['Records']; }) { return await sqsQueueHandler(event); } + +export async function handler(event: { + readonly Records: SQSEvent['Records']; +}) { + //return await sqsQueueHandler(event); + const cognitoId = event.Records[0].body; + console.log('cognitoId: ', cognitoId); + return await resyncUser(cognitoId); +} From c432ad43c9611ea4d7d45a43a4a3021518e87605 Mon Sep 17 00:00:00 2001 From: t Date: Thu, 5 Dec 2024 16:11:56 +0100 Subject: [PATCH 03/19] changeset --- .changeset/cuddly-cats-retire.md | 5 +++++ packages/active-campaign-client/src/helpers/resyncUser.ts | 6 ------ 2 files changed, 5 insertions(+), 6 deletions(-) create mode 100644 .changeset/cuddly-cats-retire.md diff --git a/.changeset/cuddly-cats-retire.md b/.changeset/cuddly-cats-retire.md new file mode 100644 index 0000000000..ee0ece7cd1 --- /dev/null +++ b/.changeset/cuddly-cats-retire.md @@ -0,0 +1,5 @@ +--- +"active-campaign-client": minor +--- + +Resync user diff --git a/packages/active-campaign-client/src/helpers/resyncUser.ts b/packages/active-campaign-client/src/helpers/resyncUser.ts index 13cf815b7b..ae916f7004 100644 --- a/packages/active-campaign-client/src/helpers/resyncUser.ts +++ b/packages/active-campaign-client/src/helpers/resyncUser.ts @@ -9,12 +9,6 @@ import { addContactToList } from './manageListSubscription'; export async function resyncUser( cognitoId: string ): Promise { - /* - La lambda cancella l’utente e, se esiste ancora su Cognito, lo ricrea e lo associa ai webinar corrispondenti (liste su AC). - - Capire se esiste già uno script python (fatto da Christian) che fa la stessa cosa. - - */ // Step 1: Delete user on active campaign const deletionResult = await deleteContact(cognitoId); if (deletionResult.statusCode != 200 && deletionResult.statusCode != 404) { From 5865dc1ebddea177125d758ab5e5cdbbc3897147 Mon Sep 17 00:00:00 2001 From: tommaso1 Date: Fri, 6 Dec 2024 10:38:52 +0100 Subject: [PATCH 04/19] Update .changeset/cuddly-cats-retire.md Co-authored-by: Marco Ponchia --- .changeset/cuddly-cats-retire.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/cuddly-cats-retire.md b/.changeset/cuddly-cats-retire.md index ee0ece7cd1..ef17a074d8 100644 --- a/.changeset/cuddly-cats-retire.md +++ b/.changeset/cuddly-cats-retire.md @@ -2,4 +2,4 @@ "active-campaign-client": minor --- -Resync user +Add resync user handler From b1b81805cb6afd5cfb315975cfc09b40bc7c52db Mon Sep 17 00:00:00 2001 From: tommaso1 Date: Fri, 6 Dec 2024 10:39:47 +0100 Subject: [PATCH 05/19] Update packages/active-campaign-client/src/index.ts Co-authored-by: Marco Ponchia --- packages/active-campaign-client/src/index.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/active-campaign-client/src/index.ts b/packages/active-campaign-client/src/index.ts index f939993611..9c6f30827c 100644 --- a/packages/active-campaign-client/src/index.ts +++ b/packages/active-campaign-client/src/index.ts @@ -11,8 +11,5 @@ export async function sqsQueue(event: { export async function handler(event: { readonly Records: SQSEvent['Records']; }) { - //return await sqsQueueHandler(event); - const cognitoId = event.Records[0].body; - console.log('cognitoId: ', cognitoId); - return await resyncUser(cognitoId); + return await resyncUserHandler(event); } From 6ec2fc2bbd87cea73442ec080c2220f35d5d54bb Mon Sep 17 00:00:00 2001 From: tommaso1 Date: Fri, 6 Dec 2024 10:39:55 +0100 Subject: [PATCH 06/19] Update packages/active-campaign-client/src/index.ts Co-authored-by: Marco Ponchia --- packages/active-campaign-client/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/active-campaign-client/src/index.ts b/packages/active-campaign-client/src/index.ts index 9c6f30827c..f35d8db501 100644 --- a/packages/active-campaign-client/src/index.ts +++ b/packages/active-campaign-client/src/index.ts @@ -1,6 +1,6 @@ import { SQSEvent } from 'aws-lambda'; import { sqsQueueHandler } from './handlers/sqsQueueHandler'; -import { resyncUser } from './helpers/resyncUser'; +import { resyncUserHandler } from './helpers/resyncUser'; export async function sqsQueue(event: { readonly Records: SQSEvent['Records']; From b102553c95480da29f2636bb083fd970871bf1ea Mon Sep 17 00:00:00 2001 From: tommaso1 Date: Fri, 6 Dec 2024 10:40:06 +0100 Subject: [PATCH 07/19] Update packages/active-campaign-client/src/helpers/resyncUser.ts Co-authored-by: Marco Ponchia --- .../src/helpers/resyncUser.ts | 122 +++++++++--------- 1 file changed, 64 insertions(+), 58 deletions(-) diff --git a/packages/active-campaign-client/src/helpers/resyncUser.ts b/packages/active-campaign-client/src/helpers/resyncUser.ts index ae916f7004..32bcd19a84 100644 --- a/packages/active-campaign-client/src/helpers/resyncUser.ts +++ b/packages/active-campaign-client/src/helpers/resyncUser.ts @@ -2,73 +2,79 @@ import { deleteContact } from './deleteContact'; import { getUserFromCognitoByUsername } from './getUserFromCognito'; import { fetchSubscribedWebinarsFromDynamo } from './fetchSubscribedWebinarsFromDynamo'; import { addContact } from './addContact'; -import { User } from '../types/user'; -import { APIGatewayProxyResult } from 'aws-lambda'; +import { APIGatewayProxyResult, SQSEvent } from 'aws-lambda'; import { addContactToList } from './manageListSubscription'; +import { queueEventParser } from './queueEventParser'; -export async function resyncUser( - cognitoId: string -): Promise { - // Step 1: Delete user on active campaign - const deletionResult = await deleteContact(cognitoId); - if (deletionResult.statusCode != 200 && deletionResult.statusCode != 404) { - console.log('Error deleting contact', deletionResult); - return deletionResult; - } - - // Step 2: Check if user exists on Cognito - // eslint-disable-next-line functional/no-let - let user: User | null = null; - +export async function resyncUserHandler(event: { + readonly Records: SQSEvent['Records']; +}): Promise { try { - user = await getUserFromCognitoByUsername(cognitoId); - } catch (e) { - // User not found -> user stays null - } + const queueEvent = queueEventParser(event); + const cognitoId = queueEvent.detail.additionalEventData.sub; + const deletionResult = await deleteContact(cognitoId); + if (deletionResult.statusCode != 200 && deletionResult.statusCode != 404) { + // eslint-disable-next-line functional/no-throw-statements + throw new Error('Error adding contact'); + } - // If the user is not present the sync is done - if (!user) { - console.log(`User: ${cognitoId} not present on Cognito, sync done.`); - return { - statusCode: 200, - body: JSON.stringify({ - message: 'User not present on Cognito, sync done.', - }), - }; - } + const user = await getUserFromCognitoByUsername(cognitoId); - // Fetch all the webinars the user is subscribed to - const webinars = await fetchSubscribedWebinarsFromDynamo(cognitoId); + if (!user) { + console.log(`User: ${cognitoId} not present on Cognito, sync done.`); + return { + statusCode: 200, + body: JSON.stringify({ + message: 'User not present on Cognito, sync done.', + }), + }; + } - const webinarIds = JSON.parse(webinars.body) - .map( - (webinar: { readonly webinarId: { readonly S: string } }) => - webinar?.webinarId?.S - ) - .filter(Boolean); + const userWebinarsSubscriptions = await fetchSubscribedWebinarsFromDynamo( + cognitoId + ); - console.log('Webinar IDs:', webinarIds); + const webinarIds = JSON.parse(userWebinarsSubscriptions.body) + .map( + (webinar: { readonly webinarId: { readonly S: string } }) => + webinar?.webinarId?.S + ) + .filter(Boolean); - // Step 3: Create user on active campaign - const res = await addContact(user); - console.log('Add contact result:', res); + console.log('Webinar IDs:', webinarIds); // TODO: Remove after testing - // Step 4: Add user to the webinars lists - // eslint-disable-next-line functional/no-loop-statements - for (const webinarId of webinarIds) { - console.log('Adding contact to list:', webinarId); - try { - const result = await addContactToList(cognitoId, webinarId); - console.log('Add contact to list result:', result); - // wait 1 sec to avoid rate limiting - await new Promise((resolve) => setTimeout(resolve, 1000)); - } catch (e) { - console.error('Error adding contact to list', e); + const res = await addContact(user); + if (res.statusCode !== 200) { + // eslint-disable-next-line functional/no-throw-statements + throw new Error('Error adding contact'); } - } - return { - statusCode: 200, - body: JSON.stringify({ message: 'User resynced' }), - }; + await webinarIds.reduce( + async ( + prevPromise: Promise, + webinarId: string + ) => { + await prevPromise; + try { + const result = await addContactToList(cognitoId, webinarId); + console.log('Add contact to list result:', result, webinarId); // TODO: Remove after testing + await new Promise((resolve) => setTimeout(resolve, 1000)); // wait 1 sec to avoid rate limiting + } catch (e) { + console.error('Error adding contact to list', e); // TODO: Remove after testing + } + }, + Promise.resolve() + ); + + return { + statusCode: 200, + body: JSON.stringify({ message: 'User resynced' }), + }; + } catch (error) { + return { + statusCode: 500, + body: JSON.stringify({ message: error }), + }; + } } + From 3a7569d888870aa1090d2c29ff634d3308914730 Mon Sep 17 00:00:00 2001 From: t Date: Fri, 6 Dec 2024 10:43:50 +0100 Subject: [PATCH 08/19] pr comments --- .../resyncUserHandler.ts} | 13 ++++++------- .../src/helpers/getUserFromCognito.ts | 14 +++++++------- packages/active-campaign-client/src/index.ts | 4 ++-- 3 files changed, 15 insertions(+), 16 deletions(-) rename packages/active-campaign-client/src/{helpers/resyncUser.ts => handlers/resyncUserHandler.ts} (84%) diff --git a/packages/active-campaign-client/src/helpers/resyncUser.ts b/packages/active-campaign-client/src/handlers/resyncUserHandler.ts similarity index 84% rename from packages/active-campaign-client/src/helpers/resyncUser.ts rename to packages/active-campaign-client/src/handlers/resyncUserHandler.ts index 32bcd19a84..a2496306a9 100644 --- a/packages/active-campaign-client/src/helpers/resyncUser.ts +++ b/packages/active-campaign-client/src/handlers/resyncUserHandler.ts @@ -1,10 +1,10 @@ -import { deleteContact } from './deleteContact'; -import { getUserFromCognitoByUsername } from './getUserFromCognito'; -import { fetchSubscribedWebinarsFromDynamo } from './fetchSubscribedWebinarsFromDynamo'; -import { addContact } from './addContact'; +import { deleteContact } from '../helpers/deleteContact'; +import { getUserFromCognitoByUsername } from '../helpers/getUserFromCognito'; +import { fetchSubscribedWebinarsFromDynamo } from '../helpers/fetchSubscribedWebinarsFromDynamo'; +import { addContact } from '../helpers/addContact'; import { APIGatewayProxyResult, SQSEvent } from 'aws-lambda'; -import { addContactToList } from './manageListSubscription'; -import { queueEventParser } from './queueEventParser'; +import { addContactToList } from '../helpers/manageListSubscription'; +import { queueEventParser } from '../helpers/queueEventParser'; export async function resyncUserHandler(event: { readonly Records: SQSEvent['Records']; @@ -77,4 +77,3 @@ export async function resyncUserHandler(event: { }; } } - diff --git a/packages/active-campaign-client/src/helpers/getUserFromCognito.ts b/packages/active-campaign-client/src/helpers/getUserFromCognito.ts index 325b4d2efd..30bec5a410 100644 --- a/packages/active-campaign-client/src/helpers/getUserFromCognito.ts +++ b/packages/active-campaign-client/src/helpers/getUserFromCognito.ts @@ -4,9 +4,13 @@ import { QueueEvent } from '../types/queueEvent'; import { listUsersCommandOutputToUser } from './listUsersCommandOutputToUser'; export async function getUserFromCognito(queueEvent: QueueEvent) { - return await getUserFromCognitoByUsername( - queueEvent.detail.additionalEventData.sub - ); + const username = queueEvent.detail.additionalEventData.sub; + const user = await getUserFromCognitoByUsername(username); + if (!user) { + // eslint-disable-next-line functional/no-throw-statements + throw new Error('User not found'); + } + return user; } export async function getUserFromCognitoByUsername(username: string) { @@ -16,10 +20,6 @@ export async function getUserFromCognitoByUsername(username: string) { }); const listUsersCommandOutput = await cognitoClient.send(command); const user = listUsersCommandOutputToUser(listUsersCommandOutput); - if (!user) { - // eslint-disable-next-line functional/no-throw-statements - throw new Error('User not found'); - } console.log('User:', JSON.stringify(user, null, 2)); // TODO: Remove after testing return user; } diff --git a/packages/active-campaign-client/src/index.ts b/packages/active-campaign-client/src/index.ts index f35d8db501..cab74a8145 100644 --- a/packages/active-campaign-client/src/index.ts +++ b/packages/active-campaign-client/src/index.ts @@ -1,6 +1,6 @@ import { SQSEvent } from 'aws-lambda'; import { sqsQueueHandler } from './handlers/sqsQueueHandler'; -import { resyncUserHandler } from './helpers/resyncUser'; +import { resyncUserHandler } from './handlers/resyncUserHandler'; export async function sqsQueue(event: { readonly Records: SQSEvent['Records']; @@ -8,7 +8,7 @@ export async function sqsQueue(event: { return await sqsQueueHandler(event); } -export async function handler(event: { +export async function resyncQueue(event: { readonly Records: SQSEvent['Records']; }) { return await resyncUserHandler(event); From 941f3a75a6a8534197f4389d9677685842862218 Mon Sep 17 00:00:00 2001 From: t Date: Tue, 10 Dec 2024 14:40:20 +0100 Subject: [PATCH 09/19] bulk add contact --- .../helpers/bulkAddContactToList.test.ts | 19 +++++++ .../src/clients/activeCampaignClient.ts | 23 +++++++- .../src/helpers/bulkAddContactsToLists.ts | 52 +++++++++++++++++++ 3 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 packages/active-campaign-client/src/__tests__/helpers/bulkAddContactToList.test.ts create mode 100644 packages/active-campaign-client/src/helpers/bulkAddContactsToLists.ts diff --git a/packages/active-campaign-client/src/__tests__/helpers/bulkAddContactToList.test.ts b/packages/active-campaign-client/src/__tests__/helpers/bulkAddContactToList.test.ts new file mode 100644 index 0000000000..6cba407f84 --- /dev/null +++ b/packages/active-campaign-client/src/__tests__/helpers/bulkAddContactToList.test.ts @@ -0,0 +1,19 @@ +import { bulkAddContactToList } from '../../helpers/bulkAddContactsToLists'; +import { User } from '../../types/user'; + +const user: User = { + username: '466e0280-9061-7007-c3e0-beb6be672f68', + email: `test@example${new Date().getTime()}e.com`, + given_name: 'Giovanni', + family_name: 'Doe', + 'custom:mailinglist_accepted': 'true', + 'custom:company_type': 'Test Co', + 'custom:job_role': 'Developer', +}; + +describe('Active campaign integration contact flow', () => { + it('should bulk add contacts to a list', async () => { + const response = await bulkAddContactToList([user], [[28]]); + expect(response.statusCode).toBe(200); + }); +}); diff --git a/packages/active-campaign-client/src/clients/activeCampaignClient.ts b/packages/active-campaign-client/src/clients/activeCampaignClient.ts index 7b3c12492f..2f02ed9d63 100644 --- a/packages/active-campaign-client/src/clients/activeCampaignClient.ts +++ b/packages/active-campaign-client/src/clients/activeCampaignClient.ts @@ -43,7 +43,15 @@ export class ActiveCampaignClient { private async makeRequest( method: string, path: string, - data?: ContactPayload | ListPayload | ListStatusPayload, + data?: + | ContactPayload + | ListPayload + | ListStatusPayload + | { + readonly contacts: readonly (ContactPayload & { + readonly listIds: readonly number[]; + })[]; + }, params?: Record ): Promise { const [apiKey, baseUrl] = await Promise.all([ @@ -137,6 +145,19 @@ export class ActiveCampaignClient { return this.makeRequest('DELETE', `/api/3/lists/${id}`); } + async bulkAddContactToList( + contacts: readonly (ContactPayload & { + readonly listIds: readonly number[]; + })[] + ) { + return this.makeRequest('POST', `/api/3/import/bulk_import`, { + contacts: contacts.map((contact) => ({ + ...contact, + subscribe: contact.listIds.map((listId) => ({ listid: listId })), + })), + }); + } + async addContactToList(contactId: string, listId: number) { return this.makeRequest('POST', `/api/3/contactLists`, { contactList: { diff --git a/packages/active-campaign-client/src/helpers/bulkAddContactsToLists.ts b/packages/active-campaign-client/src/helpers/bulkAddContactsToLists.ts new file mode 100644 index 0000000000..fe1d6e2abe --- /dev/null +++ b/packages/active-campaign-client/src/helpers/bulkAddContactsToLists.ts @@ -0,0 +1,52 @@ +import { APIGatewayProxyResult } from 'aws-lambda'; +import { acClient } from '../clients/activeCampaignClient'; +import { ContactPayload } from '../types/contactPayload'; +import { User } from '../types/user'; + +export async function bulkAddContactToList( + users: readonly User[], + listIds: readonly (readonly number[])[] +): Promise { + try { + // Transform to AC payload + const acPayload: readonly (ContactPayload & { + readonly listIds: readonly number[]; + })[] = users.map((user, index) => ({ + contact: { + email: user.email, + firstName: user.given_name, + lastName: user.family_name, + phone: `cognito:${user.username}`, + fieldValues: [ + { + field: '2', + value: user['custom:company_type'], + }, + { + field: '1', + value: user['custom:job_role'], + }, + { + field: '3', + value: + user['custom:mailinglist_accepted'] === 'true' ? 'TRUE' : 'FALSE', + }, + ], + }, + listIds: listIds[index], + })); + + const response = await acClient.bulkAddContactToList(acPayload); + + return { + statusCode: 200, + body: JSON.stringify(response), + }; + } catch (error) { + console.error('Error:', error); + return { + statusCode: 500, + body: JSON.stringify({ message: 'Internal server error' }), + }; + } +} From cdbf4cc4b4e5f7102043553444e42703d9e4261d Mon Sep 17 00:00:00 2001 From: t Date: Tue, 10 Dec 2024 15:32:16 +0100 Subject: [PATCH 10/19] bulk add contact --- .../helpers/bulkAddContactToList.test.ts | 2 +- .../src/clients/activeCampaignClient.ts | 28 ++++++++++++------- .../src/types/bulkAddContactPayload.ts | 11 ++++++++ 3 files changed, 30 insertions(+), 11 deletions(-) create mode 100644 packages/active-campaign-client/src/types/bulkAddContactPayload.ts diff --git a/packages/active-campaign-client/src/__tests__/helpers/bulkAddContactToList.test.ts b/packages/active-campaign-client/src/__tests__/helpers/bulkAddContactToList.test.ts index 6cba407f84..a022c20fc9 100644 --- a/packages/active-campaign-client/src/__tests__/helpers/bulkAddContactToList.test.ts +++ b/packages/active-campaign-client/src/__tests__/helpers/bulkAddContactToList.test.ts @@ -11,7 +11,7 @@ const user: User = { 'custom:job_role': 'Developer', }; -describe('Active campaign integration contact flow', () => { +describe.skip('Active campaign integration contact flow', () => { it('should bulk add contacts to a list', async () => { const response = await bulkAddContactToList([user], [[28]]); expect(response.statusCode).toBe(200); diff --git a/packages/active-campaign-client/src/clients/activeCampaignClient.ts b/packages/active-campaign-client/src/clients/activeCampaignClient.ts index 2f02ed9d63..ce446c7337 100644 --- a/packages/active-campaign-client/src/clients/activeCampaignClient.ts +++ b/packages/active-campaign-client/src/clients/activeCampaignClient.ts @@ -3,6 +3,7 @@ import { SSMClient, GetParameterCommand } from '@aws-sdk/client-ssm'; import { ContactPayload } from '../types/contactPayload'; import { ListPayload } from '../types/listPayload'; import { ListStatusPayload } from '../types/listStatusPayload'; +import { BulkAddContactPayload } from '../types/bulkAddContactPayload'; async function getParameter( paramName: string, @@ -47,11 +48,7 @@ export class ActiveCampaignClient { | ContactPayload | ListPayload | ListStatusPayload - | { - readonly contacts: readonly (ContactPayload & { - readonly listIds: readonly number[]; - })[]; - }, + | BulkAddContactPayload, params?: Record ): Promise { const [apiKey, baseUrl] = await Promise.all([ @@ -92,6 +89,7 @@ export class ActiveCampaignClient { reject(new Error('Failed to parse response data')); } } else { + console.log(data); reject( new Error(`Request failed with status code ${res.statusCode}`) ); @@ -150,12 +148,22 @@ export class ActiveCampaignClient { readonly listIds: readonly number[]; })[] ) { - return this.makeRequest('POST', `/api/3/import/bulk_import`, { - contacts: contacts.map((contact) => ({ - ...contact, - subscribe: contact.listIds.map((listId) => ({ listid: listId })), + const body = { + contacts: contacts.map((payload) => ({ + email: payload.contact.email, + first_name: payload.contact.firstName, + last_name: payload.contact.lastName, + phone: payload.contact.phone, + customer_acct_name: payload.contact.lastName, + fields: payload.contact.fieldValues.map((field) => ({ + id: Number(field.field), + value: field.value, + })), + subscribe: payload.listIds.map((listId) => ({ listid: listId })), })), - }); + }; + + return this.makeRequest('POST', `/api/3/import/bulk_import`, body); } async addContactToList(contactId: string, listId: number) { diff --git a/packages/active-campaign-client/src/types/bulkAddContactPayload.ts b/packages/active-campaign-client/src/types/bulkAddContactPayload.ts new file mode 100644 index 0000000000..dea49a5db2 --- /dev/null +++ b/packages/active-campaign-client/src/types/bulkAddContactPayload.ts @@ -0,0 +1,11 @@ +export type BulkAddContactPayload = { + readonly contacts: readonly { + readonly email: string; + readonly first_name: string; + readonly last_name: string; + readonly phone: string | undefined; + readonly customer_acct_name: string; + readonly fields: readonly { readonly id: number; readonly value: string }[]; + readonly subscribe: readonly { readonly listid: number }[]; + }[]; +}; From 210f14443bdb48a08a5a88a35467ecd87a39d339 Mon Sep 17 00:00:00 2001 From: t Date: Tue, 10 Dec 2024 15:40:54 +0100 Subject: [PATCH 11/19] resync user --- .../src/handlers/resyncUserHandler.ts | 32 +++++++------------ 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/packages/active-campaign-client/src/handlers/resyncUserHandler.ts b/packages/active-campaign-client/src/handlers/resyncUserHandler.ts index a2496306a9..25d5a20c10 100644 --- a/packages/active-campaign-client/src/handlers/resyncUserHandler.ts +++ b/packages/active-campaign-client/src/handlers/resyncUserHandler.ts @@ -5,6 +5,8 @@ import { addContact } from '../helpers/addContact'; import { APIGatewayProxyResult, SQSEvent } from 'aws-lambda'; import { addContactToList } from '../helpers/manageListSubscription'; import { queueEventParser } from '../helpers/queueEventParser'; +import { acClient } from '../clients/activeCampaignClient'; +import { bulkAddContactToList } from '../helpers/bulkAddContactsToLists'; export async function resyncUserHandler(event: { readonly Records: SQSEvent['Records']; @@ -43,29 +45,17 @@ export async function resyncUserHandler(event: { console.log('Webinar IDs:', webinarIds); // TODO: Remove after testing - const res = await addContact(user); - if (res.statusCode !== 200) { - // eslint-disable-next-line functional/no-throw-statements - throw new Error('Error adding contact'); - } - - await webinarIds.reduce( - async ( - prevPromise: Promise, - webinarId: string - ) => { - await prevPromise; - try { - const result = await addContactToList(cognitoId, webinarId); - console.log('Add contact to list result:', result, webinarId); // TODO: Remove after testing - await new Promise((resolve) => setTimeout(resolve, 1000)); // wait 1 sec to avoid rate limiting - } catch (e) { - console.error('Error adding contact to list', e); // TODO: Remove after testing - } - }, - Promise.resolve() + const webinarListIds = await Promise.all( + webinarIds.map(async (webinarId: string) => { + const listId = await acClient.getListIdByName(webinarId); + return listId; + }) ); + const res = await bulkAddContactToList([user], [webinarListIds]); + + console.log('Res:', res); + return { statusCode: 200, body: JSON.stringify({ message: 'User resynced' }), From 53f3ea13535d54ea713b37238b55715713dc854a Mon Sep 17 00:00:00 2001 From: t Date: Tue, 10 Dec 2024 15:43:54 +0100 Subject: [PATCH 12/19] changeset --- .changeset/loud-otters-unite.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/loud-otters-unite.md diff --git a/.changeset/loud-otters-unite.md b/.changeset/loud-otters-unite.md new file mode 100644 index 0000000000..6b7b9d4d7b --- /dev/null +++ b/.changeset/loud-otters-unite.md @@ -0,0 +1,5 @@ +--- +"active-campaign-client": patch +--- + +Add bulk import of contacts From 49f2320bd74d6b6f590f54ed309eca93be74138d Mon Sep 17 00:00:00 2001 From: tommaso1 Date: Mon, 16 Dec 2024 19:37:07 +0100 Subject: [PATCH 13/19] Update packages/active-campaign-client/src/clients/activeCampaignClient.ts Co-authored-by: Marco Ponchia --- .../active-campaign-client/src/clients/activeCampaignClient.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/active-campaign-client/src/clients/activeCampaignClient.ts b/packages/active-campaign-client/src/clients/activeCampaignClient.ts index ce446c7337..e09f70bc38 100644 --- a/packages/active-campaign-client/src/clients/activeCampaignClient.ts +++ b/packages/active-campaign-client/src/clients/activeCampaignClient.ts @@ -89,7 +89,6 @@ export class ActiveCampaignClient { reject(new Error('Failed to parse response data')); } } else { - console.log(data); reject( new Error(`Request failed with status code ${res.statusCode}`) ); From 29e7407f2786d06a49521d87526dc2e92759ba1b Mon Sep 17 00:00:00 2001 From: tommaso1 Date: Mon, 16 Dec 2024 19:37:35 +0100 Subject: [PATCH 14/19] Update packages/active-campaign-client/src/handlers/resyncUserHandler.ts Co-authored-by: Marco Ponchia --- .../active-campaign-client/src/handlers/resyncUserHandler.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/active-campaign-client/src/handlers/resyncUserHandler.ts b/packages/active-campaign-client/src/handlers/resyncUserHandler.ts index 25d5a20c10..029f7ecaf0 100644 --- a/packages/active-campaign-client/src/handlers/resyncUserHandler.ts +++ b/packages/active-campaign-client/src/handlers/resyncUserHandler.ts @@ -54,7 +54,6 @@ export async function resyncUserHandler(event: { const res = await bulkAddContactToList([user], [webinarListIds]); - console.log('Res:', res); return { statusCode: 200, From 2e93e4c108665aef690d45b690fd1180787cd78d Mon Sep 17 00:00:00 2001 From: t Date: Mon, 16 Dec 2024 19:42:43 +0100 Subject: [PATCH 15/19] pr changes --- packages/active-campaign-client/.env.example | 1 + .../src/__tests__/helpers/bulkAddContactToList.test.ts | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/active-campaign-client/.env.example b/packages/active-campaign-client/.env.example index c9ffddf5e6..557196b2d5 100644 --- a/packages/active-campaign-client/.env.example +++ b/packages/active-campaign-client/.env.example @@ -7,3 +7,4 @@ COGNITO_USER_ID=66ae52a0-f051-7080-04a1-465b3a4f44cc LIST_NAME=test-webinar-1732097286071 AC_BASE_URL_PARAM='/ac/base_url' AC_API_KEY_PARAM='/ac/api_key' +TEST_AC_LIST_ID=28 \ No newline at end of file diff --git a/packages/active-campaign-client/src/__tests__/helpers/bulkAddContactToList.test.ts b/packages/active-campaign-client/src/__tests__/helpers/bulkAddContactToList.test.ts index a022c20fc9..18d5dc3739 100644 --- a/packages/active-campaign-client/src/__tests__/helpers/bulkAddContactToList.test.ts +++ b/packages/active-campaign-client/src/__tests__/helpers/bulkAddContactToList.test.ts @@ -13,7 +13,10 @@ const user: User = { describe.skip('Active campaign integration contact flow', () => { it('should bulk add contacts to a list', async () => { - const response = await bulkAddContactToList([user], [[28]]); + const response = await bulkAddContactToList( + [user], + [[Number(process.env.TEST_AC_LIST_ID)]] + ); expect(response.statusCode).toBe(200); }); }); From e1949e6149fba5468d178e57524fc27dd8e7f471 Mon Sep 17 00:00:00 2001 From: tommaso1 Date: Tue, 17 Dec 2024 20:20:12 +0100 Subject: [PATCH 16/19] Update packages/active-campaign-client/src/helpers/fetchSubscribedWebinarsFromDynamo.ts Co-authored-by: Marco Ponchia --- .../src/helpers/fetchSubscribedWebinarsFromDynamo.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/active-campaign-client/src/helpers/fetchSubscribedWebinarsFromDynamo.ts b/packages/active-campaign-client/src/helpers/fetchSubscribedWebinarsFromDynamo.ts index 7f59d6d1bc..ff17598aef 100644 --- a/packages/active-campaign-client/src/helpers/fetchSubscribedWebinarsFromDynamo.ts +++ b/packages/active-campaign-client/src/helpers/fetchSubscribedWebinarsFromDynamo.ts @@ -21,7 +21,6 @@ export async function fetchSubscribedWebinarsFromDynamo( body: JSON.stringify(response.Items), }; } catch (error) { - console.error('Error querying items by username:', error); return { statusCode: 500, body: JSON.stringify({ message: 'Internal server error' }), From a082a7b00ffa2002d66e9c574c7222247c6ef87a Mon Sep 17 00:00:00 2001 From: tommaso1 Date: Tue, 17 Dec 2024 20:20:22 +0100 Subject: [PATCH 17/19] Update packages/active-campaign-client/.env.example Co-authored-by: marcobottaro <39835990+marcobottaro@users.noreply.github.com> --- packages/active-campaign-client/.env.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/active-campaign-client/.env.example b/packages/active-campaign-client/.env.example index 557196b2d5..3def88bcaa 100644 --- a/packages/active-campaign-client/.env.example +++ b/packages/active-campaign-client/.env.example @@ -7,4 +7,4 @@ COGNITO_USER_ID=66ae52a0-f051-7080-04a1-465b3a4f44cc LIST_NAME=test-webinar-1732097286071 AC_BASE_URL_PARAM='/ac/base_url' AC_API_KEY_PARAM='/ac/api_key' -TEST_AC_LIST_ID=28 \ No newline at end of file +TEST_AC_LIST_ID=28 From 696fab5fac52724091f9d0eb84c58a2c5bffed19 Mon Sep 17 00:00:00 2001 From: tommaso1 Date: Tue, 17 Dec 2024 20:20:27 +0100 Subject: [PATCH 18/19] Update packages/active-campaign-client/src/handlers/resyncUserHandler.ts Co-authored-by: Marco Ponchia --- .../active-campaign-client/src/handlers/resyncUserHandler.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/active-campaign-client/src/handlers/resyncUserHandler.ts b/packages/active-campaign-client/src/handlers/resyncUserHandler.ts index 029f7ecaf0..da7bb1c8d0 100644 --- a/packages/active-campaign-client/src/handlers/resyncUserHandler.ts +++ b/packages/active-campaign-client/src/handlers/resyncUserHandler.ts @@ -23,7 +23,6 @@ export async function resyncUserHandler(event: { const user = await getUserFromCognitoByUsername(cognitoId); if (!user) { - console.log(`User: ${cognitoId} not present on Cognito, sync done.`); return { statusCode: 200, body: JSON.stringify({ From 2874681558849f962903603eabbaaf4c45bed002 Mon Sep 17 00:00:00 2001 From: t Date: Wed, 18 Dec 2024 10:41:34 +0100 Subject: [PATCH 19/19] pr changes --- .changeset/cuddly-cats-retire.md | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 .changeset/cuddly-cats-retire.md diff --git a/.changeset/cuddly-cats-retire.md b/.changeset/cuddly-cats-retire.md deleted file mode 100644 index ef17a074d8..0000000000 --- a/.changeset/cuddly-cats-retire.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"active-campaign-client": minor ---- - -Add resync user handler