From 1864d6d8a2548a741b80e260fcfe127e4b944375 Mon Sep 17 00:00:00 2001 From: Dario Ocles Date: Wed, 21 Jan 2026 12:11:30 +0100 Subject: [PATCH 1/5] feat(IM-1857): adding aws sns subscription support --- .../javascript/event/src/connector/actions.js | 12 ++++ .../event/src/connector/post-deploy.js | 58 ++++++++++--------- .../typescript/event/src/connector/actions.ts | 13 +++++ .../event/src/connector/post-deploy.ts | 58 ++++++++++--------- 4 files changed, 85 insertions(+), 56 deletions(-) diff --git a/application-templates/javascript/event/src/connector/actions.js b/application-templates/javascript/event/src/connector/actions.js index 7d4bc94..3ba03d1 100644 --- a/application-templates/javascript/event/src/connector/actions.js +++ b/application-templates/javascript/event/src/connector/actions.js @@ -25,6 +25,18 @@ export async function createAzureServiceBusCustomerCreateSubscription( await createSubscription(apiRoot, destination); } +export async function createAwsSnsCustomerCreateSubscription( + apiRoot, + topicArn +) { + const destination = { + type: 'SNS', + topicArn: topicArn, + authenticationMode: 'IAM', + }; + await createSubscription(apiRoot, destination); +} + async function createSubscription(apiRoot, destination) { await deleteCustomerCreateSubscription(apiRoot); await apiRoot diff --git a/application-templates/javascript/event/src/connector/post-deploy.js b/application-templates/javascript/event/src/connector/post-deploy.js index a17eb92..ddf59b6 100644 --- a/application-templates/javascript/event/src/connector/post-deploy.js +++ b/application-templates/javascript/event/src/connector/post-deploy.js @@ -5,43 +5,45 @@ import { createApiRoot } from '../client/create.client.js'; import { assertError, assertString } from '../utils/assert.utils.js'; import { createGcpPubSubCustomerCreateSubscription, - createAzureServiceBusCustomerCreateSubscription, + createAwsSnsCustomerCreateSubscription, } from './actions.js'; const CONNECT_GCP_TOPIC_NAME_KEY = 'CONNECT_GCP_TOPIC_NAME'; const CONNECT_GCP_PROJECT_ID_KEY = 'CONNECT_GCP_PROJECT_ID'; -const CONNECT_PROVIDER_KEY = 'CONNECT_PROVIDER'; -const CONNECT_AZURE_CONNECTION_STRING_KEY = 'CONNECT_AZURE_CONNECTION_STRING'; +const CONNECT_SUBSCRIPTION_DESTINATION_KEY = 'CONNECT_SUBSCRIPTION_DESTINATION'; +const CONNECT_AWS_TOPIC_ARN_KEY = 'CONNECT_AWS_TOPIC_ARN'; async function postDeploy(properties) { - const connectProvider = properties.get(CONNECT_PROVIDER_KEY); - assertString(connectProvider, CONNECT_PROVIDER_KEY); + const subscriptionDestination = properties.get( + CONNECT_SUBSCRIPTION_DESTINATION_KEY + ); const apiRoot = createApiRoot(); - switch (connectProvider) { - case 'AZURE': { - const connectionString = properties.get( - CONNECT_AZURE_CONNECTION_STRING_KEY - ); - assertString(connectionString, CONNECT_AZURE_CONNECTION_STRING_KEY); - await createAzureServiceBusCustomerCreateSubscription( - apiRoot, - connectionString - ); - break; - } - default: { - const topicName = properties.get(CONNECT_GCP_TOPIC_NAME_KEY); - const projectId = properties.get(CONNECT_GCP_PROJECT_ID_KEY); - assertString(topicName, CONNECT_GCP_TOPIC_NAME_KEY); - assertString(projectId, CONNECT_GCP_PROJECT_ID_KEY); - await createGcpPubSubCustomerCreateSubscription( - apiRoot, - topicName, - projectId - ); - } + // Google Cloud Pub/Sub subscription + if (subscriptionDestination === 'GoogleCloudPubSub') { + const topicName = properties.get(CONNECT_GCP_TOPIC_NAME_KEY); + const projectId = properties.get(CONNECT_GCP_PROJECT_ID_KEY); + assertString(topicName, CONNECT_GCP_TOPIC_NAME_KEY); + assertString(projectId, CONNECT_GCP_PROJECT_ID_KEY); + await createGcpPubSubCustomerCreateSubscription( + apiRoot, + topicName, + projectId + ); + return; } + + // AWS SNS subscription + if (subscriptionDestination === 'SNS') { + const topicArn = properties.get(CONNECT_AWS_TOPIC_ARN_KEY); + assertString(topicArn, CONNECT_AWS_TOPIC_ARN_KEY); + await createAwsSnsCustomerCreateSubscription(apiRoot, topicArn); + return; + } + + throw new Error( + `Unknown subscription destination type: ${subscriptionDestination}` + ); } async function run() { try { diff --git a/application-templates/typescript/event/src/connector/actions.ts b/application-templates/typescript/event/src/connector/actions.ts index 78cf9cc..b00c4c4 100644 --- a/application-templates/typescript/event/src/connector/actions.ts +++ b/application-templates/typescript/event/src/connector/actions.ts @@ -2,6 +2,7 @@ import { AzureServiceBusDestination, Destination, GoogleCloudPubSubDestination, + SnsDestination, } from '@commercetools/platform-sdk'; import { ByProjectKeyRequestBuilder } from '@commercetools/platform-sdk/dist/declarations/src/generated/client/by-project-key-request-builder'; @@ -32,6 +33,18 @@ export async function createAzureServiceBusCustomerCreateSubscription( await createSubscription(apiRoot, destination); } +export async function createAwsSnsCustomerCreateSubscription( + apiRoot: ByProjectKeyRequestBuilder, + topicArn: string +): Promise { + const destination: SnsDestination = { + type: 'SNS', + topicArn: topicArn, + authenticationMode: 'IAM', + }; + await createSubscription(apiRoot, destination); +} + async function createSubscription( apiRoot: ByProjectKeyRequestBuilder, destination: Destination diff --git a/application-templates/typescript/event/src/connector/post-deploy.ts b/application-templates/typescript/event/src/connector/post-deploy.ts index 3270ee4..ce52368 100644 --- a/application-templates/typescript/event/src/connector/post-deploy.ts +++ b/application-templates/typescript/event/src/connector/post-deploy.ts @@ -4,44 +4,46 @@ dotenv.config(); import { createApiRoot } from '../client/create.client'; import { assertError, assertString } from '../utils/assert.utils'; import { - createAzureServiceBusCustomerCreateSubscription, + createAwsSnsCustomerCreateSubscription, createGcpPubSubCustomerCreateSubscription, } from './actions'; const CONNECT_GCP_TOPIC_NAME_KEY = 'CONNECT_GCP_TOPIC_NAME'; const CONNECT_GCP_PROJECT_ID_KEY = 'CONNECT_GCP_PROJECT_ID'; -const CONNECT_PROVIDER_KEY = 'CONNECT_PROVIDER'; -const CONNECT_AZURE_CONNECTION_STRING_KEY = 'CONNECT_AZURE_CONNECTION_STRING'; +const CONNECT_SUBSCRIPTION_DESTINATION_KEY = 'CONNECT_SUBSCRIPTION_DESTINATION'; +const CONNECT_AWS_TOPIC_ARN_KEY = 'CONNECT_AWS_TOPIC_ARN'; async function postDeploy(properties: Map): Promise { - const connectProvider = properties.get(CONNECT_PROVIDER_KEY); - assertString(connectProvider, CONNECT_PROVIDER_KEY); + const subscriptionDestination = properties.get( + CONNECT_SUBSCRIPTION_DESTINATION_KEY + ) as string | null | undefined; const apiRoot = createApiRoot(); - switch (connectProvider) { - case 'AZURE': { - const connectionString = properties.get( - CONNECT_AZURE_CONNECTION_STRING_KEY - ); - assertString(connectionString, CONNECT_AZURE_CONNECTION_STRING_KEY); - await createAzureServiceBusCustomerCreateSubscription( - apiRoot, - connectionString - ); - break; - } - default: { - const topicName = properties.get(CONNECT_GCP_TOPIC_NAME_KEY); - const projectId = properties.get(CONNECT_GCP_PROJECT_ID_KEY); - assertString(topicName, CONNECT_GCP_TOPIC_NAME_KEY); - assertString(projectId, CONNECT_GCP_PROJECT_ID_KEY); - await createGcpPubSubCustomerCreateSubscription( - apiRoot, - topicName, - projectId - ); - } + // Google Cloud Pub/Sub subscription + if (subscriptionDestination === 'GoogleCloudPubSub') { + const topicName = properties.get(CONNECT_GCP_TOPIC_NAME_KEY); + const projectId = properties.get(CONNECT_GCP_PROJECT_ID_KEY); + assertString(topicName, CONNECT_GCP_TOPIC_NAME_KEY); + assertString(projectId, CONNECT_GCP_PROJECT_ID_KEY); + await createGcpPubSubCustomerCreateSubscription( + apiRoot, + topicName, + projectId + ); + return; } + + // AWS SNS subscription + if (subscriptionDestination === 'SNS') { + const topicArn = properties.get(CONNECT_AWS_TOPIC_ARN_KEY); + assertString(topicArn, CONNECT_AWS_TOPIC_ARN_KEY); + await createAwsSnsCustomerCreateSubscription(apiRoot, topicArn); + return; + } + + throw new Error( + `Unknown subscription destination type: ${subscriptionDestination}` + ); } async function run(): Promise { From b1c97e8b9ab5a515af273194426be81215571f38 Mon Sep 17 00:00:00 2001 From: Dario Ocles Date: Wed, 21 Jan 2026 12:29:11 +0100 Subject: [PATCH 2/5] feat(IM-1857): removing azure from code, not longer supported --- .../javascript/event/src/connector/actions.js | 11 ----------- .../typescript/event/src/connector/actions.ts | 12 ------------ 2 files changed, 23 deletions(-) diff --git a/application-templates/javascript/event/src/connector/actions.js b/application-templates/javascript/event/src/connector/actions.js index 3ba03d1..71968ae 100644 --- a/application-templates/javascript/event/src/connector/actions.js +++ b/application-templates/javascript/event/src/connector/actions.js @@ -14,17 +14,6 @@ export async function createGcpPubSubCustomerCreateSubscription( await createSubscription(apiRoot, destination); } -export async function createAzureServiceBusCustomerCreateSubscription( - apiRoot, - connectionString -) { - const destination = { - type: 'AzureServiceBus', - connectionString: connectionString, - }; - await createSubscription(apiRoot, destination); -} - export async function createAwsSnsCustomerCreateSubscription( apiRoot, topicArn diff --git a/application-templates/typescript/event/src/connector/actions.ts b/application-templates/typescript/event/src/connector/actions.ts index b00c4c4..91d23fd 100644 --- a/application-templates/typescript/event/src/connector/actions.ts +++ b/application-templates/typescript/event/src/connector/actions.ts @@ -1,5 +1,4 @@ import { - AzureServiceBusDestination, Destination, GoogleCloudPubSubDestination, SnsDestination, @@ -22,17 +21,6 @@ export async function createGcpPubSubCustomerCreateSubscription( await createSubscription(apiRoot, destination); } -export async function createAzureServiceBusCustomerCreateSubscription( - apiRoot: ByProjectKeyRequestBuilder, - connectionString: string -): Promise { - const destination: AzureServiceBusDestination = { - type: 'AzureServiceBus', - connectionString: connectionString, - }; - await createSubscription(apiRoot, destination); -} - export async function createAwsSnsCustomerCreateSubscription( apiRoot: ByProjectKeyRequestBuilder, topicArn: string From 4226650c7d052a3ae1c0500123e7f69cfd286197 Mon Sep 17 00:00:00 2001 From: Dario Ocles Date: Tue, 27 Jan 2026 16:42:16 +0100 Subject: [PATCH 3/5] feat: improving subscriptions logic --- .../javascript/event/.env.example | 14 +- .../javascript/event/src/connector/actions.js | 129 ++++++++++++----- .../event/src/connector/post-deploy.js | 53 ++----- .../event/src/connector/pre-undeploy.js | 3 - .../event/src/utils/assert.utils.js | 7 + .../event/src/utils/config.utils.js | 6 + .../event/src/validators/env.validators.js | 52 ++++++- .../typescript/event/.env.example | 14 +- .../typescript/event/src/connector/actions.ts | 136 ++++++++++++------ .../event/src/connector/post-deploy.ts | 53 ++----- .../event/src/connector/pre-undeploy.ts | 3 - .../event/src/interfaces/config.interface.ts | 4 + .../event/src/utils/assert.utils.ts | 10 ++ .../event/src/utils/config.utils.ts | 12 +- .../event/src/validators/env.validators.ts | 52 ++++++- 15 files changed, 365 insertions(+), 183 deletions(-) diff --git a/application-templates/javascript/event/.env.example b/application-templates/javascript/event/.env.example index 563a2e2..9afc536 100644 --- a/application-templates/javascript/event/.env.example +++ b/application-templates/javascript/event/.env.example @@ -2,4 +2,16 @@ CTP_CLIENT_ID= CTP_CLIENT_SECRET= CTP_PROJECT_KEY= CTP_SCOPE= -CTP_REGION= \ No newline at end of file +CTP_REGION= +PORT= + +# Subscription destination configuration +# Options: "GoogleCloudPubSub" or "SNS" +CONNECT_SUBSCRIPTION_DESTINATION= + +# GCP Pub/Sub configuration (required if CONNECT_SUBSCRIPTION_DESTINATION=GoogleCloudPubSub) +CONNECT_GCP_TOPIC_NAME= +CONNECT_GCP_PROJECT_ID= + +# AWS SNS configuration (required if CONNECT_SUBSCRIPTION_DESTINATION=SNS) +CONNECT_AWS_TOPIC_ARN= \ No newline at end of file diff --git a/application-templates/javascript/event/src/connector/actions.js b/application-templates/javascript/event/src/connector/actions.js index 71968ae..4f777e1 100644 --- a/application-templates/javascript/event/src/connector/actions.js +++ b/application-templates/javascript/event/src/connector/actions.js @@ -1,48 +1,101 @@ +import { assertNonNullable } from '../utils/assert.utils.js'; + const CUSTOMER_CREATE_SUBSCRIPTION_KEY = 'myconnector-customerCreateSubscription'; -export async function createGcpPubSubCustomerCreateSubscription( - apiRoot, - topicName, - projectId -) { - const destination = { - type: 'GoogleCloudPubSub', - topic: topicName, - projectId, - }; - await createSubscription(apiRoot, destination); -} - -export async function createAwsSnsCustomerCreateSubscription( - apiRoot, - topicArn -) { - const destination = { - type: 'SNS', - topicArn: topicArn, - authenticationMode: 'IAM', - }; - await createSubscription(apiRoot, destination); -} - -async function createSubscription(apiRoot, destination) { - await deleteCustomerCreateSubscription(apiRoot); - await apiRoot +export async function createCustomerCreateSubscription(apiRoot, config) { + // Delete existing subscription first (inline deletion) + const { + body: { results: subscriptions }, + } = await apiRoot .subscriptions() - .post({ - body: { - key: CUSTOMER_CREATE_SUBSCRIPTION_KEY, - destination, - messages: [ - { - resourceTypeId: 'customer', - types: ['CustomerCreated'], - }, - ], + .get({ + queryArgs: { + where: `key = "${CUSTOMER_CREATE_SUBSCRIPTION_KEY}"`, }, }) .execute(); + + if (subscriptions.length > 0) { + const subscription = subscriptions[0]; + await apiRoot + .subscriptions() + .withKey({ key: CUSTOMER_CREATE_SUBSCRIPTION_KEY }) + .delete({ + queryArgs: { + version: subscription.version, + }, + }) + .execute(); + } + + // Google Cloud Pub/Sub subscription + if (config.connectSubscriptionDestination === 'GoogleCloudPubSub') { + assertNonNullable( + config.connectGcpTopicName, + 'GCP Topic Name must be provided for GoogleCloudPubSub destination' + ); + assertNonNullable( + config.connectGcpProjectId, + 'GCP Project ID must be provided for GoogleCloudPubSub destination' + ); + + await apiRoot + .subscriptions() + .post({ + body: { + key: CUSTOMER_CREATE_SUBSCRIPTION_KEY, + destination: { + type: 'GoogleCloudPubSub', + topic: config.connectGcpTopicName, + projectId: config.connectGcpProjectId, + }, + messages: [ + { + resourceTypeId: 'customer', + types: ['CustomerCreated'], + }, + ], + }, + }) + .execute(); + + return; + } + + // AWS SNS subscription + if (config.connectSubscriptionDestination === 'SNS') { + assertNonNullable( + config.connectAwsTopicArn, + 'AWS Topic ARN must be provided for SNS destination' + ); + + await apiRoot + .subscriptions() + .post({ + body: { + key: CUSTOMER_CREATE_SUBSCRIPTION_KEY, + destination: { + type: 'SNS', + topicArn: config.connectAwsTopicArn, + authenticationMode: 'IAM', + }, + messages: [ + { + resourceTypeId: 'customer', + types: ['CustomerCreated'], + }, + ], + }, + }) + .execute(); + + return; + } + + throw new Error( + `Unknown subscription destination type: ${config.connectSubscriptionDestination}` + ); } export async function deleteCustomerCreateSubscription(apiRoot) { diff --git a/application-templates/javascript/event/src/connector/post-deploy.js b/application-templates/javascript/event/src/connector/post-deploy.js index ddf59b6..f0f5928 100644 --- a/application-templates/javascript/event/src/connector/post-deploy.js +++ b/application-templates/javascript/event/src/connector/post-deploy.js @@ -1,54 +1,17 @@ -import dotenv from 'dotenv'; -dotenv.config(); - import { createApiRoot } from '../client/create.client.js'; -import { assertError, assertString } from '../utils/assert.utils.js'; -import { - createGcpPubSubCustomerCreateSubscription, - createAwsSnsCustomerCreateSubscription, -} from './actions.js'; - -const CONNECT_GCP_TOPIC_NAME_KEY = 'CONNECT_GCP_TOPIC_NAME'; -const CONNECT_GCP_PROJECT_ID_KEY = 'CONNECT_GCP_PROJECT_ID'; -const CONNECT_SUBSCRIPTION_DESTINATION_KEY = 'CONNECT_SUBSCRIPTION_DESTINATION'; -const CONNECT_AWS_TOPIC_ARN_KEY = 'CONNECT_AWS_TOPIC_ARN'; +import { assertError } from '../utils/assert.utils.js'; +import { createCustomerCreateSubscription } from './actions.js'; +import { readConfiguration } from '../utils/config.utils.js'; -async function postDeploy(properties) { - const subscriptionDestination = properties.get( - CONNECT_SUBSCRIPTION_DESTINATION_KEY - ); +async function postDeploy(config) { const apiRoot = createApiRoot(); - - // Google Cloud Pub/Sub subscription - if (subscriptionDestination === 'GoogleCloudPubSub') { - const topicName = properties.get(CONNECT_GCP_TOPIC_NAME_KEY); - const projectId = properties.get(CONNECT_GCP_PROJECT_ID_KEY); - assertString(topicName, CONNECT_GCP_TOPIC_NAME_KEY); - assertString(projectId, CONNECT_GCP_PROJECT_ID_KEY); - await createGcpPubSubCustomerCreateSubscription( - apiRoot, - topicName, - projectId - ); - return; - } - - // AWS SNS subscription - if (subscriptionDestination === 'SNS') { - const topicArn = properties.get(CONNECT_AWS_TOPIC_ARN_KEY); - assertString(topicArn, CONNECT_AWS_TOPIC_ARN_KEY); - await createAwsSnsCustomerCreateSubscription(apiRoot, topicArn); - return; - } - - throw new Error( - `Unknown subscription destination type: ${subscriptionDestination}` - ); + await createCustomerCreateSubscription(apiRoot, config); } + async function run() { try { - const properties = new Map(Object.entries(process.env)); - await postDeploy(properties); + const config = readConfiguration(); + await postDeploy(config); } catch (error) { assertError(error); process.stderr.write(`Post-deploy failed: ${error.message}\n`); diff --git a/application-templates/javascript/event/src/connector/pre-undeploy.js b/application-templates/javascript/event/src/connector/pre-undeploy.js index a3228b9..2afc5e5 100644 --- a/application-templates/javascript/event/src/connector/pre-undeploy.js +++ b/application-templates/javascript/event/src/connector/pre-undeploy.js @@ -1,6 +1,3 @@ -import dotenv from 'dotenv'; -dotenv.config(); - import { createApiRoot } from '../client/create.client.js'; import { assertError } from '../utils/assert.utils.js'; import { deleteCustomerCreateSubscription } from './actions.js'; diff --git a/application-templates/javascript/event/src/utils/assert.utils.js b/application-templates/javascript/event/src/utils/assert.utils.js index acd0868..907c097 100644 --- a/application-templates/javascript/event/src/utils/assert.utils.js +++ b/application-templates/javascript/event/src/utils/assert.utils.js @@ -11,3 +11,10 @@ export function assertError(value, message) { export function assertString(value, message) { assert(typeof value === 'string', message ?? 'Invalid string value'); } + +export function assertNonNullable(value, message) { + assert( + value !== null && value !== undefined, + message ?? 'Value is null or undefined' + ); +} diff --git a/application-templates/javascript/event/src/utils/config.utils.js b/application-templates/javascript/event/src/utils/config.utils.js index 933c646..e867d62 100644 --- a/application-templates/javascript/event/src/utils/config.utils.js +++ b/application-templates/javascript/event/src/utils/config.utils.js @@ -16,6 +16,12 @@ export const readConfiguration = () => { projectKey: process.env.CTP_PROJECT_KEY, scope: process.env.CTP_SCOPE, region: process.env.CTP_REGION, + port: process.env.PORT, + connectSubscriptionDestination: + process.env.CONNECT_SUBSCRIPTION_DESTINATION, + connectGcpTopicName: process.env.CONNECT_GCP_TOPIC_NAME, + connectGcpProjectId: process.env.CONNECT_GCP_PROJECT_ID, + connectAwsTopicArn: process.env.CONNECT_AWS_TOPIC_ARN, }; const validationErrors = getValidateMessages(envValidators, envVars); diff --git a/application-templates/javascript/event/src/validators/env.validators.js b/application-templates/javascript/event/src/validators/env.validators.js index 3f211b2..e4a1e75 100644 --- a/application-templates/javascript/event/src/validators/env.validators.js +++ b/application-templates/javascript/event/src/validators/env.validators.js @@ -35,7 +35,7 @@ const envValidators = [ referencedBy: 'environmentVariables', }), - optional(standardString)( + standardString( ['scope'], { code: 'InvalidScope', @@ -50,6 +50,56 @@ const envValidators = [ message: 'Not a valid region.', referencedBy: 'environmentVariables', }), + + standardString( + ['port'], + { + code: 'InvalidPort', + message: 'Port should be a valid string.', + referencedBy: 'environmentVariables', + }, + { min: 1, max: undefined } + ), + + standardString( + ['connectSubscriptionDestination'], + { + code: 'InvalidSubscriptionDestination', + message: 'Subscription destination should be a valid string.', + referencedBy: 'environmentVariables', + }, + { min: 2, max: undefined } + ), + + optional(standardString)( + ['connectGcpTopicName'], + { + code: 'InvalidGcpTopicName', + message: 'GCP Topic Name should be a valid string.', + referencedBy: 'environmentVariables', + }, + { min: 2, max: undefined } + ), + + optional(standardString)( + ['connectGcpProjectId'], + { + code: 'InvalidGcpProjectId', + message: 'GCP Project ID should be a valid string.', + referencedBy: 'environmentVariables', + }, + { min: 2, max: undefined } + ), + + optional(standardString)( + ['connectAwsTopicArn'], + { + code: 'InvalidAwsTopicArn', + message: 'AWS Topic ARN should be a valid string.', + referencedBy: 'environmentVariables', + }, + { min: 2, max: undefined } + ), ]; export default envValidators; diff --git a/application-templates/typescript/event/.env.example b/application-templates/typescript/event/.env.example index 563a2e2..9afc536 100644 --- a/application-templates/typescript/event/.env.example +++ b/application-templates/typescript/event/.env.example @@ -2,4 +2,16 @@ CTP_CLIENT_ID= CTP_CLIENT_SECRET= CTP_PROJECT_KEY= CTP_SCOPE= -CTP_REGION= \ No newline at end of file +CTP_REGION= +PORT= + +# Subscription destination configuration +# Options: "GoogleCloudPubSub" or "SNS" +CONNECT_SUBSCRIPTION_DESTINATION= + +# GCP Pub/Sub configuration (required if CONNECT_SUBSCRIPTION_DESTINATION=GoogleCloudPubSub) +CONNECT_GCP_TOPIC_NAME= +CONNECT_GCP_PROJECT_ID= + +# AWS SNS configuration (required if CONNECT_SUBSCRIPTION_DESTINATION=SNS) +CONNECT_AWS_TOPIC_ARN= \ No newline at end of file diff --git a/application-templates/typescript/event/src/connector/actions.ts b/application-templates/typescript/event/src/connector/actions.ts index 91d23fd..6b88980 100644 --- a/application-templates/typescript/event/src/connector/actions.ts +++ b/application-templates/typescript/event/src/connector/actions.ts @@ -1,58 +1,106 @@ -import { - Destination, - GoogleCloudPubSubDestination, - SnsDestination, -} from '@commercetools/platform-sdk'; import { ByProjectKeyRequestBuilder } from '@commercetools/platform-sdk/dist/declarations/src/generated/client/by-project-key-request-builder'; +import { assertNonNullable } from '../utils/assert.utils'; +import { Config } from '../interfaces/config.interface'; const CUSTOMER_CREATE_SUBSCRIPTION_KEY = 'myconnector-customerCreateSubscription'; -export async function createGcpPubSubCustomerCreateSubscription( +export async function createCustomerCreateSubscription( apiRoot: ByProjectKeyRequestBuilder, - topicName: string, - projectId: string + config: Config ): Promise { - const destination: GoogleCloudPubSubDestination = { - type: 'GoogleCloudPubSub', - topic: topicName, - projectId, - }; - await createSubscription(apiRoot, destination); -} - -export async function createAwsSnsCustomerCreateSubscription( - apiRoot: ByProjectKeyRequestBuilder, - topicArn: string -): Promise { - const destination: SnsDestination = { - type: 'SNS', - topicArn: topicArn, - authenticationMode: 'IAM', - }; - await createSubscription(apiRoot, destination); -} - -async function createSubscription( - apiRoot: ByProjectKeyRequestBuilder, - destination: Destination -) { - await deleteCustomerCreateSubscription(apiRoot); - await apiRoot + // Delete existing subscription first (inline deletion) + const { + body: { results: subscriptions }, + } = await apiRoot .subscriptions() - .post({ - body: { - key: CUSTOMER_CREATE_SUBSCRIPTION_KEY, - destination, - messages: [ - { - resourceTypeId: 'customer', - types: ['CustomerCreated'], - }, - ], + .get({ + queryArgs: { + where: `key = "${CUSTOMER_CREATE_SUBSCRIPTION_KEY}"`, }, }) .execute(); + + if (subscriptions.length > 0) { + const subscription = subscriptions[0]; + await apiRoot + .subscriptions() + .withKey({ key: CUSTOMER_CREATE_SUBSCRIPTION_KEY }) + .delete({ + queryArgs: { + version: subscription.version, + }, + }) + .execute(); + } + + // Google Cloud Pub/Sub subscription + if (config.connectSubscriptionDestination === 'GoogleCloudPubSub') { + assertNonNullable( + config.connectGcpTopicName, + 'GCP Topic Name must be provided for GoogleCloudPubSub destination' + ); + assertNonNullable( + config.connectGcpProjectId, + 'GCP Project ID must be provided for GoogleCloudPubSub destination' + ); + + await apiRoot + .subscriptions() + .post({ + body: { + key: CUSTOMER_CREATE_SUBSCRIPTION_KEY, + destination: { + type: 'GoogleCloudPubSub', + topic: config.connectGcpTopicName, + projectId: config.connectGcpProjectId, + }, + messages: [ + { + resourceTypeId: 'customer', + types: ['CustomerCreated'], + }, + ], + }, + }) + .execute(); + + return; + } + + // AWS SNS subscription + if (config.connectSubscriptionDestination === 'SNS') { + assertNonNullable( + config.connectAwsTopicArn, + 'AWS Topic ARN must be provided for SNS destination' + ); + + await apiRoot + .subscriptions() + .post({ + body: { + key: CUSTOMER_CREATE_SUBSCRIPTION_KEY, + destination: { + type: 'SNS', + topicArn: config.connectAwsTopicArn, + authenticationMode: 'IAM', + }, + messages: [ + { + resourceTypeId: 'customer', + types: ['CustomerCreated'], + }, + ], + }, + }) + .execute(); + + return; + } + + throw new Error( + `Unknown subscription destination type: ${config.connectSubscriptionDestination}` + ); } export async function deleteCustomerCreateSubscription( diff --git a/application-templates/typescript/event/src/connector/post-deploy.ts b/application-templates/typescript/event/src/connector/post-deploy.ts index ce52368..1642ffd 100644 --- a/application-templates/typescript/event/src/connector/post-deploy.ts +++ b/application-templates/typescript/event/src/connector/post-deploy.ts @@ -1,55 +1,18 @@ -import dotenv from 'dotenv'; -dotenv.config(); - import { createApiRoot } from '../client/create.client'; -import { assertError, assertString } from '../utils/assert.utils'; -import { - createAwsSnsCustomerCreateSubscription, - createGcpPubSubCustomerCreateSubscription, -} from './actions'; - -const CONNECT_GCP_TOPIC_NAME_KEY = 'CONNECT_GCP_TOPIC_NAME'; -const CONNECT_GCP_PROJECT_ID_KEY = 'CONNECT_GCP_PROJECT_ID'; -const CONNECT_SUBSCRIPTION_DESTINATION_KEY = 'CONNECT_SUBSCRIPTION_DESTINATION'; -const CONNECT_AWS_TOPIC_ARN_KEY = 'CONNECT_AWS_TOPIC_ARN'; +import { assertError } from '../utils/assert.utils'; +import { Config } from '../interfaces/config.interface'; +import { createCustomerCreateSubscription } from './actions'; +import { readConfiguration } from '../utils/config.utils'; -async function postDeploy(properties: Map): Promise { - const subscriptionDestination = properties.get( - CONNECT_SUBSCRIPTION_DESTINATION_KEY - ) as string | null | undefined; +async function postDeploy(config: Config): Promise { const apiRoot = createApiRoot(); - - // Google Cloud Pub/Sub subscription - if (subscriptionDestination === 'GoogleCloudPubSub') { - const topicName = properties.get(CONNECT_GCP_TOPIC_NAME_KEY); - const projectId = properties.get(CONNECT_GCP_PROJECT_ID_KEY); - assertString(topicName, CONNECT_GCP_TOPIC_NAME_KEY); - assertString(projectId, CONNECT_GCP_PROJECT_ID_KEY); - await createGcpPubSubCustomerCreateSubscription( - apiRoot, - topicName, - projectId - ); - return; - } - - // AWS SNS subscription - if (subscriptionDestination === 'SNS') { - const topicArn = properties.get(CONNECT_AWS_TOPIC_ARN_KEY); - assertString(topicArn, CONNECT_AWS_TOPIC_ARN_KEY); - await createAwsSnsCustomerCreateSubscription(apiRoot, topicArn); - return; - } - - throw new Error( - `Unknown subscription destination type: ${subscriptionDestination}` - ); + await createCustomerCreateSubscription(apiRoot, config); } async function run(): Promise { try { - const properties = new Map(Object.entries(process.env)); - await postDeploy(properties); + const config = readConfiguration(); + await postDeploy(config); } catch (error) { assertError(error); process.stderr.write(`Post-deploy failed: ${error.message}\n`); diff --git a/application-templates/typescript/event/src/connector/pre-undeploy.ts b/application-templates/typescript/event/src/connector/pre-undeploy.ts index 3b7e721..8367b42 100644 --- a/application-templates/typescript/event/src/connector/pre-undeploy.ts +++ b/application-templates/typescript/event/src/connector/pre-undeploy.ts @@ -1,6 +1,3 @@ -import dotenv from 'dotenv'; -dotenv.config(); - import { createApiRoot } from '../client/create.client'; import { assertError } from '../utils/assert.utils'; import { deleteCustomerCreateSubscription } from './actions'; diff --git a/application-templates/typescript/event/src/interfaces/config.interface.ts b/application-templates/typescript/event/src/interfaces/config.interface.ts index cbaf35e..70d698a 100644 --- a/application-templates/typescript/event/src/interfaces/config.interface.ts +++ b/application-templates/typescript/event/src/interfaces/config.interface.ts @@ -5,4 +5,8 @@ export interface Config { scope: string; region: string; port: string; + connectSubscriptionDestination?: string | null; + connectGcpTopicName?: string; + connectGcpProjectId?: string; + connectAwsTopicArn?: string; } diff --git a/application-templates/typescript/event/src/utils/assert.utils.ts b/application-templates/typescript/event/src/utils/assert.utils.ts index b7cff04..5468dd3 100644 --- a/application-templates/typescript/event/src/utils/assert.utils.ts +++ b/application-templates/typescript/event/src/utils/assert.utils.ts @@ -17,3 +17,13 @@ export function assertString( ): asserts value is string { assert(typeof value === 'string', message ?? 'Invalid string value'); } + +export function assertNonNullable( + value: T, + message?: string +): asserts value is NonNullable { + assert( + value !== null && value !== undefined, + message ?? 'Value is null or undefined' + ); +} diff --git a/application-templates/typescript/event/src/utils/config.utils.ts b/application-templates/typescript/event/src/utils/config.utils.ts index f40b030..4f29489 100644 --- a/application-templates/typescript/event/src/utils/config.utils.ts +++ b/application-templates/typescript/event/src/utils/config.utils.ts @@ -13,8 +13,18 @@ export const readConfiguration = () => { clientId: process.env.CTP_CLIENT_ID as string, clientSecret: process.env.CTP_CLIENT_SECRET as string, projectKey: process.env.CTP_PROJECT_KEY as string, - scope: process.env.CTP_SCOPE, + scope: process.env.CTP_SCOPE as string, region: process.env.CTP_REGION as string, + port: process.env.PORT as string, + connectSubscriptionDestination: process.env + .CONNECT_SUBSCRIPTION_DESTINATION as string, + connectGcpTopicName: process.env.CONNECT_GCP_TOPIC_NAME as + | string + | undefined, + connectGcpProjectId: process.env.CONNECT_GCP_PROJECT_ID as + | string + | undefined, + connectAwsTopicArn: process.env.CONNECT_AWS_TOPIC_ARN as string | undefined, }; const validationErrors = getValidateMessages(envValidators, envVars); diff --git a/application-templates/typescript/event/src/validators/env.validators.ts b/application-templates/typescript/event/src/validators/env.validators.ts index 5f27625..6844482 100644 --- a/application-templates/typescript/event/src/validators/env.validators.ts +++ b/application-templates/typescript/event/src/validators/env.validators.ts @@ -35,7 +35,7 @@ const envValidators = [ referencedBy: 'environmentVariables', }), - optional(standardString)( + standardString( ['scope'], { code: 'InvalidScope', @@ -50,6 +50,56 @@ const envValidators = [ message: 'Not a valid region.', referencedBy: 'environmentVariables', }), + + standardString( + ['port'], + { + code: 'InvalidPort', + message: 'Port should be a valid string.', + referencedBy: 'environmentVariables', + }, + { min: 1, max: undefined } + ), + + standardString( + ['connectSubscriptionDestination'], + { + code: 'InvalidSubscriptionDestination', + message: 'Subscription destination should be a valid string.', + referencedBy: 'environmentVariables', + }, + { min: 2, max: undefined } + ), + + optional(standardString)( + ['connectGcpTopicName'], + { + code: 'InvalidGcpTopicName', + message: 'GCP Topic Name should be a valid string.', + referencedBy: 'environmentVariables', + }, + { min: 2, max: undefined } + ), + + optional(standardString)( + ['connectGcpProjectId'], + { + code: 'InvalidGcpProjectId', + message: 'GCP Project ID should be a valid string.', + referencedBy: 'environmentVariables', + }, + { min: 2, max: undefined } + ), + + optional(standardString)( + ['connectAwsTopicArn'], + { + code: 'InvalidAwsTopicArn', + message: 'AWS Topic ARN should be a valid string.', + referencedBy: 'environmentVariables', + }, + { min: 2, max: undefined } + ), ]; export default envValidators; From 6ef72de95a6ba41c76eee49329459b40b246adaa Mon Sep 17 00:00:00 2001 From: Dario Ocles Date: Wed, 28 Jan 2026 12:24:07 +0100 Subject: [PATCH 4/5] fix: typing issues with environment variables --- .../event/src/interfaces/config.interface.ts | 2 +- .../event/src/utils/config.utils.ts | 29 +++++++++---------- .../event/src/validators/env.validators.ts | 2 +- 3 files changed, 15 insertions(+), 18 deletions(-) diff --git a/application-templates/typescript/event/src/interfaces/config.interface.ts b/application-templates/typescript/event/src/interfaces/config.interface.ts index 70d698a..c83d226 100644 --- a/application-templates/typescript/event/src/interfaces/config.interface.ts +++ b/application-templates/typescript/event/src/interfaces/config.interface.ts @@ -5,7 +5,7 @@ export interface Config { scope: string; region: string; port: string; - connectSubscriptionDestination?: string | null; + connectSubscriptionDestination?: string; connectGcpTopicName?: string; connectGcpProjectId?: string; connectAwsTopicArn?: string; diff --git a/application-templates/typescript/event/src/utils/config.utils.ts b/application-templates/typescript/event/src/utils/config.utils.ts index 4f29489..38d33d9 100644 --- a/application-templates/typescript/event/src/utils/config.utils.ts +++ b/application-templates/typescript/event/src/utils/config.utils.ts @@ -1,4 +1,5 @@ import CustomError from '../errors/custom.error'; +import { Config } from '../interfaces/config.interface'; import envValidators from '../validators/env.validators'; import { getValidateMessages } from '../validators/helpers.validators'; @@ -10,22 +11,18 @@ import { getValidateMessages } from '../validators/helpers.validators'; */ export const readConfiguration = () => { const envVars = { - clientId: process.env.CTP_CLIENT_ID as string, - clientSecret: process.env.CTP_CLIENT_SECRET as string, - projectKey: process.env.CTP_PROJECT_KEY as string, - scope: process.env.CTP_SCOPE as string, - region: process.env.CTP_REGION as string, - port: process.env.PORT as string, - connectSubscriptionDestination: process.env - .CONNECT_SUBSCRIPTION_DESTINATION as string, - connectGcpTopicName: process.env.CONNECT_GCP_TOPIC_NAME as - | string - | undefined, - connectGcpProjectId: process.env.CONNECT_GCP_PROJECT_ID as - | string - | undefined, - connectAwsTopicArn: process.env.CONNECT_AWS_TOPIC_ARN as string | undefined, - }; + clientId: process.env.CTP_CLIENT_ID, + clientSecret: process.env.CTP_CLIENT_SECRET, + projectKey: process.env.CTP_PROJECT_KEY, + scope: process.env.CTP_SCOPE, + region: process.env.CTP_REGION, + port: process.env.PORT, + connectSubscriptionDestination: + process.env.CONNECT_SUBSCRIPTION_DESTINATION, + connectGcpTopicName: process.env.CONNECT_GCP_TOPIC_NAME, + connectGcpProjectId: process.env.CONNECT_GCP_PROJECT_ID, + connectAwsTopicArn: process.env.CONNECT_AWS_TOPIC_ARN, + } as Config; const validationErrors = getValidateMessages(envValidators, envVars); diff --git a/application-templates/typescript/event/src/validators/env.validators.ts b/application-templates/typescript/event/src/validators/env.validators.ts index 6844482..246a111 100644 --- a/application-templates/typescript/event/src/validators/env.validators.ts +++ b/application-templates/typescript/event/src/validators/env.validators.ts @@ -61,7 +61,7 @@ const envValidators = [ { min: 1, max: undefined } ), - standardString( + optional(standardString)( ['connectSubscriptionDestination'], { code: 'InvalidSubscriptionDestination', From 480e4ad49a95b1a00382256b4915ad1ec7924551 Mon Sep 17 00:00:00 2001 From: Dario Ocles Date: Tue, 10 Feb 2026 12:41:14 +0100 Subject: [PATCH 5/5] fix: optional environment variable --- .../javascript/event/src/validators/env.validators.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application-templates/javascript/event/src/validators/env.validators.js b/application-templates/javascript/event/src/validators/env.validators.js index e4a1e75..f938d88 100644 --- a/application-templates/javascript/event/src/validators/env.validators.js +++ b/application-templates/javascript/event/src/validators/env.validators.js @@ -61,7 +61,7 @@ const envValidators = [ { min: 1, max: undefined } ), - standardString( + optional(standardString)( ['connectSubscriptionDestination'], { code: 'InvalidSubscriptionDestination',