From 07703f2f49815ddad29e773b6ab8eba4dacec819 Mon Sep 17 00:00:00 2001 From: tommaso1 Date: Tue, 19 Nov 2024 09:36:36 +0100 Subject: [PATCH] [DEV-1980] Add user to active campaign (#1220) * add api signature for contacts * add list management * update package-lock * add eslintrc.json * update package.json and eslint * migrate eslint * fix issues with linting * add changeset * scaffold, and api calls * add contact fix * delete non add * Create contact * env example * remove files * Update packages/active-campaign-client/src/handlers/addContact.ts Co-authored-by: Marco Ponchia * Update packages/active-campaign-client/src/activeCampaignClient.ts Co-authored-by: Marco Ponchia * Update packages/active-campaign-client/src/__tests__/handlers/addContact.test.ts Co-authored-by: Marco Ponchia * readme update * changeset * Update packages/active-campaign-client/src/__tests__/handlers/addContact.test.ts Co-authored-by: Marco Ponchia * Update packages/active-campaign-client/src/handlers/addContact.ts Co-authored-by: Marco Ponchia * Update .changeset/flat-walls-cough.md Co-authored-by: marcobottaro <39835990+marcobottaro@users.noreply.github.com> * Update packages/active-campaign-client/src/activeCampaignClient.ts Co-authored-by: marcobottaro <39835990+marcobottaro@users.noreply.github.com> * Update packages/active-campaign-client/.env.example Co-authored-by: marcobottaro <39835990+marcobottaro@users.noreply.github.com> * Update packages/active-campaign-client/src/activeCampaign.ts Co-authored-by: marcobottaro <39835990+marcobottaro@users.noreply.github.com> * Update packages/active-campaign-client/src/activeCampaign.ts Co-authored-by: marcobottaro <39835990+marcobottaro@users.noreply.github.com> * Update packages/active-campaign-client/src/activeCampaign.ts Co-authored-by: marcobottaro <39835990+marcobottaro@users.noreply.github.com> * Update packages/active-campaign-client/.env.example Co-authored-by: marcobottaro <39835990+marcobottaro@users.noreply.github.com> * Pull request changes * Pull request changes * Pull request changes --------- Co-authored-by: Marcello Bertoli Co-authored-by: t Co-authored-by: Marco Ponchia Co-authored-by: marcobottaro <39835990+marcobottaro@users.noreply.github.com> --- .changeset/flat-walls-cough.md | 5 ++ packages/active-campaign-client/.env.example | 3 ++ .../active-campaign-client/.eslintrc.json | 3 +- packages/active-campaign-client/README.md | 6 +++ .../src/__tests__/handlers/addContact.test.ts | 38 ++++++++++++++ .../src/activeCampaignClient.ts | 34 +++++++++++++ .../active-campaign-client/src/empty-file.ts | 0 .../src/handlers/addContact.ts | 50 +++++++++++++++++++ .../src/types/contactPayload.ts | 12 +++++ .../src/types/listPayload.ts | 10 ++++ .../src/types/listStatusPayload.ts | 7 +++ 11 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 .changeset/flat-walls-cough.md create mode 100644 packages/active-campaign-client/.env.example create mode 100644 packages/active-campaign-client/src/__tests__/handlers/addContact.test.ts create mode 100644 packages/active-campaign-client/src/activeCampaignClient.ts delete mode 100644 packages/active-campaign-client/src/empty-file.ts create mode 100644 packages/active-campaign-client/src/handlers/addContact.ts create mode 100644 packages/active-campaign-client/src/types/contactPayload.ts create mode 100644 packages/active-campaign-client/src/types/listPayload.ts create mode 100644 packages/active-campaign-client/src/types/listStatusPayload.ts diff --git a/.changeset/flat-walls-cough.md b/.changeset/flat-walls-cough.md new file mode 100644 index 0000000000..e35e9653fe --- /dev/null +++ b/.changeset/flat-walls-cough.md @@ -0,0 +1,5 @@ +--- +"active-campaign-client": minor +--- + +Add package setup and the lambda function to add user to Active Campaign diff --git a/packages/active-campaign-client/.env.example b/packages/active-campaign-client/.env.example new file mode 100644 index 0000000000..aabb8523e9 --- /dev/null +++ b/packages/active-campaign-client/.env.example @@ -0,0 +1,3 @@ +AC_BASE_URL=your_account_url +AC_API_KEY=your_api_key +SENDER_URL=localhost:3000 diff --git a/packages/active-campaign-client/.eslintrc.json b/packages/active-campaign-client/.eslintrc.json index e800f40dc9..b79e714255 100644 --- a/packages/active-campaign-client/.eslintrc.json +++ b/packages/active-campaign-client/.eslintrc.json @@ -7,6 +7,7 @@ "functional/no-expression-statements": "off", "functional/no-this-expressions": "off", "functional/no-classes": "off", - "functional/immutable-data": "off" + "functional/immutable-data": "off", + "functional/no-try-statements": "off" } } diff --git a/packages/active-campaign-client/README.md b/packages/active-campaign-client/README.md index 59b896fcbd..bf2d37b88a 100644 --- a/packages/active-campaign-client/README.md +++ b/packages/active-campaign-client/README.md @@ -96,3 +96,9 @@ All requests require authentication using an API key. The key is passed in the h 'Content-Type': 'application/json' } ``` + +# Tests + +Since the client is a wrapper around the Active Campaign API, it is not possible to test the client without making actual requests to the API. +The tests are therefore integration tests that require a valid Active Campaign account and API key to run. +They are skipped by default but can be run by deleting the `.skip` from the test suite. \ No newline at end of file diff --git a/packages/active-campaign-client/src/__tests__/handlers/addContact.test.ts b/packages/active-campaign-client/src/__tests__/handlers/addContact.test.ts new file mode 100644 index 0000000000..418acbdb18 --- /dev/null +++ b/packages/active-campaign-client/src/__tests__/handlers/addContact.test.ts @@ -0,0 +1,38 @@ +import { handler } from '../../handlers/addContact'; +import { SQSEvent } from 'aws-lambda'; + +// remove .skip to run the test, be aware it does a real API call so it will create a contact in the active campaign account +describe('addContact handler', () => { + it('should create a contact successfully', async () => { + const event: SQSEvent = { + Records: [ + { + messageId: '1', + receiptHandle: '1', + body: JSON.stringify({ + username: `test@example${new Date().getTime()}e.com`, + firstName: 'Giovanni', + lastName: 'Doe', + company: 'Test Co', + role: 'Developer', + mailinglistAccepted: true, + }), + attributes: { + ApproximateReceiveCount: '1', + SentTimestamp: '1', + SenderId: '1', + ApproximateFirstReceiveTimestamp: '1', + }, + messageAttributes: {}, + md5OfBody: '1', + eventSource: 'aws:sqs', + eventSourceARN: 'arn:aws:sqs:region:account-id:queue-name', + awsRegion: 'region', + }, + ], + }; + + const response = await handler(event); + expect(response.statusCode).toBe(200); + }); +}); diff --git a/packages/active-campaign-client/src/activeCampaignClient.ts b/packages/active-campaign-client/src/activeCampaignClient.ts new file mode 100644 index 0000000000..f62f57e634 --- /dev/null +++ b/packages/active-campaign-client/src/activeCampaignClient.ts @@ -0,0 +1,34 @@ +import axios from 'axios'; +import * as dotenv from 'dotenv'; +import { ContactPayload } from './types/contactPayload'; + +dotenv.config({ path: '.env' }); + +export class ActiveCampaignClient { + private readonly baseUrl: string; + private readonly apiKey: string; + + constructor(baseUrl: string, apiKey: string) { + this.baseUrl = baseUrl; + this.apiKey = apiKey; + } + + private getHeaders() { + return { + 'Api-Token': this.apiKey, + 'Content-Type': 'application/json', + }; + } + + async createContact(data: ContactPayload) { + const response = await axios.post(`${this.baseUrl}/api/3/contacts`, data, { + headers: this.getHeaders(), + }); + return response.data; + } +} + +export const acClient = new ActiveCampaignClient( + process.env.AC_BASE_URL!, + process.env.AC_API_KEY! +); diff --git a/packages/active-campaign-client/src/empty-file.ts b/packages/active-campaign-client/src/empty-file.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/active-campaign-client/src/handlers/addContact.ts b/packages/active-campaign-client/src/handlers/addContact.ts new file mode 100644 index 0000000000..b27373886f --- /dev/null +++ b/packages/active-campaign-client/src/handlers/addContact.ts @@ -0,0 +1,50 @@ +import { APIGatewayProxyResult, SQSEvent } from 'aws-lambda'; +import { acClient } from '../activeCampaignClient'; +import { SignUpUserData } from 'nextjs-website/src/lib/types/sign-up'; +import { ContactPayload } from '../types/contactPayload'; + +export async function handler(event: { + readonly Records: SQSEvent['Records']; +}): Promise { + try { + const firstMessage = event.Records[0] ?? { body: '{}' }; + // Parse request body + const userData: SignUpUserData = JSON.parse(firstMessage.body); + + // Transform to AC payload + const acPayload: ContactPayload = { + contact: { + email: userData.username, + firstName: userData.firstName, + lastName: userData.lastName, + fieldValues: [ + { + field: '2', + value: userData.company, + }, + { + field: '1', + value: userData.role, + }, + { + field: '3', + value: userData.mailinglistAccepted ? 'TRUE' : 'FALSE', + }, + ], + }, + }; + + const response = await acClient.createContact(acPayload); + + return { + statusCode: 200, + body: JSON.stringify(response), + }; + } catch (error) { + console.error('Error:', error); + return { + statusCode: 500, + body: JSON.stringify({ message: 'Internal server error' }), + }; + } +} diff --git a/packages/active-campaign-client/src/types/contactPayload.ts b/packages/active-campaign-client/src/types/contactPayload.ts new file mode 100644 index 0000000000..da7b70cfeb --- /dev/null +++ b/packages/active-campaign-client/src/types/contactPayload.ts @@ -0,0 +1,12 @@ +export type ContactPayload = { + readonly contact: { + readonly email: string; + readonly firstName: string; + readonly lastName: string; + readonly phone?: string; + readonly fieldValues: readonly { + readonly field: string; + readonly value: string; + }[]; + }; +}; diff --git a/packages/active-campaign-client/src/types/listPayload.ts b/packages/active-campaign-client/src/types/listPayload.ts new file mode 100644 index 0000000000..2f62a0b7cc --- /dev/null +++ b/packages/active-campaign-client/src/types/listPayload.ts @@ -0,0 +1,10 @@ +export type ListPayload = { + readonly list: { + readonly name: string; + readonly stringid: string; + readonly sender_url: string; + readonly sender_reminder: string; + readonly subscription_notify?: string; + readonly unsubscription_notify?: string; + }; +}; diff --git a/packages/active-campaign-client/src/types/listStatusPayload.ts b/packages/active-campaign-client/src/types/listStatusPayload.ts new file mode 100644 index 0000000000..84b34131b8 --- /dev/null +++ b/packages/active-campaign-client/src/types/listStatusPayload.ts @@ -0,0 +1,7 @@ +export type ListStatusPayload = { + readonly contactList: { + readonly list: string; + readonly contact: string; + readonly status: string; + }; +};