diff --git a/.changeset/cuddly-cats-retire.md b/.changeset/cuddly-cats-retire.md new file mode 100644 index 000000000..ef17a074d --- /dev/null +++ b/.changeset/cuddly-cats-retire.md @@ -0,0 +1,5 @@ +--- +"active-campaign-client": minor +--- + +Add resync user handler diff --git a/packages/active-campaign-client/src/handlers/resyncUserHandler.ts b/packages/active-campaign-client/src/handlers/resyncUserHandler.ts new file mode 100644 index 000000000..a9edc97d5 --- /dev/null +++ b/packages/active-campaign-client/src/handlers/resyncUserHandler.ts @@ -0,0 +1,82 @@ +import { deleteContact } from '../helpers/deleteContact'; +import { getUserFromCognitoUsername } from '../helpers/getUserFromCognito'; +import { getSubscribedWebinars } from '../helpers/getSubscribedWebinars'; +import { addContact } from '../helpers/addContact'; +import { APIGatewayProxyResult, SQSEvent } from 'aws-lambda'; +import { addContactToList } from '../helpers/manageListSubscription'; +import { queueEventParser } from '../helpers/queueEventParser'; + +export async function resyncUserHandler(event: { + readonly Records: SQSEvent['Records']; +}): Promise { + try { + const queueEvent = queueEventParser(event); + const cognitoUsername = queueEvent.detail.additionalEventData.sub; + const deletionResult = await deleteContact(cognitoUsername); + if ( + deletionResult.statusCode !== 200 && + deletionResult.statusCode !== 404 + ) { + // eslint-disable-next-line functional/no-throw-statements + throw new Error('Error adding contact'); + } + + const user = await getUserFromCognitoUsername(cognitoUsername); + + if (!user) { + console.log( + `User: ${cognitoUsername} not present on Cognito, sync done.` + ); + return { + statusCode: 200, + body: JSON.stringify({ + message: 'User not present on Cognito, sync done.', + }), + }; + } + + const userWebinarsSubscriptions = await getSubscribedWebinars( + cognitoUsername + ); + + const webinarIds = JSON.parse(userWebinarsSubscriptions.body) + .map( + (webinar: { readonly webinarId: { readonly S: string } }) => + webinar?.webinarId?.S + ) + .filter(Boolean); + + 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(cognitoUsername, 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 }), + }; + } +} diff --git a/packages/active-campaign-client/src/helpers/getSubscribedWebinars.ts b/packages/active-campaign-client/src/helpers/getSubscribedWebinars.ts new file mode 100644 index 000000000..1d6d75738 --- /dev/null +++ b/packages/active-campaign-client/src/helpers/getSubscribedWebinars.ts @@ -0,0 +1,30 @@ +import { DynamoDBClient, QueryCommand } from '@aws-sdk/client-dynamodb'; +import { APIGatewayProxyResult } from 'aws-lambda'; + +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); + 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 41d02e7c7..c3630ad89 100644 --- a/packages/active-campaign-client/src/helpers/getUserFromCognito.ts +++ b/packages/active-campaign-client/src/helpers/getUserFromCognito.ts @@ -4,16 +4,22 @@ import { QueueEvent } from '../types/queueEvent'; import { listUsersCommandOutputToUser } from './listUsersCommandOutputToUser'; export async function getUserFromCognito(queueEvent: QueueEvent) { + const username = queueEvent.detail.additionalEventData.sub; + const user = await getUserFromCognitoUsername(username); + if (!user) { + // eslint-disable-next-line functional/no-throw-statements + throw new Error('User not found'); + } + return user; +} + +export async function getUserFromCognitoUsername(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); - 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 0d1edb3b6..cab74a814 100644 --- a/packages/active-campaign-client/src/index.ts +++ b/packages/active-campaign-client/src/index.ts @@ -1,8 +1,15 @@ import { SQSEvent } from 'aws-lambda'; import { sqsQueueHandler } from './handlers/sqsQueueHandler'; +import { resyncUserHandler } from './handlers/resyncUserHandler'; export async function sqsQueue(event: { readonly Records: SQSEvent['Records']; }) { return await sqsQueueHandler(event); } + +export async function resyncQueue(event: { + readonly Records: SQSEvent['Records']; +}) { + return await resyncUserHandler(event); +}