From 4984a2a4f22763d38182c358c641af86b5eff468 Mon Sep 17 00:00:00 2001 From: t Date: Thu, 5 Dec 2024 15:48:26 +0100 Subject: [PATCH 01/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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 From d86cd549a9fbb8d862c8f86f139ca85f4e2202bb Mon Sep 17 00:00:00 2001 From: Marco Ponchia Date: Thu, 19 Dec 2024 12:47:46 +0100 Subject: [PATCH 20/47] add makeContactPayload --- .../src/helpers/addContact.ts | 27 ++--------------- .../src/helpers/makeContactPayload.ts | 29 +++++++++++++++++++ .../src/helpers/updateContact.ts | 26 ++--------------- 3 files changed, 33 insertions(+), 49 deletions(-) create mode 100644 packages/active-campaign-client/src/helpers/makeContactPayload.ts diff --git a/packages/active-campaign-client/src/helpers/addContact.ts b/packages/active-campaign-client/src/helpers/addContact.ts index 1b5c843d9d..022548a828 100644 --- a/packages/active-campaign-client/src/helpers/addContact.ts +++ b/packages/active-campaign-client/src/helpers/addContact.ts @@ -1,34 +1,11 @@ import { APIGatewayProxyResult } from 'aws-lambda'; import { acClient } from '../clients/activeCampaignClient'; -import { ContactPayload } from '../types/contactPayload'; import { User } from '../types/user'; +import { makeContactPayload } from './makeContactPayload'; export async function addContact(user: User): Promise { try { - // Transform to AC payload - const acPayload: ContactPayload = { - 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', - }, - ], - }, - }; + const acPayload = makeContactPayload(user); const response = await acClient.createContact(acPayload); diff --git a/packages/active-campaign-client/src/helpers/makeContactPayload.ts b/packages/active-campaign-client/src/helpers/makeContactPayload.ts new file mode 100644 index 0000000000..8dbca22020 --- /dev/null +++ b/packages/active-campaign-client/src/helpers/makeContactPayload.ts @@ -0,0 +1,29 @@ +import { ContactPayload } from '../types/contactPayload'; +import { User } from '../types/user'; + +export function makeContactPayload(user: User): ContactPayload { + return { + contact: { + // email: user.email, + 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', + }, + ], + }, + }; +} diff --git a/packages/active-campaign-client/src/helpers/updateContact.ts b/packages/active-campaign-client/src/helpers/updateContact.ts index 469b7b138d..275d2c1017 100644 --- a/packages/active-campaign-client/src/helpers/updateContact.ts +++ b/packages/active-campaign-client/src/helpers/updateContact.ts @@ -1,35 +1,13 @@ import { APIGatewayProxyResult } from 'aws-lambda'; import { acClient } from '../clients/activeCampaignClient'; -import { ContactPayload } from '../types/contactPayload'; import { User } from '../types/user'; +import { makeContactPayload } from './makeContactPayload'; export async function updateContact( user: User ): Promise { try { - const acPayload: ContactPayload = { - 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', - }, - ], - }, - }; + const acPayload = makeContactPayload(user); const contactId = await acClient.getContactByCognitoId(user.username); if (!contactId) { return { From f3846c807e469b3e717abbbe8e36b1882d87d027 Mon Sep 17 00:00:00 2001 From: Marco Ponchia Date: Thu, 19 Dec 2024 12:48:12 +0100 Subject: [PATCH 21/47] Add types --- .../src/types/activeCampaignList.ts | 4 ++++ .../src/types/contactResponse.ts | 12 ++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 packages/active-campaign-client/src/types/activeCampaignList.ts create mode 100644 packages/active-campaign-client/src/types/contactResponse.ts diff --git a/packages/active-campaign-client/src/types/activeCampaignList.ts b/packages/active-campaign-client/src/types/activeCampaignList.ts new file mode 100644 index 0000000000..30d8e6ecea --- /dev/null +++ b/packages/active-campaign-client/src/types/activeCampaignList.ts @@ -0,0 +1,4 @@ +export type ActiveCampaignList = { + readonly id: number; + readonly name: string; +}; diff --git a/packages/active-campaign-client/src/types/contactResponse.ts b/packages/active-campaign-client/src/types/contactResponse.ts new file mode 100644 index 0000000000..e72fe264fd --- /dev/null +++ b/packages/active-campaign-client/src/types/contactResponse.ts @@ -0,0 +1,12 @@ +export type ContactResponse = { + readonly contact: { + readonly id: string; + }; +}; + +export type ContactResponseWithLists = { + readonly contactLists: ReadonlyArray<{ + readonly list: string; + readonly status: string; + }>; +} & ContactResponse; From 466b1a941188ff5631592aaebccb919193f89eef Mon Sep 17 00:00:00 2001 From: Marco Ponchia Date: Thu, 19 Dec 2024 12:50:03 +0100 Subject: [PATCH 22/47] refactor AC client --- .../src/clients/activeCampaignClient.ts | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/packages/active-campaign-client/src/clients/activeCampaignClient.ts b/packages/active-campaign-client/src/clients/activeCampaignClient.ts index e09f70bc38..608a8a97f8 100644 --- a/packages/active-campaign-client/src/clients/activeCampaignClient.ts +++ b/packages/active-campaign-client/src/clients/activeCampaignClient.ts @@ -4,6 +4,11 @@ import { ContactPayload } from '../types/contactPayload'; import { ListPayload } from '../types/listPayload'; import { ListStatusPayload } from '../types/listStatusPayload'; import { BulkAddContactPayload } from '../types/bulkAddContactPayload'; +import { + ContactResponse, + ContactResponseWithLists, +} from '../types/contactResponse'; +import { ActiveCampaignList } from '../types/activeCampaignList'; async function getParameter( paramName: string, @@ -109,11 +114,15 @@ export class ActiveCampaignClient { } async createContact(data: ContactPayload) { - return this.makeRequest('POST', '/api/3/contacts', data); + return this.makeRequest('POST', '/api/3/contacts', data); } async updateContact(contactId: string, data: ContactPayload) { - return this.makeRequest('PUT', `/api/3/contacts/${contactId}`, data); + return this.makeRequest( + 'PUT', + `/api/3/contacts/${contactId}`, + data + ); } async deleteContact(contactId: string) { @@ -127,6 +136,13 @@ export class ActiveCampaignClient { return response?.contacts?.[0]?.id; } + async getContact(id: string) { + return await this.makeRequest( + 'GET', + `/api/3/contacts/${id}` + ); + } + async createList(data: ListPayload) { return this.makeRequest('POST', '/api/3/lists', data); } @@ -142,6 +158,17 @@ export class ActiveCampaignClient { return this.makeRequest('DELETE', `/api/3/lists/${id}`); } + async getLists(ids: readonly string[]) { + return this.makeRequest( + 'DELETE', + `/api/3/lists`, + undefined, + { + ids: ids.join(','), + } + ); + } + async bulkAddContactToList( contacts: readonly (ContactPayload & { readonly listIds: readonly number[]; From 7ea308cf64c2a7d00fb3fbea0b2829d2295a768f Mon Sep 17 00:00:00 2001 From: Marco Ponchia Date: Thu, 19 Dec 2024 14:31:31 +0100 Subject: [PATCH 23/47] add getListByName return type --- .../active-campaign-client/src/clients/activeCampaignClient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/active-campaign-client/src/clients/activeCampaignClient.ts b/packages/active-campaign-client/src/clients/activeCampaignClient.ts index 608a8a97f8..f091d160f6 100644 --- a/packages/active-campaign-client/src/clients/activeCampaignClient.ts +++ b/packages/active-campaign-client/src/clients/activeCampaignClient.ts @@ -147,7 +147,7 @@ export class ActiveCampaignClient { return this.makeRequest('POST', '/api/3/lists', data); } - async getListIdByName(name: string) { + async getListIdByName(name: string): Promise { const response = await this.makeRequest<{ readonly lists: ReadonlyArray<{ readonly id: number }>; }>('GET', '/api/3/lists', undefined, { 'filters[name][eq]': name }); From ab13428d69866497a06241cf6d8e5f3652ecad8c Mon Sep 17 00:00:00 2001 From: Marco Ponchia Date: Thu, 19 Dec 2024 14:32:09 +0100 Subject: [PATCH 24/47] refactor getListIdByName and add error manage to sqsQueueHandler --- .../src/handlers/sqsQueueHandler.ts | 37 ++++++++++++++----- .../src/helpers/manageListSubscription.ts | 22 ++++++++--- 2 files changed, 44 insertions(+), 15 deletions(-) diff --git a/packages/active-campaign-client/src/handlers/sqsQueueHandler.ts b/packages/active-campaign-client/src/handlers/sqsQueueHandler.ts index 3522ef77fb..2edeb3db76 100644 --- a/packages/active-campaign-client/src/handlers/sqsQueueHandler.ts +++ b/packages/active-campaign-client/src/handlers/sqsQueueHandler.ts @@ -9,6 +9,15 @@ import { removeContactToList, } from '../helpers/manageListSubscription'; +function manageError(result: APIGatewayProxyResult) { + if (result.statusCode === 500) { + // eslint-disable-next-line functional/no-throw-statements + throw new Error('Internal server error'); + } + + return result; +} + export async function sqsQueueHandler(event: { readonly Records: SQSEvent['Records']; }): Promise { @@ -17,20 +26,30 @@ export async function sqsQueueHandler(event: { const queueEvent = queueEventParser(event); switch (queueEvent.detail.eventName) { case 'ConfirmSignUp': - return await addContact(await getUserFromCognito(queueEvent)); + return manageError( + await addContact(await getUserFromCognito(queueEvent)) + ); case 'UpdateUserAttributes': - return await updateContact(await getUserFromCognito(queueEvent)); + return manageError( + await updateContact(await getUserFromCognito(queueEvent)) + ); case 'DeleteUser': - return await deleteContact(queueEvent.detail.additionalEventData.sub); + return manageError( + await deleteContact(queueEvent.detail.additionalEventData.sub) + ); case 'DynamoINSERT': - return await addContactToList( - queueEvent.detail.additionalEventData.sub, - queueEvent.webinarId || '' + return manageError( + await addContactToList( + queueEvent.detail.additionalEventData.sub, + queueEvent.webinarId || '' + ) ); case 'DynamoREMOVE': - return await removeContactToList( - queueEvent.detail.additionalEventData.sub, - queueEvent.webinarId || '' + return manageError( + await removeContactToList( + queueEvent.detail.additionalEventData.sub, + queueEvent.webinarId || '' + ) ); default: // eslint-disable-next-line functional/no-throw-statements diff --git a/packages/active-campaign-client/src/helpers/manageListSubscription.ts b/packages/active-campaign-client/src/helpers/manageListSubscription.ts index fe610f656b..bc4791398a 100644 --- a/packages/active-campaign-client/src/helpers/manageListSubscription.ts +++ b/packages/active-campaign-client/src/helpers/manageListSubscription.ts @@ -8,14 +8,19 @@ export async function addContactToList( try { const contactId = await acClient.getContactByCognitoId(cognitoId); if (!contactId) { + // eslint-disable-next-line functional/no-throw-statements + throw new Error('Contact not found'); + } + + const listId = await acClient.getListIdByName(listName); + + if (!listId) { return { statusCode: 404, - body: JSON.stringify({ message: 'Contact not found' }), + body: JSON.stringify({ message: 'List not found' }), }; } - const listId = await acClient.getListIdByName(listName); - const response = await acClient.addContactToList(contactId, listId); return { statusCode: 200, @@ -37,14 +42,19 @@ export async function removeContactToList( try { const contactId = await acClient.getContactByCognitoId(cognitoId); if (!contactId) { + // eslint-disable-next-line functional/no-throw-statements + throw new Error('Contact not found'); + } + + const listId = await acClient.getListIdByName(listName); + + if (!listId) { return { statusCode: 404, - body: JSON.stringify({ message: 'Contact not found' }), + body: JSON.stringify({ message: 'List not found' }), }; } - const listId = await acClient.getListIdByName(listName); - const response = await acClient.removeContactFromList(contactId, listId); return { statusCode: 200, From d9170942df71515efaac9ae723c0bb6e316649c5 Mon Sep 17 00:00:00 2001 From: Marco Ponchia Date: Thu, 19 Dec 2024 14:32:55 +0100 Subject: [PATCH 25/47] Add addOrUpdateContact helper --- .../src/helpers/addOrUpdateContact.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 packages/active-campaign-client/src/helpers/addOrUpdateContact.ts diff --git a/packages/active-campaign-client/src/helpers/addOrUpdateContact.ts b/packages/active-campaign-client/src/helpers/addOrUpdateContact.ts new file mode 100644 index 0000000000..6d1a4f0b2e --- /dev/null +++ b/packages/active-campaign-client/src/helpers/addOrUpdateContact.ts @@ -0,0 +1,14 @@ +import { User } from '../types/user'; +import { makeContactPayload } from './makeContactPayload'; +import { acClient } from '../clients/activeCampaignClient'; + +export async function addOrUpdateContact(user: User) { + const acPayload = makeContactPayload(user); + const contactId = await acClient.getContactByCognitoId(user.username); + + const { contact } = contactId + ? await acClient.updateContact(contactId, acPayload) + : await acClient.createContact(acPayload); + + return await acClient.getContact(contact.id); +} From 06b99b1233617781acfbe853a97f12b5a7386895 Mon Sep 17 00:00:00 2001 From: Marco Ponchia Date: Thu, 19 Dec 2024 14:33:09 +0100 Subject: [PATCH 26/47] add getNewWebinarsAndUnsubsriptionLists --- .../getNewWebinarsAndUnsubsriptionLists.ts | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 packages/active-campaign-client/src/helpers/getNewWebinarsAndUnsubsriptionLists.ts diff --git a/packages/active-campaign-client/src/helpers/getNewWebinarsAndUnsubsriptionLists.ts b/packages/active-campaign-client/src/helpers/getNewWebinarsAndUnsubsriptionLists.ts new file mode 100644 index 0000000000..89bf9071e6 --- /dev/null +++ b/packages/active-campaign-client/src/helpers/getNewWebinarsAndUnsubsriptionLists.ts @@ -0,0 +1,49 @@ +import { acClient } from '../clients/activeCampaignClient'; +import { fetchSubscribedWebinarsFromDynamo } from './fetchSubscribedWebinarsFromDynamo'; +import { ActiveCampaignList } from '../types/activeCampaignList'; +import { ContactResponseWithLists } from '../types/contactResponse'; + +export async function getNewWebinarsAndUnsubsriptionLists( + contactResponse: ContactResponseWithLists, + cognitoId: string +) { + // eslint-disable-next-line functional/prefer-readonly-type + const contactLists: ActiveCampaignList[] = [ + ...(await acClient.getLists( + contactResponse.contactLists + .filter(({ status }) => status === '1') + .map(({ list }) => list) + )), + ]; + + const userWebinarsSubscriptions = await fetchSubscribedWebinarsFromDynamo( + cognitoId + ); + + const webinarSlugs: readonly string[] = JSON.parse( + userWebinarsSubscriptions.body + ) + .map( + (webinar: { readonly webinarId: { readonly S: string } }) => + webinar?.webinarId?.S + ) + .filter(Boolean); + + // eslint-disable-next-line functional/prefer-readonly-type + const newWebinarSlugs: string[] = []; + + webinarSlugs.forEach((webinarSlug) => { + const index = contactLists.findIndex(({ name }) => name === webinarSlug); + if (index) { + contactLists.splice(index, 1); + } else { + newWebinarSlugs.push(webinarSlug); + } + }); + + const listsToUnsubscribe = contactLists; + console.log('listsToUnsubscribe:', listsToUnsubscribe); // TODO: Remove after testing + console.log('New webinar Slugs:', newWebinarSlugs); // TODO: Remove after testing + + return { listsToUnsubscribe, newWebinarSlugs }; +} From e58012d1ebb2c09c6e51c7318f4f128591cb9c1f Mon Sep 17 00:00:00 2001 From: Marco Ponchia Date: Thu, 19 Dec 2024 14:33:40 +0100 Subject: [PATCH 27/47] add addArrayOfListToContact and removeArrayOfListFromContact helpers --- .../src/helpers/addArrayOfListToContact.ts | 35 +++++++++++++++++++ .../helpers/removeArrayOfListFromContact.ts | 23 ++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 packages/active-campaign-client/src/helpers/addArrayOfListToContact.ts create mode 100644 packages/active-campaign-client/src/helpers/removeArrayOfListFromContact.ts diff --git a/packages/active-campaign-client/src/helpers/addArrayOfListToContact.ts b/packages/active-campaign-client/src/helpers/addArrayOfListToContact.ts new file mode 100644 index 0000000000..e3ebf1b224 --- /dev/null +++ b/packages/active-campaign-client/src/helpers/addArrayOfListToContact.ts @@ -0,0 +1,35 @@ +import { addContactToList } from './manageListSubscription'; + +export async function addArrayOfListToContact(event: { + readonly webinarSlugs: ReadonlyArray; + readonly cognitoId: string; + readonly resyncTimeoutMilliseconds: number; +}) { + const { webinarSlugs, cognitoId, resyncTimeoutMilliseconds } = event; + // eslint-disable-next-line functional/prefer-readonly-type + const addWithErrors: string[] = []; + // add contact to list for each item in subscription lists + const responses = await webinarSlugs.reduce( + async (prevPromise: Promise, webinarSlug: string) => { + await prevPromise; + try { + const result = await addContactToList(cognitoId, webinarSlug); // AC call 2 * N + console.log('Add contact to list result:', result, webinarSlug); // TODO: Remove after testing + await new Promise((resolve) => + setTimeout(resolve, resyncTimeoutMilliseconds) + ); // wait 1 sec to avoid rate limiting + } catch (e) { + addWithErrors.push(webinarSlug); + } + }, + Promise.resolve() + ); + + if (addWithErrors.length > 0) { + console.error('Error adding contact to list', addWithErrors.join(',')); // TODO: Remove after testing + // eslint-disable-next-line functional/no-throw-statements + throw new Error('Error adding contact to list'); + } + + return responses; +} diff --git a/packages/active-campaign-client/src/helpers/removeArrayOfListFromContact.ts b/packages/active-campaign-client/src/helpers/removeArrayOfListFromContact.ts new file mode 100644 index 0000000000..bd003d6bfc --- /dev/null +++ b/packages/active-campaign-client/src/helpers/removeArrayOfListFromContact.ts @@ -0,0 +1,23 @@ +import { acClient } from '../clients/activeCampaignClient'; +import { ActiveCampaignList } from '../types/activeCampaignList'; + +export async function removeArrayOfListFromContact(event: { + readonly listsToUnsubscribe: ReadonlyArray; + readonly contactId: string; + readonly resyncTimeoutMilliseconds: number; +}) { + const { listsToUnsubscribe, contactId, resyncTimeoutMilliseconds } = event; + // remove contact from list for each item in unsubscription lists + await listsToUnsubscribe.reduce( + async (prevPromise: Promise, list: ActiveCampaignList) => { + await prevPromise; + // AC call * M + const result = await acClient.removeContactFromList(contactId, list.id); + console.log('Remove contact from list result:', result, list); // TODO: Remove after testing + await new Promise((resolve) => + setTimeout(resolve, resyncTimeoutMilliseconds) + ); // wait 1 sec to avoid rate limiting + }, + Promise.resolve() + ); +} From 7c73575efe8273914e18bdfd74753b7f59c0d6d7 Mon Sep 17 00:00:00 2001 From: Marco Ponchia Date: Thu, 19 Dec 2024 14:33:58 +0100 Subject: [PATCH 28/47] Update resync user handlers --- .../src/handlers/resyncUserHandler.ts | 74 +++++++++---------- 1 file changed, 33 insertions(+), 41 deletions(-) diff --git a/packages/active-campaign-client/src/handlers/resyncUserHandler.ts b/packages/active-campaign-client/src/handlers/resyncUserHandler.ts index 029f7ecaf0..58ae1b102a 100644 --- a/packages/active-campaign-client/src/handlers/resyncUserHandler.ts +++ b/packages/active-campaign-client/src/handlers/resyncUserHandler.ts @@ -1,12 +1,11 @@ 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 '../helpers/manageListSubscription'; import { queueEventParser } from '../helpers/queueEventParser'; -import { acClient } from '../clients/activeCampaignClient'; -import { bulkAddContactToList } from '../helpers/bulkAddContactsToLists'; +import { addOrUpdateContact } from '../helpers/addOrUpdateContact'; +import { getNewWebinarsAndUnsubsriptionLists } from '../helpers/getNewWebinarsAndUnsubsriptionLists'; +import { addArrayOfListToContact } from '../helpers/addArrayOfListToContact'; +import { removeArrayOfListFromContact } from '../helpers/removeArrayOfListFromContact'; export async function resyncUserHandler(event: { readonly Records: SQSEvent['Records']; @@ -14,47 +13,40 @@ export async function resyncUserHandler(event: { try { 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'); - } const user = await getUserFromCognitoByUsername(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 deletionResult = await deleteContact(cognitoId); // AC call * 2 + if ( + deletionResult.statusCode != 200 && + deletionResult.statusCode != 404 + ) { + // eslint-disable-next-line functional/no-throw-statements + throw new Error('Error adding contact'); + } + } else { + const contactResponse = await addOrUpdateContact(user); // AC call * 3 + + const { listsToUnsubscribe, newWebinarSlugs } = + await getNewWebinarsAndUnsubsriptionLists(contactResponse, cognitoId); // AC call * 1 + + const resyncTimeoutMilliseconds: number = parseInt( + process.env.AC_RESYNC_TIMEOUT_IN_MS || '1000' + ); + + await addArrayOfListToContact({ + webinarSlugs: newWebinarSlugs, + cognitoId, + resyncTimeoutMilliseconds, + }); + + await removeArrayOfListFromContact({ + listsToUnsubscribe, + contactId: contactResponse.contact.id, + resyncTimeoutMilliseconds, + }); } - - const userWebinarsSubscriptions = await fetchSubscribedWebinarsFromDynamo( - cognitoId - ); - - const webinarIds = JSON.parse(userWebinarsSubscriptions.body) - .map( - (webinar: { readonly webinarId: { readonly S: string } }) => - webinar?.webinarId?.S - ) - .filter(Boolean); - - console.log('Webinar IDs:', webinarIds); // TODO: Remove after testing - - const webinarListIds = await Promise.all( - webinarIds.map(async (webinarId: string) => { - const listId = await acClient.getListIdByName(webinarId); - return listId; - }) - ); - - const res = await bulkAddContactToList([user], [webinarListIds]); - - return { statusCode: 200, body: JSON.stringify({ message: 'User resynced' }), From 3d705b266569f3c33bc20736dac5b4ffd0a3510e Mon Sep 17 00:00:00 2001 From: Marco Ponchia Date: Thu, 19 Dec 2024 14:36:24 +0100 Subject: [PATCH 29/47] Fix manageError --- .../active-campaign-client/src/handlers/sqsQueueHandler.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/active-campaign-client/src/handlers/sqsQueueHandler.ts b/packages/active-campaign-client/src/handlers/sqsQueueHandler.ts index 2edeb3db76..1a3034c631 100644 --- a/packages/active-campaign-client/src/handlers/sqsQueueHandler.ts +++ b/packages/active-campaign-client/src/handlers/sqsQueueHandler.ts @@ -15,7 +15,10 @@ function manageError(result: APIGatewayProxyResult) { throw new Error('Internal server error'); } - return result; + return { + statusCode: 200, + body: JSON.stringify(result), + }; } export async function sqsQueueHandler(event: { From 3d70311529f0fb52fdcc12c409e55c7db24ec450 Mon Sep 17 00:00:00 2001 From: Marco Ponchia Date: Thu, 19 Dec 2024 14:40:11 +0100 Subject: [PATCH 30/47] Fix after merge --- .../src/helpers/getNewWebinarsAndUnsubsriptionLists.ts | 4 ++-- .../src/helpers/removeArrayOfListFromContact.ts | 10 +++++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/active-campaign-client/src/helpers/getNewWebinarsAndUnsubsriptionLists.ts b/packages/active-campaign-client/src/helpers/getNewWebinarsAndUnsubsriptionLists.ts index 89bf9071e6..391ae4fb8c 100644 --- a/packages/active-campaign-client/src/helpers/getNewWebinarsAndUnsubsriptionLists.ts +++ b/packages/active-campaign-client/src/helpers/getNewWebinarsAndUnsubsriptionLists.ts @@ -5,7 +5,7 @@ import { ContactResponseWithLists } from '../types/contactResponse'; export async function getNewWebinarsAndUnsubsriptionLists( contactResponse: ContactResponseWithLists, - cognitoId: string + cognitoUsername: string ) { // eslint-disable-next-line functional/prefer-readonly-type const contactLists: ActiveCampaignList[] = [ @@ -17,7 +17,7 @@ export async function getNewWebinarsAndUnsubsriptionLists( ]; const userWebinarsSubscriptions = await fetchSubscribedWebinarsFromDynamo( - cognitoId + cognitoUsername ); const webinarSlugs: readonly string[] = JSON.parse( diff --git a/packages/active-campaign-client/src/helpers/removeArrayOfListFromContact.ts b/packages/active-campaign-client/src/helpers/removeArrayOfListFromContact.ts index bd003d6bfc..ede58d8286 100644 --- a/packages/active-campaign-client/src/helpers/removeArrayOfListFromContact.ts +++ b/packages/active-campaign-client/src/helpers/removeArrayOfListFromContact.ts @@ -3,16 +3,20 @@ import { ActiveCampaignList } from '../types/activeCampaignList'; export async function removeArrayOfListFromContact(event: { readonly listsToUnsubscribe: ReadonlyArray; - readonly contactId: string; + readonly cognitoUsername: string; readonly resyncTimeoutMilliseconds: number; }) { - const { listsToUnsubscribe, contactId, resyncTimeoutMilliseconds } = event; + const { listsToUnsubscribe, cognitoUsername, resyncTimeoutMilliseconds } = + event; // remove contact from list for each item in unsubscription lists await listsToUnsubscribe.reduce( async (prevPromise: Promise, list: ActiveCampaignList) => { await prevPromise; // AC call * M - const result = await acClient.removeContactFromList(contactId, list.id); + const result = await acClient.removeContactFromList( + cognitoUsername, + list.id + ); console.log('Remove contact from list result:', result, list); // TODO: Remove after testing await new Promise((resolve) => setTimeout(resolve, resyncTimeoutMilliseconds) From a3b8052472dfc6117250eb53c074d4329e7a2a2d Mon Sep 17 00:00:00 2001 From: Marco Ponchia Date: Thu, 19 Dec 2024 14:43:08 +0100 Subject: [PATCH 31/47] add changeset --- .changeset/khaki-knives-unite.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/khaki-knives-unite.md diff --git a/.changeset/khaki-knives-unite.md b/.changeset/khaki-knives-unite.md new file mode 100644 index 0000000000..e3a768d274 --- /dev/null +++ b/.changeset/khaki-knives-unite.md @@ -0,0 +1,5 @@ +--- +"active-campaign-client": minor +--- + +Refactor resyncUserHandler update active campaing only with updated data From 2416aba42da260b3460b5de2c3228980e1833ff3 Mon Sep 17 00:00:00 2001 From: Marco Ponchia Date: Thu, 19 Dec 2024 16:10:23 +0100 Subject: [PATCH 32/47] Fix list ac client call --- .../src/clients/activeCampaignClient.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/active-campaign-client/src/clients/activeCampaignClient.ts b/packages/active-campaign-client/src/clients/activeCampaignClient.ts index f091d160f6..32ad57ce35 100644 --- a/packages/active-campaign-client/src/clients/activeCampaignClient.ts +++ b/packages/active-campaign-client/src/clients/activeCampaignClient.ts @@ -158,14 +158,15 @@ export class ActiveCampaignClient { return this.makeRequest('DELETE', `/api/3/lists/${id}`); } - async getLists(ids: readonly string[]) { + async getLists(ids?: readonly string[]) { + const limitParams = { limit: '1000' }; return this.makeRequest( - 'DELETE', + 'GET', `/api/3/lists`, undefined, - { - ids: ids.join(','), - } + ids && ids.length > 0 + ? { ids: ids.join(','), ...limitParams } + : limitParams ); } From 2477350981a2d8055e11eb0f6aad043d282c29f0 Mon Sep 17 00:00:00 2001 From: Marco Ponchia Date: Thu, 19 Dec 2024 18:14:34 +0100 Subject: [PATCH 33/47] Refactor env var for test --- packages/active-campaign-client/.env.example | 11 ++++++----- .../__tests__/helpers/manageListSubscription.test.ts | 4 ++-- .../src/clients/activeCampaignClient.ts | 8 ++++---- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/packages/active-campaign-client/.env.example b/packages/active-campaign-client/.env.example index 3def88bcaa..68bb08ca8b 100644 --- a/packages/active-campaign-client/.env.example +++ b/packages/active-campaign-client/.env.example @@ -1,10 +1,11 @@ -AC_BASE_URL=your_account_url -AC_API_KEY=your_api_key SENDER_URL=localhost:3000 AWS_REGION="region" -AWS_USER_POOL_ID="region_DFWF81fRa" -COGNITO_USER_ID=66ae52a0-f051-7080-04a1-465b3a4f44cc -LIST_NAME=test-webinar-1732097286071 +COGNITO_USER_POOL_ID="region_DFWF81fRa" AC_BASE_URL_PARAM='/ac/base_url' AC_API_KEY_PARAM='/ac/api_key' + +TEST_AC_BASE_URL=your_account_url +TEST_AC_API_KEY=your_api_key TEST_AC_LIST_ID=28 +TEST_COGNITO_USER_ID=66ae52a0-f051-7080-04a1-465b3a4f44cc +TEST_LIST_NAME=test-webinar-1732097286071 diff --git a/packages/active-campaign-client/src/__tests__/helpers/manageListSubscription.test.ts b/packages/active-campaign-client/src/__tests__/helpers/manageListSubscription.test.ts index a49abb0dd2..61e97cea04 100644 --- a/packages/active-campaign-client/src/__tests__/helpers/manageListSubscription.test.ts +++ b/packages/active-campaign-client/src/__tests__/helpers/manageListSubscription.test.ts @@ -5,8 +5,8 @@ import { } from '../../helpers/manageListSubscription'; describe.skip('manage list subscription', () => { - const cognitoUserId = process.env.COGNITO_USER_ID || ''; - const listName = process.env.LIST_NAME || ''; + const cognitoUserId = process.env.TEST_COGNITO_USER_ID || ''; + const listName = process.env.TEST_LIST_NAME || ''; it('should subscribe the contact to the list', async () => { const result = await addContactToList(cognitoUserId, listName); diff --git a/packages/active-campaign-client/src/clients/activeCampaignClient.ts b/packages/active-campaign-client/src/clients/activeCampaignClient.ts index 32ad57ce35..e0f1f9e79b 100644 --- a/packages/active-campaign-client/src/clients/activeCampaignClient.ts +++ b/packages/active-campaign-client/src/clients/activeCampaignClient.ts @@ -57,8 +57,8 @@ export class ActiveCampaignClient { params?: Record ): Promise { const [apiKey, baseUrl] = await Promise.all([ - getParameter(this.apiKeyParam, this.ssm, process.env.AC_API_KEY), - getParameter(this.baseUrlParam, this.ssm, process.env.AC_BASE_URL), + getParameter(this.apiKeyParam, this.ssm, process.env.TEST_AC_API_KEY), + getParameter(this.baseUrlParam, this.ssm, process.env.TEST_AC_BASE_URL), ]); return new Promise((resolve, reject) => { // Parse the base URL to get hostname and path and remove any trailing slashes from the baseUrl @@ -160,9 +160,9 @@ export class ActiveCampaignClient { async getLists(ids?: readonly string[]) { const limitParams = { limit: '1000' }; - return this.makeRequest( + return this.makeRequest<{ readonly lists: readonly ActiveCampaignList[] }>( 'GET', - `/api/3/lists`, + '/api/3/lists', undefined, ids && ids.length > 0 ? { ids: ids.join(','), ...limitParams } From a3d4bed3b13ff28e644fc398fdce048b3a67bb23 Mon Sep 17 00:00:00 2001 From: Marco Ponchia Date: Thu, 19 Dec 2024 18:15:03 +0100 Subject: [PATCH 34/47] fix make payload --- .../active-campaign-client/src/helpers/addOrUpdateContact.ts | 2 +- .../active-campaign-client/src/helpers/makeContactPayload.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/active-campaign-client/src/helpers/addOrUpdateContact.ts b/packages/active-campaign-client/src/helpers/addOrUpdateContact.ts index 6d1a4f0b2e..421048a697 100644 --- a/packages/active-campaign-client/src/helpers/addOrUpdateContact.ts +++ b/packages/active-campaign-client/src/helpers/addOrUpdateContact.ts @@ -3,9 +3,9 @@ import { makeContactPayload } from './makeContactPayload'; import { acClient } from '../clients/activeCampaignClient'; export async function addOrUpdateContact(user: User) { - const acPayload = makeContactPayload(user); const contactId = await acClient.getContactByCognitoId(user.username); + const acPayload = makeContactPayload(user); const { contact } = contactId ? await acClient.updateContact(contactId, acPayload) : await acClient.createContact(acPayload); diff --git a/packages/active-campaign-client/src/helpers/makeContactPayload.ts b/packages/active-campaign-client/src/helpers/makeContactPayload.ts index 8dbca22020..f848b453e0 100644 --- a/packages/active-campaign-client/src/helpers/makeContactPayload.ts +++ b/packages/active-campaign-client/src/helpers/makeContactPayload.ts @@ -4,8 +4,7 @@ import { User } from '../types/user'; export function makeContactPayload(user: User): ContactPayload { return { contact: { - // email: user.email, - email: '', + email: user.email, firstName: user.given_name, lastName: user.family_name, phone: `cognito:${user.username}`, From 294c2b944a222933cdc4456b6db9e485526a9bfc Mon Sep 17 00:00:00 2001 From: Marco Ponchia Date: Thu, 19 Dec 2024 18:15:19 +0100 Subject: [PATCH 35/47] fix resync function --- .../src/handlers/resyncUserHandler.ts | 5 ++++ .../getNewWebinarsAndUnsubsriptionLists.ts | 24 ++++++++++--------- .../helpers/removeArrayOfListFromContact.ts | 12 ++++------ .../src/types/activeCampaignList.ts | 2 +- 4 files changed, 23 insertions(+), 20 deletions(-) diff --git a/packages/active-campaign-client/src/handlers/resyncUserHandler.ts b/packages/active-campaign-client/src/handlers/resyncUserHandler.ts index 1c179bb2b6..e33b1286a5 100644 --- a/packages/active-campaign-client/src/handlers/resyncUserHandler.ts +++ b/packages/active-campaign-client/src/handlers/resyncUserHandler.ts @@ -11,11 +11,14 @@ export async function resyncUserHandler(event: { readonly Records: SQSEvent['Records']; }): Promise { try { + console.log('resyncUserHandler: Event:', event); // TODO: Remove after testing const queueEvent = queueEventParser(event); const cognitoUsername = queueEvent.detail.additionalEventData.sub; const user = await getUserFromCognitoUsername(cognitoUsername); + console.log('user:', user); // TODO: Remove after testing + if (!user) { const deletionResult = await deleteContact(cognitoUsername); // AC call * 2 if ( @@ -28,6 +31,8 @@ export async function resyncUserHandler(event: { } else { const contactResponse = await addOrUpdateContact(user); // AC call * 3 + console.log('contactResponse:', contactResponse); // TODO: Remove after testing + const { listsToUnsubscribe, newWebinarSlugs } = await getNewWebinarsAndUnsubsriptionLists( contactResponse, diff --git a/packages/active-campaign-client/src/helpers/getNewWebinarsAndUnsubsriptionLists.ts b/packages/active-campaign-client/src/helpers/getNewWebinarsAndUnsubsriptionLists.ts index 391ae4fb8c..a2278f2770 100644 --- a/packages/active-campaign-client/src/helpers/getNewWebinarsAndUnsubsriptionLists.ts +++ b/packages/active-campaign-client/src/helpers/getNewWebinarsAndUnsubsriptionLists.ts @@ -1,20 +1,17 @@ import { acClient } from '../clients/activeCampaignClient'; import { fetchSubscribedWebinarsFromDynamo } from './fetchSubscribedWebinarsFromDynamo'; -import { ActiveCampaignList } from '../types/activeCampaignList'; import { ContactResponseWithLists } from '../types/contactResponse'; export async function getNewWebinarsAndUnsubsriptionLists( contactResponse: ContactResponseWithLists, cognitoUsername: string ) { - // eslint-disable-next-line functional/prefer-readonly-type - const contactLists: ActiveCampaignList[] = [ - ...(await acClient.getLists( - contactResponse.contactLists - .filter(({ status }) => status === '1') - .map(({ list }) => list) - )), - ]; + const idsParams = contactResponse.contactLists + .filter(({ status }) => status === '1') + .map(({ list }) => list); + + console.log('idsParams:', idsParams); // TODO: Remove after testing + const getListResponse = await acClient.getLists(idsParams); const userWebinarsSubscriptions = await fetchSubscribedWebinarsFromDynamo( cognitoUsername @@ -31,17 +28,22 @@ export async function getNewWebinarsAndUnsubsriptionLists( // eslint-disable-next-line functional/prefer-readonly-type const newWebinarSlugs: string[] = []; + // eslint-disable-next-line functional/prefer-readonly-type + const contactLists: { name: string; id: string }[] = + getListResponse.lists.map(({ name, id }) => ({ name, id })); webinarSlugs.forEach((webinarSlug) => { const index = contactLists.findIndex(({ name }) => name === webinarSlug); - if (index) { + if (index >= 0) { contactLists.splice(index, 1); } else { newWebinarSlugs.push(webinarSlug); } }); - const listsToUnsubscribe = contactLists; + const listsToUnsubscribe: readonly number[] = contactLists.map(({ id }) => + Number(id) + ); console.log('listsToUnsubscribe:', listsToUnsubscribe); // TODO: Remove after testing console.log('New webinar Slugs:', newWebinarSlugs); // TODO: Remove after testing diff --git a/packages/active-campaign-client/src/helpers/removeArrayOfListFromContact.ts b/packages/active-campaign-client/src/helpers/removeArrayOfListFromContact.ts index ede58d8286..551ddf8e99 100644 --- a/packages/active-campaign-client/src/helpers/removeArrayOfListFromContact.ts +++ b/packages/active-campaign-client/src/helpers/removeArrayOfListFromContact.ts @@ -1,8 +1,7 @@ import { acClient } from '../clients/activeCampaignClient'; -import { ActiveCampaignList } from '../types/activeCampaignList'; export async function removeArrayOfListFromContact(event: { - readonly listsToUnsubscribe: ReadonlyArray; + readonly listsToUnsubscribe: readonly number[]; readonly cognitoUsername: string; readonly resyncTimeoutMilliseconds: number; }) { @@ -10,14 +9,11 @@ export async function removeArrayOfListFromContact(event: { event; // remove contact from list for each item in unsubscription lists await listsToUnsubscribe.reduce( - async (prevPromise: Promise, list: ActiveCampaignList) => { + async (prevPromise: Promise, id: number) => { await prevPromise; // AC call * M - const result = await acClient.removeContactFromList( - cognitoUsername, - list.id - ); - console.log('Remove contact from list result:', result, list); // TODO: Remove after testing + const result = await acClient.removeContactFromList(cognitoUsername, id); + console.log('Remove contact from list result:', result, id); // TODO: Remove after testing await new Promise((resolve) => setTimeout(resolve, resyncTimeoutMilliseconds) ); // wait 1 sec to avoid rate limiting diff --git a/packages/active-campaign-client/src/types/activeCampaignList.ts b/packages/active-campaign-client/src/types/activeCampaignList.ts index 30d8e6ecea..0ed43ba70e 100644 --- a/packages/active-campaign-client/src/types/activeCampaignList.ts +++ b/packages/active-campaign-client/src/types/activeCampaignList.ts @@ -1,4 +1,4 @@ export type ActiveCampaignList = { - readonly id: number; + readonly id: string; readonly name: string; }; From 55eabb97f39793aeeaa7709bdad2703eb98c3b7f Mon Sep 17 00:00:00 2001 From: Marco Ponchia Date: Thu, 19 Dec 2024 18:39:36 +0100 Subject: [PATCH 36/47] add sync user script --- .../src/scripts/sync_all_users.ts | 169 ++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 packages/active-campaign-client/src/scripts/sync_all_users.ts diff --git a/packages/active-campaign-client/src/scripts/sync_all_users.ts b/packages/active-campaign-client/src/scripts/sync_all_users.ts new file mode 100644 index 0000000000..161a82f38a --- /dev/null +++ b/packages/active-campaign-client/src/scripts/sync_all_users.ts @@ -0,0 +1,169 @@ +// This script is only ment to be launched manually to sync all users from Cognito to ActiveCampaign +/* eslint-disable */ +import { ActiveCampaignClient } from '../clients/activeCampaignClient'; +import { CognitoIdentityServiceProvider } from 'aws-sdk'; +import { DynamoDBClient, QueryCommand } from '@aws-sdk/client-dynamodb'; +import { ContactPayload } from '../types/contactPayload'; + +const activeCampaignClient = new ActiveCampaignClient( + process.env.TEST_AC_BASE_URL, + process.env.TEST_AC_API_KEY +); + +const userPoolId = process.env.COGNITO_USER_POOL_ID!; + +// Initialize Cognito client +const cognito = new CognitoIdentityServiceProvider({ + region: process.env.AWS_REGION, + credentials: { + accessKeyId: process.env.AWS_ACCESS_KEY_ID!, + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!, + }, +}); + +export async function getSubscribedWebinars(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); + return response.Items as any[]; + } catch (error) { + console.error('Error querying items by username:', error); + return []; + } +} + +// Load all users from Cognito +async function listAllUsers() { + try { + const allUsers = []; + let paginationToken = undefined; + + do { + const params: any = { + UserPoolId: userPoolId, + PaginationToken: paginationToken, + }; + + const response = await cognito.listUsers(params).promise(); + if (response.Users) { + allUsers.push(...response.Users); + } + paginationToken = response.PaginationToken; + } while (paginationToken); + + return allUsers; + } catch (error) { + console.error('Error fetching users:', error); + return []; + } +} + +async function main() { + const users = await listAllUsers(); + + console.log(process.env.DYNAMO_WEBINARS_TABLE_NAME); + + const usersAndWebinars = []; + + for (const user of users) { + if (!user.Username) { + continue; + } + const subscribedWebinars = await getSubscribedWebinars(user.Username); + usersAndWebinars.push({ + user, + subscribedWebinars: subscribedWebinars + .map((webinar: any) => webinar?.webinarId?.S) + .filter(Boolean), + }); + } + + const allLists: any = await activeCampaignClient.getLists(); + + const webinarIdByName = allLists.lists.reduce((acc: any, list: any) => { + acc[list.name] = Number(list.id); + return acc; + }, {}); + + const emails = new Set(); + // remove duplicates on emails + const uniqueUsersAndWebinars = usersAndWebinars.filter((userAndWebinars) => { + const email = userAndWebinars?.user?.Attributes?.find( + (attr: any) => attr.Name === 'email' + )?.Value; + if (emails.has(email)) { + return false; + } + emails.add(email); + return true; + }); + + // TODO: limit uniqueUsersAndWebinars to 10 remove after testing + const limitedUniqueUsersAndWebinars = uniqueUsersAndWebinars + .filter((userAndWebinars) => userAndWebinars.subscribedWebinars.length > 0) + .slice(0, 10); + + const acPayload = limitedUniqueUsersAndWebinars.map( + (userAndWebinars, index) => { + const attributes = userAndWebinars.user.Attributes; + return { + contact: { + email: attributes?.find((attr: any) => attr.Name === 'email')?.Value, + firstName: attributes?.find((attr: any) => attr.Name === 'given_name') + ?.Value, + lastName: attributes?.find((attr: any) => attr.Name === 'family_name') + ?.Value, + phone: `cognito:${userAndWebinars.user.Username}`, + fieldValues: [ + { + field: '2', + value: attributes?.find( + (attr: any) => attr.Name === 'custom:company_type' + )?.Value, + }, + { + field: '1', + value: attributes?.find( + (attr: any) => attr.Name === 'custom:job_role' + )?.Value, + }, + { + field: '3', + value: + attributes?.find( + (attr: any) => attr.Name === 'custom:mailinglist_accepted' + )?.Value === 'true' + ? 'TRUE' + : 'FALSE', + }, + ], + }, + listIds: userAndWebinars.subscribedWebinars + .map((webinar: any) => webinarIdByName[webinar]) + .filter(Boolean) as unknown as number[], + }; + } + ) as unknown as readonly (ContactPayload & { + readonly listIds: readonly number[]; + })[]; + + console.log(acPayload.filter((user: any) => user.listIds.length !== 0)); + + const response = await activeCampaignClient.bulkAddContactToList(acPayload); + + console.log(response); +} + +// Execute the main function +main().catch((error) => { + console.error('Error executing sync:', error); + process.exit(1); +}); From cd19ea429ac8a8414c6f194953ca7b5bd2fc74d6 Mon Sep 17 00:00:00 2001 From: Marco Ponchia Date: Thu, 19 Dec 2024 18:40:50 +0100 Subject: [PATCH 37/47] add changeset --- .changeset/heavy-points-clean.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/heavy-points-clean.md diff --git a/.changeset/heavy-points-clean.md b/.changeset/heavy-points-clean.md new file mode 100644 index 0000000000..9c83c72c3e --- /dev/null +++ b/.changeset/heavy-points-clean.md @@ -0,0 +1,5 @@ +--- +"active-campaign-client": minor +--- + +Add sync users script From 62a6f6b10be6fb349ce8926c8d301bf23eee368b Mon Sep 17 00:00:00 2001 From: Marco Ponchia Date: Fri, 20 Dec 2024 11:40:22 +0100 Subject: [PATCH 38/47] add env --- .../active-campaign-client/src/scripts/sync_all_users.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/active-campaign-client/src/scripts/sync_all_users.ts b/packages/active-campaign-client/src/scripts/sync_all_users.ts index 161a82f38a..81af09be6e 100644 --- a/packages/active-campaign-client/src/scripts/sync_all_users.ts +++ b/packages/active-campaign-client/src/scripts/sync_all_users.ts @@ -4,13 +4,14 @@ import { ActiveCampaignClient } from '../clients/activeCampaignClient'; import { CognitoIdentityServiceProvider } from 'aws-sdk'; import { DynamoDBClient, QueryCommand } from '@aws-sdk/client-dynamodb'; import { ContactPayload } from '../types/contactPayload'; +import * as dotenv from 'dotenv'; const activeCampaignClient = new ActiveCampaignClient( process.env.TEST_AC_BASE_URL, process.env.TEST_AC_API_KEY ); -const userPoolId = process.env.COGNITO_USER_POOL_ID!; +const userPoolId = () => process.env.COGNITO_USER_POOL_ID!; // Initialize Cognito client const cognito = new CognitoIdentityServiceProvider({ @@ -48,7 +49,7 @@ async function listAllUsers() { do { const params: any = { - UserPoolId: userPoolId, + UserPoolId: userPoolId(), PaginationToken: paginationToken, }; @@ -67,6 +68,8 @@ async function listAllUsers() { } async function main() { + dotenv.config(); + console.log(process.env); const users = await listAllUsers(); console.log(process.env.DYNAMO_WEBINARS_TABLE_NAME); From cf461b96f9203fd397091e605aee8d99868cd9b2 Mon Sep 17 00:00:00 2001 From: t Date: Fri, 20 Dec 2024 12:14:17 +0100 Subject: [PATCH 39/47] import users --- package-lock.json | 3 +- packages/active-campaign-client/package.json | 5 +- .../src/clients/activeCampaignClient.ts | 14 +- .../src/scripts/sync_all_users.ts | 131 +++++++----------- 4 files changed, 65 insertions(+), 88 deletions(-) diff --git a/package-lock.json b/package-lock.json index cb7c44223b..5a3c00876b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -54235,7 +54235,7 @@ "devDependencies": { "@eslint/eslintrc": "^2.1.4", "@eslint/js": "^8.57.1", - "dotenv": "16.4.5", + "dotenv": "^16.4.5", "eslint": "^8.57.1", "eslint-config-custom": "^0.1.0" } @@ -54279,6 +54279,7 @@ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=12" }, diff --git a/packages/active-campaign-client/package.json b/packages/active-campaign-client/package.json index 4b0b81bca5..9e1ced53a8 100644 --- a/packages/active-campaign-client/package.json +++ b/packages/active-campaign-client/package.json @@ -5,12 +5,13 @@ "scripts": { "lint": "eslint src", "test": "jest", - "build": "tsc" + "build": "tsc", + "sync-all-users": "ts-node src/scripts/sync_all_users.ts" }, "devDependencies": { "@eslint/eslintrc": "^2.1.4", "@eslint/js": "^8.57.1", - "dotenv": "16.4.5", + "dotenv": "^16.4.5", "eslint": "^8.57.1", "eslint-config-custom": "^0.1.0" }, diff --git a/packages/active-campaign-client/src/clients/activeCampaignClient.ts b/packages/active-campaign-client/src/clients/activeCampaignClient.ts index 608f6c776c..1b928d9c43 100644 --- a/packages/active-campaign-client/src/clients/activeCampaignClient.ts +++ b/packages/active-campaign-client/src/clients/activeCampaignClient.ts @@ -60,8 +60,8 @@ export class ActiveCampaignClient { ): Promise { const [apiKey, baseUrl] = await Promise.all([ // Fallback env variable exists only for manual testing purposes - getParameter(this.apiKeyParam, this.ssm, process.env.TEST_AC_API_KEY), - getParameter(this.baseUrlParam, this.ssm, process.env.TEST_AC_BASE_URL), + getParameter(this.apiKeyParam, this.ssm, process.env.AC_API_KEY), + getParameter(this.baseUrlParam, this.ssm, process.env.AC_BASE_URL), ]); return new Promise((resolve, reject) => { // Parse the base URL to get hostname and path and remove any trailing slashes from the baseUrl @@ -187,10 +187,12 @@ export class ActiveCampaignClient { 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, - })), + fields: payload.contact.fieldValues + .filter((field) => field.value) + .map((field) => ({ + id: Number(field.field), + value: field.value, + })), subscribe: payload.listIds.map((listId) => ({ listid: listId })), })), }; diff --git a/packages/active-campaign-client/src/scripts/sync_all_users.ts b/packages/active-campaign-client/src/scripts/sync_all_users.ts index 81af09be6e..de7882eb65 100644 --- a/packages/active-campaign-client/src/scripts/sync_all_users.ts +++ b/packages/active-campaign-client/src/scripts/sync_all_users.ts @@ -1,28 +1,30 @@ -// This script is only ment to be launched manually to sync all users from Cognito to ActiveCampaign /* eslint-disable */ -import { ActiveCampaignClient } from '../clients/activeCampaignClient'; +import { ActiveCampaignClient } from "../clients/activeCampaignClient"; import { CognitoIdentityServiceProvider } from 'aws-sdk'; import { DynamoDBClient, QueryCommand } from '@aws-sdk/client-dynamodb'; -import { ContactPayload } from '../types/contactPayload'; -import * as dotenv from 'dotenv'; +import dotenv from 'dotenv'; + +dotenv.config(); const activeCampaignClient = new ActiveCampaignClient( - process.env.TEST_AC_BASE_URL, - process.env.TEST_AC_API_KEY + process.env.AC_BASE_URL!, + process.env.AC_API_KEY! ); -const userPoolId = () => process.env.COGNITO_USER_POOL_ID!; +const userPoolId = process.env.AWS_USER_POOL_ID!; // Initialize Cognito client const cognito = new CognitoIdentityServiceProvider({ - region: process.env.AWS_REGION, + region: process.env.AWS_REGION!, credentials: { accessKeyId: process.env.AWS_ACCESS_KEY_ID!, - secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!, - }, + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY! + } }); -export async function getSubscribedWebinars(username: string): Promise { +export async function getSubscribedWebinars( + username: string +): Promise { try { const dynamoClient = new DynamoDBClient({ region: process.env.AWS_REGION }); const command = new QueryCommand({ @@ -49,14 +51,12 @@ async function listAllUsers() { do { const params: any = { - UserPoolId: userPoolId(), - PaginationToken: paginationToken, + UserPoolId: userPoolId, + PaginationToken: paginationToken }; - const response = await cognito.listUsers(params).promise(); - if (response.Users) { - allUsers.push(...response.Users); - } + const response: any = await cognito.listUsers(params).promise(); + allUsers.push(...response.Users); paginationToken = response.PaginationToken; } while (paginationToken); @@ -68,8 +68,6 @@ async function listAllUsers() { } async function main() { - dotenv.config(); - console.log(process.env); const users = await listAllUsers(); console.log(process.env.DYNAMO_WEBINARS_TABLE_NAME); @@ -77,18 +75,15 @@ async function main() { const usersAndWebinars = []; for (const user of users) { - if (!user.Username) { - continue; - } const subscribedWebinars = await getSubscribedWebinars(user.Username); usersAndWebinars.push({ - user, - subscribedWebinars: subscribedWebinars - .map((webinar: any) => webinar?.webinarId?.S) - .filter(Boolean), + username: user, + subscribedWebinars: subscribedWebinars.map((webinar: any) => webinar?.webinarId?.S).filter(Boolean) }); } + const subscribedWebinarNames = usersAndWebinars.flatMap((userAndWebinars) => userAndWebinars.subscribedWebinars); + const allLists: any = await activeCampaignClient.getLists(); const webinarIdByName = allLists.lists.reduce((acc: any, list: any) => { @@ -96,12 +91,12 @@ async function main() { return acc; }, {}); + console.log(webinarIdByName); + const emails = new Set(); // remove duplicates on emails const uniqueUsersAndWebinars = usersAndWebinars.filter((userAndWebinars) => { - const email = userAndWebinars?.user?.Attributes?.find( - (attr: any) => attr.Name === 'email' - )?.Value; + const email = userAndWebinars.username.Attributes.find((attr: any) => attr.Name === 'email')?.Value; if (emails.has(email)) { return false; } @@ -109,56 +104,34 @@ async function main() { return true; }); - // TODO: limit uniqueUsersAndWebinars to 10 remove after testing + // limit uniqueUsersAndWebinars to 10 const limitedUniqueUsersAndWebinars = uniqueUsersAndWebinars .filter((userAndWebinars) => userAndWebinars.subscribedWebinars.length > 0) - .slice(0, 10); - - const acPayload = limitedUniqueUsersAndWebinars.map( - (userAndWebinars, index) => { - const attributes = userAndWebinars.user.Attributes; - return { - contact: { - email: attributes?.find((attr: any) => attr.Name === 'email')?.Value, - firstName: attributes?.find((attr: any) => attr.Name === 'given_name') - ?.Value, - lastName: attributes?.find((attr: any) => attr.Name === 'family_name') - ?.Value, - phone: `cognito:${userAndWebinars.user.Username}`, - fieldValues: [ - { - field: '2', - value: attributes?.find( - (attr: any) => attr.Name === 'custom:company_type' - )?.Value, - }, - { - field: '1', - value: attributes?.find( - (attr: any) => attr.Name === 'custom:job_role' - )?.Value, - }, - { - field: '3', - value: - attributes?.find( - (attr: any) => attr.Name === 'custom:mailinglist_accepted' - )?.Value === 'true' - ? 'TRUE' - : 'FALSE', - }, - ], + .slice(0, 2); + + const acPayload = limitedUniqueUsersAndWebinars.map((userAndWebinars, index) => ({ + contact: { + email: userAndWebinars.username.Attributes.find((attr: any) => attr.Name === 'email')?.Value, + firstName: userAndWebinars.username.Attributes.find((attr: any) => attr.Name === 'given_name')?.Value, + lastName: userAndWebinars.username.Attributes.find((attr: any) => attr.Name === 'family_name')?.Value, + phone: `cognito:${userAndWebinars.username.Username}`, + fieldValues: [ + { + field: '2', + value: userAndWebinars.username.Attributes.find((attr: any) => attr.Name === 'custom:company_type')?.Value, }, - listIds: userAndWebinars.subscribedWebinars - .map((webinar: any) => webinarIdByName[webinar]) - .filter(Boolean) as unknown as number[], - }; - } - ) as unknown as readonly (ContactPayload & { - readonly listIds: readonly number[]; - })[]; - - console.log(acPayload.filter((user: any) => user.listIds.length !== 0)); + { + field: '1', + value: userAndWebinars.username.Attributes.find((attr: any) => attr.Name === 'custom:job_role')?.Value, + }, + { + field: '3', + value: userAndWebinars.username.Attributes.find((attr: any) => attr.Name === 'custom:mailinglist_accepted')?.Value === 'true' ? 'TRUE' : 'FALSE', + }, + ], + }, + listIds: userAndWebinars.subscribedWebinars.map((webinar: any) => webinarIdByName[webinar]).filter(Boolean), + })); const response = await activeCampaignClient.bulkAddContactToList(acPayload); @@ -166,7 +139,7 @@ async function main() { } // Execute the main function -main().catch((error) => { - console.error('Error executing sync:', error); +main().catch(error => { + console.error('Error executing script:', error); process.exit(1); -}); +}); \ No newline at end of file From c4834f50845f0cacba77cf95fbae2ec01d4be132 Mon Sep 17 00:00:00 2001 From: Marco Ponchia Date: Fri, 20 Dec 2024 12:58:21 +0100 Subject: [PATCH 40/47] Fix env --- packages/active-campaign-client/.env.example | 4 ++++ packages/active-campaign-client/src/scripts/sync_all_users.ts | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/active-campaign-client/.env.example b/packages/active-campaign-client/.env.example index 3dad1f2978..595d145160 100644 --- a/packages/active-campaign-client/.env.example +++ b/packages/active-campaign-client/.env.example @@ -4,6 +4,10 @@ COGNITO_USER_POOL_ID="your_region" AC_BASE_URL_PARAM='/ac/base_url' AC_API_KEY_PARAM='/ac/api_key' +# Only needed run import script locally +AWS_ACCESS_KEY_ID=AWS_ACCESS_KEY_ID +AWS_SECRET_ACCESS_KEY=AWS_SECRET_ACCESS_KEY + TEST_AC_BASE_URL=your_account_url TEST_AC_API_KEY=your_api_key TEST_AC_LIST_ID=28 diff --git a/packages/active-campaign-client/src/scripts/sync_all_users.ts b/packages/active-campaign-client/src/scripts/sync_all_users.ts index de7882eb65..2af40ce0f9 100644 --- a/packages/active-campaign-client/src/scripts/sync_all_users.ts +++ b/packages/active-campaign-client/src/scripts/sync_all_users.ts @@ -11,7 +11,7 @@ const activeCampaignClient = new ActiveCampaignClient( process.env.AC_API_KEY! ); -const userPoolId = process.env.AWS_USER_POOL_ID!; +const userPoolId = process.env.COGNITO_USER_POOL_ID!; // Initialize Cognito client const cognito = new CognitoIdentityServiceProvider({ From 84dde4abf609fba78a8d0f4fc7b4deecf79a9a0c Mon Sep 17 00:00:00 2001 From: Marco Ponchia Date: Fri, 20 Dec 2024 14:55:32 +0100 Subject: [PATCH 41/47] update script and configuration --- packages/active-campaign-client/.env.example | 4 +++- .../src/clients/activeCampaignClient.ts | 4 ++-- packages/active-campaign-client/src/scripts/sync_all_users.ts | 4 ++++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/active-campaign-client/.env.example b/packages/active-campaign-client/.env.example index 595d145160..51912617d7 100644 --- a/packages/active-campaign-client/.env.example +++ b/packages/active-campaign-client/.env.example @@ -1,8 +1,10 @@ -SENDER_URL=localhost:3000 +SENDER_URL=http://localhost:3000/ AWS_REGION="region" COGNITO_USER_POOL_ID="your_region" +DYNAMO_WEBINARS_TABLE_NAME="name" AC_BASE_URL_PARAM='/ac/base_url' AC_API_KEY_PARAM='/ac/api_key' +AC_RESYNC_TIMEOUT_IN_MS=1000 # Only needed run import script locally AWS_ACCESS_KEY_ID=AWS_ACCESS_KEY_ID diff --git a/packages/active-campaign-client/src/clients/activeCampaignClient.ts b/packages/active-campaign-client/src/clients/activeCampaignClient.ts index 1b928d9c43..20eeaae8d5 100644 --- a/packages/active-campaign-client/src/clients/activeCampaignClient.ts +++ b/packages/active-campaign-client/src/clients/activeCampaignClient.ts @@ -60,8 +60,8 @@ export class ActiveCampaignClient { ): Promise { const [apiKey, baseUrl] = await Promise.all([ // Fallback env variable exists only for manual testing purposes - getParameter(this.apiKeyParam, this.ssm, process.env.AC_API_KEY), - getParameter(this.baseUrlParam, this.ssm, process.env.AC_BASE_URL), + getParameter(this.apiKeyParam, this.ssm, process.env.TEST_AC_API_KEY), + getParameter(this.baseUrlParam, this.ssm, process.env.TEST_AC_BASE_URL), ]); return new Promise((resolve, reject) => { // Parse the base URL to get hostname and path and remove any trailing slashes from the baseUrl diff --git a/packages/active-campaign-client/src/scripts/sync_all_users.ts b/packages/active-campaign-client/src/scripts/sync_all_users.ts index 2af40ce0f9..f8146f936a 100644 --- a/packages/active-campaign-client/src/scripts/sync_all_users.ts +++ b/packages/active-campaign-client/src/scripts/sync_all_users.ts @@ -1,3 +1,7 @@ +// To run this script you need to have proper environment variables set up. +// Must be logged in to AWS CLI and have the proper permissions to access the Cognito User Pool and DynamoDB tables. +// Then run 'npm run sync-all-users -w active-campaign-client' + /* eslint-disable */ import { ActiveCampaignClient } from "../clients/activeCampaignClient"; import { CognitoIdentityServiceProvider } from 'aws-sdk'; From f8b597fb895c449e554a6216a0f251cdcbd3aa10 Mon Sep 17 00:00:00 2001 From: Marco Ponchia Date: Fri, 20 Dec 2024 14:56:46 +0100 Subject: [PATCH 42/47] Add changeset --- .changeset/sour-zebras-bathe.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/sour-zebras-bathe.md diff --git a/.changeset/sour-zebras-bathe.md b/.changeset/sour-zebras-bathe.md new file mode 100644 index 0000000000..c1fd9bd71a --- /dev/null +++ b/.changeset/sour-zebras-bathe.md @@ -0,0 +1,5 @@ +--- +"active-campaign-client": patch +--- + +Add sync users script From 2f1c8a54a853c37a3eed1c976d403fafc9aa7c10 Mon Sep 17 00:00:00 2001 From: Marco Ponchia Date: Fri, 20 Dec 2024 17:27:25 +0100 Subject: [PATCH 43/47] update after review --- .changeset/heavy-points-clean.md | 5 ----- packages/active-campaign-client/.env.example | 1 + .../active-campaign-client/src/scripts/sync_all_users.ts | 9 +++++---- 3 files changed, 6 insertions(+), 9 deletions(-) delete mode 100644 .changeset/heavy-points-clean.md diff --git a/.changeset/heavy-points-clean.md b/.changeset/heavy-points-clean.md deleted file mode 100644 index 9c83c72c3e..0000000000 --- a/.changeset/heavy-points-clean.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"active-campaign-client": minor ---- - -Add sync users script diff --git a/packages/active-campaign-client/.env.example b/packages/active-campaign-client/.env.example index 51912617d7..e4be95c482 100644 --- a/packages/active-campaign-client/.env.example +++ b/packages/active-campaign-client/.env.example @@ -9,6 +9,7 @@ AC_RESYNC_TIMEOUT_IN_MS=1000 # Only needed run import script locally AWS_ACCESS_KEY_ID=AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY=AWS_SECRET_ACCESS_KEY +USERS_TO_IMPORT_LIMIT=5 #optional only for local testing TEST_AC_BASE_URL=your_account_url TEST_AC_API_KEY=your_api_key diff --git a/packages/active-campaign-client/src/scripts/sync_all_users.ts b/packages/active-campaign-client/src/scripts/sync_all_users.ts index f8146f936a..82b129362a 100644 --- a/packages/active-campaign-client/src/scripts/sync_all_users.ts +++ b/packages/active-campaign-client/src/scripts/sync_all_users.ts @@ -108,10 +108,11 @@ async function main() { return true; }); - // limit uniqueUsersAndWebinars to 10 - const limitedUniqueUsersAndWebinars = uniqueUsersAndWebinars + const usersToImportLimit = !!process.env.USERS_TO_IMPORT_LIMIT && parseInt(process.env.USERS_TO_IMPORT_LIMIT); + const limitedUniqueUsersAndWebinars = usersToImportLimit ? uniqueUsersAndWebinars .filter((userAndWebinars) => userAndWebinars.subscribedWebinars.length > 0) - .slice(0, 2); + .slice(0, usersToImportLimit) + : uniqueUsersAndWebinars; const acPayload = limitedUniqueUsersAndWebinars.map((userAndWebinars, index) => ({ contact: { @@ -146,4 +147,4 @@ async function main() { main().catch(error => { console.error('Error executing script:', error); process.exit(1); -}); \ No newline at end of file +}); From 7a04af4795bae44234f5bc4df86847a01bcea69a Mon Sep 17 00:00:00 2001 From: Marco Ponchia Date: Thu, 9 Jan 2025 12:36:04 +0100 Subject: [PATCH 44/47] Manage error during webinar fetching --- .../src/helpers/getNewWebinarsAndUnsubsriptionLists.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/active-campaign-client/src/helpers/getNewWebinarsAndUnsubsriptionLists.ts b/packages/active-campaign-client/src/helpers/getNewWebinarsAndUnsubsriptionLists.ts index 35a5f38484..c801b55da8 100644 --- a/packages/active-campaign-client/src/helpers/getNewWebinarsAndUnsubsriptionLists.ts +++ b/packages/active-campaign-client/src/helpers/getNewWebinarsAndUnsubsriptionLists.ts @@ -22,6 +22,11 @@ export async function getNewWebinarsAndUnsubsriptionLists( cognitoUsername ); + if (userWebinarsSubscriptions.statusCode !== 200) { + // eslint-disable-next-line functional/no-throw-statements + throw new Error('Error fetching user webinars subscriptions'); + } + const webinarSlugs: readonly string[] = JSON.parse( userWebinarsSubscriptions.body ) From d8966ef87f3b697ab7284d9c49117db87a4507bd Mon Sep 17 00:00:00 2001 From: Marco Ponchia Date: Thu, 9 Jan 2025 17:20:42 +0100 Subject: [PATCH 45/47] remove try catch --- .../src/handlers/resyncUserHandler.ts | 91 +++++++++---------- 1 file changed, 42 insertions(+), 49 deletions(-) diff --git a/packages/active-campaign-client/src/handlers/resyncUserHandler.ts b/packages/active-campaign-client/src/handlers/resyncUserHandler.ts index 2d82df59c8..351e7a3c34 100644 --- a/packages/active-campaign-client/src/handlers/resyncUserHandler.ts +++ b/packages/active-campaign-client/src/handlers/resyncUserHandler.ts @@ -10,64 +10,57 @@ import { removeArrayOfListFromContact } from '../helpers/removeArrayOfListFromCo export async function resyncUserHandler(event: { readonly Records: SQSEvent['Records']; }): Promise { - try { - console.log('resyncUserHandler: Event:', event); // TODO: Remove after testing - const queueEvent = queueEventParser(event); - const cognitoUsername = queueEvent.detail.additionalEventData.sub; + console.log('resyncUserHandler: Event:', event); // TODO: Remove after testing + const queueEvent = queueEventParser(event); + const cognitoUsername = queueEvent.detail.additionalEventData.sub; - const user = await getUserFromCognitoUsername(cognitoUsername); + const user = await getUserFromCognitoUsername(cognitoUsername); - console.log('user:', user); // TODO: Remove after testing + console.log('user:', user); // TODO: Remove after testing - if (!user) { - const deletionResult = await deleteContact(cognitoUsername); - if ( - deletionResult.statusCode !== 200 && - deletionResult.statusCode !== 404 - ) { - // eslint-disable-next-line functional/no-throw-statements - throw new Error('Error deleting contact'); - } - } else { - const contactResponse = await addOrUpdateContact(user); - - console.log('contactResponse:', contactResponse); // TODO: Remove after testing + if (!user) { + const deletionResult = await deleteContact(cognitoUsername); + if ( + deletionResult.statusCode !== 200 && + deletionResult.statusCode !== 404 + ) { + // eslint-disable-next-line functional/no-throw-statements + throw new Error('Error deleting contact'); + } + } else { + const contactResponse = await addOrUpdateContact(user); - const { listsToUnsubscribe, newWebinarSlugs } = - await getNewWebinarsAndUnsubsriptionLists( - contactResponse, - cognitoUsername - ); + console.log('contactResponse:', contactResponse); // TODO: Remove after testing - const resyncTimeoutMilliseconds: number = parseInt( - process.env.AC_RESYNC_TIMEOUT_IN_MS || '1000' + const { listsToUnsubscribe, newWebinarSlugs } = + await getNewWebinarsAndUnsubsriptionLists( + contactResponse, + cognitoUsername ); - const subscriptionsresult = await addArrayOfListToContact({ - webinarSlugs: newWebinarSlugs, - cognitoUsername: cognitoUsername, - resyncTimeoutMilliseconds, - }); + const resyncTimeoutMilliseconds: number = parseInt( + process.env.AC_RESYNC_TIMEOUT_IN_MS || '1000' + ); + + const subscriptionsresult = await addArrayOfListToContact({ + webinarSlugs: newWebinarSlugs, + cognitoUsername: cognitoUsername, + resyncTimeoutMilliseconds, + }); - const unsubscriptionsResult = await removeArrayOfListFromContact({ - listsToUnsubscribe, - contactId: contactResponse.contact.id, - resyncTimeoutMilliseconds, - }); + const unsubscriptionsResult = await removeArrayOfListFromContact({ + listsToUnsubscribe, + contactId: contactResponse.contact.id, + resyncTimeoutMilliseconds, + }); - if (!subscriptionsresult || !unsubscriptionsResult) { - // eslint-disable-next-line functional/no-throw-statements - throw new Error('Error managing list subscriptions'); - } + if (!subscriptionsresult || !unsubscriptionsResult) { + // eslint-disable-next-line functional/no-throw-statements + throw new Error('Error managing list subscriptions'); } - return { - statusCode: 200, - body: JSON.stringify({ message: 'User resynced' }), - }; - } catch (error) { - return { - statusCode: 500, - body: JSON.stringify({ message: error }), - }; } + return { + statusCode: 200, + body: JSON.stringify({ message: 'User resynced' }), + }; } From 9d201b96c5189a5bef56f042601c4ca9a07b2e42 Mon Sep 17 00:00:00 2001 From: Marco Ponchia Date: Thu, 9 Jan 2025 18:01:23 +0100 Subject: [PATCH 46/47] Manage AC POST contactList error --- .../src/clients/activeCampaignClient.ts | 5 +++-- .../src/handlers/resyncUserHandler.ts | 16 +++++++++------- .../src/helpers/addArrayOfListToContact.ts | 3 +++ .../src/helpers/manageListSubscription.ts | 6 ++++++ .../src/helpers/removeArrayOfListFromContact.ts | 3 +++ .../src/types/contactList.ts | 3 +++ 6 files changed, 27 insertions(+), 9 deletions(-) create mode 100644 packages/active-campaign-client/src/types/contactList.ts diff --git a/packages/active-campaign-client/src/clients/activeCampaignClient.ts b/packages/active-campaign-client/src/clients/activeCampaignClient.ts index 20eeaae8d5..2e461971e1 100644 --- a/packages/active-campaign-client/src/clients/activeCampaignClient.ts +++ b/packages/active-campaign-client/src/clients/activeCampaignClient.ts @@ -9,6 +9,7 @@ import { ContactResponseWithLists, } from '../types/contactResponse'; import { ActiveCampaignList } from '../types/activeCampaignList'; +import { ContactList } from '../types/contactList'; const MAX_NUMBER_OF_LISTS = '1000'; @@ -201,7 +202,7 @@ export class ActiveCampaignClient { } async addContactToList(contactId: string, listId: number) { - return this.makeRequest('POST', `/api/3/contactLists`, { + return this.makeRequest('POST', `/api/3/contactLists`, { contactList: { contact: contactId, list: listId, @@ -211,7 +212,7 @@ export class ActiveCampaignClient { } async removeContactFromList(contactId: string, listId: number) { - return this.makeRequest('POST', `/api/3/contactLists`, { + return this.makeRequest('POST', `/api/3/contactLists`, { contactList: { contact: contactId, list: listId, diff --git a/packages/active-campaign-client/src/handlers/resyncUserHandler.ts b/packages/active-campaign-client/src/handlers/resyncUserHandler.ts index 351e7a3c34..8dad211c80 100644 --- a/packages/active-campaign-client/src/handlers/resyncUserHandler.ts +++ b/packages/active-campaign-client/src/handlers/resyncUserHandler.ts @@ -42,19 +42,21 @@ export async function resyncUserHandler(event: { process.env.AC_RESYNC_TIMEOUT_IN_MS || '1000' ); - const subscriptionsresult = await addArrayOfListToContact({ + const succesfullyUpdateSubscriptions = await addArrayOfListToContact({ webinarSlugs: newWebinarSlugs, cognitoUsername: cognitoUsername, resyncTimeoutMilliseconds, }); - const unsubscriptionsResult = await removeArrayOfListFromContact({ - listsToUnsubscribe, - contactId: contactResponse.contact.id, - resyncTimeoutMilliseconds, - }); + const succesfullyUpdateUnsubscriptions = await removeArrayOfListFromContact( + { + listsToUnsubscribe, + contactId: contactResponse.contact.id, + resyncTimeoutMilliseconds, + } + ); - if (!subscriptionsresult || !unsubscriptionsResult) { + if (!succesfullyUpdateSubscriptions || !succesfullyUpdateUnsubscriptions) { // eslint-disable-next-line functional/no-throw-statements throw new Error('Error managing list subscriptions'); } diff --git a/packages/active-campaign-client/src/helpers/addArrayOfListToContact.ts b/packages/active-campaign-client/src/helpers/addArrayOfListToContact.ts index e009290fbd..db3c76fc81 100644 --- a/packages/active-campaign-client/src/helpers/addArrayOfListToContact.ts +++ b/packages/active-campaign-client/src/helpers/addArrayOfListToContact.ts @@ -14,6 +14,9 @@ export async function addArrayOfListToContact(event: { try { const result = await addContactToList(cognitoUsername, webinarSlug); console.log('Add contact to list result:', result, webinarSlug); // TODO: Remove after testing + if (!result.statusCode || result.statusCode !== 200) { + subscriptionsWithErrors.push(webinarSlug); + } await new Promise((resolve) => setTimeout(resolve, resyncTimeoutMilliseconds) ); // wait to avoid rate limiting diff --git a/packages/active-campaign-client/src/helpers/manageListSubscription.ts b/packages/active-campaign-client/src/helpers/manageListSubscription.ts index ddf6c237ce..9c368d8b5f 100644 --- a/packages/active-campaign-client/src/helpers/manageListSubscription.ts +++ b/packages/active-campaign-client/src/helpers/manageListSubscription.ts @@ -24,6 +24,12 @@ export async function addContactToList( } const response = await acClient.addContactToList(contactId, listId); + if (!response.contactList) { + return { + statusCode: 500, + body: JSON.stringify({ message: 'Failed to add contact to list' }), + }; + } return { statusCode: 200, body: JSON.stringify(response), diff --git a/packages/active-campaign-client/src/helpers/removeArrayOfListFromContact.ts b/packages/active-campaign-client/src/helpers/removeArrayOfListFromContact.ts index e53be7400d..6466e8a4fd 100644 --- a/packages/active-campaign-client/src/helpers/removeArrayOfListFromContact.ts +++ b/packages/active-campaign-client/src/helpers/removeArrayOfListFromContact.ts @@ -14,6 +14,9 @@ export async function removeArrayOfListFromContact(event: { try { const result = await acClient.removeContactFromList(contactId, id); console.log('Remove contact from list result:', result, id); // TODO: Remove after testing + if (!result.contactList) { + unsubscriptionsWithErrors.push(id.toString()); + } await new Promise((resolve) => setTimeout(resolve, resyncTimeoutMilliseconds) ); // wait to avoid rate limiting diff --git a/packages/active-campaign-client/src/types/contactList.ts b/packages/active-campaign-client/src/types/contactList.ts new file mode 100644 index 0000000000..1c13231322 --- /dev/null +++ b/packages/active-campaign-client/src/types/contactList.ts @@ -0,0 +1,3 @@ +export type ContactList = { + readonly contactList?: { readonly contact: string }; +}; From 21591872b44c8b7b4c1ba7ac7b7ed3eb82fc542b Mon Sep 17 00:00:00 2001 From: Marco Ponchia Date: Fri, 10 Jan 2025 09:56:41 +0100 Subject: [PATCH 47/47] Remove unused type --- .../src/clients/activeCampaignClient.ts | 5 ++--- .../src/helpers/manageListSubscription.ts | 6 ------ .../src/helpers/removeArrayOfListFromContact.ts | 3 --- packages/active-campaign-client/src/types/contactList.ts | 3 --- 4 files changed, 2 insertions(+), 15 deletions(-) delete mode 100644 packages/active-campaign-client/src/types/contactList.ts diff --git a/packages/active-campaign-client/src/clients/activeCampaignClient.ts b/packages/active-campaign-client/src/clients/activeCampaignClient.ts index 2e461971e1..20eeaae8d5 100644 --- a/packages/active-campaign-client/src/clients/activeCampaignClient.ts +++ b/packages/active-campaign-client/src/clients/activeCampaignClient.ts @@ -9,7 +9,6 @@ import { ContactResponseWithLists, } from '../types/contactResponse'; import { ActiveCampaignList } from '../types/activeCampaignList'; -import { ContactList } from '../types/contactList'; const MAX_NUMBER_OF_LISTS = '1000'; @@ -202,7 +201,7 @@ export class ActiveCampaignClient { } async addContactToList(contactId: string, listId: number) { - return this.makeRequest('POST', `/api/3/contactLists`, { + return this.makeRequest('POST', `/api/3/contactLists`, { contactList: { contact: contactId, list: listId, @@ -212,7 +211,7 @@ export class ActiveCampaignClient { } async removeContactFromList(contactId: string, listId: number) { - return this.makeRequest('POST', `/api/3/contactLists`, { + return this.makeRequest('POST', `/api/3/contactLists`, { contactList: { contact: contactId, list: listId, diff --git a/packages/active-campaign-client/src/helpers/manageListSubscription.ts b/packages/active-campaign-client/src/helpers/manageListSubscription.ts index 9c368d8b5f..ddf6c237ce 100644 --- a/packages/active-campaign-client/src/helpers/manageListSubscription.ts +++ b/packages/active-campaign-client/src/helpers/manageListSubscription.ts @@ -24,12 +24,6 @@ export async function addContactToList( } const response = await acClient.addContactToList(contactId, listId); - if (!response.contactList) { - return { - statusCode: 500, - body: JSON.stringify({ message: 'Failed to add contact to list' }), - }; - } return { statusCode: 200, body: JSON.stringify(response), diff --git a/packages/active-campaign-client/src/helpers/removeArrayOfListFromContact.ts b/packages/active-campaign-client/src/helpers/removeArrayOfListFromContact.ts index 6466e8a4fd..e53be7400d 100644 --- a/packages/active-campaign-client/src/helpers/removeArrayOfListFromContact.ts +++ b/packages/active-campaign-client/src/helpers/removeArrayOfListFromContact.ts @@ -14,9 +14,6 @@ export async function removeArrayOfListFromContact(event: { try { const result = await acClient.removeContactFromList(contactId, id); console.log('Remove contact from list result:', result, id); // TODO: Remove after testing - if (!result.contactList) { - unsubscriptionsWithErrors.push(id.toString()); - } await new Promise((resolve) => setTimeout(resolve, resyncTimeoutMilliseconds) ); // wait to avoid rate limiting diff --git a/packages/active-campaign-client/src/types/contactList.ts b/packages/active-campaign-client/src/types/contactList.ts deleted file mode 100644 index 1c13231322..0000000000 --- a/packages/active-campaign-client/src/types/contactList.ts +++ /dev/null @@ -1,3 +0,0 @@ -export type ContactList = { - readonly contactList?: { readonly contact: string }; -};