Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into dev-1614
Browse files Browse the repository at this point in the history
# Conflicts:
#	packages/active-campaign-client/src/handlers/resyncUserHandler.ts
#	packages/active-campaign-client/src/helpers/getUserFromCognito.ts
  • Loading branch information
petros-double-test1 committed Dec 18, 2024
2 parents 2874681 + 5e861a0 commit 3808be8
Show file tree
Hide file tree
Showing 18 changed files with 172 additions and 38 deletions.
5 changes: 5 additions & 0 deletions .changeset/cuddly-cats-retire.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"active-campaign-client": minor
---

Add resync user handler
5 changes: 5 additions & 0 deletions .changeset/cyan-dolls-reply.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"strapi-cms": patch
---

Refactor move ACTIVE_CAMPAIGN_INTEGRATION_ENABLED env var in getter function
5 changes: 5 additions & 0 deletions .changeset/plenty-buses-hope.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"infrastructure": patch
---

Added environment variables that allow strapi to integrate with active campaign
5 changes: 5 additions & 0 deletions .changeset/pretty-gorillas-bow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"strapi-cms": minor
---

Fix webinar validateSlug on update
3 changes: 2 additions & 1 deletion apps/infrastructure/src/env/dev/terraform.tfvars
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,5 @@ dns_domain_name_cms = {
}
}

create_chatbot = true
create_chatbot = true
ac_integration_is_enabled = true
3 changes: 2 additions & 1 deletion apps/infrastructure/src/env/prod/terraform.tfvars
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,5 @@ dns_domain_name_cms = {
}
}

create_chatbot = true
create_chatbot = true
ac_integration_is_enabled = false
12 changes: 8 additions & 4 deletions apps/infrastructure/src/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,12 @@ module "cms" {
github_repository = var.github_repository
tags = var.tags

dns_domain_name = var.dns_domain_name
dns_domain_name_cms = var.dns_domain_name_cms
hosted_zone_id = module.core.hosted_zone_id
dns_domain_name = var.dns_domain_name
dns_domain_name_cms = var.dns_domain_name_cms
hosted_zone_id = module.core.hosted_zone_id
ac_integration_is_enabled = var.ac_integration_is_enabled
ac_base_url_param = var.ac_integration_is_enabled ? module.active_campaign[0].base_url_param : null
ac_api_key_param = var.ac_integration_is_enabled ? module.active_campaign[0].api_key_param : null
}

module "chatbot" {
Expand Down Expand Up @@ -148,11 +151,12 @@ module "cicd" {
}

module "active_campaign" {
count = var.ac_integration_is_enabled ? 1 : 0
source = "./modules/active_campaign"

environment = var.environment
tags = var.tags

cognito_user_pool = module.website.cognito_user_pool
webinar_subscriptions_ddb_stream_arn = module.website.webinar_subscriptions_ddb_stream_arn
}
}
5 changes: 3 additions & 2 deletions apps/infrastructure/src/modules/active_campaign/lambda.tf
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ module "lambda_sync" {
ignore_source_code_hash = true
create_current_version_allowed_triggers = false

timeout = 15
memory_size = 256
timeout = 15
memory_size = 256
maximum_retry_attempts = 0

event_source_mapping = {
sqs = {
Expand Down
7 changes: 7 additions & 0 deletions apps/infrastructure/src/modules/active_campaign/output.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
output "base_url_param" {
value = module.active_campaign_base_url.ssm_parameter_arn
}

output "api_key_param" {
value = module.active_campaign_api_key.ssm_parameter_arn
}
4 changes: 4 additions & 0 deletions apps/infrastructure/src/modules/cms/ecs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ resource "aws_ecs_task_definition" "cms_task_def" {
google_oauth_client_id = module.secret_cms_google_oauth_client_id.ssm_parameter_arn
google_oauth_client_secret = module.secret_cms_google_oauth_client_secret.ssm_parameter_arn
google_oauth_redirect_uri = format("https://cms.%s/strapi-plugin-sso/google/callback", var.dns_domain_name)
ac_integration_is_enabled = var.ac_integration_is_enabled ? "True" : "False"
ac_base_url = var.ac_integration_is_enabled ? var.ac_base_url_param : module.secret_cms_transfer_token_salt.ssm_parameter_arn
ac_api_key = var.ac_integration_is_enabled ? var.ac_api_key_param : module.secret_cms_transfer_token_salt.ssm_parameter_arn
ac_sender_url = "https://${var.dns_domain_name}"
})
}

Expand Down
6 changes: 4 additions & 2 deletions apps/infrastructure/src/modules/cms/iam_policy.tf
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ data "aws_iam_policy_document" "ecs_task_execution" {
actions = [
"ssm:GetParameters"
]
resources = [
resources = concat([
module.secret_cms_database_password.ssm_parameter_arn,
module.secret_cms_admin_jwt_secret.ssm_parameter_arn,
module.secret_cms_app_keys.ssm_parameter_arn,
Expand All @@ -95,7 +95,9 @@ data "aws_iam_policy_document" "ecs_task_execution" {
module.secret_cms_google_gsuite_hd.ssm_parameter_arn,
module.secret_cms_google_oauth_client_id.ssm_parameter_arn,
module.secret_cms_google_oauth_client_secret.ssm_parameter_arn,
]
],
(var.ac_integration_is_enabled ? [var.ac_base_url_param, var.ac_api_key_param] : [])
)
}

statement {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,14 @@
{
"name": "DEPLOY_WEBSITE_TARGET_BRANCH",
"value": "${target_branch}"
},
{
"name": "ACTIVE_CAMPAIGN_INTEGRATION_IS_ENABLED",
"value": "${ac_integration_is_enabled}"
},
{
"name": "SENDER_URL",
"value": "${ac_sender_url}"
}
],
"secrets" : [
Expand Down Expand Up @@ -141,7 +149,15 @@
{
"name": "GOOGLE_OAUTH_CLIENT_SECRET",
"valueFrom": "${google_oauth_client_secret}"
}
},
{
"name": "AC_BASE_URL",
"valueFrom": "${ac_base_url}"
},
{
"name": "AC_API_KEY",
"valueFrom": "${ac_api_key}"
}
]
}
]
19 changes: 19 additions & 0 deletions apps/infrastructure/src/modules/cms/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,23 @@ variable "dns_domain_name_cms" {
variable "hosted_zone_id" {
type = string
description = "The ID of the hosted zone to create the public DNS records in"
}

## Active Campaign configuration for Strapi
variable "ac_integration_is_enabled" {
type = bool
description = "Enable Active Campaign integration for Strapi"
default = false
}

variable "ac_base_url_param" {
type = string
description = "Active Campaign base URL SSM parameter ARN"
default = null
}

variable "ac_api_key_param" {
type = string
description = "Active Campaign API key SSM parameter ARN"
default = null
}
9 changes: 9 additions & 0 deletions apps/infrastructure/src/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -138,4 +138,13 @@ variable "chatbot_ecs_monitoring" {
image_uri = "ghcr.io/langfuse/langfuse:sha-9375250"
port = 3000
}
}

################################################################################
# Active Campaign integration
################################################################################
variable "ac_integration_is_enabled" {
type = bool
description = "Defines if Active Campaign integration should be enabled"
default = false
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ interface IActiveCampaignListPayload {
};
}

const activeCampaignIntegrationIsEnabled =
env('ACTIVE_CAMPAIGN_INTEGRATION_IS_ENABLED', 'False') === 'True';
function getActiveCampaignIntegrationIsEnabled() {
return env('ACTIVE_CAMPAIGN_INTEGRATION_ENABLED', 'false') === 'true';
}

function getHeaders() {
return {
Expand Down Expand Up @@ -65,7 +66,11 @@ const validateDates = (event: IWebinarEvent): boolean => {
};

const validateSlug = async (event: IWebinarEvent): Promise<boolean> => {
const { id } = event.params.data;
if (!event.params.data.slug || !getActiveCampaignIntegrationIsEnabled()) {
return true;
}

const id = event.params.where?.id;
if (!id) {
throw new errors.ApplicationError('Webinar id not found');
}
Expand Down Expand Up @@ -93,7 +98,7 @@ const createActiveCampaignList = async (
event: IWebinarEvent
): Promise<boolean> => {
if (
!activeCampaignIntegrationIsEnabled ||
!getActiveCampaignIntegrationIsEnabled() ||
!event.result?.slug ||
!event.result?.title
) {
Expand Down Expand Up @@ -146,7 +151,7 @@ const deleteActiveCampaignList = async (
event: IWebinarEvent
): Promise<boolean> => {
if (
!activeCampaignIntegrationIsEnabled ||
!getActiveCampaignIntegrationIsEnabled() ||
!event?.params.where ||
!event.params.where.id
) {
Expand Down Expand Up @@ -192,7 +197,7 @@ module.exports = {
await deleteActiveCampaignList(event);
},
beforeDeleteMany() {
if (activeCampaignIntegrationIsEnabled) {
if (getActiveCampaignIntegrationIsEnabled()) {
throw new errors.ApplicationError(
'Bulk deletion is not allowed for webinars if Active Campaign integration is enabled'
);
Expand Down
53 changes: 34 additions & 19 deletions packages/active-campaign-client/src/handlers/resyncUserHandler.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,32 @@
import { deleteContact } from '../helpers/deleteContact';
import { getUserFromCognitoByUsername } from '../helpers/getUserFromCognito';
import { fetchSubscribedWebinarsFromDynamo } from '../helpers/fetchSubscribedWebinarsFromDynamo';
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';
import { acClient } from '../clients/activeCampaignClient';
import { bulkAddContactToList } from '../helpers/bulkAddContactsToLists';

export async function resyncUserHandler(event: {
readonly Records: SQSEvent['Records'];
}): Promise<APIGatewayProxyResult> {
try {
const queueEvent = queueEventParser(event);
const cognitoId = queueEvent.detail.additionalEventData.sub;
const deletionResult = await deleteContact(cognitoId);
if (deletionResult.statusCode != 200 && deletionResult.statusCode != 404) {
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 getUserFromCognitoByUsername(cognitoId);
const user = await getUserFromCognitoUsername(cognitoUsername);

if (!user) {
console.log(
`User: ${cognitoUsername} not present on Cognito, sync done.`
);
return {
statusCode: 200,
body: JSON.stringify({
Expand All @@ -31,8 +35,8 @@ export async function resyncUserHandler(event: {
};
}

const userWebinarsSubscriptions = await fetchSubscribedWebinarsFromDynamo(
cognitoId
const userWebinarsSubscriptions = await getSubscribedWebinars(
cognitoUsername
);

const webinarIds = JSON.parse(userWebinarsSubscriptions.body)
Expand All @@ -42,18 +46,29 @@ export async function resyncUserHandler(event: {
)
.filter(Boolean);

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');
}

const webinarListIds = await Promise.all(
webinarIds.map(async (webinarId: string) => {
const listId = await acClient.getListIdByName(webinarId);
return listId;
})
await webinarIds.reduce(
async (
prevPromise: Promise<APIGatewayProxyResult>,
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()
);

const res = await bulkAddContactToList([user], [webinarListIds]);


return {
statusCode: 200,
body: JSON.stringify({ message: 'User resynced' }),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { DynamoDBClient, QueryCommand } from '@aws-sdk/client-dynamodb';
import { APIGatewayProxyResult } from 'aws-lambda';

export async function getSubscribedWebinars(
username: string
): Promise<APIGatewayProxyResult> {
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' }),
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ import { listUsersCommandOutputToUser } from './listUsersCommandOutputToUser';

export async function getUserFromCognito(queueEvent: QueueEvent) {
const username = queueEvent.detail.additionalEventData.sub;
const user = await getUserFromCognitoByUsername(username);
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 getUserFromCognitoByUsername(username: string) {
export async function getUserFromCognitoUsername(username: string) {
const command = new ListUsersCommand({
UserPoolId: process.env.COGNITO_USER_POOL_ID,
Filter: `username = "${username}"`,
Expand Down

0 comments on commit 3808be8

Please sign in to comment.